From 9c3e3fe32bd51addeccc8737ba517e79f56f4afe Mon Sep 17 00:00:00 2001 From: break Date: Fri, 11 Sep 2026 09:45:12 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BD=92=E6=A1=A3=E5=91=98=E5=B7=A5=E4=BB=A3?= =?UTF-8?q?=E6=94=B6=E6=AC=BE=E8=B4=A6=E5=8D=95=E9=97=AD=E7=8E=AF=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E5=B9=B6=E5=90=8C=E6=AD=A5=E4=B8=BB=E8=A7=84=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增主规格 openspec/specs/employee-collection-bill/spec.md(5 条 Requirement、22 个 Scenario) - 变更目录归档至 openspec/changes/archive/2026-09-11-add-employee-collection-bills --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/employee-collection-bill/spec.md | 0 .../tasks.md | 0 .../specs/employee-collection-bill/spec.md | 127 ++++++++++++++++++ 6 files changed, 127 insertions(+) rename openspec/changes/{add-employee-collection-bills => archive/2026-09-11-add-employee-collection-bills}/.openspec.yaml (100%) rename openspec/changes/{add-employee-collection-bills => archive/2026-09-11-add-employee-collection-bills}/design.md (100%) rename openspec/changes/{add-employee-collection-bills => archive/2026-09-11-add-employee-collection-bills}/proposal.md (100%) rename openspec/changes/{add-employee-collection-bills => archive/2026-09-11-add-employee-collection-bills}/specs/employee-collection-bill/spec.md (100%) rename openspec/changes/{add-employee-collection-bills => archive/2026-09-11-add-employee-collection-bills}/tasks.md (100%) create mode 100644 openspec/specs/employee-collection-bill/spec.md diff --git a/openspec/changes/add-employee-collection-bills/.openspec.yaml b/openspec/changes/archive/2026-09-11-add-employee-collection-bills/.openspec.yaml similarity index 100% rename from openspec/changes/add-employee-collection-bills/.openspec.yaml rename to openspec/changes/archive/2026-09-11-add-employee-collection-bills/.openspec.yaml diff --git a/openspec/changes/add-employee-collection-bills/design.md b/openspec/changes/archive/2026-09-11-add-employee-collection-bills/design.md similarity index 100% rename from openspec/changes/add-employee-collection-bills/design.md rename to openspec/changes/archive/2026-09-11-add-employee-collection-bills/design.md diff --git a/openspec/changes/add-employee-collection-bills/proposal.md b/openspec/changes/archive/2026-09-11-add-employee-collection-bills/proposal.md similarity index 100% rename from openspec/changes/add-employee-collection-bills/proposal.md rename to openspec/changes/archive/2026-09-11-add-employee-collection-bills/proposal.md diff --git a/openspec/changes/add-employee-collection-bills/specs/employee-collection-bill/spec.md b/openspec/changes/archive/2026-09-11-add-employee-collection-bills/specs/employee-collection-bill/spec.md similarity index 100% rename from openspec/changes/add-employee-collection-bills/specs/employee-collection-bill/spec.md rename to openspec/changes/archive/2026-09-11-add-employee-collection-bills/specs/employee-collection-bill/spec.md diff --git a/openspec/changes/add-employee-collection-bills/tasks.md b/openspec/changes/archive/2026-09-11-add-employee-collection-bills/tasks.md similarity index 100% rename from openspec/changes/add-employee-collection-bills/tasks.md rename to openspec/changes/archive/2026-09-11-add-employee-collection-bills/tasks.md diff --git a/openspec/specs/employee-collection-bill/spec.md b/openspec/specs/employee-collection-bill/spec.md new file mode 100644 index 0000000..9c85b0e --- /dev/null +++ b/openspec/specs/employee-collection-bill/spec.md @@ -0,0 +1,127 @@ +# employee-collection-bill Specification + +## Purpose + +为后台账号代客户经办的线下套餐购买和代理预存款充值建立独立的员工应收与核销核验闭环,核销申请以企业微信为唯一终审;该能力只保存可追溯的本地业务事实,不推测或回填历史第三方付款。 + +## Requirements + +### Requirement: 员工代收款账单来源、金额与建账判据 +系统 SHALL 仅为下列业务创建员工代收款账单,并以实际发起该业务的后台账号作为不可修改的欠款人: + +- 后台线下套餐订单,同时满足 `payment_method = offline`、`operator_account_type = platform`、订单不含赠送套餐、且订单 `actual_paid_amount > 0`;账单应收金额取订单 `actual_paid_amount`,代理代购和无代理归属自营 C 端均适用。`actual_paid_amount ≤ 0` 的订单 MUST NOT 创建账单。该订单创建成功后仍按既有规则立即激活。 +- 代理线下预存款/主钱包充值完成入账后,账单金额取对应充值记录 `amount`;入账完成以“平台账号发起的线下充值入账成功”这一既有事实为准,既有企业微信终审通过入账与既有线下充值人工确认入账均适用。 + +客户自行线上支付、平台代理 C 端客户充值资产钱包、订单失败或取消、赠送套餐等不产生员工账单的既有线下订单,以及“其他”手工来源 MUST NOT 创建账单。系统 MUST 为同一来源业务建立至多一张账单,并保存来源类型、来源 ID、来源单号、客户/店铺快照、欠款人、应收金额和创建时间。建账 MUST 只由来源成功事务携带的来源主键触发;系统 MUST NOT 通过扫描历史订单或充值记录补建账单。 + +#### Scenario: 后台线下套餐订单产生账单 +- **WHEN** 平台业务员或超级管理员成功创建一个满足建账判据、且 `actual_paid_amount` 大于零的后台线下套餐订单 +- **THEN** 系统以该操作账号为欠款人、以订单 `actual_paid_amount` 为应收金额创建唯一待核销账单,且订单无需因未上传付款凭证而阻断 + +#### Scenario: 线下充值完成入账产生账单 +- **WHEN** 平台账号发起的代理线下预存款/主钱包充值经企业微信最终通过并完成入账 +- **THEN** 系统以实际发起充值的后台账号和充值 `amount` 创建唯一待核销账单 + +#### Scenario: 人工确认入账同样产生账单 +- **WHEN** 未关联企业微信审批实例的线下充值经后台人工确认入口完成入账 +- **THEN** 系统同样以发起充值的后台账号和充值 `amount` 创建唯一待核销账单 + +#### Scenario: 重复来源不产生重复账单 +- **WHEN** 同一来源业务被重复处理、重复回调或重试 +- **THEN** 系统至多保留一张来源关联账单,不新增第二张账单 + +#### Scenario: 零金额与赠送订单不建账 +- **WHEN** 后台线下套餐订单的 `actual_paid_amount` 小于等于零,或订单包含赠送套餐 +- **THEN** 系统不创建任何账单,且该订单的创建时付款凭证要求保持既有规则 + +#### Scenario: 历史来源记录不被扫描建账 +- **WHEN** 建账用例运行,而某笔上线前已存在的订单或充值从未由本次来源成功事务携带来源主键 +- **THEN** 系统不为该记录创建账单 + +### Requirement: 账单余额、状态与关闭 +账单 SHALL 独立维护 `待核销`、`部分核销`、`已核销`、`已关闭` 状态及应收金额、已核销金额、审批中预占金额和剩余可核销金额。只有企业微信最终通过的分摊增加已核销金额;审批中的分摊预占剩余可核销金额,防止并发申请超额核销。账单已核销金额等于应收金额时 MUST 为已核销;关闭账单只作废当时未核销余额,已核销金额必须保留。 + +欠款人离职、禁用或变更组织后,账单欠款人身份和既有账单范围 MUST 保持不变。欠款人(平台账号)仅可查询本人账单与本人申请;超级管理员可查询全部;本期 MUST NOT 引入“财务”角色,MUST NOT 依赖 `RequirePermission` 路由鉴权体系实现可见性。仅超级管理员可代办创建、修改或重提申请,且必须记录实际代办人和原因。 + +系统 SHALL 提供与账单列表相同筛选条件和相同可见性范围的统计聚合,返回应收金额合计、已核销金额合计、未核销金额合计和待处理账单数。 + +#### Scenario: 欠款人仅见本人账单与申请 +- **WHEN** 非超级管理员的后台账号查询账单列表、账单详情或申请 +- **THEN** 系统仅返回以该账号为欠款人的账单及由该账号发起的申请,且无权限与不存在不产生可枚举差异 + +#### Scenario: 超级管理员查询全部账单 +- **WHEN** 超级管理员查询账单列表 +- **THEN** 系统返回其可见范围内的全部账单,不按欠款人账号过滤 + +#### Scenario: 统计与列表同口径 +- **WHEN** 查询统计聚合与使用相同筛选条件的账单列表 +- **THEN** 统计的应收、已核销、未核销金额与待处理账单数与列表数据一致 + +#### Scenario: 部分核销后仍可继续核销 +- **WHEN** 一张账单存在企业微信已通过但未结清的分摊 +- **THEN** 系统增加已核销金额、将账单标记为部分核销,并仅允许新的分摊使用未被已通过或审批中分摊占用的余额 + +#### Scenario: 关闭未结清账单 +- **WHEN** 超级管理员对待核销、部分核销或已驳回关联申请的账单填写关闭原因并执行关闭,且账单不存在审批中申请 +- **THEN** 系统作废未核销余额、将账单标记为已关闭、保留已核销金额和操作审计 + +#### Scenario: 审批中账单不可关闭 +- **WHEN** 超级管理员尝试关闭存在审批中核销申请的账单 +- **THEN** 系统拒绝关闭,账单金额和状态不变 + +### Requirement: 外部付款核销申请与分摊 +员工 SHALL 按一笔外部付款创建一张核销申请。申请 MUST 选择一个启用的线下收款方式字典项,并保存其稳定编码和名称快照;必须保存经人工确认的付款金额、付款方、付款时间、外部交易流水号、至少一个支付凭证和可选其他凭证、备注及一个或多个账单分摊。支付凭证 MUST 复用既有附件键,只保存对象键引用并经既有预签名下载能力访问,本能力 MUST NOT 承诺资源级附件鉴权。申请人只能通过勾选可见账单创建分摊,系统带出只读来源订单、客户和资产信息。 + +系统 SHALL 校验申请人提交的账单分摊:申请人按账单产生时间从早到晚用本次付款金额预填分摊、最后一张填入剩余金额,并可在提交前修改各分摊金额;服务端 MUST 按校验规则拒绝越界分摊。单笔分摊 MUST 大于零且不得超过该账单可核销余额;分摊总额 MUST 不超过本次人工确认付款金额。同一外部付款可被多个核销申请引用,本期 MUST NOT 对跨申请累计分摊金额实施系统防重或金额上限校验。 + +#### Scenario: 一笔付款分摊多张账单 +- **WHEN** 员工选择多张可见账单并提交一笔外部付款的核销申请 +- **THEN** 系统校验每张账单可核销余额和申请总额、拒绝越界分摊,并为该申请创建唯一企业微信审批实例 + +#### Scenario: 账单并发申请预占 +- **WHEN** 两个核销申请并发选择同一账单的剩余余额 +- **THEN** 系统至多接受不超过该账单未核销余额的审批中和已通过分摊,其余申请返回余额不足且不创建超额分摊 + +### Requirement: 核销申请审批、重提与幂等 +核销申请状态 SHALL 为 `审批中`、`已通过`、`已驳回`、`已撤销/已关闭`,且不得以申请状态覆盖账单核销状态。提交或重提时系统 MUST 新增一条不可变审批尝试记录(冻结当次收款方式、外部付款、附件、备注、账单分摊及审批材料快照)并为其创建新的企业微信审批实例;申请只保存最新审批实例 ID 用于展示。企业微信业务类型 MUST 为 `employee_collection_approval`,其业务标识 MUST 取审批尝试记录主键,使同一申请的多次提交各自持有独立审批实例,且 MUST NOT 修改既有审批实例的共享唯一约束或既有 `refund_approval`、`offline_recharge_approval` 场景语义。 + +企业微信最终通过时,系统 MUST 幂等地将申请标记为已通过、将各分摊写入账单已核销金额并释放其预占;最终驳回时 MUST 标记申请已驳回、释放全部预占且保留审批意见。已驳回申请可修改全部申请内容后重提,历史审批实例、材料和结果不得覆盖;已通过分摊不可修改。企业微信提交失败、回调延迟或结果未知时申请保持在途,系统 MUST 使用既有查询/恢复机制确认渠道结果,且不得由本地人工通过或拒绝绕过企业微信。企业微信对已通过申请撤销时,系统 MUST NOT 回滚已核销金额,MUST 将申请转为异常终态、保留审计与账单详情提示,并禁止自动重提。同一撤销结果在本地申请仍为审批中时(回调乱序或通过结果未被消费),系统 MUST 释放该次审批尝试的全部审批中预占、将申请转为异常终态并记录终态原因、保留审计,MUST NOT 自动重提。 + +#### Scenario: 企业微信通过核销申请 +- **WHEN** 企业微信对含多笔分摊的核销申请返回最终通过,且该结果首次被消费 +- **THEN** 系统仅一次更新申请、各账单已核销金额和状态,并保留审批实例及冻结快照 + +#### Scenario: 企业微信驳回后重提 +- **WHEN** 企业微信最终驳回核销申请 +- **THEN** 系统释放预占、保留驳回实例和意见;员工或有代办权限的超级管理员修改申请后重提时新增审批尝试记录并创建新的审批实例,历史实例与材料不被覆盖 + +#### Scenario: 企业微信通过后撤销 +- **WHEN** 已通过的核销申请收到企业微信撤销结果 +- **THEN** 系统不回滚已核销金额,将申请标记为异常终态、保留审计并在账单详情提示,且不允许自动重提 + +#### Scenario: 审批中收到通过后撤销 +- **WHEN** 企业微信对仍处于审批中的核销申请返回“通过后撤销”(回调乱序或本地未消费到通过结果) +- **THEN** 系统释放该申请全部审批中预占、将申请转为异常终态并记录终态原因、保留审计,且不回滚任何已核销金额;该申请禁止自动重提,账单可重新发起新的核销申请 + +### Requirement: 收款方式字典、退款联动与可追溯性 +系统 SHALL 提供唯一固定分类的线下收款方式字典,其对外契约 MUST 包含 ID、稳定编码、名称和启用状态,引用方 MUST 保存名称快照。超级管理员可维护名称、稳定编码、排序、启停和备注;已被业务引用的字典项 MUST NOT 被物理删除,只能停用,且历史申请继续显示冻结名称。该字典分类由本能力独占维护,MUST NOT 被定义为“核销专用”,本能力也 MUST NOT 修改企业微信 `offline_recharge_approval` 场景的业务字段。 + +来源套餐订单退款成功时,系统 SHALL 以本次退款成功金额与账单应收(即来源订单 `actual_paid_amount`)比较:退款成功金额等于或超过账单应收且账单不存在已通过或审批中分摊时,系统 MUST 自动关闭账单并记录“来源订单全额退款”;退款成功金额小于账单应收且账单不存在已通过或审批中分摊时,系统 MUST 按退款金额冲减账单应收金额并保留来源订单退款冲销记录。账单存在任一已通过分摊或任一审批中(预占)分摊时,系统 MUST NOT 自动冲销,仅在账单详情提示来源订单退款。同一退款对同一账单 MUST 至多产生一条冲销记录。退款不恢复已核销账单的员工欠款,系统 MUST NOT 实现退款审批、退款状态机或支付渠道退款。 + +账单、申请、分摊、附件、审批实例、字典快照、关闭和退款冲销 MUST 可按权限查询并记录操作审计;审计和日志不得保存完整支付凭证敏感内容。OCR 若可用仅用于预填,必须允许申请人或审核人更正,且识别值不是资金事实。 + +#### Scenario: 来源订单部分退款且未核销 +- **WHEN** 来源套餐订单退款成功金额小于账单应收,且其账单不存在任何已通过或审批中分摊 +- **THEN** 系统按退款金额冲减账单应收金额,保留退款冲销关联,并重新计算账单状态和可核销余额 + +#### Scenario: 来源订单全额退款且未核销 +- **WHEN** 来源套餐订单退款成功金额等于或超过账单应收,且其账单不存在任何已通过或审批中分摊 +- **THEN** 系统自动关闭账单、记录“来源订单全额退款”,并保留已核销金额与审计 + +#### Scenario: 存在审批中分摊的账单退款不自动冲销 +- **WHEN** 来源套餐订单退款成功,而其账单存在审批中(预占)分摊但不存在已通过分摊 +- **THEN** 系统不修改账单应收、已核销或预占金额,仅写退款关联提示 + +#### Scenario: 被引用字典项停用 +- **WHEN** 超级管理员停用已被核销申请引用的线下收款方式 +- **THEN** 新申请不可选择该方式,历史申请仍展示其冻结名称和稳定编码