records = $records; $this->snapshotter = $snapshotter; $this->resolver = $resolver; } /** * Headline profit summary for a period. * * @param array $args Range args (range|from|to). * @return array */ public function summary( array $args ): array { [ $from, $to ] = $this->resolve_range( $args ); $statuses = $this->snapshotter->counted_statuses(); $t = $this->records->totals( $from, $to, $statuses ); $tax_in_profit = (bool) ( get_option( 'profit_engine_settings' )['tax_in_profit'] ?? false ); $total_cost = ( $t['cost'] ?? 0 ) + ( $t['shipping'] ?? 0 ) + ( $t['payment_fee'] ?? 0 ) + ( $t['other_cost'] ?? 0 ) + ( $tax_in_profit ? ( $t['tax'] ?? 0 ) : 0 ); $revenue = (float) ( $t['revenue'] ?? 0 ); $profit = (float) ( $t['profit'] ?? 0 ); return array( 'range' => array( 'from' => $from, 'to' => $to, ), 'revenue' => $revenue, 'cost' => (float) ( $t['cost'] ?? 0 ), 'shipping' => (float) ( $t['shipping'] ?? 0 ), 'payment_fee' => (float) ( $t['payment_fee'] ?? 0 ), 'other_cost' => (float) ( $t['other_cost'] ?? 0 ), 'tax' => (float) ( $t['tax'] ?? 0 ), 'total_cost' => (float) $total_cost, 'profit' => $profit, 'margin' => $revenue > 0 ? ( $profit / $revenue ) : 0.0, 'units' => (float) ( $t['units'] ?? 0 ), 'orders' => (int) ( $t['orders'] ?? 0 ), ); } /** * Profit broken down by product/variation. * * @param array $args Range args. * @return array> */ public function by_product( array $args ): array { [ $from, $to ] = $this->resolve_range( $args ); $rows = $this->records->by_product( $from, $to, $this->snapshotter->counted_statuses(), 500 ); return array_map( function ( array $r ): array { $revenue = (float) $r['revenue']; $profit = (float) $r['profit']; $pid = (int) $r['product_id']; // Prefer the identity snapshotted at sale time so renamed or deleted // products still read correctly; fall back to a live resolve, then to // a clear "deleted product" label when neither is available. $snapshot_name = isset( $r['product_name'] ) ? trim( (string) $r['product_name'] ) : ''; $snapshot_sku = isset( $r['sku'] ) ? trim( (string) $r['sku'] ) : ''; $live_product = $this->resolver->product( $pid ); if ( '' !== $snapshot_name ) { $label = $snapshot_name; } elseif ( $live_product ) { $label = $this->resolver->unit_label( $pid ); } else { $label = __( '(deleted product)', 'profit-engine' ); } if ( '' !== $snapshot_sku ) { $sku = $snapshot_sku; } else { $sku = $live_product ? $this->resolver->sku( $pid ) : ''; } return array( 'product_id' => $pid, 'label' => $label, 'sku' => $sku, 'revenue' => $revenue, 'cost' => (float) $r['cost'], 'profit' => $profit, 'margin' => $revenue > 0 ? ( $profit / $revenue ) : 0.0, 'units' => (float) $r['units'], ); }, $rows ); } /** * Resolve range args into UTC datetime bounds. * * Explicit from/to (Y-m-d) win; otherwise a named preset. Bounds are treated * as UTC to match the stored order_date column. * * @param array $args Args. * @return array{0:string,1:string} */ private function resolve_range( array $args ): array { return DateRange::resolve( $args ); } }