Files
junhong_cmp_fiber/openspec/changes/archive/2026-09-18-close-august-iteration-gaps/design.md
break 5ed6b39deb
Some checks failed
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Has been cancelled
feat(收口): 补齐 8 月迭代缺口并同步 Spec 与证据链
- 新增六对成对迁移 000232–000237:H5 弹窗类型、退款结算标识与申请人备注、优先轮询事实字段与两个新终态、通道阈值命中留痕、手机号最近解绑人、提现资格校验留痕
- 退款:原因必填与申请人备注、来源支付与渠道流水冻结、线下处理流水号补录审计、按订单查询可选退款方式、企微审批材料补齐且新增字段缺失映射即明确失败
- 优先轮询:人工关闭、有效期到期独立周期任务、失败与过期人工重触发、事实字段与异常重试查询、资产解析端点只读投影
- 通道阈值:命中事实同事务留痕与命中记录查询;员工账单:列表筛选与详情投影;商户池:列表投影与统计周期语义;H5:弹窗类型与类别排序
- 手机号:有效关联数量与最近解绑人、短信验证码失败次数限制;导出:佣金明细十五列与报表序号列
- 时间筛选:三处新增筛选纳入统一严格解析契约,员工账单产生时间参数改名
- 同步 12 份主 Spec 需求、两端点与异步任务证据链,门禁 context-health 与 OpenSpec 校验通过
2026-09-18 15:34:29 +08:00

