- V7 四表:performance_ledgers(append-only 版本行,uk(receipt_id,calc_version) 库级防重兜底)/monthly_settlements(月份 uk,通过即锁定终态)/ settlement_items(按人固化可负工资,uk 三列)/adjustment_orders(TZ 单号) - registry +1:approval.adjustment.enabled(19 项,BOOL 首组扩至三开关) - formula 补核算纯函数:clamp_weight_r(从预览内联提取共用)/monthly_salary 三口径/probation_annual_note/manager_team_share(#12 暂口径:基数不含 经理个人户业绩,聚合集中一处答复后只改) - 迁移 downgrade 只保留 drop_table+alter 回滚+种子行反向 DELETE; performance_ledgers 表名复数(test_models 红线断言) - 断言同步:EXPECTED_TABLES 四新表/registry 19/BOOL 三开关/conftest 清理四新表与 ParamValue 边界 22 - schema_sync+全量单测 460 passed Co-Authored-By: Claude Code <noreply@anthropic.com>
134 lines
5.6 KiB
Python
134 lines
5.6 KiB
Python
"""审批模型:审批单与节点记录(2026-10-15 复核审批补充需求)。
|
||
|
||
三级链路:营销后勤提交 → 财务复核 → 领导审核。行为由参数体系开关驱动
|
||
(`approval.*.enabled` BOOL):关闭=提交即生成「自动通过」节点留痕,
|
||
留痕不断链(开发计划 3.2(三));开启=按节点人工审核,一驳全驳。
|
||
|
||
依赖说明:节点准入按登录角色编码判定(ROLE_FINANCE/ROLE_LEADER),
|
||
不动 `permissions`/`role_permissions` 闲置表 —— 细粒度权限清单待业务方
|
||
答复(待澄清 #6)后再激活。
|
||
|
||
ref_id 为**固化无外键**(同 business_status_logs 先例):order_type=2
|
||
(月结预留)将指向 ④ 尚未落地的表,外键无法统一指向;单据删除保护由
|
||
服务层承担(详见 approval_service)。
|
||
"""
|
||
|
||
from datetime import datetime
|
||
|
||
from sqlalchemy import DateTime, ForeignKey, Index, String, text
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from app.models.base import BIGINT_UNSIGNED, TINYINT_TYPE, Base, TimestampMixin, mysql_table_args
|
||
|
||
# 审批单状态(approval_orders.status;流转机见 approval_service)
|
||
APPROVAL_STATUS_PENDING_FINANCE = 1 # 待财务复核
|
||
APPROVAL_STATUS_PENDING_LEADER = 2 # 待领导审核
|
||
APPROVAL_STATUS_APPROVED = 3 # 已通过(终态)
|
||
APPROVAL_STATUS_REJECTED = 4 # 已驳回(终态)
|
||
APPROVAL_STATUS_WITHDRAWN = 5 # 已撤回(被引用方重新提交时旧在途单注销)
|
||
|
||
# 节点(approval_steps.node;0 仅用于撤回留痕)
|
||
APPROVAL_NODE_FINANCE = 1
|
||
APPROVAL_NODE_LEADER = 2
|
||
APPROVAL_NODE_WITHDRAW = 0
|
||
APPROVAL_NODES = {
|
||
APPROVAL_STATUS_PENDING_FINANCE: APPROVAL_NODE_FINANCE,
|
||
APPROVAL_STATUS_PENDING_LEADER: APPROVAL_NODE_LEADER,
|
||
}
|
||
|
||
# 动作(approval_steps.action)
|
||
APPROVAL_ACTION_APPROVE = 1 # 人工通过
|
||
APPROVAL_ACTION_REJECT = 2 # 人工驳回
|
||
APPROVAL_ACTION_AUTO_APPROVE = 3 # 系统自动通过(开关关闭时逐节点留痕)
|
||
APPROVAL_ACTION_WITHDRAW = 4 # 撤回(业务重新提交时旧在途单注销)
|
||
|
||
# 审批对象类型(approval_orders.order_type)
|
||
APPROVAL_TYPE_SECONDARY_ALLOCATION = 1
|
||
APPROVAL_TYPE_MONTHLY_SETTLEMENT = 2
|
||
APPROVAL_TYPE_ADJUSTMENT = 3
|
||
|
||
|
||
class ApprovalOrder(Base, TimestampMixin):
|
||
"""审批单:一张待审对象一条审批流(状态可流转,含 update_time)。"""
|
||
|
||
__tablename__ = "approval_orders"
|
||
__table_args__ = mysql_table_args(
|
||
"审批单表(营销后勤提交→财务复核→领导审核;开关关闭时自动通过留痕)",
|
||
Index("idx_approval_orders_type_status", "order_type", "status"),
|
||
)
|
||
|
||
id: Mapped[int] = mapped_column(
|
||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||
)
|
||
order_no: Mapped[str] = mapped_column(
|
||
String(32),
|
||
nullable=False,
|
||
unique=True,
|
||
comment="审批单号,自动生成 AP{YYYYMMDD}-{4位当日序号}",
|
||
)
|
||
order_type: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE, nullable=False, comment="审批对象类型:1二次分配方案 2月度结算 3调整单"
|
||
)
|
||
ref_id: Mapped[int] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
comment="引用对象ID(order_type=1 时为 businesses.id;固化无外键,删除保护由服务层承担)",
|
||
)
|
||
refer_no: Mapped[str] = mapped_column(
|
||
String(32), nullable=False, comment="引用对象单号(冗余固化,列表页免联查)"
|
||
)
|
||
status: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE,
|
||
nullable=False,
|
||
server_default="1",
|
||
comment="审批状态:1待财务复核 2待领导审核 3已通过 4已驳回 5已撤回",
|
||
)
|
||
submitter_id: Mapped[int | None] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
ForeignKey("users.id", ondelete="SET NULL"),
|
||
comment="提交人ID(账号删除后置空)",
|
||
)
|
||
submitter_name: Mapped[str] = mapped_column(
|
||
String(64), nullable=False, comment="提交人用户名(冗余固化)"
|
||
)
|
||
|
||
|
||
class ApprovalStep(Base):
|
||
"""审批节点记录表(append-only):每次动作只增不改,构成时间线。
|
||
|
||
不继承 TimestampMixin(留痕无更新语义,同 business_status_logs 先例)。
|
||
"""
|
||
|
||
__tablename__ = "approval_steps"
|
||
__table_args__ = mysql_table_args(
|
||
"审批节点记录表(append-only;自动通过/撤回均有留痕)",
|
||
Index("idx_approval_steps_order_id", "order_id"),
|
||
)
|
||
|
||
id: Mapped[int] = mapped_column(
|
||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||
)
|
||
order_id: Mapped[int] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
ForeignKey("approval_orders.id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
comment="审批单ID(approval_orders.id;单删记录随删)",
|
||
)
|
||
node: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE, nullable=False, comment="审批节点:0撤回留痕 1财务复核 2领导审核"
|
||
)
|
||
action: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE, nullable=False, comment="动作:1通过 2驳回 3自动通过 4撤回"
|
||
)
|
||
opinion: Mapped[str | None] = mapped_column(String(255), comment="审批意见(驳回时填写)")
|
||
operator_id: Mapped[int | None] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
ForeignKey("users.id", ondelete="SET NULL"),
|
||
comment="操作人ID(自动通过/系统动作置空)",
|
||
)
|
||
operator_name: Mapped[str] = mapped_column(
|
||
String(64), nullable=False, comment="操作人用户名(冗余固化;系统动作记「系统」)"
|
||
)
|
||
create_time: Mapped[datetime] = mapped_column(
|
||
DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment="创建时间"
|
||
)
|