- flask_app.py 1166行→33行纯入口
- 新建 db.py(配置+连接+SQL工具)
- 新建 helpers.py(attach_common/monthly_finance/add_file_index)
- 新建 routes.py(全路由 Blueprint + 装饰器 + TABLES)
- 新建 migrations/seed_data.py(seed_db 搬迁)
- migrations/{tables,columns,data_fixes,seed}.py 改 import 为 from db
- 删除死代码 init_db(228行)+ latest_followup(10行)
- 反向依赖消除:migrations 不再 import flask_app
- 前端零改动,URL 不变
106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
# 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
|