Files
opc-manager/backend/helpers.py
mac ec140bf105 fix: 年份动态计算 + 首页布局优化
- 后端所有硬编码2026替换为date.today().year动态计算
- 首页移除4个快捷跳转卡片
- 首页新增时间段筛选下拉框
- 版本号更新至v1.0.0.3
2026-07-07 17:02:47 +08:00

129 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# helpers.py — 业务查询辅助函数
# 依赖db.py被 routes.py 和 migrations/seed_data.py 调用
import json
from datetime import date
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 ""
# 批量查 filesproposals + 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="科普·无界"):
_year = date.today().year
months = [f"{_year}-{m:02d}" for m in range(1, 13)]
pfs = rows(conn,
"SELECT sign_amount, sign_month, status, budget_data, expense_data FROM project_finances WHERE tenant=? AND status='已签约'",
[tenant])
parsed_budgets = []
expense_maps = {}
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),
}
parsed_budgets.append((pf, budget_map))
# Parse expense_data for cost/paid
try:
ed = json.loads(pf.get("expense_data") or "[]")
except (json.JSONDecodeError, TypeError):
ed = []
em = {}
for e in ed:
key = (e.get("month") or "").replace("-", "_")
em[key] = {
"cost": float(e.get("cost") or 0),
"paid": float(e.get("paid") or 0),
}
expense_maps[pf.get("id", id(pf))] = em
data = []
for month in months:
key = month.replace("-", "_")
revenue = gross = payment = cost = paid = 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"]
em = expense_maps.get(pf.get("id", id(pf)), {})
e = em.get(key)
if e:
cost += e["cost"]
paid += e["paid"]
data.append({
"month": month,
"revenue": round(revenue, 2),
"gross": round(gross, 2),
"payment": round(payment, 2),
"cost": round(cost, 2),
"paid": round(paid, 2),
"sign": round(sign, 2),
})
return data