Files
cma-management/backend/app/models/__init__.py
T
Hermes CI Fix b06821a426 feat: KPI多粒度目标智能派生 — 基准值+按类型派生+手动覆盖
- 后端: target_calc_type字段(accumulate累计/ratio比率) + infer_calc_type名称/单位推断
- 派生规则: 累计型 月×3=季×12=年(季×4=年); 比率型 季/年沿用基准不可乘
- 虚拟派生不落库: kpi_to_dict返回derived_targets+derived_flags(自动标记)
- DB: 302个KPI回填类型(226累计/76比率)
- 前端KPIList: 指标类型选择 + 季/年自动派生预览(↳自动=N)
- 前端KPIDetail: 元数据卡片自动标记 + 编辑表单指标类型
- 回归: pytest 451 passed
2026-08-21 10:59:43 +08:00

724 lines
41 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.
"""管理会计OS 数据模型"""
from sqlalchemy import Column, Integer, String, Text, Float, DateTime, ForeignKey, Boolean, JSON, func, UniqueConstraint, Numeric, Date
from app.database import Base
from app.models.budget_plan import BudgetPlan
from app.models.cost_model import StandardCost, ActualCost, AbcActivity, AbcAllocation
from app.models.knowledge import KnowledgeEvent, KnowledgeSummary
from app.models.driver_budget import DriverFactorTemplate, DriverFactorBudget
class Entity(Base):
"""企业实体"""
__tablename__ = "entities"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False, comment="企业全称")
short_name = Column(String(50), comment="企业简称")
industry = Column(String(50), comment="行业")
status = Column(String(20), default="active", comment="active/inactive/demo")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class User(Base):
"""用户"""
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False)
password_hash = Column(String(128), nullable=False)
name = Column(String(100), nullable=False)
role = Column(String(20), default="finance") # ceo / finance / business / it
phone = Column(String(20), nullable=True)
created_at = Column(DateTime, server_default=func.now())
class UserEntity(Base):
"""用户-企业授权(账套模式多对多)— 新表必须带entity_id(开发规范)"""
__tablename__ = "user_entities"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True, comment="用户ID")
entity_id = Column(Integer, ForeignKey("entities.id"), nullable=False, index=True, comment="企业ID(账套)")
granted_by = Column(Integer, nullable=True, comment="授权人")
created_at = Column(DateTime, server_default=func.now())
__table_args__ = (UniqueConstraint("user_id", "entity_id", name="uq_user_entity"),)
class StrategicMap(Base):
"""战略地图"""
__tablename__ = "strategic_maps"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), nullable=False, comment="地图名称")
version = Column(String(20), default="v1.0", comment="版本号")
status = Column(String(20), default="draft", comment="draft/published")
dimensions = Column(JSON, nullable=True, comment="四维度和目标列表")
canvas_data = Column(JSON, nullable=True, comment="画布连线数据")
created_by = Column(Integer, nullable=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class KPIDefinition(Base):
"""KPI字典"""
__tablename__ = "kpi_definitions"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, default=1, comment="企业ID")
map_id = Column(Integer, ForeignKey("strategic_maps.id"), nullable=True, comment="关联战略地图")
kpi_code = Column(String(50), unique=True, nullable=False, comment="KPI编码")
kpi_name = Column(String(200), nullable=False, comment="KPI名称")
dimension = Column(String(50), comment="所属维度: finance/customer/process/learning")
objective = Column(String(200), comment="关联战略目标")
formula = Column(Text, nullable=True, comment="计算公式")
formula_desc = Column(String(500), nullable=True, comment="公式说明")
data_source_type = Column(String(20), default="manual", comment="erp/business/excel/manual")
data_source_config = Column(JSON, nullable=True, comment="数据源配置")
data_source = Column(String(500), default="待补充", comment="数据来源")
data_owner = Column(String(100), default="待指定", comment="数据责任人")
frequency = Column(String(20), default="monthly", comment="daily/weekly/monthly/quarterly/yearly")
unit = Column(String(50), default="%", comment="单位")
target_value = Column(Float, nullable=True, comment="目标值(兼容旧字段)")
target_monthly = Column(Float, nullable=True, comment="月度目标值")
target_quarterly = Column(Float, nullable=True, comment="季度目标值")
target_yearly = Column(Float, nullable=True, comment="年度目标值")
target_calc_type = Column(String(20), nullable=True, comment="指标类型: accumulate累计(月×3=季,×12=年) / ratio比率(季/年沿用基准,可手调)")
threshold_green = Column(String(100), nullable=True, comment="绿灯阈值")
threshold_yellow = Column(String(100), nullable=True, comment="黄灯阈值")
threshold_red = Column(String(100), nullable=True, comment="红灯阈值")
category = Column(String(50), nullable=True, comment="BSC二级类别: revenue_growth/profitability/cost_control/asset_efficiency/cash_risk/customer_scale/customer_concentration/customer_satisfaction/supply_chain/delivery_quality/talent_pipeline/employee_engagement/innovation")
responsible_dept = Column(String(200), nullable=True, comment="负责部门")
responsible_user = Column(String(100), nullable=True, comment="负责人")
kpi_level = Column(String(20), default="operational", comment="strategic/operational")
status = Column(String(20), default="active")
bot_source = Column(String(50), nullable=True, comment="Bot标识: finance-bot/ops-bot等")
epic = Column(String(50), default="Epic2", comment="所属Epic")
created_by = Column(Integer, nullable=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class KPIValue(Base):
"""KPI实际值"""
__tablename__ = "kpi_values"
id = Column(Integer, primary_key=True, index=True)
kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False)
period = Column(String(20), nullable=False, comment="期间 2026-05")
actual_value = Column(Float, nullable=True, comment="实际值")
source_type = Column(String(20), default="manual", comment="erp/excel/manual")
source_batch = Column(String(100), nullable=True, comment="导入批次号")
data_status = Column(String(20), default="pending", comment="pending/verified/error")
calculated_at = Column(DateTime, server_default=func.now())
remark = Column(String(500), nullable=True)
class DataSourceConfig(Base):
"""数据源配置"""
__tablename__ = "data_source_config"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(200), nullable=False, comment="数据源名称")
source_type = Column(String(20), nullable=False, comment="erp/business/excel")
api_endpoint = Column(String(500), nullable=True, comment="API地址")
api_key = Column(String(200), nullable=True, comment="API Key")
query_sql = Column(Text, nullable=True, comment="SQL查询语句")
sync_type = Column(String(20), default="realtime", comment="realtime/batch")
status = Column(String(20), default="active")
last_sync_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, server_default=func.now())
class KPIAlert(Base):
"""预警记录"""
__tablename__ = "kpi_alerts"
id = Column(Integer, primary_key=True, index=True)
kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False)
kpi_value_id = Column(Integer, ForeignKey("kpi_values.id"), nullable=True)
alert_level = Column(String(20), default="yellow", comment="green/yellow/red")
alert_message = Column(String(500), nullable=False)
alert_type = Column(String(30), default="actual", comment="actual/forecast — 实际值超限/预测值超限")
suggestion = Column(Text, nullable=True, comment="情景建议")
action_plan_linked_id = Column(Integer, nullable=True, comment="关联的改善计划ID")
status = Column(String(20), default="pending", comment="pending/processing/resolved")
assignee = Column(String(100), nullable=True, comment="处理人")
resolution = Column(Text, nullable=True, comment="处理结果")
resolved_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, server_default=func.now())
class OperationLog(Base):
"""操作日志"""
__tablename__ = "operation_logs"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, nullable=True)
action = Column(String(50), nullable=False, comment="create/update/delete/calculate/import")
target_type = Column(String(50), nullable=False, comment="kpi/map/alert/source")
target_id = Column(Integer, nullable=True)
detail = Column(JSON, nullable=True)
created_at = Column(DateTime, server_default=func.now())
class NotificationChannel(Base):
"""通知渠道配置"""
__tablename__ = "notification_channels"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False, comment="渠道名称")
channel_type = Column(String(30), nullable=False, comment="wecom/mail/sms")
config = Column(JSON, nullable=True, comment="渠道配置")
enabled = Column(Boolean, default=True)
created_at = Column(DateTime, server_default=func.now())
class NotificationLog(Base):
"""通知发送日志"""
__tablename__ = "notification_logs"
id = Column(Integer, primary_key=True, index=True)
alert_id = Column(Integer, ForeignKey("kpi_alerts.id"), nullable=True)
channel = Column(String(30), nullable=False, comment="wecom/mail")
recipient = Column(String(200), nullable=True, comment="收件人")
title = Column(String(200), nullable=True)
content = Column(Text, nullable=True)
status = Column(String(20), default="pending", comment="pending/sent/failed")
error_msg = Column(String(500), nullable=True)
sent_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, server_default=func.now())
class RolePermission(Base):
"""角色权限配置(单条记录,key-value"""
__tablename__ = "role_permissions"
id = Column(Integer, primary_key=True, index=True)
key = Column(String(50), unique=True, nullable=False, comment="配置键: route_permissions / action_permissions")
value = Column(JSON, nullable=False, comment="配置值")
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class Objective(Base):
"""OKR目标"""
__tablename__ = "objectives"
id = Column(Integer, primary_key=True, index=True)
title = Column(String(200), nullable=False, comment="目标标题")
description = Column(Text, nullable=True, comment="目标描述")
dimension = Column(String(50), nullable=True, comment="关联维度: finance/customer/process/learning")
strategic_map_id = Column(Integer, ForeignKey("strategic_maps.id"), nullable=True, comment="关联战略地图")
quarter = Column(String(20), nullable=False, comment="季度: 2026Q3")
owner = Column(String(100), nullable=True, comment="负责人")
status = Column(String(20), default="active", comment="active/completed/cancelled")
progress = Column(Integer, default=0, comment="整体进度 0-100")
confidence = Column(Integer, default=5, comment="信心指数 1-10")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class ActionPlan(Base):
"""改善行动计划"""
__tablename__ = "action_plans"
id = Column(Integer, primary_key=True, index=True)
alert_id = Column(Integer, ForeignKey("kpi_alerts.id"), nullable=True, comment="关联预警")
kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="关联KPI")
objective_id = Column(Integer, ForeignKey("objectives.id"), nullable=True, comment="关联OKR目标")
title = Column(String(200), nullable=False, comment="计划标题")
description = Column(Text, nullable=True, comment="详细描述")
assignee = Column(String(100), nullable=True, comment="负责人")
priority = Column(String(20), default="medium", comment="high/medium/low")
due_date = Column(DateTime, nullable=True, comment="截止日期")
status = Column(String(20), default="pending", comment="pending/in_progress/completed/cancelled")
progress = Column(Integer, default=0, comment="完成进度 0-100")
result = Column(Text, nullable=True, comment="改善结果")
monthly_milestones = Column(JSON, nullable=True, comment="月度里程碑: [{\"month\":\"2026-07\",\"label\":\"...\",\"status\":\"completed\"}]")
auto_verify_rule = Column(JSON, nullable=True, comment="自动验证规则: {\"condition\": \"value > target\"}")
verify_result = Column(String(20), nullable=True, comment="验证结果: pass/fail/pending")
verify_log = Column(JSON, nullable=True, comment="验证历史日志")
verify_status = Column(String(20), default="pending", comment="验证状态: pending/passed/failed/retrying/escalated")
verify_attempts = Column(Integer, default=0, comment="验证尝试次数")
verified_at = Column(DateTime, nullable=True, comment="验证完成时间")
kpi_current_before = Column(Float, nullable=True, comment="执行前KPI值")
kpi_current_after = Column(Float, nullable=True, comment="执行后KPI值")
created_by = Column(String(100), nullable=True, comment="创建人")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class OrgNode(Base):
"""组织节点: 集团→事业部→区域→部门→班组 5级"""
__tablename__ = "org_nodes"
id = Column(Integer, primary_key=True, index=True)
parent_id = Column(Integer, ForeignKey("org_nodes.id"), nullable=True, comment="父节点ID")
name = Column(String(100), nullable=False, comment="节点名称")
code = Column(String(50), unique=True, nullable=True, comment="编码")
level = Column(Integer, nullable=False, comment="1=集团 2=事业部 3=区域 4=部门 5=班组")
sort_order = Column(Integer, default=0, comment="排序")
enabled = Column(Integer, default=1, comment="1启用 0禁用")
path = Column(String(500), nullable=True, comment="路径")
remark = Column(String(200), nullable=True, comment="备注")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class StrategicMapVersion(Base):
"""战略地图版本快照"""
__tablename__ = "strategic_map_versions"
id = Column(Integer, primary_key=True, index=True)
map_id = Column(Integer, ForeignKey("strategic_maps.id", ondelete="CASCADE"), nullable=False, comment="关联地图")
version = Column(String(20), nullable=False, comment="版本号 v1.0 v1.1 ...")
dimensions = Column(JSON, nullable=False, comment="维度数据快照")
canvas_data = Column(JSON, nullable=False, comment="画布数据快照")
comment = Column(String(500), nullable=True, comment="说明")
created_by = Column(Integer, nullable=True)
created_at = Column(DateTime, server_default=func.now())
class MapObjective(Base):
"""战略地图目标: 每个维度下的具体目标"""
__tablename__ = "map_objectives"
id = Column(Integer, primary_key=True, index=True)
map_id = Column(Integer, ForeignKey("strategic_maps.id", ondelete="CASCADE"), nullable=False, comment="关联地图")
dimension_key = Column(String(50), nullable=False, comment="所属维度: finance/customer/process/learning")
name = Column(String(200), nullable=False, comment="目标名称")
description = Column(Text, nullable=True, comment="描述")
icon = Column(String(50), default="target", comment="图标标识")
kpis = Column(JSON, nullable=True, comment="关联KPI编码数组")
sort_order = Column(Integer, default=0, comment="排序")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class KPICausality(Base):
"""KPI因果链 — 记录KPI间的因果关系"""
__tablename__ = "kpi_causality"
id = Column(Integer, primary_key=True, index=True)
source_kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="源KPI(因)")
target_kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="目标KPI(果)")
strength = Column(Float, default=0.5, comment="影响强度 0~1")
lag_months = Column(Integer, default=1, comment="滞后期(月)")
formula = Column(String(500), nullable=True, comment="影响公式描述")
direction = Column(String(10), default="positive", comment="positive/negative 正向/负向影响")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class MpmResult(Base):
"""MPM财务Bot分析结果记录"""
__tablename__ = "mpm_results"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, ForeignKey("entities.id"), nullable=False, comment="企业实体ID")
period = Column(String(20), nullable=False, comment="期间 YYYY-MM")
source = Column(String(50), default="finance-bot", comment="来源Bot标识")
raw_data = Column(JSON, nullable=False, comment="完整的MPM计算结果")
created_at = Column(DateTime, server_default=func.now())
class BotBridgeConfig(Base):
"""Bot桥接鉴权配置"""
__tablename__ = "bot_bridge_config"
id = Column(Integer, primary_key=True, index=True)
bot_name = Column(String(50), unique=True, nullable=False, comment="Bot名称")
token = Column(String(64), nullable=False, comment="鉴权Token")
is_active = Column(Boolean, default=True, comment="是否激活")
created_at = Column(DateTime, server_default=func.now())
class KpiDataQualityLog(Base):
"""数据质量监控日志"""
__tablename__ = "kpi_data_quality_log"
id = Column(Integer, primary_key=True, index=True)
kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="关联KPI")
check_type = Column(String(30), nullable=False, comment="abnormal_change/flat_data/missing_data/value_outlier")
severity = Column(String(20), default="warning", comment="info/warning/critical")
detail = Column(JSON, nullable=True, comment="检测详情")
suggestion = Column(String(500), nullable=True, comment="建议操作")
status = Column(String(20), default="open", comment="open/resolved/ignored")
resolved_at = Column(DateTime, nullable=True)
created_at = Column(DateTime, server_default=func.now())
class BiReportTemplate(Base):
"""BI报表模板"""
__tablename__ = "bi_report_templates"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(200), nullable=False, comment="模板名称")
report_type = Column(String(50), nullable=False, comment="overview/trend/comparison/topn/causality")
config = Column(JSON, nullable=False, comment="报表配置")
is_system = Column(Integer, default=0, comment="系统预置模板")
created_by = Column(Integer, nullable=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class BiReport(Base):
"""用户保存的BI报表"""
__tablename__ = "bi_reports"
id = Column(Integer, primary_key=True, index=True)
template_id = Column(Integer, ForeignKey("bi_report_templates.id"), nullable=True)
name = Column(String(200), nullable=False, comment="报表名称")
config = Column(JSON, nullable=False, comment="报表配置(行/列/值)")
chart_type = Column(String(50), default="auto", comment="图表类型")
is_shared = Column(Integer, default=0, comment="是否分享")
created_by = Column(Integer, nullable=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class BscLayerConfig(Base):
"""BSC四层配置 — 不同企业的权重配置"""
__tablename__ = "bsc_layer_config"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, ForeignKey("entities.id"), nullable=False, comment="企业ID")
layer = Column(String(20), nullable=False, comment="financial/customer/process/learning")
weight = Column(Float, nullable=False, comment="该层权重(%")
kpi_count_min = Column(Integer, default=2, comment="最少KPI数")
kpi_count_max = Column(Integer, default=5, comment="最多KPI数")
class KPIHierarchy(Base):
"""KPI层级关系 — 公司→部门→个人三级分解"""
__tablename__ = "kpi_hierarchy"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, ForeignKey("entities.id"), default=1, comment="企业ID")
parent_kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="上级KPI")
child_kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="下级KPI")
level = Column(Integer, default=1, comment="1=公司级 2=部门级 3=个人级")
weight = Column(Float, default=1.0, comment="下级对上级的贡献权重(%)")
child_name = Column(String(200), nullable=True, comment="下级节点名称(个人或部门名)")
created_at = Column(DateTime, server_default=func.now())
# 兼容性: P2开发新增的模板API需要的模型
# KPI模板(独立表)
class KPITemplate(Base):
__tablename__ = "kpi_templates"
id = Column(Integer, primary_key=True, index=True)
kpi_code = Column(String(50), unique=True, nullable=False)
kpi_name = Column(String(200), nullable=False)
dimension = Column(String(50))
category = Column(String(50))
formula = Column(Text)
formula_desc = Column(String(500))
unit = Column(String(50))
target_value = Column(Float)
description = Column(Text)
is_system = Column(Integer, default=0)
usage_count = Column(Integer, default=0)
created_by = Column(Integer)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class OKRTemplate(Base):
"""OKR模板库"""
__tablename__ = "okr_templates"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False, comment="O名称")
description = Column(Text, comment="O描述")
dimension = Column(String(20), nullable=False, comment="finance/customer/process/learning")
layer = Column(String(20), default="level1", comment="level1/level2/level3")
industry_tag = Column(String(50), default="general", comment="行业标签")
preset_krs = Column(JSON, nullable=False, comment="预设关键结果列表")
source = Column(String(20), default="system", comment="system/user/industry_pack")
use_count = Column(Integer, default=0, comment="使用次数")
sort_order = Column(Integer, default=0)
is_active = Column(Integer, default=1)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class CashForecast(Base):
"""现金流预测 — 每日未来30天预测"""
__tablename__ = "cash_forecasts"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, ForeignKey("entities.id"), nullable=False, comment="企业ID")
forecast_date = Column(DateTime, nullable=False, comment="预测日期(每天一条)")
predicted_cash = Column(Float, nullable=True, comment="预测现金余额")
lower_bound = Column(Float, nullable=True, comment="置信区间下界")
upper_bound = Column(Float, nullable=True, comment="置信区间上界")
alert_status = Column(String(20), default="green", comment="green/yellow/red")
created_at = Column(DateTime, server_default=func.now())
class CashPlan(Base):
"""收付款计划 — 资金管理智能体(唯一应收载体:含回款登记、负责人、数据来源)"""
__tablename__ = "cash_plans"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, ForeignKey("entities.id"), default=1, comment="企业ID")
plan_type = Column(String(10), nullable=False, comment="receive收/pay付")
amount = Column(Float, nullable=False, comment="金额(万元)")
plan_date = Column(DateTime, nullable=False, comment="计划日期(应收即到期日)")
counterparty = Column(String(200), nullable=True, comment="关联客户/供应商")
description = Column(String(500), nullable=True, comment="说明")
status = Column(String(20), default="pending", comment="pending/completed/cancelled")
owner = Column(String(100), nullable=True, comment="负责人/业务员(应收催收责任人)")
source = Column(String(50), default="manual", comment="数据来源: manual/bohai_ar/receivables_migrate")
paid_amount = Column(Float, default=0, comment="已回款金额(万元)")
completed_at = Column(DateTime, nullable=True, comment="完成时间")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class SystemConfig(Base):
"""系统配置 — key-value存储"""
__tablename__ = "system_configs"
id = Column(Integer, primary_key=True, index=True)
config_key = Column(String(100), unique=True, nullable=False, comment="配置键")
config_value = Column(String(500), nullable=True, comment="配置值")
description = Column(String(500), nullable=True, comment="配置说明")
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class BudgetDeviationAlert(Base):
"""预算偏差预警记录"""
__tablename__ = "budget_deviation_alerts"
id = Column(Integer, primary_key=True, index=True)
kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="关联KPI")
period = Column(String(20), nullable=False, comment="期间 YYYY-MM")
budget_value = Column(Float, nullable=True, comment="预算值")
actual_value = Column(Float, nullable=True, comment="实际值")
deviation_rate = Column(Float, nullable=True, comment="偏差率 %")
deviation_value = Column(Float, nullable=True, comment="偏差绝对值")
alert_level = Column(String(20), default="warning", comment="warning/critical")
status = Column(String(20), default="open", comment="open/resolved/ignored")
suggestion = Column(String(500), nullable=True, comment="处理建议")
created_at = Column(DateTime, server_default=func.now())
class ReportHistory(Base):
"""自动生成的经营分析报告记录"""
__tablename__ = "report_history"
id = Column(Integer, primary_key=True, index=True)
report_type = Column(String(20), nullable=False, comment="weekly/monthly/special")
period = Column(String(20), nullable=False, comment="期间: 2026-W30 / 2026-07 / 2026-Q2")
title = Column(String(200), nullable=False, comment="报告标题")
markdown_content = Column(Text, nullable=True, comment="Markdown格式报告(用于微信推送)")
json_content = Column(JSON, nullable=True, comment="JSON结构化数据(写入CMA系统)")
status = Column(String(20), default="generated", comment="generated/pushed/failed")
trigger_type = Column(String(20), default="manual", comment="manual/scheduled/event")
alert_ref = Column(String(50), nullable=True, comment="事件触发时的预警引用")
created_at = Column(DateTime, server_default=func.now())
class AnalysisResult(Base):
"""财务Bot分析结论 — 带置信度评分"""
__tablename__ = "analysis_results"
id = Column(Integer, primary_key=True, index=True)
period = Column(String(20), nullable=False, comment="期间 YYYY-MM")
conclusion = Column(String(1000), nullable=False, comment="分析结论")
confidence = Column(Integer, nullable=False, comment="置信度 0-100")
data_source = Column(String(500), nullable=True, comment="数据来源")
calculation_logic = Column(String(1000), nullable=True, comment="计算逻辑")
comparable_benchmark = Column(String(500), nullable=True, comment="可比基准")
limitations = Column(String(1000), nullable=True, comment="局限说明")
has_actual = Column(Integer, default=0, comment="有实际值")
has_target = Column(Integer, default=0, comment="有目标值")
has_trend = Column(Integer, default=0, comment="有历史趋势")
has_review = Column(Integer, default=0, comment="有人工复核")
kpi_code = Column(String(50), nullable=True, comment="关联KPI编码")
kpi_name = Column(String(200), nullable=True, comment="关联KPI名称")
created_at = Column(DateTime, server_default=func.now())
class ForecastAccuracy(Base):
"""预测准确率 — 上期预测 vs 本期实际"""
__tablename__ = "forecast_accuracy"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, ForeignKey("entities.id"), nullable=False, comment="企业ID")
period = Column(String(20), nullable=False, comment="期间 2026-07")
forecast_value = Column(Float, nullable=True, comment="预测值")
actual_value = Column(Float, nullable=True, comment="实际值")
mae = Column(Float, nullable=True, comment="绝对误差")
mape = Column(Float, nullable=True, comment="百分比误差")
created_at = Column(DateTime, server_default=func.now())
class ScenarioSuggestion(Base):
"""情景建议模板 — 根据不同预警类型自动生成建议"""
__tablename__ = "scenario_suggestions"
id = Column(Integer, primary_key=True, index=True)
alert_type = Column(String(30), nullable=False, comment="预警类型: cash_low/cash_critical/cost_high/revenue_drop")
title = Column(String(200), nullable=False, comment="建议标题")
description = Column(Text, nullable=True, comment="详细建议")
action_template = Column(Text, nullable=True, comment="改善行动模板")
priority = Column(String(20), default="medium", comment="high/medium/low")
sort_order = Column(Integer, default=0, comment="排序")
created_at = Column(DateTime, server_default=func.now())
# ============================================================
# 新30号准则模型 (2027)
# ============================================================
class Subject(Base):
"""会计科目 — 新30号准则分类"""
__tablename__ = "subjects"
id = Column(Integer, primary_key=True, index=True)
subject_code = Column(String(20), nullable=False, unique=True, comment="科目编码")
subject_name = Column(String(200), nullable=False, comment="科目名称")
parent_code = Column(String(20), nullable=True, comment="上级科目编码")
level = Column(Integer, default=1, comment="科目级别 1-4")
category = Column(String(50), nullable=True, comment="科目类别")
new_standard_category = Column(String(20), nullable=True, comment="新30号准则分类: operating/investing/financing/tax/discontinued")
is_active = Column(Integer, default=1, comment="是否启用")
remark = Column(String(500), nullable=True, comment="备注")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class VoucherDetail(Base):
"""凭证明细 — 新30号准则分类"""
__tablename__ = "voucher_details"
id = Column(Integer, primary_key=True, index=True)
voucher_no = Column(String(50), nullable=False, comment="凭证编号")
voucher_date = Column(DateTime, nullable=False, comment="凭证日期")
subject_code = Column(String(20), nullable=False, comment="科目编码")
subject_name = Column(String(200), nullable=True, comment="科目名称")
debit_amount = Column(Float, default=0, comment="借方金额")
credit_amount = Column(Float, default=0, comment="贷方金额")
summary = Column(String(500), nullable=True, comment="摘要")
new_standard_category = Column(String(20), nullable=True, comment="新30号准则分类")
period = Column(String(20), nullable=True, comment="期间 YYYY-MM")
created_at = Column(DateTime, server_default=func.now())
# ============================================================
# 费用审核智能体 (2026-08)
# ============================================================
class ExpenseRule(Base):
"""费用规则 — 自动校验报销单的标准"""
__tablename__ = "expense_rules"
id = Column(Integer, primary_key=True, index=True)
rule_name = Column(String(100), nullable=False, comment="规则名称")
dimension = Column(String(20), nullable=False, comment="维度: department/person/expense_type")
dimension_value = Column(String(100), nullable=True, comment="维度值: 部门名/人员名/费用类型(空=全局)")
expense_type = Column(String(30), nullable=False, comment="费用类型: entertainment/travel/office/management")
limit_type = Column(String(20), nullable=False, comment="限额类型: single/monthly/yearly 单笔/月度累计/年度累计")
limit_amount = Column(Float, nullable=False, comment="限额金额")
cycle = Column(String(20), default="monthly", comment="周期: single/monthly/yearly")
status = Column(String(20), default="active", comment="active/inactive")
remark = Column(String(500), nullable=True, comment="备注")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class ExpenseReimbursement(Base):
"""费用报销单 — 提交后自动校验规则,超限自动打回"""
__tablename__ = "expense_reimbursements"
id = Column(Integer, primary_key=True, index=True)
reimb_no = Column(String(50), unique=True, nullable=False, comment="报销单号")
applicant = Column(String(100), nullable=False, comment="申请人")
department = Column(String(100), nullable=True, comment="部门")
expense_type = Column(String(30), nullable=False, comment="费用类型: entertainment/travel/office/management")
title = Column(String(200), nullable=False, comment="事由/摘要")
amount = Column(Float, nullable=False, comment="报销金额")
expense_date = Column(DateTime, nullable=True, comment="费用发生日期")
attachment = Column(String(500), nullable=True, comment="附件文件名")
status = Column(String(20), default="pending", comment="pending待审批/approved已通过/rejected已拒绝/returned已打回")
check_result = Column(String(20), default="pass", comment="自动校验结果: pass/fail")
check_reason = Column(String(1000), nullable=True, comment="超限原因")
check_detail = Column(JSON, nullable=True, comment="校验明细: [{rule_name, limit, actual, passed}]")
checked_at = Column(DateTime, nullable=True, comment="自动校验时间")
approver = Column(String(100), nullable=True, comment="审批人")
approve_comment = Column(String(500), nullable=True, comment="审批意见")
approved_at = Column(DateTime, nullable=True, comment="审批时间")
created_by = Column(String(100), nullable=True, comment="提交人")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
# ============================================================
# 税务合规智能体 (2026-08)
# ① 税负监控 ② 发票校验 ③ 社保比对
# ============================================================
class TaxRecord(Base):
"""税务记录 — 税负监控:应纳税额/实缴额/税负率 vs 行业基准"""
__tablename__ = "tax_records"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, default=1, comment="企业ID")
period = Column(String(20), nullable=False, comment="期间 YYYY-MM")
tax_type = Column(String(30), nullable=False, comment="税种: vat增值税/income所得税/surtax附加税")
tax_payable = Column(Float, default=0, comment="应纳税额")
tax_paid = Column(Float, default=0, comment="实缴税额")
tax_rate = Column(Float, nullable=True, comment="适用税率 %")
income = Column(Float, default=0, comment="当期收入/计税收入(税负率分母)")
tax_burden_rate = Column(Float, nullable=True, comment="税负率 % = 实缴税额/收入×100")
burden_status = Column(String(20), default="normal", comment="normal正常/warning超基准±20%内/alert超基准±20%")
warning_msg = Column(String(500), nullable=True, comment="预警信息")
remark = Column(String(500), nullable=True, comment="备注")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class InvoiceCheck(Base):
"""发票校验 — 录入后按规则自动校验:号码格式/金额匹配报销单/供应商匹配合同"""
__tablename__ = "invoice_check"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, default=1, comment="企业ID")
invoice_no = Column(String(50), nullable=False, comment="发票号码")
amount = Column(Float, nullable=False, comment="发票金额")
invoice_type = Column(String(30), default="vat", comment="发票类型: vat专用/vat普通/electronic电子/other其他")
invoice_date = Column(DateTime, nullable=True, comment="开票日期")
supplier = Column(String(200), nullable=True, comment="供应商名称")
reimb_no = Column(String(50), nullable=True, comment="关联报销单号")
contract_no = Column(String(50), nullable=True, comment="关联合同编号")
check_status = Column(String(20), default="pending", comment="校验状态: pending待校验/valid通过/invalid异常/warning提醒")
check_result = Column(JSON, nullable=True, comment="校验明细: [{rule, passed, message}]")
check_reason = Column(String(1000), nullable=True, comment="异常原因汇总")
checked_at = Column(DateTime, nullable=True, comment="校验时间")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
class SocialSecurity(Base):
"""社保缴费记录 — 社保比对:基数与工资匹配/单位缴纳比例/漏缴提醒"""
__tablename__ = "social_security"
id = Column(Integer, primary_key=True, index=True)
entity_id = Column(Integer, default=1, comment="企业ID")
employee = Column(String(100), nullable=False, comment="人员姓名")
period = Column(String(20), nullable=False, comment="期间 YYYY-MM")
base_amount = Column(Float, default=0, comment="缴费基数")
salary = Column(Float, nullable=True, comment="申报工资")
company_amount = Column(Float, default=0, comment="单位缴纳金额")
personal_amount = Column(Float, default=0, comment="个人缴纳金额")
company_rate = Column(Float, nullable=True, comment="单位缴纳比例 % (养老16%+医疗8%+失业0.5%≈24.5%)")
check_status = Column(String(20), default="normal", comment="normal正常/warning基数或比例异常/alert漏缴")
warning_msg = Column(String(500), nullable=True, comment="异常提醒: 漏缴/基数不符/比例异常")
remark = Column(String(500), nullable=True, comment="备注")
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
# ============================================================
# 本体三支柱: 科目↔KPI↔OKR 三层互联 (2026-08-19)
# 追溯链: 目标(O) → 指标(KPI) → 科目(数据)
# ============================================================
class KPISubjectMap(Base):
"""科目↔KPI映射 — 指标计算依赖的底层会计科目"""
__tablename__ = "kpi_subject_map"
id = Column(Integer, primary_key=True, index=True)
kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="KPI ID")
subject_code = Column(String(20), nullable=False, comment="科目编码")
calc_type = Column(String(20), default="sum", comment="sum/avg/ratio/other")
weight = Column(Numeric(5, 2), default=1.00, comment="权重(负=扣减项)")
remark = Column(String(200), nullable=True, comment="备注")
__table_args__ = (UniqueConstraint("kpi_id", "subject_code", name="uk_kpi_subject"),)
class ObjectiveKPI(Base):
"""KPI↔O支撑 — 目标由哪些KPI度量"""
__tablename__ = "objective_kpi"
id = Column(Integer, primary_key=True, index=True)
objective_id = Column(Integer, ForeignKey("objectives.id"), nullable=False, comment="OKR目标ID")
kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="KPI ID")
weight = Column(Numeric(5, 2), default=1.00, comment="支撑权重")
__table_args__ = (UniqueConstraint("objective_id", "kpi_id", name="uk_obj_kpi"),)
class KR(Base):
"""关键结果KR — OKR完整化 (O→KR→KPI)"""
__tablename__ = "krs"
id = Column(Integer, primary_key=True, index=True)
objective_id = Column(Integer, ForeignKey("objectives.id"), nullable=False, comment="OKR目标ID")
title = Column(String(200), nullable=False, comment="KR标题")
metric_kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=True, comment="度量KPI ID")
target_value = Column(Numeric(15, 2), nullable=True, comment="目标值")
current_value = Column(Numeric(15, 2), nullable=True, comment="当前值")
progress = Column(Integer, default=0, comment="完成进度 0-100")
status = Column(String(20), default="pending", comment="pending/in_progress/completed/cancelled")
due_date = Column(Date, nullable=True, comment="截止日期")
created_at = Column(DateTime, server_default=func.now())