VWCG-1120 消费策略适用账户与命中规则技术开发文档
版本:V1.1 修订评审稿
日期:2026-07-28
目标仓库:store
当前分支:codex/VWCG-1121-pc-account-settings-20260727
风险等级:高
实施结论:核心方案已按需求收敛,可进入研发评审;编码前需确认VWCG-1121环境依赖和管理费资格规则,UI 验收前需完成原型视觉核对
1. 结论摘要
本需求不仅是在 PC 表单增加多选字段,还会改变支付前的策略资格判断。首期采用“账户关系表 + 允许余额不足的原始金额预分摊 + 商户隔离的账户交集查询 + 异步策略计算标识 + 单字段资格快照”的最小闭环方案:
- 新建
ydy_marketing_strategy_account,保存策略与个人、餐补、赠送账户的多选关系。 - 对订单原始应付金额做只读账户使用预览。预览只分配现有可用金额,允许返回缺口,不因原始金额余额不足而提前失败。
- 只把预分摊金额大于
0.00的一级账户视为“本单使用账户”。 - 使用带
store_id条件的EXISTS账户交集查询;高优先级账户不匹配时自然继续下一条策略。 - 计算优惠或管理费后,才按最终应付金额执行严格余额校验和正式扣款。
- 异步事件显式保存“策略已计算”标识。策略 ID 为
0也代表合法冻结的“未命中”,队列不得重算。 - 历史未删除策略回填全部三种账户;新建策略默认不选择账户,由用户主动选择。
- 订单只增加一个最小资格快照;最终账户拆分继续使用
VWCG-1121已有字段。
不在首期强制范围内:新增全局功能开关、完整余额审计 JSON、额外三列资格快照、首单优惠候选算法重构。
2. 需求追踪矩阵
证据标识:
REQ:云效明确需求,实施必须满足。FACT:当前分支代码事实。DECISION:为消除循环依赖或保证一致性形成的技术决策。PENDING:需要产品、研发或视觉确认。OPTIONAL:增强项,不阻塞首期。
| 编号 | 云效需求 | 技术落点 | 状态 |
|---|---|---|---|
| R1 | 新增、编辑增加“适用账户” | PC 弹窗增加 accountTypes |
REQ |
| R2 | 个人、餐补、赠送账户支持多选 | 账户枚举 1/2/3 + 关系表 | REQ |
| R3 | 标签旁显示小 i 提示 |
el-tooltip + el-icon-info,文案采用云效原文 |
REQ |
| R4 | 多账户为 OR,任一交集即命中 | usedAccountTypes ∩ strategyAccountTypes ≠ ∅ |
REQ |
| R5 | 未选择不允许保存 | 前后端均校验非空,错误文案一致 | REQ |
| R6 | 列表增加展示列、筛选和回显 | 列表/详情接口、筛选区、表格列 | REQ |
| R7 | 高优先级账户不匹配继续下一条 | 账户条件进入同一策略查询 | REQ |
| R8 | 历史策略默认全部账户 | 迁移回填 1/2/3 | REQ |
| A1 | 有余额但预分摊为 0 不算使用 | 只提取预分摊金额大于 0 的账户 | REQ |
| A2 | 同步、异步、重试结果一致 | 同一预览合同 + 异步计算标识 | REQ |
3. 需求解释与边界
3.1 账户映射
| 产品名称 | 算法键 | 存储值 | 当前组成 | 命中条件 |
|---|---|---|---|---|
| 个人账户 | cash |
1 |
活动本金 + 可提现现金 + 合法透支 | 预览 cash > 0 |
| 餐补账户 | subsidy |
2 |
消费时点有效的餐补批次 | 预览 subsidy > 0 |
| 赠送账户 | gift |
3 |
独立赠送余额 | 预览 gift > 0 |
个人账户的内部现金拆分属于 VWCG-1121 结算事实,对消费策略只暴露一个“个人账户”选项。该映射属于当前分支代码事实,不新增第四种策略账户。
3.2 “账户有余额”不等于“账户被使用”
策略资格只看原始金额预分摊:
used_account_types = {
cash if preview.cash > 0,
subsidy if preview.subsidy > 0,
gift if preview.gift > 0
}
例如扣款顺序为餐补 → 个人 → 赠送,订单原始应付 20 元,餐补 30 元、个人 100 元,则预分摊为餐补 20 元、个人 0 元。个人账户虽然有余额,但个人账户策略不命中。
3.3 原始金额允许存在预分摊缺口
账户资格预览不能复用当前严格扣款方法的“余额不足即抛异常”行为。
例如:
原始应付:100 元
可用余额:餐补 50 元 + 个人 30 元
策略:固定优惠 30 元
最终应付:70 元
原始金额预览应返回:
used_account_types = [餐补, 个人]
allocated_amount = 80.00
shortfall_amount = 20.00
随后允许这两个账户参与策略匹配。只有优惠计算完成后,才校验最终应付 70 元能否正式扣款。否则会把“优惠后可支付”的合法订单错误拦截为余额不足。
3.4 非目标
- 不增加第四种账户。
- 不修改
VWCG-1121的扣款顺序、现金内部顺序或退款顺序。 - 不按优惠后的最终拆分二次选择策略。
- 不允许一单叠加多条消费策略。
- 不重算历史订单的策略资格。
- 不修改消费策略菜单、路由或权限模型。
- 不在本需求内修改“仅首单优惠”不满足时是否继续下一条策略。
4. 当前实现与关键缺口
4.1 配置链路
当前消费策略通过独立关系表保存部门、身份、档口、场景、餐次和时间段。新增适用账户沿用相同模式,避免把数组保存为主表逗号字符串或 JSON。
当前接口:
POST /api/marketingStrategy/lists
POST /api/marketingStrategy/create
POST /api/marketingStrategy/update
POST /api/marketingStrategy/detail
POST /api/marketingStrategy/delete
4.2 匹配链路
当前 Consume::applyMarketingStrategy() 先选择策略,再校验最终余额;findApplicableMarketingStrategy() 按身份、部门、档口、场景和时间段过滤后直接取一条。
本需求需要在选策略前得到原始金额账户使用预览,并把账户条件放入同一候选查询。不能先取最高优先级策略,再在 PHP 中因账户不匹配返回空。
4.3 异步队列缺口
当前队列使用 marketing_strategy_id <= 0 判断是否需要重新计算。该判断无法区分:
- 还没有计算策略;
- 已经计算,但合法结果是没有命中策略。
本需求必须增加独立的 marketing_strategy_evaluated 标识;不能继续用策略 ID 是否大于 0 代表计算状态。
4.4 三账户能力依赖
当前分支已包含:
GiftAccountPolicy::planDeduction():严格的三账户最终分摊。AccountSettlementService:最终扣款、退款、拆分和结算幂等。- Redis 三账户余额快照。
- 订单三账户支付拆分和扣款顺序快照。
本需求需要新增“允许缺口的只读账户使用预览”,不能直接调用会写余额的 deduct(),也不能直接复用余额不足即抛异常的严格 planDeduction()。
5. 核心架构决策
5.1 ADR-01:资格使用允许缺口的原始金额预览
状态:ACCEPTED
理由:
- 账户资格必须基于原始金额,避免优惠与账户资格循环依赖。
- 预览允许缺口,避免误伤优惠后可支付订单。
- 正式扣款仍使用严格算法,资金安全边界不放宽。
5.2 ADR-02:同步预览与扣款使用同一账户事实
状态:ACCEPTED
同步链路在账户库事务内锁定用户和有效餐补行,构建一次账户事实快照。资格预览和最终扣款都基于这份快照,不能预览时无锁查一次、扣款时重新查一次。
建议把账户能力拆成:
loadLockedAccountSnapshot()
previewAccountUsage()
deductWithLockedSnapshot()
三者复用同一余额结构和扣款顺序。现有跨业务库、账户库事务继续使用账户结算单和订单号作为幂等锚点,不在本需求中扩展分布式事务。
5.3 ADR-03:管理费不触发策略二次选择
状态:PENDING
若原始 20 元仅预览使用餐补,命中管理费策略后最终应付 25 元,新增 5 元可能使用个人账户。建议资格仍冻结为“仅餐补”,不根据最终拆分重新选择策略。
原因是二次选择会形成:
命中策略 → 金额变化 → 使用账户变化 → 重选策略
该规则需要产品和研发在编码前确认,但不改变其余模块的实施方案。
5.4 ADR-04:异步“未命中策略”也是冻结结果
状态:ACCEPTED
异步事件必须保存:
{
"marketing_strategy_evaluated": 1,
"strategy_snapshot_version": 1,
"marketing_strategy_id": 0,
"strategy_account_types": [],
"strategy_hit_account_types": [2],
"pay_price": "20.00"
}
当 marketing_strategy_evaluated = 1 时,队列必须沿用全部冻结字段。marketing_strategy_id = 0 表示已计算但未命中,不允许重算。
6. 数据库设计
6.1 账户关系表
CREATE TABLE `ydy_marketing_strategy_account` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`strategy_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '消费策略ID',
`account_type` tinyint(3) unsigned NOT NULL DEFAULT '1'
COMMENT '适用账户:1个人账户 2餐补账户 3赠送账户',
`store_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '商城ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_store_strategy_account`
(`store_id`,`strategy_id`,`account_type`),
KEY `idx_strategy_account` (`strategy_id`,`account_type`),
KEY `idx_store_account` (`store_id`,`account_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci COMMENT='消费策略-适用账户关联';
约束:
- 应用层只接受 1、2、3。
- 每条可新增、可编辑策略至少保存一条关系。
- 主表和关系表在同一业务库事务中保存。
- 软删除策略时删除关系行。
- 所有读写同时限定
store_id和strategy_id。
6.2 历史策略回填
只回填未删除历史策略,SQL 可重复执行:
INSERT INTO `ydy_marketing_strategy_account`
(`strategy_id`, `account_type`, `store_id`)
SELECT ms.`id`, accounts.`account_type`, ms.`store_id`
FROM `ydy_marketing_strategy` ms
CROSS JOIN (
SELECT 1 AS `account_type`
UNION ALL SELECT 2
UNION ALL SELECT 3
) accounts
LEFT JOIN `ydy_marketing_strategy_account` msa
ON msa.`store_id` = ms.`store_id`
AND msa.`strategy_id` = ms.`id`
AND msa.`account_type` = accounts.`account_type`
WHERE ms.`is_delete` = 0
AND msa.`id` IS NULL;
迁移校验:
SELECT ms.store_id, ms.id
FROM ydy_marketing_strategy ms
LEFT JOIN ydy_marketing_strategy_account msa
ON msa.store_id = ms.store_id
AND msa.strategy_id = ms.id
WHERE ms.is_delete = 0
GROUP BY ms.store_id, ms.id
HAVING COUNT(DISTINCT msa.account_type) <> 3;
结果必须为 0 行。
6.3 最小订单资格快照
在 ydy_meal_order 增加一个字段:
ALTER TABLE `ydy_meal_order`
ADD COLUMN `strategy_account_snapshot` varchar(512) NOT NULL DEFAULT ''
COMMENT '消费策略账户资格快照JSON'
AFTER `actual_strategy_value`;
示例:
{
"v": 1,
"configured": [1, 3],
"used": [3],
"original_amount": "20.00",
"allocated_amount": "20.00",
"shortfall_amount": "0.00",
"order": [2, 1, 3]
}
该字段只保存解释策略资格所需的最小事实,不保存账户全量余额、身份、姓名或凭据。最终 cash/subsidy/gift 和现金内部拆分继续使用 VWCG-1121 已有字段。
7. API 合同
7.1 账户枚举
[
{"value": 1, "label": "个人账户", "key": "cash"},
{"value": 2, "label": "餐补账户", "key": "subsidy"},
{"value": 3, "label": "赠送账户", "key": "gift"}
]
前端可保存本地常量;后端必须保留唯一枚举定义,供校验、名称转换、关系保存和策略匹配复用。
7.2 列表
POST /api/marketingStrategy/lists
新增请求参数:
{
"accountTypes": [1, 3]
}
筛选为空表示不限账户;多选筛选按 OR 处理。
列表新增字段:
{
"account_types": [1, 3],
"account_names": "个人账户、赠送账户"
}
列表 account_names 使用字符串,与现有 department_names、restaurant_names 风格一致,不再增加第二个同义展示字段。
7.3 新增和编辑
POST /api/marketingStrategy/create
POST /api/marketingStrategy/update
{
"accountTypes": [1, 2, 3]
}
服务端规则:
- 参数必须可格式化为数组。
- 去重后至少一项。
- 每项必须是整数 1、2、3。
- 空数组返回“请至少选择一个适用账户”。
- 即使绕过前端,也不能保存空关系。
7.4 详情
POST /api/marketingStrategy/detail
{
"account_types": [1, 2, 3],
"account_names": ["个人账户", "餐补账户", "赠送账户"]
}
迁移窗口内遇到无关系旧数据时,可短期兼容读为 [1,2,3] 并记录告警。迁移完成后不能长期把空关系静默解释为全部账户。
8. 后端改造
8.1 配置 CRUD
MarketingStrategy 控制器:
lists/create/update格式化accountTypes。- 不新增路由。
- 沿用现有登录、功能权限和档口数据权限。
MarketingStrategy 校验器:
'accountTypes' => 'require|array|checkAccountTypes'
MarketingStrategy 服务:
getList()使用账户关系EXISTS筛选。syncRelations()删除后按去重账户批量插入。loadRelations()批量加载账户值和名称,避免 N+1。detail()返回账户值和名称数组。delete()删除账户关系。
8.2 允许缺口的账户使用预览
新增纯计算方法:
GiftAccountPolicy::previewAccountUsage(
string $originalAmount,
array $balances,
array $deductionOrder,
string $overdraftLimit = '0.00'
): array
返回:
[
'subsidy' => '50.00',
'cash' => '30.00',
'gift' => '0.00',
'used_account_types' => [2, 1],
'allocated_amount' => '80.00',
'shortfall_amount' => '20.00',
'deduction_order' => ['subsidy', 'cash', 'gift'],
]
实现规则:
- 复用
planDeduction()的账户顺序和现金内部顺序。 - 分配到可用金额耗尽为止。
- 不因
shortfall_amount > 0抛异常。 - 不写数据库、Redis、流水或结算单。
- 金额计算继续使用整数分或项目现有高精度金额工具。
严格 planDeduction() 仍用于最终扣款,不修改其余额不足异常合同。
8.3 锁定账户快照
在 AccountSettlementService 内增加:
loadLockedAccountSnapshot($accountDb, array $params): array
deductWithLockedSnapshot($accountDb, array $params, array $snapshot): array
快照至少包含:
- 有效餐补批次及合计;
- 活动本金;
- 可提现现金;
- 赠送余额;
- 透支额度;
- 扣款顺序;
- 账户事实时间。
同步预览和最终扣款复用同一快照,避免并发期间资格与正式扣款看到不同余额。
8.4 策略候选查询
扩展签名:
findApplicableMarketingStrategy(
array $profile,
string $consumeTime,
array $usedAccountTypes
): ?array
查询必须包含商户隔离和账户交集:
->where('ms.store_id', $this->storeId)
->whereExists(function ($query) use ($usedAccountTypes) {
$query->name('marketing_strategy_account')
->alias('msa')
->whereRaw('msa.strategy_id = ms.id')
->whereRaw('msa.store_id = ms.store_id')
->whereIn('msa.account_type', $usedAccountTypes);
})
继续保留身份、部门、档口、场景、时间段、有效期和优先级排序。使用 EXISTS 而不是额外普通 JOIN,避免账户多选与时间段联表放大结果行。
usedAccountTypes 为空时直接返回无策略。原始金额为 0 的订单不命中金额策略。
9. 各消费链路设计
9.1 实时消费
- 解析人员、档口、场景、餐次、消费时间和原始金额。
- 读取扣款配置和透支额度。
- 开启账户库事务,锁定用户和有效餐补,构建账户快照。
- 对原始金额调用
previewAccountUsage()。 - 使用
used_account_types查询消费策略。 - 计算最终应付金额,并按最终金额进行严格余额校验。
- 开启业务库事务并创建订单。
- 基于同一锁定快照正式扣款,保存最终拆分和资格快照。
- 按现有顺序提交两库事务;失败使用订单号和账户结算单幂等回读/补偿。
9.2 异步 Redis 快照消费
- 在卡片 Redis 锁内读取三账户快照。
- 对原始金额执行允许缺口的账户使用预览。
- 查询策略并计算最终应付金额。
- 冻结以下字段:
{
"marketing_strategy_evaluated": 1,
"strategy_snapshot_version": 1,
"marketing_strategy_id": 0,
"marketing_strategy_time_range_id": 0,
"strategy_account_types": [],
"strategy_hit_account_types": [2],
"strategy_account_snapshot": {},
"management_fee": "0.00",
"discount_amount": "0.00",
"pay_price": "20.00"
}
- 用最终应付金额更新 Redis 账户快照。
- 将冻结字段完整放入队列消息。
策略 ID 为 0 仍是合法冻结结果。
9.3 队列扣款
if ((int)($params['marketing_strategy_evaluated'] ?? 0) === 1) {
// 沿用策略 ID、金额、账户资格和版本;策略 ID 可为 0
} else {
// 仅兼容旧消息,且订单/结算单尚未创建时才允许完整计算一次
}
其他约束:
- 重复
msgid/order_no只回读原订单和账户结算单。 - 不因余额、策略配置或扣款顺序变化覆盖冻结结果。
- 队列失败重试不能叠加优惠或生成第二个结算单。
9.4 绑盘后结算
- 策略有效期、时段和餐次使用订单记录的消费时间事实。
- 账户预览必须与最终扣款使用同一时点、同一有效餐补规则和同一锁定快照。
- 在真正扣款前完成预览、策略计算和正式结算。
- 将策略、资格快照和最终拆分写回待支付订单后再进入
PaySuccess。
如果“餐补有效性按绑盘时间还是结算时间”在现有结算链路中没有统一合同,必须先以最终扣款使用的规则为准,不能让资格预览单独选择另一个时间口径。
9.5 线上订餐和其他来源
| 来源 | 值 | 要求 |
|---|---|---|
| 绑盘机 | 1 | 结算前预览并冻结 |
| 消费机 | 2 | 实时和异步合同一致 |
| 虚拟订单 | 3 | 复用统一策略服务 |
| 线上订餐 | 4 | 最终余额结算时冻结 |
| 外部订单 | 5 | 复用统一策略服务 |
| 闸机 | 6 | 复用统一策略服务 |
| 点餐机 | 7 | 复用统一策略服务 |
| 聚合父单 | 10 | 不重复选择策略或扣款 |
所有来源都调用统一的“账户快照 → 使用预览 → 策略选择”能力,不在入口内复制账户算法。
10. PC 前端设计
10.1 筛选区
- 增加“适用账户”筛选。
- 选项顺序:个人账户、餐补账户、赠送账户。
- 清空表示不限账户。
- 多选筛选按 OR 处理。
10.2 列表
- 增加“适用账户”列。
- 展示后端
account_names字符串。 - 迁移完成后正常数据不应出现空值;异常空值显示
-。
10.3 新增和编辑
- 新增默认
accountTypes = [],不默认勾选三种账户。 - 历史策略通过数据库迁移得到全部三账户,不依赖前端默认值。
- 编辑按详情
account_types回显。 - 标签旁使用
el-tooltip + el-icon-info。 - 提示文案严格采用云效正文。
- 空选择提示“请至少选择一个适用账户”。
10.4 原型视觉门禁
当前环境仍无法访问原型站。字段位置、图标、列宽、弹窗高度和筛选控件样式必须在 UI 验收前由可访问环境核对。功能合同不依赖原型可访问性,视觉验收不能省略。
11. 并发、幂等与一致性
11.1 同步一致性
- 账户使用预览与最终扣款使用同一锁定账户快照。
- 资格预览允许余额缺口;最终扣款严格校验。
- 业务订单保存资格快照,账户结算单保存最终账户拆分。
- 两库事务失败按订单号和结算单回读,不能重新发放优惠。
11.2 异步一致性
marketing_strategy_evaluated表示计算状态。strategy_snapshot_version支持新旧队列消息兼容。- ID 为 0 的未命中结果同样冻结。
- Redis 回滚只能恢复本次扣减前快照,不能覆盖其他已提交消费。
- 队列落库不得重新读取最新余额或最新策略。
11.3 配置变化
- 新消费事件读取当前策略和扣款顺序。
- 已冻结异步事件使用事件快照。
- 已创建订单或结算单只回读历史结果。
- 编辑策略不重算历史订单。
12. 历史兼容与发布顺序
推荐顺序:
- 确认
VWCG-1121已合并,相关 DDL 和三账户结算测试通过。 - 执行账户关系表建表和历史未删除策略三账户回填。
- 执行迁移校验,结果为 0 行。
- 发布后端兼容读取能力。
- 同一发布窗口发布强制非空校验和 PC 前端。
- 小流量验证实时消费。
- 再验证异步、队列、绑盘和线上订餐。
兼容规则:
- 新建策略必须显式传
accountTypes。 - 编辑旧策略时由详情返回迁移后的三账户。
- 短期兼容旧前端时,只允许通过明确的客户端版本窗口处理,不能长期把缺失字段默认为全部账户。
- 历史订单不补资格快照、不重算优惠。
13. 测试方案
13.1 账户使用预览单元测试
- 按扣款顺序分配账户。
- 有余额但分摊为 0 不进入使用账户。
- 个人账户合并活动本金、可提现现金和透支。
- 原始金额余额不足时返回缺口,不抛异常。
- 原始金额 100、余额 80、优惠后 70 可以进入最终扣款。
- 优惠后仍余额不足时由严格扣款拒绝。
- 0 元和负数输入边界。
13.2 策略配置合同测试
- 关系表、唯一键和商户隔离。
- 历史未删除策略回填三账户。
- 新建空数组失败。
- 新建、编辑、详情、列表保存回显一致。
- 列表账户筛选为 OR。
- 非法枚举失败。
- 不同
store_id的策略不能互相命中。
13.3 优先级测试
| 高优先级 | 低优先级 | 本单账户 | 预期 |
|---|---|---|---|
| 个人 | 餐补 | 餐补 | 命中低优先级 |
| 个人、赠送 | 餐补 | 赠送 | 命中高优先级 |
| 个人 | 餐补、赠送 | 餐补、个人 | 命中高优先级 |
| 个人 | 餐补 | 赠送 | 不命中任何策略 |
13.4 异步和重试测试
- 异步命中策略,队列沿用策略与金额。
- 异步未命中策略且 ID 为 0,队列不重算。
- 事件入队后修改策略,队列结果不变化。
- 事件入队后修改余额或扣款顺序,队列资格不变化。
- 重复消息只生成一个订单和一个账户结算单。
- 旧版无
marketing_strategy_evaluated消息只在无订单、无结算单时兼容计算。
13.5 云效验收映射
| 验收 | 自动化建议 |
|---|---|
| 仅个人使用命中个人策略 | 策略计算集成测试 |
| 仅餐补不命中个人策略 | 策略计算集成测试 |
| 餐补+个人命中个人策略 | 多账户预览测试 |
| 餐补+赠送命中个人/赠送策略 | OR 交集测试 |
| 有余额但分摊 0 不算使用 | 边界单测 |
| 高优先级不匹配继续低优先级 | 查询集成测试 |
| CRUD/列表一致 | API 合同 + 前端测试 |
| 异步/同步一致、重试不重复 | Redis/队列/幂等集成测试 |
| 历史策略行为不变 | 迁移前后对比测试 |
13.6 必跑命令
vendor/bin/phpunit tests/GiftAccount
vendor/bin/phpunit tests/AccountSetting
vendor/bin/phpunit tests/apifox/pc/MarketingStrategyScenarioTimeRangeContractTest.php
vendor/bin/phpunit tests/MarketingStrategy
vendor/bin/phpunit
cd public/static
npm run lint
npm run unit
npm run build
应新增真实 HTTP 或服务集成用例,保存同步、异步、无策略队列、绑盘和线上订餐的订单及结算快照证据。
14. 异步导出联动
本需求修改的是消费策略列表,不属于仓库登记的 9 类异步导出列表,因此无需新增异步导出筛选或展示列。
策略资格会改变新订单的优惠、管理费和实付金额。消费订单及人员、部门、设备消费明细的异步导出必须继续读取已持久化订单金额,不得按当前策略重新计算。
结论:
无需修改异步导出逻辑,但必须回归页面金额、导出金额和订单持久化金额一致。
15. 发布、回滚与可选增强
15.1 发布验证
- 无账户关系的未删除策略数量为 0。
- 同一
msgid/order_no不出现不同策略 ID。 - 同步和异步同场景命中结果一致。
- 未命中策略的队列消息不发生重算。
- 不同商户策略不能交叉命中。
日志只能记录订单号、商户 ID、账户类型集合、策略 ID、快照版本和必要金额摘要,不记录完整余额或个人敏感信息。
15.2 回滚
- 回滚 PC 前端和后端账户匹配代码。
- 旧代码忽略账户关系表,历史策略恢复原有匹配行为。
- 保留关系表和订单资格快照,不删除交易证据。
- 不回滚已发生订单的优惠或账户扣款。
- 默认前向修复,不在发布回滚中删除数据表或字段。
15.3 可选增强
以下能力不阻塞首期:
- 门店级账户匹配灰度开关;
- 策略候选数、账户跳过数监控;
- 资格预览与最终拆分差异报表;
- 完整的跨库对账任务。
16. 开发切片与门禁
16.1 建议切片
| 切片 | 范围 | 独立验收 |
|---|---|---|
| S1 | 关系表、迁移、CRUD、列表、PC 页面 | 保存、筛选、回显、历史兼容 |
| S2 | 允许缺口的账户使用预览、商户隔离策略查询 | 账户资格、优先级、余额缺口 |
| S3 | 实时、异步、队列、绑盘、线上订餐接入 | 同步异步一致、ID=0 不重算 |
| S4 | 自动化、迁移验证、发布和回滚证据 | 全量回归与证据包 |
工时应在 S1-S4 的实际代码改动点和测试范围确认后,分别填写到云效研发子任务;本评审稿不再用附加增强项提前固化总工时。
16.2 开发前门禁
VWCG-1121已合并,三账户 DDL 和结算能力在目标环境可用。- 产品和研发确认“管理费导致最终新增账户时不反向重选策略”。
- UI 验收前由可访问环境完成原型视觉核对。
其中门禁 1 阻塞全链路联调,门禁 2 阻塞管理费策略编码,门禁 3 不阻塞后端开发但阻塞 UI 验收。