import csv import io from fastapi import APIRouter from fastapi.responses import StreamingResponse from ..services import order_reader, validation_service from ..database import get_sqlite router = APIRouter(prefix="/api/validate", tags=["validation"]) @router.post("/scan") async def scan_and_validate(): """Scan JSON files and validate all SKUs.""" orders, json_count = order_reader.read_json_orders() if not orders: return {"orders": 0, "json_files": json_count, "skus": {}, "message": "No orders found"} all_skus = order_reader.get_all_skus(orders) result = validation_service.validate_skus(all_skus) importable, skipped = validation_service.classify_orders(orders, result) # Track missing SKUs in SQLite db = await get_sqlite() try: for sku in result["missing"]: # Find product name from orders product_name = "" for order in orders: for item in order.items: if item.sku == sku: product_name = item.name break if product_name: break await db.execute(""" INSERT OR IGNORE INTO missing_skus (sku, product_name) VALUES (?, ?) """, (sku, product_name)) await db.commit() finally: await db.close() return { "json_files": json_count, "total_orders": len(orders), "total_skus": len(all_skus), "importable": len(importable), "skipped": len(skipped), "skus": { "mapped": len(result["mapped"]), "direct": len(result["direct"]), "missing": len(result["missing"]), "missing_list": sorted(result["missing"]) }, "skipped_orders": [ { "number": order.number, "customer": order.billing.company_name or f"{order.billing.firstname} {order.billing.lastname}", "items_count": len(order.items), "missing_skus": missing } for order, missing in skipped[:50] # limit to 50 ] } @router.get("/missing-skus") async def get_missing_skus(): """Get all tracked missing SKUs.""" db = await get_sqlite() try: cursor = await db.execute(""" SELECT sku, product_name, first_seen, resolved, resolved_at FROM missing_skus ORDER BY resolved ASC, first_seen DESC """) rows = await cursor.fetchall() return { "missing_skus": [dict(row) for row in rows], "total": len(rows), "unresolved": sum(1 for r in rows if not r["resolved"]) } finally: await db.close() @router.get("/missing-skus-csv") async def export_missing_skus_csv(): """Export missing SKUs as CSV.""" db = await get_sqlite() try: cursor = await db.execute(""" SELECT sku, product_name, first_seen, resolved FROM missing_skus WHERE resolved = 0 ORDER BY first_seen DESC """) rows = await cursor.fetchall() finally: await db.close() output = io.StringIO() writer = csv.writer(output) writer.writerow(["sku", "product_name", "first_seen"]) for row in rows: writer.writerow([row["sku"], row["product_name"], row["first_seen"]]) return StreamingResponse( io.BytesIO(output.getvalue().encode("utf-8-sig")), media_type="text/csv", headers={"Content-Disposition": "attachment; filename=missing_skus.csv"} )