# helpers.py — 业务查询辅助函数 # 依赖:db.py;被 routes.py 和 migrations/seed_data.py 调用 import json from pathlib import Path from db import _exec, rows, one def add_file_index(conn, module, owner_id, owner_version, category, path, external=True): path = Path(path) if not path.exists(): return _exec(conn, """INSERT INTO file_assets (module,owner_id,owner_version,file_category,file_name,file_type,file_size,file_path,is_external) VALUES (?,?,?,?,?,?,?,?,?)""", (module, owner_id, owner_version, category, path.name, path.suffix.lower().lstrip("."), path.stat().st_size, str(path), 1 if external else 0), ) def attach_common(conn, resource, items): """批量加载 followups 和 files,避免 N+1 查询""" if not items: return items target_map = {"sales": "sales", "proposals": "proposal", "operations": "operation", "products": "product"} target_type = target_map.get(resource) ids = [item["id"] for item in items] # 批量查 followups(一次性 IN 查询) if target_type: placeholders = ",".join(["?"] * len(ids)) all_followups = rows( conn, f"SELECT * FROM follow_up_records WHERE target_type=? AND target_id IN ({placeholders}) ORDER BY followed_at DESC, id DESC", [target_type] + ids, ) followups_by_id = {} for fu in all_followups: followups_by_id.setdefault(fu["target_id"], []).append(fu) for item in items: item["followups"] = followups_by_id.get(item["id"], []) item["latest_follow_up_record"] = item["followups"][0]["content"] if item["followups"] else "" # 批量查 files(proposals + operations) file_modules = {"proposals": "proposal", "operations": "operation"} if resource in file_modules: module = file_modules[resource] placeholders = ",".join(["?"] * len(ids)) all_files = rows( conn, f"SELECT * FROM file_assets WHERE module=? AND owner_id IN ({placeholders}) ORDER BY id DESC", [module] + ids, ) files_by_id = {} for f in all_files: files_by_id.setdefault(f["owner_id"], []).append(f) for item in items: item["files"] = files_by_id.get(item["id"], []) return items def monthly_finance(conn, tenant="科普·无界"): months = [f"2026-{m:02d}" for m in range(1, 13)] pfs = rows(conn, "SELECT sign_amount, sign_month, status, budget_data FROM project_finances WHERE tenant=? AND status='已签约'", [tenant]) parsed_budgets = [] for pf in pfs: try: budget = json.loads(pf.get("budget_data") or "[]") except (json.JSONDecodeError, TypeError): budget = [] budget_map = {} for b in budget: key = (b.get("month") or "").replace("-", "_") budget_map[key] = { "rev": float(b.get("rev") or 0), "gross": float(b.get("gross") or 0), "payment": float(b.get("payment") or 0), "cost": float(b.get("cost") or 0), } parsed_budgets.append((pf, budget_map)) data = [] for month in months: key = month.replace("-", "_") revenue = gross = payment = cost = sign = 0 for pf, budget_map in parsed_budgets: if pf["status"] == "已签约" and (pf.get("sign_month") or "") == month: sign += float(pf["sign_amount"] or 0) b = budget_map.get(key) if b: revenue += b["rev"] gross += b["gross"] payment += b["payment"] cost += b["cost"] data.append({ "month": month, "revenue": revenue, "labor": 0, "expense": 0, "purchase": 0, "gross": gross, "sign": sign, "payment": payment, "cost": cost, }) return data