feat: 财务报表助手—对外法定报表(三表合一+Excel导出)

This commit is contained in:
Hermes CI Fix
2026-08-04 15:35:15 +08:00
parent 38b603f263
commit f33162b6c9
3 changed files with 894 additions and 1 deletions
+587 -1
View File
@@ -601,7 +601,7 @@ def get_profit_statement(
def _build_new_format_profit(db: Session, period: str) -> dict:
"""构建新30号准则五板块利润表"""
"""构建新30号准则五板块利润表(含附注明细)"""
blocks = []
total_net_profit = 0
all_items_have_data = True
@@ -644,6 +644,9 @@ def _build_new_format_profit(db: Session, period: str) -> dict:
blocks.append(block_result)
total_net_profit += block_subtotal
# 附注明细(对外法定报表披露要求)
notes = _build_profit_notes(db, period, blocks, total_net_profit)
# 合计行:净利润 = 一二三+四+五
return {
"period": period,
@@ -653,10 +656,87 @@ def _build_new_format_profit(db: Session, period: str) -> dict:
"net_profit": round(total_net_profit, 2),
"net_profit_name": "净利润",
"all_items_have_data": all_items_have_data,
"notes": notes,
"prev_period": None, # TODO: P1追溯调整
}
def _build_profit_notes(db: Session, period: str, blocks: list, net_profit: float) -> dict:
"""利润表附注明细 — 收入/费用/财务费用拆解 + 板块勾稽 + 关键比率"""
def amt(code):
return _get_subject_amount(db, code, period)
revenue_main = amt("6001")
revenue_other = amt("6051")
revenue_total = None
if revenue_main is not None or revenue_other is not None:
revenue_total = round((revenue_main or 0) + (revenue_other or 0), 2)
revenue_breakdown = [
{"name": "主营业务收入", "code": "6001", "amount": round(revenue_main, 2) if revenue_main is not None else None},
{"name": "其他业务收入", "code": "6051", "amount": round(revenue_other, 2) if revenue_other is not None else None},
]
expense_items = [
{"name": "营业成本", "code": "6401", "amount": amt("6401")},
{"name": "其他业务成本", "code": "6402", "amount": amt("6402")},
{"name": "销售费用", "code": "6601", "amount": amt("6601")},
{"name": "管理费用", "code": "6602", "amount": amt("6602")},
{"name": "研发费用", "code": "660204", "amount": amt("660204")},
{"name": "经营资产减值损失", "code": "6701", "amount": amt("6701")},
]
expense_breakdown = [
{"name": e["name"], "code": e["code"], "amount": round(e["amount"], 2) if e["amount"] is not None else None}
for e in expense_items
]
finance_breakdown = [
{"name": "利息收入(投资类)", "code": "6011", "amount": amt("6011")},
{"name": "投资收益(投资类)", "code": "6111", "amount": amt("6111")},
{"name": "利息支出(筹资类)", "code": "660301", "amount": amt("660301")},
{"name": "经营汇兑损益", "code": "6603", "amount": amt("6603")},
{"name": "筹资汇兑损益", "code": "660302", "amount": amt("660302")},
]
finance_breakdown = [
{"name": f["name"], "code": f["code"], "amount": round(f["amount"], 2) if f["amount"] is not None else None}
for f in finance_breakdown
]
# 板块勾稽(净利润 = 五板块之和)
block_reconciliation = [
{"name": b["name"], "key": b["key"], "amount": b["subtotal"], "result_name": b["subtotal_name"]}
for b in blocks
]
# 关键比率
key_ratios = []
if revenue_total:
key_ratios.append({
"name": "毛利率",
"value": round((revenue_total - (amt("6401") or 0) - (amt("6402") or 0)) / revenue_total * 100, 2) if (amt("6401") is not None or amt("6402") is not None) else None,
})
key_ratios.append({
"name": "净利率",
"value": round(net_profit / revenue_total * 100, 2),
})
rd = amt("660204")
if rd is not None:
key_ratios.append({"name": "研发费用率", "value": round(rd / revenue_total * 100, 2)})
else:
key_ratios.append({"name": "毛利率", "value": None})
key_ratios.append({"name": "净利率", "value": None})
return {
"revenue_total": round(revenue_total, 2) if revenue_total is not None else None,
"revenue_breakdown": revenue_breakdown,
"expense_breakdown": expense_breakdown,
"finance_breakdown": finance_breakdown,
"block_reconciliation": block_reconciliation,
"net_profit": round(net_profit, 2),
"key_ratios": key_ratios,
}
# ============================================================
# MPM管理层指标计算器 (P2)
# ============================================================
@@ -1247,6 +1327,512 @@ def get_category_map(
}
# ============================================================
# 对外法定报表 — 新30号准则适配 (2027)
# 利润表(五板块+附注) / 资产负债表(新准则科目分类) / 现金流量表(三活动)
# ============================================================
# 资产负债表行项目: (编码列表[(code, sign)], 名称, 板块key, 新准则分类, 是否合计行)
# 新准则分类: operating经营 / investing投资 / financing筹资 / equity权益
BALANCE_SHEET_SECTIONS = [
{
"key": "current_assets",
"name": "流动资产",
"category_label": "经营资产",
"lines": [
{"codes": [("1001", 1), ("1002", 1), ("1012", 1)], "name": "货币资金", "ns_category": "operating"},
{"codes": [("1101", 1)], "name": "交易性金融资产", "ns_category": "investing"},
{"codes": [("1122", 1)], "name": "应收账款", "ns_category": "operating"},
{"codes": [("1123", 1)], "name": "预付账款", "ns_category": "operating"},
{"codes": [("1221", 1)], "name": "其他应收款", "ns_category": "operating"},
{"codes": [("1405", 1)], "name": "存货", "ns_category": "operating"},
],
},
{
"key": "non_current_assets",
"name": "非流动资产",
"category_label": "投资资产",
"lines": [
{"codes": [("1511", 1)], "name": "长期股权投资", "ns_category": "investing"},
{"codes": [("1601", 1), ("1602", -1)], "name": "固定资产净额", "ns_category": "operating"},
{"codes": [("1701", 1)], "name": "无形资产", "ns_category": "operating"},
],
},
{
"key": "current_liabilities",
"name": "流动负债",
"category_label": "经营负债",
"lines": [
{"codes": [("2001", 1)], "name": "短期借款", "ns_category": "financing"},
{"codes": [("2202", 1)], "name": "应付账款", "ns_category": "operating"},
{"codes": [("2203", 1)], "name": "预收账款", "ns_category": "operating"},
{"codes": [("2211", 1)], "name": "应付职工薪酬", "ns_category": "operating"},
{"codes": [("2221", 1)], "name": "应交税费", "ns_category": "operating"},
{"codes": [("2241", 1)], "name": "其他应付款", "ns_category": "operating"},
],
},
{
"key": "non_current_liabilities",
"name": "非流动负债",
"category_label": "筹资负债",
"lines": [
{"codes": [("2501", 1)], "name": "长期借款", "ns_category": "financing"},
{"codes": [("2502", 1)], "name": "应付债券", "ns_category": "financing"},
],
},
{
"key": "equity",
"name": "所有者权益",
"category_label": "所有者权益",
"lines": [
{"codes": [("4001", 1)], "name": "实收资本", "ns_category": "equity"},
{"codes": [("4002", 1)], "name": "资本公积", "ns_category": "equity"},
{"codes": [("4103", 1), ("4104", 1)], "name": "未分配利润", "ns_category": "equity"},
],
},
]
# 资产负债表示例数据(博海科技, 期末/期初, 单位: 千元)
BS_DEMO = {
"1001|1002|1012": {"end": 850, "begin": 780},
"1101": {"end": 250, "begin": 220},
"1122": {"end": 1100, "begin": 1050},
"1123": {"end": 180, "begin": 160},
"1221": {"end": 90, "begin": 80},
"1405": {"end": 930, "begin": 900},
"1511": {"end": 580, "begin": 550},
"1601|1602": {"end": 1220, "begin": 1150},
"1701": {"end": 130, "begin": 120},
"2001": {"end": 1500, "begin": 1300},
"2202": {"end": 1050, "begin": 980},
"2203": {"end": 280, "begin": 250},
"2211": {"end": 140, "begin": 130},
"2221": {"end": 90, "begin": 85},
"2241": {"end": 60, "begin": 50},
"2501": {"end": 800, "begin": 750},
"2502": {"end": 270, "begin": 250},
"4001": {"end": 500, "begin": 500},
"4002": {"end": 180, "begin": 175},
"4103|4104": {"end": 460, "begin": 540},
}
BS_CATEGORY_CN = {
"operating": "经营类",
"investing": "投资类",
"financing": "筹资类",
"equity": "权益类",
}
def _prev_period_str(period: str) -> str:
"""上一期间 YYYY-MM → YYYY-(MM-1)"""
try:
y, m = period.split("-")
y, m = int(y), int(m)
m -= 1
if m <= 0:
m += 12
y -= 1
return f"{y}-{m:02d}"
except Exception:
return period
def _get_bs_amount(db: Session, codes: list, period: str) -> Optional[float]:
"""资产负债表科目余额 — 优先凭证明细,无数据返回 None"""
total = 0.0
has_data = False
try:
from app.models import VoucherDetail
for code, sign in codes:
result = db.query(
func.sum(VoucherDetail.debit_amount - VoucherDetail.credit_amount)
).filter(
VoucherDetail.subject_code == code,
VoucherDetail.period == period,
).scalar()
if result is not None:
total += float(result) * sign
has_data = True
except Exception:
pass
return round(total, 2) if has_data else None
def _bs_line_amount(db: Session, line: dict, period: str, column: str = "end") -> dict:
"""单行:凭证数据优先,否则示例数据(column: end期末 / begin期初)"""
real = _get_bs_amount(db, line["codes"], period)
if real is not None:
return {"value": real, "is_demo": False}
key = "|".join(c for c, _ in line["codes"])
demo = BS_DEMO.get(key)
if demo:
return {"value": demo.get(column, demo["end"]), "is_demo": True}
return {"value": None, "is_demo": True}
@router.get("/balance-sheet")
def get_balance_sheet(
period: str = Query(None, description="格式 YYYY-MM"),
db: Session = Depends(get_db),
):
"""资产负债表 — 新30号准则科目分类(经营/投资/筹资),期末vs期初"""
if period is None:
period = datetime.now().strftime("%Y-%m")
prev_period = _prev_period_str(period)
sections = []
total_assets_end = total_assets_begin = 0
total_liab_end = total_liab_begin = 0
total_equity_end = total_equity_begin = 0
all_real = True
for sec in BALANCE_SHEET_SECTIONS:
lines = []
sec_end = sec_begin = 0.0
sec_real = False
for line in sec["lines"]:
end = _bs_line_amount(db, line, period, column="end")
begin = _bs_line_amount(db, line, prev_period, column="begin")
if end["is_demo"] or begin["is_demo"]:
all_real = False
if end["value"] is not None:
sec_end += end["value"]
sec_real = True
if begin["value"] is not None:
sec_begin += begin["value"]
lines.append({
"name": line["name"],
"ns_category": line["ns_category"],
"ns_category_label": BS_CATEGORY_CN.get(line["ns_category"], line["ns_category"]),
"end_value": round(end["value"], 2) if end["value"] is not None else None,
"begin_value": round(begin["value"], 2) if begin["value"] is not None else None,
"is_demo": end["is_demo"] or begin["is_demo"],
})
# 归属汇总
if sec["key"] in ("current_assets", "non_current_assets"):
total_assets_end += sec_end
total_assets_begin += sec_begin
elif sec["key"] in ("current_liabilities", "non_current_liabilities"):
total_liab_end += sec_end
total_liab_begin += sec_begin
else:
total_equity_end += sec_end
total_equity_begin += sec_begin
sections.append({
"key": sec["key"],
"name": sec["name"],
"category_label": sec["category_label"],
"subtotal_end": round(sec_end, 2),
"subtotal_begin": round(sec_begin, 2),
"lines": lines,
"has_real_data": sec_real,
})
return {
"period": period,
"prev_period": prev_period,
"title": f"资产负债表 — 新30号准则({period}",
"sections": sections,
"totals": {
"assets": {"end": round(total_assets_end, 2), "begin": round(total_assets_begin, 2)},
"liabilities": {"end": round(total_liab_end, 2), "begin": round(total_liab_begin, 2)},
"equity": {"end": round(total_equity_end, 2), "begin": round(total_equity_begin, 2)},
"liab_equity": {"end": round(total_liab_end + total_equity_end, 2),
"begin": round(total_liab_begin + total_equity_begin, 2)},
"balanced": abs(total_assets_end - total_liab_end - total_equity_end) < 0.01
and abs(total_assets_begin - total_liab_begin - total_equity_begin) < 0.01,
},
"all_items_have_data": all_real,
}
# 现金流量表行项目: (code, 名称, 板块, 方向, kpi_code可选)
CASH_FLOW_LINES = [
# 经营活动
{"code": "CF01", "name": "销售商品、提供劳务收到的现金", "section": "operating", "sign": 1, "kpi_code": None},
{"code": "CF02", "name": "收到的税费返还", "section": "operating", "sign": 1, "kpi_code": None},
{"code": "CF03", "name": "收到其他与经营活动有关的现金", "section": "operating", "sign": 1, "kpi_code": None},
{"code": "CF04", "name": "购买商品、接受劳务支付的现金", "section": "operating", "sign": -1, "kpi_code": None},
{"code": "CF05", "name": "支付给职工以及为职工支付的现金", "section": "operating", "sign": -1, "kpi_code": None},
{"code": "CF06", "name": "支付的各项税费", "section": "operating", "sign": -1, "kpi_code": None},
{"code": "CF07", "name": "支付其他与经营活动有关的现金", "section": "operating", "sign": -1, "kpi_code": None},
# 投资活动
{"code": "CF08", "name": "收回投资收到的现金", "section": "investing", "sign": 1, "kpi_code": None},
{"code": "CF09", "name": "取得投资收益收到的现金", "section": "investing", "sign": 1, "kpi_code": None},
{"code": "CF10", "name": "处置固定资产、无形资产等收回的现金", "section": "investing", "sign": 1, "kpi_code": None},
{"code": "CF11", "name": "购建固定资产、无形资产等支付的现金", "section": "investing", "sign": -1, "kpi_code": None},
{"code": "CF12", "name": "投资支付的现金", "section": "investing", "sign": -1, "kpi_code": None},
# 筹资活动
{"code": "CF13", "name": "吸收投资收到的现金", "section": "financing", "sign": 1, "kpi_code": None},
{"code": "CF14", "name": "取得借款收到的现金", "section": "financing", "sign": 1, "kpi_code": None},
{"code": "CF15", "name": "偿还债务支付的现金", "section": "financing", "sign": -1, "kpi_code": None},
{"code": "CF16", "name": "分配股利、利润或偿付利息支付的现金", "section": "financing", "sign": -1, "kpi_code": None},
]
# 现金流量表示例数据(2026-06, 单位: 千元, 与利润表/KPI口径一致)
CF_DEMO = {
"CF01": 5200, "CF02": 0, "CF03": 120, "CF04": -3150, "CF05": -820,
"CF06": -360, "CF07": -140,
"CF08": 200, "CF09": 50, "CF10": 30, "CF11": -330, "CF12": -100,
"CF13": 0, "CF14": 500, "CF15": -200, "CF16": -150,
}
CF_DEMO_FX = 0 # 汇率变动对现金的影响
CF_DEMO_BEGIN = 1200 # 期初现金及现金等价物余额
def _get_cf_amount(db: Session, line: dict, period: str) -> dict:
"""现金流量表行项目 — 优先KPI/凭证,否则示例数据"""
# 经营净额行特殊处理:优先取 F_OP_CFLOW
if line.get("kpi_code"):
kpi_val = _get_kpi_val(db, line["kpi_code"], period)
if kpi_val is not None:
return {"value": round(kpi_val, 2), "is_demo": False}
real = _get_bs_amount(db, [(line["code"], line["sign"])], period)
if real is not None:
return {"value": real, "is_demo": False}
demo = CF_DEMO.get(line["code"])
if demo is not None:
return {"value": float(demo), "is_demo": True}
return {"value": None, "is_demo": True}
@router.get("/cash-flow")
def get_cash_flow_statement(
period: str = Query(None, description="格式 YYYY-MM"),
db: Session = Depends(get_db),
):
"""现金流量表 — 经营/投资/筹资三活动(新30号准则直接法)"""
if period is None:
period = datetime.now().strftime("%Y-%m")
section_cfg = [
{"key": "operating", "name": "一、经营活动产生的现金流量", "short": "经营活动"},
{"key": "investing", "name": "二、投资活动产生的现金流量", "short": "投资活动"},
{"key": "financing", "name": "三、筹资活动产生的现金流量", "short": "筹资活动"},
]
sections = []
net_by_section = {}
all_real = True
for sc in section_cfg:
lines = []
subtotal = 0.0
sec_real = False
for line in CASH_FLOW_LINES:
if line["section"] != sc["key"]:
continue
v = _get_cf_amount(db, line, period)
if v["is_demo"]:
all_real = False
if v["value"] is not None:
subtotal += v["value"]
sec_real = True
lines.append({
"code": line["code"],
"name": line["name"],
"value": round(v["value"], 2) if v["value"] is not None else None,
"is_demo": v["is_demo"],
})
net_by_section[sc["key"]] = round(subtotal, 2)
sections.append({
"key": sc["key"],
"name": sc["name"],
"short": sc["short"],
"net": round(subtotal, 2),
"lines": lines,
"has_real_data": sec_real,
})
# 经营净额行优先取 KPI F_OP_CFLOW(真实数据优先)
op_kpi = _get_kpi_val(db, "F_OP_CFLOW", period)
if op_kpi is not None:
sections[0]["net"] = round(op_kpi, 2)
net_by_section["operating"] = round(op_kpi, 2)
fx = _get_kpi_val(db, "F_FX_LOSS", period)
if fx is None:
fx = CF_DEMO_FX
fx_demo = True
else:
fx_demo = False
begin_cash = CF_DEMO_BEGIN
net_increase = round(net_by_section.get("operating", 0)
+ net_by_section.get("investing", 0)
+ net_by_section.get("financing", 0)
+ float(fx or 0), 2)
end_cash = round(begin_cash + net_increase, 2)
return {
"period": period,
"title": f"现金流量表 — 新30号准则({period}",
"sections": sections,
"fx_effect": {"name": "四、汇率变动对现金及现金等价物的影响", "value": round(float(fx), 2), "is_demo": fx_demo},
"summary": {
"net_increase": net_increase,
"begin_cash": begin_cash,
"end_cash": end_cash,
},
"all_items_have_data": all_real,
}
@router.get("/statutory")
def get_statutory_reports(
period: str = Query(None, description="格式 YYYY-MM"),
db: Session = Depends(get_db),
):
"""对外法定报表(新30号准则)— 利润表+资产负债表+现金流量表 组合视图"""
if period is None:
period = datetime.now().strftime("%Y-%m")
return {
"period": period,
"title": f"对外法定报表 — 新30号准则({period}",
"profit": _build_new_format_profit(db, period),
"balance_sheet": get_balance_sheet(period=period, db=db),
"cash_flow": get_cash_flow_statement(period=period, db=db),
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
}
@router.get("/statutory/export")
def export_statutory_reports(
period: str = Query(None, description="格式 YYYY-MM"),
db: Session = Depends(get_db),
):
"""导出对外法定报表(新30号准则)— Excel 三表合一"""
if period is None:
period = datetime.now().strftime("%Y-%m")
data = get_statutory_reports(period=period, db=db)
from io import BytesIO
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from fastapi.responses import StreamingResponse
wb = Workbook()
head_fill = PatternFill("solid", fgColor="305496")
head_font = Font(color="FFFFFF", bold=True, size=12)
section_fill = PatternFill("solid", fgColor="D9E1F2")
section_font = Font(bold=True, size=11)
total_fill = PatternFill("solid", fgColor="FCE4D6")
total_font = Font(bold=True, size=11)
thin = Side(style="thin", color="BFBFBF")
border = Border(left=thin, right=thin, top=thin, bottom=thin)
demo_font = Font(color="D46B08", size=9, italic=True)
def style_header(ws, row, ncols, title):
ws.merge_cells(start_row=row, start_column=1, end_row=row, end_column=ncols)
c = ws.cell(row=row, column=1, value=title)
c.font = Font(bold=True, size=14, color="305496")
c.alignment = Alignment(horizontal="center", vertical="center")
ws.row_dimensions[row].height = 26
def write_row(ws, r, values, font=None, fill=None, use_border=True):
for ci, v in enumerate(values, start=1):
c = ws.cell(row=r, column=ci, value=v)
if font:
c.font = font
if fill:
c.fill = fill
if use_border:
c.border = border
return r + 1
# ── Sheet 1: 利润表(五板块+附注) ──
ws1 = wb.active
ws1.title = "利润表-新30号准则"
style_header(ws1, 1, 4, f"利润表(新30号准则五板块) {period} 单位: 元")
r = write_row(ws1, 2, ["板块", "项目", "科目编码", "本期金额"], head_font, head_fill)
profit = data["profit"]
for block in profit.get("blocks", []):
r = write_row(ws1, r, [block["name"], block.get("subtotal_name", ""), "", block["subtotal"]],
section_font, section_fill)
for item in block.get("items", []):
r = write_row(ws1, r, ["", item["name"], item["code"], item["effective"]])
r = write_row(ws1, r, ["合计", "净利润", "", profit.get("net_profit")], total_font, total_fill)
# 附注明细
notes = profit.get("notes") or {}
r += 1
r = write_row(ws1, r, ["附注一、收入构成", "", "", ""], section_font, section_fill)
for n in notes.get("revenue_breakdown", []):
r = write_row(ws1, r, ["", n["name"], n["code"], n["amount"]])
r = write_row(ws1, r, ["", "营业收入合计", "", notes.get("revenue_total")], total_font)
r = write_row(ws1, r, ["附注二、费用构成", "", "", ""], section_font, section_fill)
for n in notes.get("expense_breakdown", []):
r = write_row(ws1, r, ["", n["name"], n["code"], n["amount"]])
r = write_row(ws1, r, ["附注三、财务费用拆解", "", "", ""], section_font, section_fill)
for n in notes.get("finance_breakdown", []):
r = write_row(ws1, r, ["", n["name"], n["code"], n["amount"]])
r = write_row(ws1, r, ["附注四、板块勾稽(净利润=五板块之和)", "", "", ""], section_font, section_fill)
for n in notes.get("block_reconciliation", []):
r = write_row(ws1, r, ["", n["name"], "", n["amount"]])
r = write_row(ws1, r, ["", "净利润", "", notes.get("net_profit")], total_font, total_fill)
r = write_row(ws1, r, ["附注五、关键比率", "", "", ""], section_font, section_fill)
for n in notes.get("key_ratios", []):
r = write_row(ws1, r, ["", n["name"], "", n["value"]])
for col, w in zip("ABCD", [28, 40, 14, 18]):
ws1.column_dimensions[col].width = w
# ── Sheet 2: 资产负债表 ──
ws2 = wb.create_sheet("资产负债表-新30号准则")
bs = data["balance_sheet"]
style_header(ws2, 1, 5, f"资产负债表(新30号准则科目分类) {period} 单位: 元")
r = write_row(ws2, 2, ["项目", "新准则分类", "期末余额", "期初余额", "数据来源"], head_font, head_fill)
for sec in bs.get("sections", []):
r = write_row(ws2, r, [sec["name"], sec.get("category_label", ""), sec["subtotal_end"], sec["subtotal_begin"], "小计"],
section_font, section_fill)
for line in sec.get("lines", []):
src = "示例" if line.get("is_demo") else "凭证"
r = write_row(ws2, r, [line["name"], line.get("ns_category_label", ""),
line["end_value"], line["begin_value"], src])
t = bs.get("totals", {})
r = write_row(ws2, r, ["资产总计", "", t["assets"]["end"], t["assets"]["begin"], ""], total_font, total_fill)
r = write_row(ws2, r, ["负债合计", "", t["liabilities"]["end"], t["liabilities"]["begin"], ""], total_font, total_fill)
r = write_row(ws2, r, ["所有者权益合计", "", t["equity"]["end"], t["equity"]["begin"], ""], total_font, total_fill)
r = write_row(ws2, r, ["负债和所有者权益总计", "", t["liab_equity"]["end"], t["liab_equity"]["begin"], ""], total_font, total_fill)
r = write_row(ws2, r, ["勾稽校验(资产=负债+权益)", "", "✓ 平衡" if t.get("balanced") else "✗ 不平", "", ""], total_font, total_fill)
for col, w in zip("ABCDE", [36, 16, 18, 18, 12]):
ws2.column_dimensions[col].width = w
# ── Sheet 3: 现金流量表 ──
ws3 = wb.create_sheet("现金流量表-新30号准则")
cf = data["cash_flow"]
style_header(ws3, 1, 4, f"现金流量表(新30号准则三活动) {period} 单位: 元")
r = write_row(ws3, 2, ["项目", "行次", "本期金额", "数据来源"], head_font, head_fill)
for sec in cf.get("sections", []):
r = write_row(ws3, r, [sec["name"], "", "", "小计"], section_font, section_fill)
for line in sec.get("lines", []):
src = "示例" if line.get("is_demo") else "凭证"
r = write_row(ws3, r, [line["name"], line["code"], line["value"], src])
r = write_row(ws3, r, [f"{sec['name']}净额", "", sec["net"], ""], total_font, total_fill)
fx = cf.get("fx_effect", {})
r = write_row(ws3, r, [fx.get("name", ""), "", fx.get("value"), "示例" if fx.get("is_demo") else "凭证"], section_font, section_fill)
s = cf.get("summary", {})
r = write_row(ws3, r, ["现金及现金等价物净增加额", "", s.get("net_increase"), ""], total_font, total_fill)
r = write_row(ws3, r, ["加:期初现金及现金等价物余额", "", s.get("begin_cash"), ""], total_font, total_fill)
r = write_row(ws3, r, ["期末现金及现金等价物余额", "", s.get("end_cash"), ""], total_font, total_fill)
for col, w in zip("ABCD", [46, 10, 18, 12]):
ws3.column_dimensions[col].width = w
buf = BytesIO()
wb.save(buf)
buf.seek(0)
from urllib.parse import quote
filename = f"对外法定报表_新30号准则_{period}.xlsx"
return StreamingResponse(
buf,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": f"attachment; filename*=UTF-8''{quote(filename)}"},
)
@router.get("/dupont")
def get_dupont_analysis(
entity: str = Query("bohai"),