sylius

How to track product production / seller cost and analyze profits in Sylius?


I am using Sylius just as a housekeeping for my local business. My plan is to create fake online orders for each live offline order and get the sales reports.

While there’s a nice plugin for sales reports, I cannot find a way to factor in seller costs of a product. Say, it costs me $5 to produce and store 1 unit of product, then another $10 on marketing each week. I want to know how much of a profit do I have over week with actual product prices (say, last week I sold 10 units $15 each and this week was 20 units $12 each).

Am I missing some setting in product or warehouse setup?

How do I track changing costs if my supplies get more expensive or cheaper and my seller / purchase cost fluctuates?

There’s a min price in product, but it’s about discounts and minimum profitability, not actual product cost.

Update: There’s a CostPricePlugin for adding Cost Price to products, which is nice, but lacks support update. There’s a SupplierPlugin that adds cost price per supplier per variant, which is very nice, but it also lacks reports support. There’s a SyliusSalesReportsPlugin for sales reports, but it’s only for basic price reports.


Solution

  • Sylius does not support this functionality out-of-the-box, and apart from the plugins you’ve already mentioned, there doesn’t appear to be any other solutions directly addressing your needs. You’ll most likely need to develop a custom solution. Here’s a general approach based on a similar implementation I worked on:

    Step 1: Track all costs

    You’ll need to set up a way to manage the various costs associated with your products. These could be product costs, delivery charges, payment fees, warehouse fees, and other operational costs. These costs can be:

    Step 2: Implement cost calculators

    For each model, you should create cost calculators. Here's an example of interfaces for cost calculators:

    For costs associated with the payment

    interface PaymentCostCalculatorInterface
    {
        public function calculate(PaymentInterface $payment): int;
    }
    

    For costs associated with the product

    interface OrderItemUnitCostCalculatorInterface
    {
        public function calculate(OrderItemInterface $orderItem): int;
    }
    

    Costs aggregator for each expense category

    This will aggregate the costs for each expense category, like the total cost for all the order items in an order:

    interface CalculatorInterface
    {
        public function calculate(Order $order): int;
    }
    

    The aggregated costs calculators will be used in the order processors described in the next step. Each calculator should focus on one type of expense, like item costs, shipment costs, or payment fees.

    Step 3: Implement the order processors

    Implement an order processor for each type of expense to calculate the cost and save it on the corresponding model related to the order, like the order item, the shipment or the payment. Here's an example:

    final class OrderPaymentCostProcessor implements OrderProcessorInterface
    {
        public function __construct(
            private readonly PaymentCostCalculatorInterface $calculator,
        ) {
        }
    
        public function process(OrderInterface $order): void
        {
            foreach ($order->getPayments() as $payment) {
                $payment->setCost($this->calculator->calculate($payment));
            }
        }
    }
    

    Step 4: Create a composite service that will calculate the total cost for each expense category

    Aggregate all the individual cost calculators into a composite service using Symfony service tags and the tagged iterator. This service will iterate over the calculators and sum up the costs:

    final class CompositeCostCalculator implements CalculatorInterface
    {
        private iterable $calculators;
    
        public function __construct(iterable $calculators)
        {
            $this->calculators = $calculators;
        }
    
        public function calculate(OrderInterface $order): int
        {
            $totalCost = 0;
    
            foreach ($this->calculators as $calculator) {
                $totalCost += $calculator->calculate($order);
            }
    
            return $totalCost;
        }
    }
    

    Step 5: Calculate the total costs (or profit in this example) for an order

    Create an Order Processor that calculates and stores the total cost or profit of an order. This ensures the values are immutable and won’t change later.

    Example order cost (or profit in this example) processor

    final class OrderProfitProcessor implements OrderProcessorInterface
    {
        public function __construct(
            private readonly CalculatorInterface $costCalculator,
        ) {
        }
    
        public function process(OrderInterface $order): void
        {
            $total = $order->getTotal(); // Gross revenue
            $taxTotal = $order->getTaxTotal(); // Tax amount
            $cost = $this->costCalculator->calculate($order); // Total costs
    
            $profit = $total - $taxTotal - $cost; // Calculate profit
    
            $order->setProfit($profit); // Save profit to the order
        }
    }
    

    Step 5: Extend Reporting

    Finally, ensure your sales reports include the new cost (or profit) calculations. You might need to extend or customize existing reporting plugins (like SyliusSalesReportsPlugin) to incorporate this data.