I am trying to generate a report which calculates the margin from the below database. The problem is that the cost (existing in purchase_order_products table) of the product may change.
The cost of product with id 4022 on 2017-06-08 is 1110, however its cost is 1094 on 2017-07-25. This is confusing. I am unable to get the exact cost for each product sold.
I wrote a PHP algorithm which loops through all orders and purchase orders and used the oldest cost to newest cost. but the algorithm has a very high time complexity. Can this be done just using mysql query?
Please check below scenario:
Company created a purchase order for product X: quantity 3, cost 10. on day 1
Customers bought 2 product X sell price: 12 on day 1 (still have 1 item in inventory with cost 10)
Company created a purchase order for product X : quantity 4, cost 9. on day 2
Customers bought 3 product X sell price: 12 on day 2
Customers bought 2 product X sell price: 12 on day 3
Company created a purchase order for product X : quantity 2, cost 11. on day 3
Customers bought 2 product X sell price: 12 on day 3
The report:
day 1: sold 2 product X for 12 , cost 10 , profit: 2 * (12 - 10)
day 2: sold 3 product X for 12 , 1 item has a cost of 10, 2 items have a cost of 9 ,
profit: 1 * (12 - 10) + 2 * (12 - 9)
day 3: sold 2 product X for 12 , cost 9 , profit: 2 * (12 - 9)
sold 2 product X for 12 , cost 11 , profit: 2 * (12 - 11)
Therefor the profit of newly sold products is calculated using the their corresponding cost. Hope you got my point.
4 Products From Database
Products Purchase orders for the above products
Why don't you just take it easy and add a profit column to the orders table that is calculated in real time when a customer buys a product.This way you can calculate your marging solely from the sales orders given the fact that this is actualy already calculated somehow in order to generate the selling price. Of course this will work only for future sales but you can use your existing code with little modification to populate the profit column for old records and you will run this code only one time for the old transactions before the update.
To elaborate more:
Alter the table "sales_order"
adding "profit"
column . This way you can calculate the sum using the other related columns (total_paid, total_refund, total_due, grand_total)
because you may want have more control over the report by including those monetary fields as needed in your calculation for example generating a report using total_payed
only excluding tota_due
or encluding it for different type of reports, in other words you can generate multiple reports types only from this table without overwhelming the DB system by adding this one column only.
Edit:
You can also add a cost column to this table for fast retrieving purpose and minimize joins and queries to other tables and if you want to take it a step further you can add a dedicated table for reports and it will be very helpful for example to generate a missing report from last month and checking old order status.
Some disclaimers:
That being said, I think something like this should work for you:
$productsIds = array('4022', '4023', '4160', '4548', '4601');
foreach($productIds as $pid){
$sql = "SELECT (soi.revenue - sum(pop.cost)) AS profit, sum(pop.cost) AS total_cost, sum(pop.quantity) AS total_purchased, soi.revenue, soi.total_sold
FROM purchase_order_products pop
JOIN (SELECT sum(price) AS revenue, sum(quantity_ordred) AS total_sold FROM sales_order_item WHERE product_id = ".$pid.") AS soi ON soi.product_id = pop.product_id
WHERE pop.product_id = ".$pid." GROUP BY pop.product_id HAVING sum(pop.quantity) < soi.total_sold ORDER BY pop.created_at ASC;";
$conn->query($sql);
//do what you want with results
}
The key thing here is using the HAVING clause after GROUP BY to determine where you cut off finding the sum of the purchase costs. You can sum them all as long as they're within that range, and you get the right dates ordering by created_at.
Again, I can't test this, and I wouldn't recommend using this code as is, just hoping this helps from a "here's a general idea of how to make this happen".
If I had time to recreate your databases I would, or if you provide sql dump files with example data, I could try to get you a working example.
The price of an object on a given time is given by the formula : "total price of the stock / total number in the stock".
To get this, you have two queries to execute :
sql:
SELECT SUM(row_total) sale_total_cost, SUM(quantity_ordered) sale_total_number
FROM sales_order_item soi
JOIN sales_order so ON soi.sales_order_id=so.id
WHERE so.purchase_date<'2017-06-07 15:03:30'
AND soi.product_id=4160;
sql:
SELECT SUM(pop.cost * pop.quantity) purchase_total_price, SUM(pop.quantity) purchase_total_number
FROM purchase_order_products pop
JOIN purchase_order po ON pop.purchase_order_id=po.id
WHERE po.created_at<'2017-06-07 15:03:30'
AND pop.product_id=4160;
2017-02-01 14:23:35
is: (purchase_total_price - sale_total_cost) / (purchase_total_number - sale_total_number)
The problem is that your "sales_order" table start on 2017-02-01 14:23:35
, while your "purchase_order" table start on 2017-06-07 08:55:48
. So the result will be incoherent as long as you can't track all your purchases from the start.
EDIT:
purchase_order_products
table You have to modify purchase_order_products
to have the consumption for each product:
ALTER TABLE `purchase_order_products` ADD COLUMN sold_items INT DEFAULT 0;
In order for it to work, you have to make the sold_items column reflect your real stock
You should initialize your table with the following request UPDATE `purchase_order_products` SET sold_items=quantity;
and then manually update the table with your exact stock for each product (which means that quantity_ordered-sold_items must reflect your real stock.
This has to be done only once.
ALTER TABLE sales_order_item ADD total_purchase_price INT DEFAULT NULL
When you enter new sales order, you will have to get the oldest purchase order with remaining items using the following command:
SELECT * FROM `purchase_order_products` WHERE quantity!=sold_items where product_id=4160 ORDER BY `purchase_order_id` LIMIT 1;
You will have then to increment the sold_items value, and calculate the total purchase price (sum) to fill the total_purchase_price column.
The margin will be easily calculated with the difference between row_total and total_purchase_price in the sales_order_item table
I appreciate you are using the FIFO method for the management of stock. However, this does not mean you need to use FIFO to calculate margins. The article https://en.wikipedia.org/wiki/Inventory_valuation gives an overview of the options. (Regulations in your country may exclude some options.)
I believe a repeatable solution for FIFO margin calculation of an individual sale is complex. There are complexities of opening balances, returns, partial deliveries, partial shipments, out-of-order processing, stock-take adjustments, damaged goods etc.
These issues do not seem to be addressed by the database structures in your question.
Typically, these issues are addressed by computing the margin/profit of a period (day, month etc) by calculating the change in value of the inventory over the period.
If you can use the average cost method, you can calculate the margin with pure SQL. I believe other methods would seem to require some iteration as there is no inherent order in SQL. (Your could improve performance by creating a new table and storing the previous period values.)
I would not be too worried about putting the whole solution in SQL as this would not appear to reduce the computational complexity of the problem. Still there might be speed advantages in doing as much of the calculation in the database engine, particularly if the data-set is large.
You might find this article interesting: Set-based Speed Phreakery: The FIFO Stock Inventory SQL Problem . (There are some clever people out there!)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.