fix(profit-caliber): 博海口径 §4-1/§4-2/§4-5 落地 —— 固定成本仅借方 + 6403 进变动 + 财务收益单列 + 批次备注
ci/woodpecker/push/main Pipeline was successful

ASK-20261014-04(任总 2026-10-14 11:20「放行4」)批后口径:
- §4-1 博海固定成本弃净额:6601/6602/6603 仅借方(133.98 万,2026-01..09);2026-03 单月由 -9.02 万转正;
  6603 贷方(现金折扣收益 64.40 万)改 finance_income_terms 单列备查,不进固定成本/末行/勾稽。
- §4-2 博海变动成本追加 6403 营业税金及附加(530.17 万)。
- §4-3/§4-4 维持现行(酣客收入/边际贡献基数不动)。
- §4-5 管理利润表页加「数据批次/导入时刻」纯展示备注(import_logs)。
- 口径唯一源仍为 bep_caliber.ENTITY_CALIBERS;报表/卡面/派生器一律经 fixed_cost_spec() 解析(读口写口一致)。
This commit is contained in:
Hermes CI Fix
2026-10-16 12:32:52 +08:00
parent ff2d92ba54
commit 289cc8f2cc
7 changed files with 228 additions and 50 deletions
+57 -10
View File
@@ -5,7 +5,11 @@
· 收入 :`caliber['revenue_field']` + `caliber['revenue_filter']`(e1=5001 贷方 / e2=6001 贷方)
· 变动成本 :`caliber['varcost_terms']`(带号科目项;e1 = 5401借 + 5403借 + 571103借 − 530103贷)
· 固定成本 :`caliber['fixed_terms']`(带号科目项,**优先**)否则 `caliber['fixed_filters']`
(前缀元组 → 派生为 `('+', '<前缀>%', 'net')` 借−贷净额;口径升级为 `fixed_terms` 后本层零改动自动跟随)
(前缀元组 → 派生为 `('+', '<前缀>%', 'net')` 借−贷净额);两者一律经
`bep_caliber.fixed_cost_spec()` **唯一解析口**解析(报表/卡面/派生器同源)。
§4-1 批后:博海走 terms(`6601/6602/6603` **仅借方**),酣客仍走 filters(净额)。
· 财务收益 :`caliber['finance_income_terms']`(§4-1:博海 `6603` **贷方**)—— **单列备查**,
**不进**固定成本、不进末行、不参与勾稽(纯展示项)。
· 期间行域 :`carry_forward == 0` + `caliber['exclude_closing']`(结转腿未标账套须显式剔 `summary LIKE '%结转%'`)
· 作废行域 :`apply_voucher_guard`(`voucher_void_marks`,全库唯一实现)
@@ -28,8 +32,10 @@ from typing import Dict, List, Optional, Tuple
from sqlalchemy import func, or_
from app.models import VoucherDetail
from app.utils.bep_caliber import CLOSING_SUMMARY_LIKE, caliber_or_none
from app.models import ImportLog, VoucherDetail
from app.utils.bep_caliber import (CLOSING_SUMMARY_LIKE, caliber_or_none,
finance_income_terms as cal_finance_income_terms,
fixed_cost_spec)
from app.utils.voucher_guard import apply_voucher_guard
# ── 展示口径 ────────────────────────────────────────────────────────────────
@@ -166,14 +172,16 @@ def revenue_terms(cal: dict) -> list:
def fixed_cost_terms(cal: dict) -> Tuple[list, str]:
"""固定成本科目项:优先 `fixed_terms`(带号项),否则由 `fixed_filters` 派生(前缀 + 借−贷净额)。
"""固定成本科目项:**经 `bep_caliber.fixed_cost_spec()` 唯一解析口**。
优先 `fixed_terms`(带号项,§4-1 后博海形态:仅借方),否则由 `fixed_filters` 派生
(前缀 + 借−贷净额,酣客形态)。
返回 (terms, mode);mode 供报表标注当前口径形态(`fixed_terms` / `fixed_filters(net)`)。
"""
terms = cal.get("fixed_terms")
if terms:
return [tuple(t) for t in terms], "fixed_terms"
derived = [("+", str(code) + "%", "net") for _kind, code in (cal.get("fixed_filters") or [])]
mode, spec = fixed_cost_spec(cal)
if mode == "terms":
return [tuple(t) for t in spec], "fixed_terms"
derived = [("+", str(code) + "%", "net") for _kind, code in spec]
return derived, "fixed_filters(net)"
@@ -224,6 +232,7 @@ def compute_lines(db, entity_id: int, period_from: str, period_to: str) -> dict:
exclude_closing = bool(cal.get("exclude_closing"))
fix_terms, fix_mode = fixed_cost_terms(cal)
rev_terms = revenue_terms(cal)
fin_terms = cal_finance_income_terms(cal)
out["caliber"] = {
"entity_id": entity_id,
"name": cal.get("name"),
@@ -233,6 +242,7 @@ def compute_lines(db, entity_id: int, period_from: str, period_to: str) -> dict:
"varcost_terms": [list(t) for t in (cal.get("varcost_terms") or [])],
"fixed_cost_terms": [list(t) for t in fix_terms],
"fixed_mode": fix_mode,
"finance_income_terms": [list(t) for t in fin_terms],
"exclude_closing": exclude_closing,
"closing_subject": cal.get("closing_subject"),
"digest": caliber_digest(cal),
@@ -248,6 +258,10 @@ def compute_lines(db, entity_id: int, period_from: str, period_to: str) -> dict:
var = sum_terms(db, entity_id, period_from, period_to, cal.get("varcost_terms") or [],
exclude_closing=exclude_closing)
fix = sum_terms(db, entity_id, period_from, period_to, fix_terms, exclude_closing=exclude_closing)
# 财务收益(§4-1:博海 6603 贷方)—— 单列备查,**不进**固定成本/末行,不参与勾稽
fin = (sum_terms(db, entity_id, period_from, period_to, fin_terms,
exclude_closing=exclude_closing) if fin_terms
else {"total_yuan": 0.0, "parts": []})
rev_y = rev["total_yuan"]
var_y = var["total_yuan"]
@@ -255,7 +269,8 @@ def compute_lines(db, entity_id: int, period_from: str, period_to: str) -> dict:
con_y = rev_y - var_y # 边际贡献
res_y = con_y - fix_y # 末行(营业利润)
out["rows"] = {"revenue": rev, "variable_cost": var, "fixed_cost": fix}
out["rows"] = {"revenue": rev, "variable_cost": var, "fixed_cost": fix,
"finance_income": fin}
out["raw_yuan"] = {"revenue": round(rev_y, 2), "variable_cost": round(var_y, 2),
"contribution": round(con_y, 2), "fixed_cost": round(fix_y, 2),
"result": round(res_y, 2)}
@@ -265,10 +280,39 @@ def compute_lines(db, entity_id: int, period_from: str, period_to: str) -> dict:
"contribution": round(con_y / YUAN_PER_WAN, ROUND_DP),
"fixed_cost": round(fix_y / YUAN_PER_WAN, ROUND_DP),
"result": round(res_y / YUAN_PER_WAN, ROUND_DP)}
out["finance_income"] = {
"value": round(fin["total_yuan"] / YUAN_PER_WAN, ROUND_DP),
"raw_yuan": round(fin["total_yuan"], ROUND_DP),
"terms": fin["parts"], "unit": UNIT,
"name": "财务收益(单列备查)",
"basis": ("ENTITY_CALIBERS[entity]['finance_income_terms'](§4-1:6603 贷方 = 现金折扣收益等)"
";单列备查,不进固定成本、不参与勾稽"),
}
out["available"] = True
return out
def import_batches(db, entity_id: int, period: str, limit: int = 5) -> list:
"""数据批次 / 导入时刻(**纯展示备注**,4-5)—— 不参与任何计算。
取自 `import_logs`(该账套 + 该期间),倒序最多 `limit` 条:批次号 / 行数 / 导入时刻。
无记录 ⇒ 空列表(页面不显示备注,不编造批次)。
"""
try:
rows = (db.query(ImportLog)
.filter(ImportLog.entity_id == entity_id, ImportLog.period == period)
.order_by(ImportLog.id.desc()).limit(limit).all())
except Exception: # noqa: BLE001 —— 展示项:取数失败不得影响报表主体
return []
return [{"batch": r.batch,
"rows": int(r.success_rows if r.success_rows is not None else (r.total_rows or 0)),
"total_rows": int(r.total_rows or 0),
"failed_rows": int(r.failed_rows or 0),
"import_type": r.import_type,
"imported_at": r.created_at.strftime("%Y-%m-%d %H:%M") if r.created_at else None,
"filename": r.filename} for r in rows]
def reconcile(revenue: Optional[float], variable_cost: Optional[float],
fixed_cost: Optional[float], result: Optional[float],
tol: float = RECON_TOL_WAN) -> dict:
@@ -358,7 +402,10 @@ def build_items(cur: dict, prev: dict, bottom_label: str = BOTTOM_LABEL) -> list
"ratio": ratio_of("fixed_cost", cv),
"raw_yuan": raw("fixed_cost"), "prev_raw_yuan": raw_prev("fixed_cost"),
"terms": terms_of("fixed_cost", cur), "prev_terms": terms_of("fixed_cost", prev),
"unit": UNIT, "basis": "ENTITY_CALIBERS[entity]['fixed_terms'|'fixed_filters'](借−贷净额)"},
"unit": UNIT,
"basis": ("ENTITY_CALIBERS[entity] 经 bep_caliber.fixed_cost_spec() 解析:"
"fixed_terms=带号科目项(§4-1 博海 6601/6602/6603 仅借方)/ "
"fixed_filters=前缀借−贷净额(酣客 5601/5602/5603)")},
{"name": f"= {bottom_label}", "line_key": "operating_profit", "is_total": True,
"value": cv.get("result"), "prev_value": pv.get("result"),
"change_rate": _calc_chg(cv.get("result"), pv.get("result")),