mirror of
https://github.com/Sea-Haven-Industries/meal-order-manager.git
synced 2026-10-02 16:33:13 +00:00
153 lines
4.9 KiB
Python
153 lines
4.9 KiB
Python
|
|
"""
|
||
|
|
Order aggregation script.
|
||
|
|
|
||
|
|
Reads all submitted orders for a given week and produces:
|
||
|
|
1. Order summary — total quantity of each meal (for submitting to Redefine Meals)
|
||
|
|
2. Payroll deduction report — CSV with employee name, email, itemized meals, total amount
|
||
|
|
"""
|
||
|
|
|
||
|
|
import csv
|
||
|
|
import json
|
||
|
|
import sys
|
||
|
|
from collections import defaultdict
|
||
|
|
from datetime import datetime
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||
|
|
CONFIG_PATH = PROJECT_ROOT / "config.json"
|
||
|
|
ORDERS_DIR = PROJECT_ROOT / "orders"
|
||
|
|
OUTPUT_DIR = PROJECT_ROOT / "output"
|
||
|
|
|
||
|
|
|
||
|
|
def load_config():
|
||
|
|
with open(CONFIG_PATH) as f:
|
||
|
|
return json.load(f)
|
||
|
|
|
||
|
|
|
||
|
|
def current_week() -> str:
|
||
|
|
return datetime.now().strftime("%Y-W%U")
|
||
|
|
|
||
|
|
|
||
|
|
def load_orders(week: str) -> list[dict]:
|
||
|
|
week_dir = ORDERS_DIR / week
|
||
|
|
if not week_dir.exists():
|
||
|
|
return []
|
||
|
|
orders = []
|
||
|
|
for f in sorted(week_dir.glob("*.json")):
|
||
|
|
with open(f) as fh:
|
||
|
|
orders.append(json.load(fh))
|
||
|
|
return orders
|
||
|
|
|
||
|
|
|
||
|
|
def generate_order_summary(orders: list[dict]) -> dict:
|
||
|
|
"""Aggregate all meals across employees into a single order for Redefine."""
|
||
|
|
meal_totals = defaultdict(lambda: {"quantity": 0, "unit_price": 0})
|
||
|
|
for order in orders:
|
||
|
|
for item in order.get("items", []):
|
||
|
|
name = item["name"]
|
||
|
|
meal_totals[name]["quantity"] += item.get("quantity", 0)
|
||
|
|
meal_totals[name]["unit_price"] = item.get("price", 0)
|
||
|
|
|
||
|
|
summary = []
|
||
|
|
for name, data in sorted(meal_totals.items()):
|
||
|
|
summary.append({
|
||
|
|
"meal": name,
|
||
|
|
"quantity": data["quantity"],
|
||
|
|
"unit_price": data["unit_price"],
|
||
|
|
"line_total": round(data["unit_price"] * data["quantity"], 2),
|
||
|
|
})
|
||
|
|
|
||
|
|
grand_total = sum(s["line_total"] for s in summary)
|
||
|
|
total_meals = sum(s["quantity"] for s in summary)
|
||
|
|
|
||
|
|
return {
|
||
|
|
"week": orders[0]["week"] if orders else "",
|
||
|
|
"generated_at": datetime.now().isoformat(),
|
||
|
|
"total_employees": len(orders),
|
||
|
|
"total_meals": total_meals,
|
||
|
|
"grand_total": round(grand_total, 2),
|
||
|
|
"meals": summary,
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def generate_payroll_csv(orders: list[dict], output_path: Path):
|
||
|
|
"""Write a CSV for payroll with one row per employee."""
|
||
|
|
with open(output_path, "w", newline="") as f:
|
||
|
|
writer = csv.writer(f)
|
||
|
|
writer.writerow(["Employee Name", "Employee Email", "Items Ordered", "Total Deduction"])
|
||
|
|
for order in sorted(orders, key=lambda o: o["employee_name"]):
|
||
|
|
items_str = "; ".join(
|
||
|
|
f"{item['name']} x{item['quantity']} (${item['subtotal']:.2f})"
|
||
|
|
for item in order.get("items", [])
|
||
|
|
)
|
||
|
|
writer.writerow([
|
||
|
|
order["employee_name"],
|
||
|
|
order["employee_email"],
|
||
|
|
items_str,
|
||
|
|
f"${order['total']:.2f}",
|
||
|
|
])
|
||
|
|
|
||
|
|
|
||
|
|
def generate_order_summary_csv(summary: dict, output_path: Path):
|
||
|
|
"""Write a CSV of the aggregated order for submitting to Redefine."""
|
||
|
|
with open(output_path, "w", newline="") as f:
|
||
|
|
writer = csv.writer(f)
|
||
|
|
writer.writerow(["Meal", "Quantity", "Unit Price", "Line Total"])
|
||
|
|
for meal in summary["meals"]:
|
||
|
|
writer.writerow([
|
||
|
|
meal["meal"],
|
||
|
|
meal["quantity"],
|
||
|
|
f"${meal['unit_price']:.2f}",
|
||
|
|
f"${meal['line_total']:.2f}",
|
||
|
|
])
|
||
|
|
writer.writerow([])
|
||
|
|
writer.writerow(["TOTAL", summary["total_meals"], "", f"${summary['grand_total']:.2f}"])
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
week = sys.argv[1] if len(sys.argv) > 1 else current_week()
|
||
|
|
orders = load_orders(week)
|
||
|
|
|
||
|
|
if not orders:
|
||
|
|
print(f"No orders found for week {week}")
|
||
|
|
print(f" Expected directory: {ORDERS_DIR / week}")
|
||
|
|
sys.exit(1)
|
||
|
|
|
||
|
|
print(f"Processing {len(orders)} orders for week {week}")
|
||
|
|
|
||
|
|
summary = generate_order_summary(orders)
|
||
|
|
OUTPUT_DIR.mkdir(exist_ok=True)
|
||
|
|
|
||
|
|
# Save order summary
|
||
|
|
summary_json = OUTPUT_DIR / f"order-summary-{week}.json"
|
||
|
|
with open(summary_json, "w") as f:
|
||
|
|
json.dump(summary, f, indent=2)
|
||
|
|
|
||
|
|
summary_csv = OUTPUT_DIR / f"order-summary-{week}.csv"
|
||
|
|
generate_order_summary_csv(summary, summary_csv)
|
||
|
|
|
||
|
|
# Save payroll report
|
||
|
|
payroll_csv = OUTPUT_DIR / f"payroll-deductions-{week}.csv"
|
||
|
|
generate_payroll_csv(orders, payroll_csv)
|
||
|
|
|
||
|
|
# Print summary
|
||
|
|
print(f"\n{'='*60}")
|
||
|
|
print(f"ORDER SUMMARY — Week {week}")
|
||
|
|
print(f"{'='*60}")
|
||
|
|
print(f"{'Meal':<45} {'Qty':>4} {'Total':>8}")
|
||
|
|
print("-" * 60)
|
||
|
|
for meal in summary["meals"]:
|
||
|
|
print(f"{meal['meal']:<45} {meal['quantity']:>4} ${meal['line_total']:>7.2f}")
|
||
|
|
print("-" * 60)
|
||
|
|
print(f"{'TOTAL':<45} {summary['total_meals']:>4} ${summary['grand_total']:>7.2f}")
|
||
|
|
print(f"\n{summary['total_employees']} employees ordered")
|
||
|
|
|
||
|
|
print(f"\nFiles generated:")
|
||
|
|
print(f" Order summary: {summary_csv}")
|
||
|
|
print(f" Payroll report: {payroll_csv}")
|
||
|
|
print(f" Raw JSON: {summary_json}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|