Files
opc-manager/backend/helpers.py
mac 56feeb0cfe feat: 首页重构 — 财务概览转置+部门经营卡片+命名统一
首页:
- 5卡片→表格(行=指标,列=时间),新增已付/现金流/项目利润行,费用行
- 行列对换:8列(年度/季度/季环比/本月/月环比/上季度/上月)×9行
- 新增「部门经营情况」卡片(部门利润/费用/净利)
- 趋势图3→4列,新增月度项目利润折线图(indigo)
- 命名:利润→项目利润,费用→项目费用,月度利润→月度项目利润
- 卡片改名:经营管理→项目管理,财务概览→业务(项目)财务概览
- 指标列统一颜色,去掉行编号

项目模块:
- 利润→项目利润,费用→项目费用,总费用→总项目费用
- 月度回款与费用→月度回款与已付,柱状图→折线图

后端:
- 新增paid/cashflow/profit指标聚合(单/多租户)
- monthly_finance新增paid字段
- helpers.py修复重复return data
2026-07-06 19:10:14 +08:00

111 lines
4.1 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 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="科普·无界"):
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),
"paid": float(b.get("paid") or 0),
}
parsed_budgets.append((pf, budget_map))
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"]
cost += b["cost"]
paid += b["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