"""Savings Engine: calculate and track how much customers save vs retail price. Provides: - Calculate savings per item at checkout - Update customer total savings - Generate savings reports """ from decimal import Decimal, ROUND_HALF_UP def calculate_item_savings(unit_price, retail_price, quantity=1): """Calculate savings for a single item. Returns: savings_amount (float), savings_pct (float) """ if not retail_price or retail_price <= 0: return 0.0, 0.0 if not unit_price or unit_price <= 0: return 0.0, 0.0 savings = (Decimal(str(retail_price)) - Decimal(str(unit_price))) * Decimal(str(quantity)) savings = savings.quantize(Decimal('0.01'), ROUND_HALF_UP) pct = (savings / (Decimal(str(retail_price)) * Decimal(str(quantity)))) * 100 pct = float(pct.quantize(Decimal('0.1'), ROUND_HALF_UP)) return float(savings), pct def record_sale_savings(conn, sale_id): """Recalculate and record savings for all items in a sale. Called after sale is created. Updates sale_items.savings_amount and sales.total_savings. Also updates customers.total_savings. """ cur = conn.cursor() # Get all items with their retail prices cur.execute(""" SELECT si.id, si.inventory_id, si.unit_price, si.quantity, i.retail_price FROM sale_items si LEFT JOIN inventory i ON si.inventory_id = i.id WHERE si.sale_id = %s """, (sale_id,)) total_savings = Decimal('0') for row in cur.fetchall(): item_id, inv_id, unit_price, qty, retail_price = row savings, _ = calculate_item_savings(unit_price, retail_price, qty) if savings > 0: cur.execute(""" UPDATE sale_items SET savings_amount = %s WHERE id = %s """, (savings, item_id)) total_savings += Decimal(str(savings)) # Update sale total savings cur.execute(""" UPDATE sales SET total_savings = %s WHERE id = %s """, (total_savings, sale_id)) # Update customer total savings cur.execute(""" UPDATE customers SET total_savings = COALESCE(total_savings, 0) + %s WHERE id = (SELECT customer_id FROM sales WHERE id = %s) """, (total_savings, sale_id)) conn.commit() cur.close() return float(total_savings) def get_customer_savings_report(conn, customer_id, months=12): """Get savings report for a customer.""" cur = conn.cursor() # Overall savings cur.execute(""" SELECT COALESCE(SUM(total_savings), 0), COUNT(*) FROM sales WHERE customer_id = %s AND status = 'completed' AND total_savings > 0 """, (customer_id,)) total_saved, orders_with_savings = cur.fetchone() # Monthly breakdown cur.execute(""" SELECT date_trunc('month', created_at) as month, COUNT(*) as orders, SUM(total) as spent, SUM(total_savings) as saved FROM sales WHERE customer_id = %s AND status = 'completed' AND total_savings > 0 AND created_at >= NOW() - interval '%s months' GROUP BY date_trunc('month', created_at) ORDER BY month DESC """, (customer_id, months)) monthly = [] for r in cur.fetchall(): monthly.append({ 'month': str(r[0])[:7], 'orders': r[1], 'spent': float(r[2]) if r[2] else 0, 'saved': float(r[3]) if r[3] else 0, 'savings_pct': round(float(r[3]) / float(r[2]) * 100, 1) if r[2] else 0, }) # Top savings items cur.execute(""" SELECT si.name, si.part_number, si.unit_price, si.retail_price, si.savings_amount FROM sale_items si JOIN sales s ON s.id = si.sale_id WHERE s.customer_id = %s AND s.status = 'completed' AND si.savings_amount > 0 ORDER BY si.savings_amount DESC LIMIT 10 """, (customer_id,)) top_items = [] for r in cur.fetchall(): top_items.append({ 'name': r[0], 'part_number': r[1], 'unit_price': float(r[2]) if r[2] else 0, 'retail_price': float(r[3]) if r[3] else 0, 'savings': float(r[4]) if r[4] else 0, }) cur.close() return { 'customer_id': customer_id, 'total_saved': float(total_saved) if total_saved else 0, 'orders_with_savings': orders_with_savings or 0, 'monthly_breakdown': monthly, 'top_items': top_items, } def get_global_savings_stats(conn, tenant_id, from_date=None, to_date=None): """Get global savings stats for a tenant.""" cur = conn.cursor() params = [tenant_id] date_filter = "" if from_date: date_filter += " AND s.created_at >= %s" params.append(from_date) if to_date: date_filter += " AND s.created_at < %s::date + interval '1 day'" params.append(to_date) cur.execute(f""" SELECT COALESCE(SUM(s.total_savings), 0), COUNT(DISTINCT s.customer_id), COUNT(*) as orders, COALESCE(AVG(s.total_savings), 0) FROM sales s JOIN customers c ON s.customer_id = c.id WHERE s.status = 'completed' AND s.total_savings > 0 {date_filter} """, params[1:] if len(params) > 1 else []) total_saved, customers_count, orders, avg_savings = cur.fetchone() cur.execute(f""" SELECT c.name, c.id, SUM(s.total_savings) as saved FROM sales s JOIN customers c ON s.customer_id = c.id WHERE s.status = 'completed' AND s.total_savings > 0 {date_filter} GROUP BY c.id, c.name ORDER BY saved DESC LIMIT 10 """, params[1:] if len(params) > 1 else []) top_customers = [] for r in cur.fetchall(): top_customers.append({'name': r[0], 'id': r[1], 'saved': float(r[2]) if r[2] else 0}) cur.close() return { 'total_saved': float(total_saved) if total_saved else 0, 'customers_count': customers_count or 0, 'orders_count': orders or 0, 'avg_savings_per_order': float(avg_savings) if avg_savings else 0, 'top_customers': top_customers, }