docs(员工代收款): 补充审批中通过后撤销兜底语义并清理死参数
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m24s

- spec.md 增补「审批中收到通过后撤销」场景与规范条文:释放该次尝试全部审批中预占、转异常终态并记录原因、保留审计、禁止自动重提
- design.md「企业微信审批结果消费」补充审批中命中该决策的兜底处理与理由(避免申请永久停在审批中且预占永久占用账单)
- query/employeecollection 删除 approvalStatusOfAttempts 恒为 true 的 withOpinion 形参、修正失真注释,行为不变

OpenSpec Change: add-employee-collection-bills
This commit is contained in:
2026-09-10 18:45:43 +08:00
parent ce24d5612e
commit 69b37eb89b
4 changed files with 10 additions and 11 deletions

View File

@@ -196,7 +196,7 @@ func (q *ApplicationQuery) applicationAttemptList(
Order("attempt_no ASC, id ASC").Find(&attempts).Error; err != nil {
return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询核销审批尝试记录失败")
}
approvalStatus, approvalOpinions, err := approvalStatusOfAttempts(ctx, q.db, attempts, true)
approvalStatus, approvalOpinions, err := approvalStatusOfAttempts(ctx, q.db, attempts)
if err != nil {
return nil, err
}

View File

@@ -365,7 +365,7 @@ func (q *BillQuery) applicationAttempts(
Order("application_id ASC, attempt_no ASC").Find(&attempts).Error; err != nil {
return nil, nil, nil, errors.Wrap(errors.CodeDatabaseError, err, "查询审批尝试记录失败")
}
approvalStatus, approvalOpinions, err := approvalStatusOfAttempts(ctx, q.db, attempts, true)
approvalStatus, approvalOpinions, err := approvalStatusOfAttempts(ctx, q.db, attempts)
if err != nil {
return nil, nil, nil, err
}
@@ -376,13 +376,11 @@ func (q *BillQuery) applicationAttempts(
return grouped, approvalStatus, approvalOpinions, nil
}
// approvalStatusOfAttempts 批量读取审批尝试记录关联的通用审批实例状态。
// withOpinion 为真时同时读取终态决策快照并提取审批意见;列表路径传 false避免批量加载渠道快照。
// approvalStatusOfAttempts 批量读取审批尝试记录关联的通用审批实例状态与终态审批意见
func approvalStatusOfAttempts(
ctx context.Context,
db *gorm.DB,
attempts []model.EmployeeCollectionApplicationAttempt,
withOpinion bool,
) (map[uint]int, map[uint]string, error) {
instanceIDs := make([]uint, 0, len(attempts))
for index := range attempts {
@@ -395,10 +393,7 @@ func approvalStatusOfAttempts(
if len(instanceIDs) == 0 {
return approvalStatus, approvalOpinions, nil
}
columns := []string{"id", "status"}
if withOpinion {
columns = append(columns, "decision_snapshot")
}
columns := []string{"id", "status", "decision_snapshot"}
var instances []model.ApprovalInstance
if err := db.WithContext(ctx).Select(columns).
Where("id IN ?", instanceIDs).Find(&instances).Error; err != nil {

View File

@@ -101,7 +101,7 @@
- 企业微信回调和既有状态恢复任务均按审批实例 ID 进入同一应用用例,不提供后台“通过/驳回”接口。
- 最终通过:锁定申请及按 ID 升序的全部账单;仅当申请仍为审批中时,将每笔分摊从预占转入已核销,重新计算账单 `待核销/部分核销/已核销` 状态,标记申请已通过,并记录审批结果。重复或乱序的同一终态不重复增加已核销金额。
- 最终驳回:仅当申请仍为审批中时释放全部预占,标记已驳回并保存审批意见;重复回调不重复释放。提交失败、回调延迟和未知结果维持在途,由既有查询恢复任务确认,不得人工改写终态。
- 通过后撤销:已通过后收到企业微信撤销时**不回滚已核销金额**;申请转异常终态、保留审计、账单详情提示来源申请异常,并禁止自动重提。
- 通过后撤销:已通过后收到企业微信撤销时**不回滚已核销金额**;申请转异常终态、保留审计、账单详情提示来源申请异常,并禁止自动重提。若该撤销结果到达时本地申请仍为审批中(回调乱序或通过结果未被消费),同样按异常终态处理并释放本次尝试的全部审批中预占——否则申请会永久停在审批中、预占永久占用账单,既不可关闭也不可重提。
### 来源建账与退款冲销

View File

@@ -83,7 +83,7 @@
### Requirement: 核销申请审批、重提与幂等
核销申请状态 SHALL 为 `审批中``已通过``已驳回``已撤销/已关闭`,且不得以申请状态覆盖账单核销状态。提交或重提时系统 MUST 新增一条不可变审批尝试记录(冻结当次收款方式、外部付款、附件、备注、账单分摊及审批材料快照)并为其创建新的企业微信审批实例;申请只保存最新审批实例 ID 用于展示。企业微信业务类型 MUST 为 `employee_collection_approval`,其业务标识 MUST 取审批尝试记录主键,使同一申请的多次提交各自持有独立审批实例,且 MUST NOT 修改既有审批实例的共享唯一约束或既有 `refund_approval``offline_recharge_approval` 场景语义。
企业微信最终通过时,系统 MUST 幂等地将申请标记为已通过、将各分摊写入账单已核销金额并释放其预占;最终驳回时 MUST 标记申请已驳回、释放全部预占且保留审批意见。已驳回申请可修改全部申请内容后重提,历史审批实例、材料和结果不得覆盖;已通过分摊不可修改。企业微信提交失败、回调延迟或结果未知时申请保持在途,系统 MUST 使用既有查询/恢复机制确认渠道结果,且不得由本地人工通过或拒绝绕过企业微信。企业微信对已通过申请撤销时,系统 MUST NOT 回滚已核销金额MUST 将申请转为异常终态、保留审计与账单详情提示,并禁止自动重提。
企业微信最终通过时,系统 MUST 幂等地将申请标记为已通过、将各分摊写入账单已核销金额并释放其预占;最终驳回时 MUST 标记申请已驳回、释放全部预占且保留审批意见。已驳回申请可修改全部申请内容后重提,历史审批实例、材料和结果不得覆盖;已通过分摊不可修改。企业微信提交失败、回调延迟或结果未知时申请保持在途,系统 MUST 使用既有查询/恢复机制确认渠道结果,且不得由本地人工通过或拒绝绕过企业微信。企业微信对已通过申请撤销时,系统 MUST NOT 回滚已核销金额MUST 将申请转为异常终态、保留审计与账单详情提示,并禁止自动重提。同一撤销结果在本地申请仍为审批中时(回调乱序或通过结果未被消费),系统 MUST 释放该次审批尝试的全部审批中预占、将申请转为异常终态并记录终态原因、保留审计MUST NOT 自动重提。
#### Scenario: 企业微信通过核销申请
- **WHEN** 企业微信对含多笔分摊的核销申请返回最终通过,且该结果首次被消费
@@ -97,6 +97,10 @@
- **WHEN** 已通过的核销申请收到企业微信撤销结果
- **THEN** 系统不回滚已核销金额,将申请标记为异常终态、保留审计并在账单详情提示,且不允许自动重提
#### Scenario: 审批中收到通过后撤销
- **WHEN** 企业微信对仍处于审批中的核销申请返回“通过后撤销”(回调乱序或本地未消费到通过结果)
- **THEN** 系统释放该申请全部审批中预占、将申请转为异常终态并记录终态原因、保留审计,且不回滚任何已核销金额;该申请禁止自动重提,账单可重新发起新的核销申请
### Requirement: 收款方式字典、退款联动与可追溯性
系统 SHALL 提供唯一固定分类的线下收款方式字典,其对外契约 MUST 包含 ID、稳定编码、名称和启用状态引用方 MUST 保存名称快照。超级管理员可维护名称、稳定编码、排序、启停和备注;已被业务引用的字典项 MUST NOT 被物理删除只能停用且历史申请继续显示冻结名称。该字典分类由本能力独占维护MUST NOT 被定义为“核销专用”,本能力也 MUST NOT 修改企业微信 `offline_recharge_approval` 场景的业务字段。