SysOps: deploy-all — 2026-06-09 10:41 UTC

This commit is contained in:
sysops
2026-06-09 10:41:13 +00:00
parent 69fe67cc0e
commit 21ea3a2c81
82 changed files with 8906 additions and 981 deletions
+141 -1
View File
@@ -168,7 +168,15 @@ class DelegateBody(BaseModel):
@admin_router.get("/clients")
def list_clients():
return {"items": _serialize_rows(fetch_all(
"SELECT * FROM clients ORDER BY updated_at DESC NULLS LAST, created_at DESC LIMIT 500"
"""SELECT c.*,
(SELECT COUNT(*) FROM client_supermarket_links l WHERE l.client_id = c.id)
+ (SELECT COUNT(*) FROM supermarkets s WHERE s.client_id = c.id
AND NOT EXISTS (
SELECT 1 FROM client_supermarket_links l2
WHERE l2.supermarket_id = s.id AND l2.client_id = c.id
)) AS store_count
FROM clients c
ORDER BY c.updated_at DESC NULLS LAST, c.created_at DESC LIMIT 500"""
))}
@@ -199,6 +207,79 @@ def delete_client(client_id: int):
return {"ok": True}
@admin_router.get("/clients/stats")
def clients_stats():
stats: dict[str, Any] = {}
try:
rows = fetch_all("SELECT stage, COUNT(*) AS n FROM clients GROUP BY stage")
stats["by_stage"] = {r["stage"]: int(r["n"]) for r in rows}
stats["total"] = sum(stats["by_stage"].values())
stats["active"] = stats["by_stage"].get("active", 0)
row = fetch_one("SELECT COALESCE(SUM(mrr_estimate),0) AS s FROM clients WHERE stage NOT IN ('churned')")
stats["mrr_total"] = float(row["s"] or 0) if row else 0
row = fetch_one(
"""SELECT COALESCE(SUM(d.value),0) AS s FROM deals d
JOIN clients c ON c.id = d.client_id
WHERE d.stage NOT IN ('won','lost')"""
)
stats["pipeline_value"] = float(row["s"] or 0) if row else 0
row = fetch_one("SELECT COUNT(*) AS n FROM client_supermarket_links")
stats["store_links"] = int(row["n"] or 0) if row else 0
row = fetch_one("SELECT COUNT(*) AS n FROM deals")
stats["deals"] = int(row["n"] or 0) if row else 0
except Exception:
stats = {"total": 0, "active": 0, "mrr_total": 0, "pipeline_value": 0, "store_links": 0, "deals": 0, "by_stage": {}}
return {"ok": True, "stats": stats}
@admin_router.get("/clients/{client_id}/detail")
def client_detail(client_id: int):
client = fetch_one("SELECT * FROM clients WHERE id = %s", (client_id,))
if not client:
raise HTTPException(404, "Client not found")
deals = fetch_all(
"""SELECT id, title, value, stage, next_action, deadline, updated_at
FROM deals WHERE client_id = %s ORDER BY updated_at DESC NULLS LAST LIMIT 20""",
(client_id,),
)
stores = fetch_all(
"""
SELECT s.id, s.name, s.chain, s.city, s.province, s.postcode, s.phone, s.email,
s.partnership_status, s.halal_certified, s.has_halal_section, s.manager_name,
l.relationship_type, l.notes AS link_notes, l.deal_id, l.created_at AS linked_at
FROM client_supermarket_links l
JOIN supermarkets s ON s.id = l.supermarket_id
WHERE l.client_id = %s
ORDER BY s.chain, s.city, s.name
""",
(client_id,),
)
direct = fetch_all(
"""
SELECT s.id, s.name, s.chain, s.city, s.province, s.postcode, s.phone, s.email,
s.partnership_status, s.halal_certified, s.has_halal_section, s.manager_name,
'direct' AS relationship_type, NULL AS link_notes, s.deal_id, s.last_updated AS linked_at
FROM supermarkets s
WHERE s.client_id = %s
AND NOT EXISTS (
SELECT 1 FROM client_supermarket_links l
WHERE l.supermarket_id = s.id AND l.client_id = s.client_id
)
ORDER BY s.chain, s.city, s.name
""",
(client_id,),
)
merged: dict[int, dict] = {}
for row in list(stores) + list(direct):
merged[int(row["id"])] = row
return {
"ok": True,
"client": _serialize(client),
"deals": _serialize_rows(deals),
"stores": _serialize_rows(list(merged.values())),
}
# --- Deals CRUD ---
@admin_router.get("/deals")
@@ -239,6 +320,36 @@ def delete_deal(deal_id: int):
# --- Products CRUD ---
@admin_router.get("/products/stats")
def products_stats():
stats: dict[str, Any] = {}
try:
row = fetch_one("SELECT COUNT(*) AS n FROM products")
stats["total"] = int(row["n"] or 0) if row else 0
rows = fetch_all("SELECT status, COUNT(*) AS n FROM products GROUP BY status")
stats["by_status"] = {r["status"]: int(r["n"]) for r in rows}
stats["active"] = stats["by_status"].get("active", 0)
row = fetch_one("SELECT AVG(margin_pct) AS a FROM products WHERE margin_pct IS NOT NULL")
stats["avg_margin"] = round(float(row["a"] or 0), 1) if row else 0
row = fetch_one("SELECT COUNT(*) AS n FROM products WHERE client_id IS NOT NULL")
stats["with_client"] = int(row["n"] or 0) if row else 0
except Exception:
stats = {"total": 0, "active": 0, "avg_margin": 0, "with_client": 0, "by_status": {}}
return {"ok": True, "stats": stats}
@admin_router.get("/products/{product_id}/detail")
def product_detail(product_id: int):
product = fetch_one(
"""SELECT p.*, c.name AS client_name, c.email AS client_email, c.stage AS client_stage
FROM products p LEFT JOIN clients c ON c.id = p.client_id WHERE p.id = %s""",
(product_id,),
)
if not product:
raise HTTPException(404, "Product not found")
return {"ok": True, "product": _serialize(product)}
@admin_router.get("/products")
def list_products():
return {"items": _serialize_rows(fetch_all(
@@ -284,6 +395,35 @@ def margin_calc(body: MarginBody):
# --- Suppliers CRUD ---
@admin_router.get("/suppliers/stats")
def suppliers_stats():
stats: dict[str, Any] = {}
try:
row = fetch_one("SELECT COUNT(*) AS n FROM suppliers")
stats["total"] = int(row["n"] or 0) if row else 0
row = fetch_one("SELECT AVG(rating) AS a FROM suppliers WHERE rating IS NOT NULL")
stats["avg_rating"] = round(float(row["a"] or 0), 1) if row else 0
row = fetch_one("SELECT COUNT(DISTINCT country) AS n FROM suppliers WHERE country IS NOT NULL AND country <> ''")
stats["countries"] = int(row["n"] or 0) if row else 0
row = fetch_one("SELECT AVG(lead_time_days) AS a FROM suppliers WHERE lead_time_days IS NOT NULL")
stats["avg_lead"] = round(float(row["a"] or 0), 0) if row else 0
rows = fetch_all(
"SELECT COALESCE(category, 'Overig') AS category, COUNT(*) AS n FROM suppliers GROUP BY category ORDER BY n DESC LIMIT 8"
)
stats["by_category"] = {r["category"]: int(r["n"]) for r in rows}
except Exception:
stats = {"total": 0, "avg_rating": 0, "countries": 0, "avg_lead": 0, "by_category": {}}
return {"ok": True, "stats": stats}
@admin_router.get("/suppliers/{supplier_id}/detail")
def supplier_detail(supplier_id: int):
supplier = fetch_one("SELECT * FROM suppliers WHERE id = %s", (supplier_id,))
if not supplier:
raise HTTPException(404, "Supplier not found")
return {"ok": True, "supplier": _serialize(supplier)}
@admin_router.get("/suppliers")
def list_suppliers():
return {"items": _serialize_rows(fetch_all("SELECT * FROM suppliers ORDER BY name LIMIT 500"))}