feat(new30-p1): 追溯调整数据准备+KPI口径更新
- 后端新增 GET /api/cma/reports/restatement 端点 - 旧口径vs新口径双列对比 - 自动标记需调整项(管理费用剥离研发费用、财务费用拆解) - 返回净利润对比汇总 - 前端报表中心新增"追溯调整"Tab - 双列表格展示(旧准则/新准则) - 差异自动高亮标记 - 调整原因列、汇总统计 - KPI口径更新(entity_id=1) - F_COST_RATIO 费用率: (销售费用+管理费用-研发费用)/营业收入×100%, target=15% - 新增 F_OP_PROFIT_MARGIN 经营利润率: 经营利润/营业收入×100%, target=5% - 构建部署验证通过
This commit is contained in:
@@ -650,6 +650,319 @@ def _get_demo_block_total(block_key: str) -> float:
|
|||||||
return demo.get(block_key, 0)
|
return demo.get(block_key, 0)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 追溯调整: 2026年数据按新30号准则重述 (P1)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/restatement")
|
||||||
|
def get_restatement(
|
||||||
|
period: str = Query(None, description="格式 YYYY-MM"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""2026年数据按新准则重述 — 旧口径vs新口径双列对比,自动标记调整项"""
|
||||||
|
if period is None:
|
||||||
|
period = datetime.now().strftime("%Y-%m")
|
||||||
|
|
||||||
|
# 旧口径数据 (传统利润表项目)
|
||||||
|
def _old_kpi_val(code: str):
|
||||||
|
kpi = db.query(KPIDefinition).filter(KPIDefinition.kpi_code == code).first()
|
||||||
|
if not kpi:
|
||||||
|
return None
|
||||||
|
v = db.query(KPIValue).filter(
|
||||||
|
KPIValue.kpi_id == kpi.id, KPIValue.period == period
|
||||||
|
).order_by(KPIValue.id.desc()).first()
|
||||||
|
return float(v.actual_value) if v and v.actual_value is not None else None
|
||||||
|
|
||||||
|
old_revenue = _old_kpi_val("F_REVENUE")
|
||||||
|
old_revenue_other = _old_kpi_val("F_REVENUE_OTHER")
|
||||||
|
old_cost = _old_kpi_val("F_COST")
|
||||||
|
old_cost_other = _old_kpi_val("F_COST_OTHER")
|
||||||
|
old_selling_exp = _old_kpi_val("F_SELLING_EXP")
|
||||||
|
old_admin_exp = _old_kpi_val("F_ADMIN_EXP")
|
||||||
|
old_finance_exp = _old_kpi_val("F_FINANCE_EXP")
|
||||||
|
old_rd_exp = _old_kpi_val("F_RD_EXP")
|
||||||
|
|
||||||
|
# 新口径数据 (从科目映射或kpi_values获取)
|
||||||
|
new_revenue = _get_subject_amount(db, "6001", period)
|
||||||
|
new_revenue_other = _get_subject_amount(db, "6051", period)
|
||||||
|
new_cost = _get_subject_amount(db, "6401", period)
|
||||||
|
new_cost_other = _get_subject_amount(db, "6402", period)
|
||||||
|
new_selling = _get_subject_amount(db, "6601", period)
|
||||||
|
new_admin = _get_subject_amount(db, "6602", period)
|
||||||
|
new_rd = _get_subject_amount(db, "660204", period)
|
||||||
|
new_interest_income = _get_subject_amount(db, "6011", period)
|
||||||
|
new_interest_exp = _get_subject_amount(db, "660301", period)
|
||||||
|
new_fx = _get_subject_amount(db, "6603", period)
|
||||||
|
new_fx_financing = _get_subject_amount(db, "660302", period)
|
||||||
|
new_invest_income = _get_subject_amount(db, "6111", period)
|
||||||
|
new_impairment = _get_subject_amount(db, "6701", period)
|
||||||
|
new_invest_impairment = _get_subject_amount(db, "670101", period)
|
||||||
|
new_tax = _get_subject_amount(db, "6801", period)
|
||||||
|
new_discontinued = _get_subject_amount(db, "6901", period)
|
||||||
|
|
||||||
|
# 旧口径汇总计算
|
||||||
|
old_operating_items = [
|
||||||
|
{"name": "营业收入", "old_value": old_revenue, "category": "operating"},
|
||||||
|
{"name": "其他业务收入", "old_value": old_revenue_other, "category": "operating"},
|
||||||
|
{"name": "减:营业成本", "old_value": old_cost, "category": "operating"},
|
||||||
|
{"name": "减:其他业务成本", "old_value": old_cost_other, "category": "operating"},
|
||||||
|
{"name": "减:销售费用", "old_value": old_selling_exp, "category": "operating"},
|
||||||
|
{"name": "减:管理费用", "old_value": old_admin_exp, "category": "operating"},
|
||||||
|
{"name": "减:财务费用", "old_value": old_finance_exp, "category": "financing"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# 新口径项目明细
|
||||||
|
new_items_map = [
|
||||||
|
# 经营类
|
||||||
|
{"name": "营业收入", "new_value": new_revenue, "category": "operating"},
|
||||||
|
{"name": "其他业务收入", "new_value": new_revenue_other, "category": "operating"},
|
||||||
|
{"name": "减:营业成本", "new_value": new_cost, "category": "operating"},
|
||||||
|
{"name": "减:其他业务成本", "new_value": new_cost_other, "category": "operating"},
|
||||||
|
{"name": "减:销售费用", "new_value": new_selling, "category": "operating"},
|
||||||
|
{"name": "减:管理费用(不含研发)", "new_value": new_admin, "category": "operating"},
|
||||||
|
{"name": "减:研发费用", "new_value": new_rd, "category": "operating"},
|
||||||
|
{"name": "减:经营资产减值损失", "new_value": new_impairment, "category": "operating"},
|
||||||
|
{"name": "经营汇兑损益", "new_value": new_fx, "category": "operating"},
|
||||||
|
# 投资类
|
||||||
|
{"name": "利息收入", "new_value": new_interest_income, "category": "investing"},
|
||||||
|
{"name": "投资收益", "new_value": new_invest_income, "category": "investing"},
|
||||||
|
{"name": "减:投资类资产减值", "new_value": new_invest_impairment, "category": "investing"},
|
||||||
|
# 筹资类
|
||||||
|
{"name": "减:利息支出", "new_value": new_interest_exp, "category": "financing"},
|
||||||
|
{"name": "筹资汇兑损益", "new_value": new_fx_financing, "category": "financing"},
|
||||||
|
# 所得税
|
||||||
|
{"name": "减:所得税费用", "new_value": new_tax, "category": "tax"},
|
||||||
|
# 终止经营
|
||||||
|
{"name": "终止经营损益", "new_value": new_discontinued, "category": "discontinued"},
|
||||||
|
]
|
||||||
|
|
||||||
|
# 构建对比行
|
||||||
|
items = []
|
||||||
|
adjusted_count = 0
|
||||||
|
new_count = 0
|
||||||
|
unchanged_count = 0
|
||||||
|
|
||||||
|
# 先处理旧口径中存在的项目,匹配新口径
|
||||||
|
old_new_mapping = {
|
||||||
|
"营业收入": "营业收入",
|
||||||
|
"其他业务收入": "其他业务收入",
|
||||||
|
"减:营业成本": "减:营业成本",
|
||||||
|
"减:其他业务成本": "减:其他业务成本",
|
||||||
|
"减:销售费用": "减:销售费用",
|
||||||
|
"减:管理费用": "减:管理费用(不含研发)",
|
||||||
|
}
|
||||||
|
|
||||||
|
for old_item in old_operating_items:
|
||||||
|
name = old_item["name"]
|
||||||
|
old_val = old_item["old_value"]
|
||||||
|
new_name = old_new_mapping.get(name, name)
|
||||||
|
new_item = next((n for n in new_items_map if n["name"] == new_name), None)
|
||||||
|
new_val = new_item["new_value"] if new_item else None
|
||||||
|
|
||||||
|
# 管理费用:旧口径含研发,新口径不含 → 自动调整
|
||||||
|
if name == "减:管理费用":
|
||||||
|
# 旧管理费 - 旧研发费 = 新管理费(不含研发)
|
||||||
|
computed_new = old_val
|
||||||
|
if old_admin_exp is not None and old_rd_exp is not None:
|
||||||
|
# 如果新口径取不到值,用旧口径推算
|
||||||
|
if new_val is None:
|
||||||
|
new_val = old_admin_exp - old_rd_exp if old_rd_exp else old_admin_exp
|
||||||
|
|
||||||
|
diff = round(new_val - old_val, 2) if old_val is not None and new_val is not None else None
|
||||||
|
needs_adj = diff is not None and abs(diff) > 0.01
|
||||||
|
if needs_adj:
|
||||||
|
adjusted_count += 1
|
||||||
|
items.append({
|
||||||
|
"item_name": name,
|
||||||
|
"old_value": old_val,
|
||||||
|
"new_value": new_val,
|
||||||
|
"difference": diff,
|
||||||
|
"needs_adjustment": needs_adj,
|
||||||
|
"adjustment_reason": "研发费用剥离" if needs_adj else None,
|
||||||
|
"category": old_item["category"],
|
||||||
|
})
|
||||||
|
# 自动带上研发费用行
|
||||||
|
rd_old = None # 旧口径无单独研发费用
|
||||||
|
rd_new = new_rd or old_rd_exp
|
||||||
|
if rd_new is not None:
|
||||||
|
adjusted_count += 1
|
||||||
|
items.append({
|
||||||
|
"item_name": "减:研发费用(单独列示)",
|
||||||
|
"old_value": rd_old,
|
||||||
|
"new_value": rd_new,
|
||||||
|
"difference": rd_new if rd_new is not None else None,
|
||||||
|
"needs_adjustment": rd_new is not None,
|
||||||
|
"adjustment_reason": "新30号准则单独列示",
|
||||||
|
"category": "operating",
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 财务费用:旧口径一行汇总,新口径拆解为利息收入(投资类)+利息支出(筹资类)
|
||||||
|
if name == "减:财务费用":
|
||||||
|
fin_old = old_val
|
||||||
|
fin_new_total = 0
|
||||||
|
fin_new_breakdown = []
|
||||||
|
|
||||||
|
# 利息支出(筹资类)
|
||||||
|
int_exp_val = new_interest_exp
|
||||||
|
if int_exp_val is not None:
|
||||||
|
fin_new_total += -int_exp_val
|
||||||
|
fin_new_breakdown.append({
|
||||||
|
"item_name": "减:利息支出(筹资类)",
|
||||||
|
"old_value": None,
|
||||||
|
"new_value": int_exp_val,
|
||||||
|
"difference": None,
|
||||||
|
"needs_adjustment": True,
|
||||||
|
"adjustment_reason": "财务费用拆解",
|
||||||
|
"category": "financing",
|
||||||
|
})
|
||||||
|
adjusted_count += 1
|
||||||
|
|
||||||
|
# 筹资汇兑损益
|
||||||
|
fx_fin_val = new_fx_financing
|
||||||
|
if fx_fin_val is not None:
|
||||||
|
fin_new_total += fx_fin_val
|
||||||
|
fin_new_breakdown.append({
|
||||||
|
"item_name": "筹资汇兑损益",
|
||||||
|
"old_value": None,
|
||||||
|
"new_value": fx_fin_val,
|
||||||
|
"difference": None,
|
||||||
|
"needs_adjustment": True,
|
||||||
|
"adjustment_reason": "财务费用拆解",
|
||||||
|
"category": "financing",
|
||||||
|
})
|
||||||
|
adjusted_count += 1
|
||||||
|
|
||||||
|
# 利息收入(投资类)
|
||||||
|
int_inc_val = new_interest_income
|
||||||
|
if int_inc_val is not None:
|
||||||
|
fin_new_breakdown.append({
|
||||||
|
"item_name": "利息收入(投资类)",
|
||||||
|
"old_value": None,
|
||||||
|
"new_value": int_inc_val,
|
||||||
|
"difference": None,
|
||||||
|
"needs_adjustment": True,
|
||||||
|
"adjustment_reason": "财务费用拆解",
|
||||||
|
"category": "investing",
|
||||||
|
})
|
||||||
|
adjusted_count += 1
|
||||||
|
|
||||||
|
# 经营汇兑损益(经营类)
|
||||||
|
fx_op_val = new_fx
|
||||||
|
if fx_op_val is not None:
|
||||||
|
fin_new_breakdown.append({
|
||||||
|
"item_name": "经营汇兑损益(经营类)",
|
||||||
|
"old_value": None,
|
||||||
|
"new_value": fx_op_val,
|
||||||
|
"difference": None,
|
||||||
|
"needs_adjustment": True,
|
||||||
|
"adjustment_reason": "财务费用拆解",
|
||||||
|
"category": "operating",
|
||||||
|
})
|
||||||
|
adjusted_count += 1
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
"item_name": "减:财务费用",
|
||||||
|
"old_value": fin_old,
|
||||||
|
"new_value": None,
|
||||||
|
"difference": None,
|
||||||
|
"needs_adjustment": True,
|
||||||
|
"adjustment_reason": "财务费用拆解为投资类利息收入和筹资类利息支出",
|
||||||
|
"category": "financing",
|
||||||
|
"breakdown": fin_new_breakdown,
|
||||||
|
})
|
||||||
|
# 把拆解项加到主列表
|
||||||
|
for brk in fin_new_breakdown:
|
||||||
|
items.append(brk)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 普通项目直接对比
|
||||||
|
diff = round(new_val - old_val, 2) if old_val is not None and new_val is not None else None
|
||||||
|
needs_adj = diff is not None and abs(diff) > 0.01
|
||||||
|
if needs_adj:
|
||||||
|
adjusted_count += 1
|
||||||
|
else:
|
||||||
|
unchanged_count += 1
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
"item_name": name,
|
||||||
|
"old_value": old_val,
|
||||||
|
"new_value": new_val,
|
||||||
|
"difference": diff,
|
||||||
|
"needs_adjustment": needs_adj,
|
||||||
|
"adjustment_reason": None,
|
||||||
|
"category": old_item["category"],
|
||||||
|
})
|
||||||
|
|
||||||
|
# 新增项目(只有新口径有)
|
||||||
|
existing_names = [i["item_name"] for i in items]
|
||||||
|
for new_item in new_items_map:
|
||||||
|
if new_item["name"] not in existing_names and new_item["new_value"] is not None:
|
||||||
|
items.append({
|
||||||
|
"item_name": new_item["name"],
|
||||||
|
"old_value": None,
|
||||||
|
"new_value": new_item["new_value"],
|
||||||
|
"difference": None,
|
||||||
|
"needs_adjustment": True,
|
||||||
|
"adjustment_reason": "新30号准则新增项目",
|
||||||
|
"category": new_item["category"],
|
||||||
|
})
|
||||||
|
new_count += 1
|
||||||
|
adjusted_count += 1
|
||||||
|
|
||||||
|
# 计算旧口径净利润和新口径净利润
|
||||||
|
def _calc_net_profit_old():
|
||||||
|
"""旧口径净利润 (简化)"""
|
||||||
|
rev = old_revenue or 0
|
||||||
|
rev_other = old_revenue_other or 0
|
||||||
|
c = old_cost or 0
|
||||||
|
c_other = old_cost_other or 0
|
||||||
|
sell = old_selling_exp or 0
|
||||||
|
admin = old_admin_exp or 0
|
||||||
|
fin = old_finance_exp or 0
|
||||||
|
return rev + rev_other - c - c_other - sell - admin - fin
|
||||||
|
|
||||||
|
def _calc_net_profit_new():
|
||||||
|
"""新口径净利润 = 经营利润 + 投资净收益 + 筹资净费用 + 所得税 + 终止经营"""
|
||||||
|
op_items = ["6001", "6051", "6401", "6402", "6601", "6602", "660204", "6603", "6701"]
|
||||||
|
inv_items = ["6011", "6111", "670101"]
|
||||||
|
fin_items = ["660301", "660302"]
|
||||||
|
tax_items = ["6801"]
|
||||||
|
dis_items = ["6901"]
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
for codes in [op_items, inv_items, fin_items, tax_items, dis_items]:
|
||||||
|
for code in codes:
|
||||||
|
amt = _get_subject_amount(db, code, period)
|
||||||
|
if amt is not None:
|
||||||
|
# 根据BLOCK_INFO中的sign处理
|
||||||
|
for bk in BLOCK_INFO.values():
|
||||||
|
for ic in bk["items"]:
|
||||||
|
if ic["code"] == code:
|
||||||
|
total += amt * ic["sign"]
|
||||||
|
return round(total, 2)
|
||||||
|
|
||||||
|
old_net = _calc_net_profit_old()
|
||||||
|
new_net = _calc_net_profit_new()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"period": period,
|
||||||
|
"items": items,
|
||||||
|
"summary": {
|
||||||
|
"total_items": len(items),
|
||||||
|
"adjusted_items": adjusted_count,
|
||||||
|
"new_items": new_count,
|
||||||
|
"unchanged_items": unchanged_count,
|
||||||
|
},
|
||||||
|
"net_profit_comparison": {
|
||||||
|
"old_net_profit": round(old_net, 2),
|
||||||
|
"new_net_profit": new_net,
|
||||||
|
"difference": round(new_net - old_net, 2),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/category-map")
|
@router.get("/category-map")
|
||||||
def get_category_map(
|
def get_category_map(
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
|
|||||||
@@ -235,6 +235,83 @@
|
|||||||
<el-empty v-else description="暂无已发布的战略地图,请先在战略地图中发布" />
|
<el-empty v-else description="暂无已发布的战略地图,请先在战略地图中发布" />
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<!-- ════ 追溯调整:2026数据按新30号准则重述 ════ -->
|
||||||
|
<el-tab-pane label="🔄 追溯调整" name="restatement">
|
||||||
|
<div v-loading="loadingRestatement">
|
||||||
|
<div class="report-desc">2026年数据按新30号准则重述 — 旧口径vs新口径双列对比,差异自动高亮。</div>
|
||||||
|
|
||||||
|
<!-- 净利润对比 -->
|
||||||
|
<div v-if="restatementNetProfit" class="restate-net-box">
|
||||||
|
<div class="restate-net-row">
|
||||||
|
<span class="restate-net-label">旧口径净利润:</span>
|
||||||
|
<span class="restate-net-val">{{ formatMoney(restatementNetProfit.old_net_profit) }}</span>
|
||||||
|
<span class="restate-net-arrow">→</span>
|
||||||
|
<span class="restate-net-label">新口径净利润:</span>
|
||||||
|
<span class="restate-net-val" :class="{ negative: restatementNetProfit.new_net_profit < 0 }">{{ formatMoney(restatementNetProfit.new_net_profit) }}</span>
|
||||||
|
<span class="restate-net-diff" :class="{ up: restatementNetProfit.difference > 0, down: restatementNetProfit.difference < 0 }">
|
||||||
|
差异:{{ restatementNetProfit.difference > 0 ? '+' : '' }}{{ formatMoney(restatementNetProfit.difference) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 汇总统计 -->
|
||||||
|
<div v-if="restatementSummary" class="restate-summary-row">
|
||||||
|
<div class="restate-stat green"><span class="restate-stat-val">{{ restatementSummary.total_items }}</span> 总项目</div>
|
||||||
|
<div class="restate-stat red"><span class="restate-stat-val">{{ restatementSummary.adjusted_items }}</span> 需调整</div>
|
||||||
|
<div class="restate-stat blue"><span class="restate-stat-val">{{ restatementSummary.new_items }}</span> 新增</div>
|
||||||
|
<div class="restate-stat gray"><span class="restate-stat-val">{{ restatementSummary.unchanged_items }}</span> 不变</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 对比表格 -->
|
||||||
|
<el-table :data="restatementItems" border stripe size="small" style="width:100%;margin-top:12px;" :row-class-name="restatementRowClass">
|
||||||
|
<el-table-column prop="item_name" label="项目" width="240">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="restate-item-name">
|
||||||
|
<el-tag v-if="row.needs_adjustment" type="danger" size="small" effect="plain" class="restate-tag">调整</el-tag>
|
||||||
|
<el-tag v-else size="small" effect="plain" type="info" class="restate-tag">不变</el-tag>
|
||||||
|
<span :class="{ 'restate-breakdown-item': row.category === '_breakdown' }">{{ row.item_name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="旧准则" width="160" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="{ 'restate-highlight-red': row.needs_adjustment && row.old_value != null }">
|
||||||
|
{{ row.old_value != null ? formatMoney(row.old_value) : '-' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="新准则" width="160" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span :class="{ 'restate-highlight-green': row.needs_adjustment && row.new_value != null }">
|
||||||
|
{{ row.new_value != null ? formatMoney(row.new_value) : '-' }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="差异" width="140" align="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.difference != null" :class="restateDiffClass(row.difference)">
|
||||||
|
{{ row.difference > 0 ? '+' : '' }}{{ formatMoney(row.difference) }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="row.needs_adjustment" class="restate-new-badge">新</span>
|
||||||
|
<span v-else style="color:#ccc;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="调整原因" width="260">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row.adjustment_reason" class="restate-reason">{{ row.adjustment_reason }}</span>
|
||||||
|
<span v-else style="color:#ccc;">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<div class="restate-legend">
|
||||||
|
<span class="restate-legend-item"><span class="restate-dot restate-dot-red"></span> 旧口径</span>
|
||||||
|
<span class="restate-legend-item"><span class="restate-dot restate-dot-green"></span> 新口径</span>
|
||||||
|
<span class="restate-legend-item"><span class="restate-dot restate-dot-orange"></span> 需调整项</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -347,6 +424,41 @@ function setTrendChartRef(id: number, el: any) {
|
|||||||
// ── 报表4:BSC评分卡 ──
|
// ── 报表4:BSC评分卡 ──
|
||||||
const bscData = ref<any>({})
|
const bscData = ref<any>({})
|
||||||
|
|
||||||
|
// ── 追溯调整:2026年数据按新30号准则重述 ──
|
||||||
|
const loadingRestatement = ref(false)
|
||||||
|
const restatementItems = ref<any[]>([])
|
||||||
|
const restatementSummary = ref<any>(null)
|
||||||
|
const restatementNetProfit = ref<any>(null)
|
||||||
|
|
||||||
|
async function loadRestatement() {
|
||||||
|
loadingRestatement.value = true
|
||||||
|
try {
|
||||||
|
const r = await api.get('/reports/restatement', { params: { period: reportPeriod.value } })
|
||||||
|
const d = (r as any).data || {}
|
||||||
|
restatementItems.value = d.items || []
|
||||||
|
restatementSummary.value = d.summary || null
|
||||||
|
restatementNetProfit.value = d.net_profit_comparison || null
|
||||||
|
} catch (e) {
|
||||||
|
restatementItems.value = []
|
||||||
|
restatementSummary.value = null
|
||||||
|
restatementNetProfit.value = null
|
||||||
|
ElMessage.error('加载追溯调整数据失败')
|
||||||
|
} finally {
|
||||||
|
loadingRestatement.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restatementRowClass({ row }: any) {
|
||||||
|
if (row.needs_adjustment) return 'restate-row-adjusted'
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function restatementDiffClass(diff: number) {
|
||||||
|
if (diff > 0) return 'restate-diff-up'
|
||||||
|
if (diff < 0) return 'restate-diff-down'
|
||||||
|
return 'restate-diff-zero'
|
||||||
|
}
|
||||||
|
|
||||||
// ── 公共 ──
|
// ── 公共 ──
|
||||||
const dimensions = [
|
const dimensions = [
|
||||||
{ value: 'finance', label: '财务维度' },
|
{ value: 'finance', label: '财务维度' },
|
||||||
@@ -417,7 +529,7 @@ async function loadKpiOptions() {
|
|||||||
|
|
||||||
function loadAll() {
|
function loadAll() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
Promise.all([loadProfit(), loadNew30(), loadBudget(), loadTrends(), loadBsc()]).finally(() => { loading.value = false })
|
Promise.all([loadProfit(), loadNew30(), loadBudget(), loadTrends(), loadBsc(), loadRestatement()]).finally(() => { loading.value = false })
|
||||||
}
|
}
|
||||||
|
|
||||||
function openDupont() {
|
function openDupont() {
|
||||||
@@ -564,4 +676,38 @@ onMounted(() => {
|
|||||||
.new30-profit-name { font-size: 13px; opacity: 0.8; }
|
.new30-profit-name { font-size: 13px; opacity: 0.8; }
|
||||||
.new30-footer { margin-top: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
.new30-footer { margin-top: 12px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||||
.new30-demo-hint { font-size: 11px; color: #d46b08; margin-left: 8px; }
|
.new30-demo-hint { font-size: 11px; color: #d46b08; margin-left: 8px; }
|
||||||
|
|
||||||
|
/* 追溯调整样式 */
|
||||||
|
.restate-net-box { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 8px; padding: 14px 18px; color: #fff; margin-top: 12px; }
|
||||||
|
.restate-net-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
|
||||||
|
.restate-net-label { font-size: 13px; opacity: 0.9; }
|
||||||
|
.restate-net-val { font-size: 18px; font-weight: 700; }
|
||||||
|
.restate-net-val.negative { color: #ffccc7; }
|
||||||
|
.restate-net-arrow { font-size: 18px; opacity: 0.7; }
|
||||||
|
.restate-net-diff { font-size: 13px; padding: 2px 10px; border-radius: 4px; }
|
||||||
|
.restate-net-diff.up { background: rgba(255,255,255,0.2); color: #b7eb8f; }
|
||||||
|
.restate-net-diff.down { background: rgba(255,255,255,0.2); color: #ffccc7; }
|
||||||
|
.restate-summary-row { display: flex; gap: 12px; margin-top: 12px; }
|
||||||
|
.restate-stat { padding: 8px 14px; border-radius: 6px; font-size: 13px; color: #555; border-left: 3px solid #ccc; background: #fafafa; }
|
||||||
|
.restate-stat.green { border-left-color: #67c23a; }
|
||||||
|
.restate-stat.red { border-left-color: #f56c6c; }
|
||||||
|
.restate-stat.blue { border-left-color: #409eff; }
|
||||||
|
.restate-stat.gray { border-left-color: #909399; }
|
||||||
|
.restate-stat-val { font-weight: 700; font-size: 16px; margin-right: 4px; }
|
||||||
|
.restate-item-name { display: flex; align-items: center; gap: 6px; }
|
||||||
|
.restate-tag { flex-shrink: 0; }
|
||||||
|
.restate-breakdown-item { padding-left: 16px; color: #666; font-size: 12px; }
|
||||||
|
.restate-highlight-red { background: #fff1f0; padding: 1px 4px; border-radius: 2px; font-weight: 600; color: #cf1322; }
|
||||||
|
.restate-highlight-green { background: #f6ffed; padding: 1px 4px; border-radius: 2px; font-weight: 600; color: #389e0d; }
|
||||||
|
.restate-diff-up { color: #f56c6c; font-weight: 600; }
|
||||||
|
.restate-diff-down { color: #67c23a; font-weight: 600; }
|
||||||
|
.restate-diff-zero { color: #999; }
|
||||||
|
.restate-new-badge { background: #409eff; color: #fff; padding: 1px 6px; border-radius: 3px; font-size: 11px; }
|
||||||
|
.restate-reason { font-size: 12px; color: #d46b08; }
|
||||||
|
.restate-legend { display: flex; gap: 16px; margin-top: 12px; padding: 8px 12px; background: #fafafa; border-radius: 6px; font-size: 12px; color: #666; }
|
||||||
|
.restate-legend-item { display: flex; align-items: center; gap: 4px; }
|
||||||
|
.restate-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
||||||
|
.restate-dot-red { background: #cf1322; }
|
||||||
|
.restate-dot-green { background: #389e0d; }
|
||||||
|
.restate-dot-orange { background: #d46b08; }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user