feat: Phase 2A — product cost tracking
- Products: add cost_simple (flat €) and cost_breakdown (JSON line items) - OrderItems: add unit_cost snapshot written at time of order creation - Snapshot logic: breakdown sum takes priority over cost_simple; NULL if neither set - Product schema: serialize/deserialize cost_breakdown as JSON; expose in API - Product performance report: add trackable_profit, uncosted_revenue, has_gap per product - Manager UI: cost section in product edit form (simple / detailed toggle with live margin %) - Products list: show margin % or cost-error badge per product - Product performance report: profit column + gap warning banner Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -270,12 +270,25 @@ def add_items(
|
||||
(o.price_delta or o.extra_cost or 0.0)
|
||||
for o in (item_in.selected_options or [])
|
||||
)
|
||||
# Phase 2A: cost snapshot — breakdown takes priority over simple cost
|
||||
unit_cost = None
|
||||
if product.cost_breakdown:
|
||||
try:
|
||||
entries = json.loads(product.cost_breakdown)
|
||||
total = sum(e.get("amount", 0.0) for e in entries if isinstance(e, dict))
|
||||
if total > 0:
|
||||
unit_cost = total
|
||||
except Exception:
|
||||
pass
|
||||
if unit_cost is None and product.cost_simple:
|
||||
unit_cost = product.cost_simple
|
||||
item = OrderItem(
|
||||
order_id=order_id,
|
||||
product_id=item_in.product_id,
|
||||
added_by=user.id,
|
||||
quantity=item_in.quantity,
|
||||
unit_price=product.base_price + extra_cost,
|
||||
unit_cost=unit_cost,
|
||||
selected_options=json.dumps([o.model_dump() for o in item_in.selected_options]) if item_in.selected_options else None,
|
||||
removed_ingredients=json.dumps(item_in.removed_ingredients) if item_in.removed_ingredients else None,
|
||||
notes=item_in.notes,
|
||||
|
||||
Reference in New Issue
Block a user