187 lines
26 KiB
Markdown
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.
## Context
本变更涉及 12 个既有能力的行为契约补齐,全部落在既有模块边界内,不引入新模块、新角色或新第三方依赖。影响面与约束:
- **代码事实**(详见各 change 归档与主 Spec员工账单列表请求当前只有 `source_type/source_no/status/debtor_account_id/customer_id/created_from/created_to``internal/model/dto/employee_collection_dto.go:57-67`);商户池响应当前无成员计数、当前命中成员与 `updated_at``internal/model/dto/payment_merchant_dto.go:82-97`);运营弹窗配置无类型字段(`internal/model/dto/h5_popup_dto.go:8-31`);退款创建请求的 `refund_reason``omitempty` 且无备注字段(`internal/model/dto/refund_dto.go:4-30`),退款审批材料只提交套餐使用记录 ID`internal/application/wecom/scene.go:325-338``internal/application/refundapproval/creation.go:548-566`);优先轮询事实表无资产、设备号、代理、下一执行时间、出队时间列(`migrations/000228_add_polling_priority_queue.up.sql`);通道阈值锁表无命中用量与阈值列(`migrations/000227_add_carrier_traffic_threshold.up.sql`);佣金明细导出表头 14 列(`internal/exporter/commission_record_scene.go:44-49`);报表导出表头首列为分组列(`internal/exporter/operations_report_scene.go:89-91`);验证码校验无失败计数(`internal/service/verification/service.go:130-160`)。
- **文档冲突**:主 Spec「商户池唯一性与轮询配置」的文字全部成员达标即失败与代码在 `每轮累计` 周期下的行为(自动开启新一轮)不一致;`111.md` §6.4(单成员池固定使用)与讨论稿 §2.6 第 6 条(全达标即失败)在单成员场景下互相张力。本变更按 111.md 收口并显式写入 Spec。
- **契约冲突**`openspec/specs/export-time-filter/spec.md` 的受影响端点表把「员工代收款账单」等端点列为「本期 MUST NOT 新增时间参数、MUST NOT 改变参数名与格式」,而 PRD §2.16 要求所有需要时间筛选的页面统一使用 `start_time`/`end_time` 与带时区 RFC3339 秒级闭区间。本变更新增时间筛选的三处端点(员工账单列表、优先轮询项查询、通道阈值命中查询)与该表述冲突,因此新增一份 `export-time-filter` delta 把这些端点改为已纳入契约,并明确员工账单既有「产生时间」参数同批替换;全变更 MUST NOT 复制第二套时间解析。
- **工程约束**:新 Schema 变化必须成对新增迁移,不修改既有迁移;自动化测试当前为 N/A验证在维护者指定的测试环境`junhong_cmp_test` 与测试 RedisENG-TEST-001运行配合 smoke 与 OpenAPI 生成物;前端不在本仓库,页面渲染类需求只落到后端契约。
- **上线前置**:企业微信审批模板控件映射由维护者在生产按既有场景接口配置,变更不得写入模板。
## Goals / Non-Goals
**Goals:**
- 把 12 组字段级与接口级缺口固化为 Spec 级可验证行为,并让代码与 Spec 文字在商户池轮询语义上重新一致。
- 新增字段一律以「只读投影优先」实现:能用既有事实派生就不新增写入路径。
- 新增迁移只做加列、重建受新增状态影响的约束与回填默认值,保持旧数据可读、旧行为可回滚。
- 新增时间筛选全部复用既有共享严格解析器(`pkg/utils.ParseTimeRange`)与闭区间语义,并消除既有契约中与本变更冲突的「不得新增时间参数」表述。
**Non-Goals:**
- 不新增能力目录、不新增命名角色(含「运营管理员」「财务」),不改既有权限模型与数据范围下推方式。
- 不实现优先轮询 P0/P1/P2 分级与优先级配置、不恢复「普通轮询异常补偿」场景、不做换货佣金回溯、不实现自动续费「有余额不停机」、不实现按导入批次配置绑定要求。
- 不新增人类可读的账单编号列(账单编号即账单主键标识;若业务需要可读编号,属独立需求)。
- 不引入前端页面、图表渲染、模板下载或二维码渲染。
- 不为任何新增字段引入运行时开关或对外可配项。
## Decisions
### D1 员工账单:筛选与投影走读侧扩展,不改写入路径
列表新增筛选(账单编号、核销通过时间、员工姓名或账号与客户名称模糊)与详情新增投影(操作日志、来源订单快照)全部在 `internal/query/employeecollection` 内实现:模糊匹配用 `ILIKE` 子串并走既有索引前缀;核销通过时间取该账单经「状态为已通过(`status = 1`)的分摊」关联到的申请表审批终态时间(`tb_employee_collection_application.decided_at`),以相关子查询表达——分摊表没有通过时间列,且其 `released_at` 在既有实现中同时承担「审批通过转已核销」与「驳回或释放预占」两种语义MUST NOT 作为核销通过时间使用;操作日志复用既有审计事实投影(按 `employee_collection_bill` 资源读取既有审计查询),来源订单快照按来源类型分别读取订单/充值记录既有列。
「账单编号」即账单主键标识:列表、详情、关闭与筛选一律以该标识定位,本变更 MUST NOT 新增编号列;若业务需要人类可读编号,登记为后续独立候选(需另定编号规则与历史回填,`111.md` 未定义格式)。
理由:这些是读侧缺口,新增写入会引入重复事实。
备选:在账单表冗余姓名与客户名称列 —— 数据会随账号改名漂移,放弃;新增人类可读账单编号列 —— 需另定编号规则与历史回填且超出本变更范围,放弃。
### D2 未核销金额与剩余可核销金额并列返回
`未核销金额 = 应收 已核销``剩余可核销金额 = 应收 已核销 审批中预占`,两者同时返回且已关闭账单均为零。
理由:原文的口径是前者,服务端校验用的是后者;并列返回消除歧义,且不改变既有预占逻辑。
备选:只改前者语义 —— 会破坏并发核销防超额校验,放弃。
### D3 商户池「全部成员达标」按统计周期分别定义
`每轮累计`:视为当轮结束,以保存成功时间为新起点开启新一轮并从零累计;`自然日累计`/`自然月累计`:拒绝创建并提示当前周期暂无可用商户。
统计分子/分母:金额与笔数累计只统计 `tb_payment_merchant_routing_success` 中支付所属池与当前统计世代匹配的记录,自然日/自然月方式再加支付时间不早于当期窗口起点;首次成功按支付 ID 唯一一次性计入创建时就冻结的商户与统计世代。
并发不变量:`每轮累计` 开启新一轮 MUST 以条件更新递增统计世代(`WHERE id = ? AND routing_epoch = ?`),仅在当前世代未被并发改变时成功;更新冲突 MUST 按并发冲突拒绝创建本次支付MUST NOT 在旧世代上重复累计或静默沿用旧世代。
理由:这正是「每轮累计在商户重新被轮到时清零」的语义,也是 `round` 与自然周期的唯一可区分点Spec 原文字面把所有周期一律写成失败,属文档过宽。
备选:让 `round` 也返回失败 —— 会使 `round` 与自然周期不可区分,且轮次永远无法推进,放弃。
### D4 单成员池固定使用该成员
池内只有一个启用成员时,该成员达到阈值后仍被选中,且不因「全部成员达标」拒绝创建。
理由:`111.md` §6.4 明确该行为;上线迁移按讨论稿 §2.6 第 7 条会为每种支付方式创建「包含一个商户的启用商户池」,若达标即拒付,单商户池在自然日/自然月周期内将当天无法再收款,业务不可接受。
备选:保持自然周期拒付 —— 需要运营把统计周期改为 `round` 才能用,隐性依赖过强,放弃。
影响:该规则优先于 D3 的自然周期拒绝语义Spec 中已写明优先级。
### D5 时间轮询起点由服务端在保存成功时重算
修改周期数值/单位、起始时间或成员顺序并保存成功时,服务端 MUST 以保存成功时间覆盖起点,忽略请求携带的旧起点;创建时按请求起点,请求缺省时取保存时间。
理由:原文要求「以保存成功时间为新起点」,把重算放在服务端可避免前端时钟与漏传导致的时段漂移。
备选:继续信任请求值 —— 前端漏传即产生错误轮转,放弃。
### D6 商户池列表的「当前命中成员」只读计算
复用 `chooseMerchant`/`chooseTimedMerchant` 的判定抽成只读函数:金额/笔数取顺序中第一个未达标成员,`round` 全达标取首位时间方式按当前时段MUST NOT 推进世代或创建支付单。既有实现依赖事务并会推进世代,不可直接复用为读侧,因此只读函数独立抽取并在列表内按页批量计算。
理由:避免为列表展示引入第二套判定,也避免查询产生副作用。
备选:列表不展示当前成员 —— 与 111.md §6.2 冲突,放弃。
### D7 H5 运营弹窗类型决定优先级类别
新增必填 `popup_type``promotion`/`announcement`),排序先按类别(风险换卡 > 推广 > 公告)再按显式优先级,同优先级取最近更新时间最新;既有配置迁移为 `announcement`
类别顺序 MUST 表达为显式的类别排序表达式MUST NOT 依赖类型取值的字典序:`announcement` 的字典序小于 `promotion`,直接按列排序会把公告排在推广之前,与 `111.md` §13.3 相反。类别序稳定后无需新增排序冗余列,既有匹配索引继续服务过滤与时间窗口筛选(本表为后台维护的小型配置表)。
理由:原文要求类型与分级;把类别作为第一排序键后,显式优先级仍可表达同类内次序,既有配置的相对顺序不变。
备选:仅加类型字段不参与排序 —— 无法满足 §13.3 的优先级要求,放弃。
### D8 退款原因必填与申请人备注
`refund_reason` 在创建与重提时必填(去空白后非空),校验放在应用层的创建与重提用例,保证内部调用同样受约束。
校验位置边界MUST NOT 把该校验放进审批尝试构造与补发历史审批的路径(`refundapproval``Execute``TriggerHistorical` 会为历史与补发申请建立首条尝试);历史申请与补发历史审批不受本约束、不追溯。
理由:原文与讨论稿 §2.3 第 15 条均要求必填,仅靠 DTO 校验会漏掉内部路径;但把校验下沉到尝试构造会阻断历史申请的补发审批。
### D9 退款渠道标识落库而非读取时推导
退款单新增 `source_payment_no``original_channel_trade_no`(冻结原成功支付事实)与 `offline_settlement_no`(财务补录,写审计)。列表/详情/导出直接返回冻结值。
申请人备注与渠道标识一样逐次冻结,但备注落在**审批尝试表**每次提交或重提一条历史尝试不被改写MUST NOT 复用或改写退款主表既有 `remark`(该列语义为审批备注,已被既有实现占用,本变更不改其语义与展示口径)。
理由:原文要求退款单可追溯原支付与退款流水;读取时跨三表推导无法表达冻结语义,也不满足对账需要的稳定性。
备选:只在响应中联表返回 —— 支付单后续被改挂或数据缺失时结果漂移,放弃。
### D10 可选退款方式走独立只读接口并复用同一判定
新增按订单查询可选方式与原因的接口,内部调用与创建/提交/执行相同的判定函数;判定事实缺失返回不可用原因而非报错。
「同一判定实现」落点:方式集合、可用性与不可用原因来自既有的方式判定实现(创建与重提已在调用);原路可退的凭证判定来自既有的凭证判定单一来源(企业微信通过后执行前的预检已在调用)。只读接口 MUST 复用这两个实现MUST NOT 引入第二套判定或独立开关。提交环节只消费已冻结材料,不新增判定。
接口落点:`GET /api/admin/refunds/order-options`;字面量路径 MUST 注册在同组参数路由(`/:id`)之前;权限沿用退款组门禁(超级管理员/平台/代理,企业账号拒绝)并按订单数据范围下推,越权与订单不存在不可区分。
理由:原文 §12.6 第 1 条要求页面可按订单类型展示;复用同一判定避免出现两套口径。
备选:由前端硬编码矩阵 —— 与渠道凭证状态相关,前端无法判断,放弃。
### D11 退款审批材料带出用量与资产维度
在退款审批材料中增加资产类型、设备类型、设备型号、套餐已用量/总量与原支付渠道交易流水号,取值复用「退款展示当前退款套餐用量」的既有解析规则,并冻结进审批尝试快照;控件缺失时明确失败而非静默丢弃。
取值来源:套餐已用量与总量复用既有展示解析规则与真流量口径;资产类型沿用退款请求的下单快照推导;设备类型与设备型号取退款请求关联设备(资产为设备时),无关联设备时以空值提交;原支付渠道交易流水号取创建申请时冻结的原成功支付事实。
控件强制范围仅对本变更新增字段强制映射——场景白名单为新增字段标记为必须映射场景映射校验在缺失映射时明确失败表单构建在快照缺值时明确失败并提示缺失控件既有未映射的可选控件保持既有静默跳过行为MUST NOT 推广为全场景强制映射。
理由:审批人需要在企业微信侧看到用量数字,静默丢弃会造成审批材料与本地不一致;但把全部白名单字段改为强制映射会误伤既有场景按模板能力裁剪的可选字段。
备选:仅保留套餐使用记录 ID —— 正是本次要修的缺口,放弃。
### D12 优先轮询补齐字段、出口与详情投影
- 事实表加列:资产类型/资产 ID、设备号快照、代理归属、任务类型既有、尝试上限常量回填、执行开始/结束时间、出队时间、下次执行时间(可空)、集成交互日志标识、有效期起算时间。
- 迁移边界:状态 CHECK 必须随新增的「已关闭」「已过期」两个终态重建(既有约束只允许待执行、执行中、已完成、失败出队四个取值);活动项部分唯一索引谓词保持 `status IN ('pending','processing')` 不变,新终态不占键位;为下次计划执行时间建部分索引以支撑到期扫描与筛选。
- 有效期起算新增有效期起算时间列up 脚本把既有活动项回填为迁移时刻,使既有未完成项不会在上线后被首轮扫描批量过期;新增行取入队时间。有效期长度与尝试上限均为代码常量,不新增可维护配置项。
- 到期处理承载:新增独立周期任务类型承载有效期到期扫描与出队,按既有周期任务注册形态注册到任务处理器与 Worker 调度MUST NOT 依赖查询或读侧隐式触发。
- 读侧兼容:既有行的下次执行时间为空时,读侧 MUST NOT 据此判定已过期。
- 新增人工关闭(原因必填,写审计)与有效期(固定常量,超期转已过期终态)两个出口;失败与已过期项支持人工重触发,复用人工入队入口并新建活动项(尝试次数归零)。
- 读侧补齐字段与「异常与重试」筛选视图(复用同一事实表,不建独立异常表);优先轮询状态投影落在既有资产解析端点(`GET /api/admin/assets/resolve/:identifier`的卡与设备两个分支MUST NOT 为投影新增独立端点(新增 `asset-device` 要求)。
理由§23.4/§23.6/§23.7/§23.9/§23.10 的字段与出口是运营排障的前提;有效期与人工关闭使失败项可被清理。本次显式取代先前 change「MUST NOT 引入有效期字段」的裁剪。
备选:维持原样只做文档登记 —— 用户明确要求补齐,放弃。
### D13 通道阈值命中事实留痕
在既有锁表加列或新建命中事实表记录:累计流量读数、阈值与单位、周期起点、判定时间、触发来源;周期恢复记录解锁时间与复机结果。
查询落点与权限:命中记录查询使用不与通道详情参数路由冲突的顶层路径,自带超级管理员与平台用户门禁并按卡数据范围下推;越权与不存在不可区分,不返回凭证或上游响应原文。
留痕失败边界:留痕与停机事实在同一事务内写入;留痕写入失败 MUST NOT 放行或延迟停机,停机事实仍为主结果。
历史行兼容:判定时间回填既有锁行的创建时间,命中数值与阈值可空(历史读数不可重建)。
理由:运维需要数字证据解释停机;把留痕放在同一事务内保证与停机事实一致。
备选:仅写运行日志 —— 日志非查询事实且随留存清理消失,放弃。
偏好:加列到既有锁表,锁表本身即「一次命中一条」,避免新表与锁表 1:1 冗余。
### D14 手机号关联数量与验证码失败限制
关联查询按当页手机号集合一次聚合有效关系数并返回最近解绑人(后台账号名称快照,按既有上限判定的同一计数口径);验证码校验在 Redis 以手机号维度原子累加失败计数并在达到阈值后短时锁定,成功后清零。
验证码失败限制细节计数在手机号维度原子累加并发不丢次数锁定检查在验证码比对之前锁定期内即使提交正确验证码也不通过窗口内校验成功清零阈值与窗口为固定常量不新增对外可配置项错误码使用既有请求过多错误码提示不泄露验证码正确性、剩余次数或内部键名。计数写入失败时放行并记录MUST NOT 因限流计数故障阻断注册、绑定、换绑、换证与登录的成功路径;校验失败不消费验证码、发送侧频率限制与验证码有效期语义保持不变。
归属:该要求挂在 `phone-asset-association`(来源为 `111.md` §15 手机号绑定章节),作用域覆盖注册、绑定、换绑与换证的全部短信验证码校验流程。
理由:数量口径必须与「最多十项」判定一致,复用同一计数函数可避免漂移;失败限制是安全基线,原子计数避免并发绕过。
### D15 报表导出与佣金导出列
报表导出首列固定为「序号」(分组行按展示顺序连续递增,合计行序号列留空且不改合计行其他列的既有表示);佣金明细导出按下述列序补齐店铺、业务员、用户组、资产类型、设备类型、设备型号、资产标识、订单号、下单时间、佣金金额、佣金来源、佣金状态、关联佣金明细、入账后金额、创建时间。归属列按导出执行时当前归属补充,缺失列以空值导出而不阻断;回溯记录金额保持负数、可提现标识保持不可提现。
理由:原文列出这两类导出的完整列清单;导出必须与页面口径一致且不因个别行缺值整批失败。
### D16 不新增「运营管理员」命名角色
本次只登记口径,不引入角色:运营类动作继续由超级管理员与平台用户在其数据范围内承担,店铺分类仍由业务用户组推导。
理由:新增角色会牵动权限体系与全部路由鉴权,远超原文「角色与权限设计」表格的表达意图,且与既有「本期 MUST NOT 引入财务角色」的裁剪方向一致。
备选:新增角色并接入 `RequirePermission` —— 本轮不引入鉴权体系改造,放弃。
### D17 新增时间筛选统一纳入既有契约
三处新增时间筛选员工账单「核销通过时间」、优先轮询项查询、通道阈值命中查询MUST 复用既有共享严格解析器(`pkg/utils.ParseTimeRange`)与同一闭区间语义:带显式时区的 RFC3339 秒级、归一为 UTC 瞬时、含两端、开始晚于结束拒绝、未传与空串等价MUST NOT 复制既有账单日期解析或客户端可选时间解析等第二套实现。
参数命名:主时间字段使用统一参数名 `start_time`/`end_time`;同一端点存在第二个时间字段时使用「业务语义 + 时间后缀」的固定命名——员工账单核销通过时间为 `decided_start_time`/`decided_end_time`,与主字段同一解析器、同一闭区间。
员工账单既有「产生时间」参数(日期形态)同批替换为统一参数名并改用严格解析:旧参数名与旧格式 MUST 被拒绝MUST NOT 保留兼容或别名。
契约侧同步:新增 `export-time-filter` delta把员工账单列表、优先轮询项查询、通道阈值命中查询从「本期不得新增时间参数」的排除列表改为受影响端点表内条目并在表中写明各自筛选的时间字段员工账单产生时间与核销通过时间、优先轮询入队时间、阈值命中判定时间
理由PRD §2.16 已确立统一时间筛选基线;同一仓库并存两套时间解析会让拒绝集与边界语义漂移,也使新增端点的行为无法被单一契约验证。
备选:新增端点为回避契约而沿用各区自行解析 —— 与 PRD §2.16 及既有主 Spec 的严格解析方向相悖,放弃。
## Risks / Trade-offs
- [新增模糊筛选拖慢员工账单列表] → 使用子串匹配并限制输入长度上限,筛选列复用既有索引;如后续数据量放大再引入检索列。
- [商户池单成员固定使用可能与运营预期(周期内限流)冲突] → Spec 已写明优先级;运营如需周期内限流,应改为多成员池。
- [退款原因必填会阻断历史习惯的内部调用] → 有意的破坏性收敛;上线说明与接口文档同步更新,错误文案明确指向缺失字段。
- [优先轮询新增有效期与人工关闭引入新状态] → 新状态只影响读侧投影与出队,不改变调度通道、并发上限与持锁拒绝;对既有活动项按无有效期处理,避免上线瞬间批量过期。
- [命中留痕加列使锁表变宽] → 只加定长数值与时间列,无新索引主键变更;锁定写入仍在停机同一事务内。
- [企微模板控件未配置会导致退款/注册审批提交失败] → 明确失败并提示缺失控件,避免静默丢弃造成数据不一致;上线前置项写入任务。
- [既有运营弹窗配置迁移为「通用公告」会改变相对排序] → 迁移只赋予类型,显式优先级与同类别内顺序保持;跨类别次序变化即 111.md §13.3 期望结果。
- [报表序号列改变既有导出列序] → 属期望列定义变化;导出为异步任务,列冻结在执行期,旧任务不受影响。
- [新列默认值与回滚] → 新列一律可空或带默认值;读侧对空值按「无」处理;既有优先轮询行的有效期起算时间非空但下次计划执行时间为空时,读侧 MUST NOT 据此判定已过期;回滚到上一版本时新列为空不影响既有读写。
- [时间参数替换影响既有调用方] → 员工账单列表「产生时间」参数改名属有意的破坏性收敛,与第 16 项对换货、分配记录、代理充值的处理同类;旧参数名与旧格式一律拒绝并返回参数非法错误,不再静默忽略。
## Migration Plan
1. **成对迁移(新文件,不改既有)**:六对,编号与内容以本文件与 `proposal.md``tasks.md` 第 1 节逐字一致:
1. `000232` H5 弹窗配置加类型列(枚举两值、非空、默认通用公告)并回填既有行。
2. `000233` 退款主表加来源支付单号、原支付渠道交易流水号、线下退款处理流水号与登记时间、登记人;退款审批尝试表加申请人备注列。
3. `000234` 优先轮询事实表加资产类型与资产 ID、设备号快照、代理归属、尝试上限默认 3、执行开始与结束时间、下次计划执行时间、出队时间、集成交互日志标识、有效期起算时间重建状态 CHECK 容纳「已关闭」「已过期」;活动项部分唯一索引谓词不变;为下次计划执行时间建部分索引;有效期起算时间回填为迁移时刻。
4. `000235` 通道阈值锁表加命中累计流量、阈值数值、阈值单位、判定时间、触发来源、解锁时间、复机结果;判定时间回填创建时间、命中数值留空;加按通道、卡与判定时间的查询索引。
5. `000236` 手机号关联加最近解绑人名称快照列(注释写明审计侧仍只写脱敏值)。
6. `000237` 提现审批尝试表加资料资格版本标识、校验时间、是否通过、未通过稳定原因列。
2. **无需 DDL**:员工账单(主键即账单编号、通过时间取自申请表)、商户池列表投影(池表已有更新时间列)、佣金明细导出与报表序号列(导出执行期 join 补齐)。
3. **回填策略**:新列一律可空或带默认值;既有优先项有效期自迁移时刻起算,不回填过期;既有运营弹窗回填通用公告;退款原因必填只对上线后新提交与重提生效,历史申请与补发历史审批不追溯。
4. **上线顺序**:先迁移 → 再发布 API/Worker 二进制(手工发布,见生产运行说明)→ 再由维护者配置企微模板控件映射(仅本次新增字段强制映射)→ 最后开放新字段使用。
5. **回滚**:代码回滚到上一版本时新增列仍为空,读侧保持兼容(空值按空处理);企微控件配置回滚需同步移除新增控件映射;时间参数替换的回滚需同步前端(旧参数名不再被接受)。
## Open Questions
- 企业微信退款审批与新增代理审批模板的控件新增项,具体控件类型与必填性待维护者按模板能力确认后配置,不影响本变更的 Spec 与任务拆分。
- 线下退款处理流水号是否需要按渠道做格式校验,待财务确认口径;当前按自由文本登记并写审计。
- 账单若需要人类可读编号(而非主键标识),需另行提出需求并定义编号规则与历史回填方案。