<<<<<<< HEAD VWCG-1119 消费限制按受限账户生效|技术开发文档

TECHNICAL DEVELOPMENT DESIGN · VWCG-1119

<<<<<<< HEAD

消费限制支持按“受限账户”生效

基于云效 PRD、交互原型、正式代码基线与 VWCG-1121 依赖关系形成的技术开发评审稿。

V1.1 开发稿 2026-07-29 目标仓库:store 风险:中高 状态:已按确认口径开发 =======

消费限制按“受限账户是否有钱”生效

覆盖 store 消费机 HTTP API、MQTT,以及 ai_api 小程序订餐的双项目技术方案。

V1.2 兼容修订稿 2026-07-29 store + ai_api 风险:高 >>>>>>> 5d05833ef476571c794ef3016336bc136526872c
<<<<<<< HEAD

1. 评审结论

这不是一个只加表单字段的需求。要真正做到“未勾选账户不受限制”,限制校验、支付预览和最终扣款必须使用同一受限账户集合。

推荐实现 新增规则—账户关系;规则命中后只排除所选账户,并使用未受限账户重算可用余额和扣款计划;剩余账户不足以覆盖整单时才拦截。

账户枚举

cashsubsidygift 全链路统一口径。

交付节奏

本期页面同时启用个人、餐补和赠送账户;三者复用同一范围/次数规则。

次数口径

按订单支付字段金额大于 0 分账户计次;混合支付对每个实际账户分别计一次。

兼容原则

历史规则回填个人与餐补,不自动扩大旧规则到赠送账户。

2. 范围边界

本期必须实现

  • 应用场景后增加必填“受限账户”多选
  • 个人账户、餐补账户可选并保存回显
  • 赠送账户作为受限账户的第三个选项,不增加独立金额配置
  • 命中范围或次数后仅排除选中账户,未选账户继续支付
  • 接口、服务、数据库均做枚举和非空校验

明确不做

  • 指定人员、身份或适用对象扩展
  • 金额限制与账户独立阈值
  • 按账户分别配置档口、餐次
  • 新增菜单、路由、列表列或筛选项
  • 在本任务实现赠送账户余额、流水、退款和导出
历史设计不是本期范围 account-type-consume-limit-20260713 是更宽的扩展方案。当前实现必须以云效 PRD 的收敛版本为准,不能顺带实现三维规则、金额限制或复杂组合。

3. 核心技术缺口

层级当前行为本需求需要
数据规则没有账户关系支持一条规则关联多个账户类型
支付时序限制校验早于价格计算与扣款拆分最终价格后排除受限账户并重算扣款计划
范围校验人员命中后整单拦截只排除规则选中的受限账户
次数统计统计人员全部成功订单cash/subsidy/gift > 0 分账户统计
日志不记录触发账户落库 account_type 便于追溯
赠送账户当前分支已具备余额、扣款、退款和订单字段页面启用且后端允许保存 gift
不能接受的简化实现 仅新增数据库字段和前端复选框,会出现“配置已保存,但实际仍然整单受限”的伪完成状态。

4. 目标处理链路

1. 定价得到最终
pay_price
2. 评估按账户判断范围与次数
3. 排除移除命中的受限账户
4. 结算按剩余账户重算并扣款
5. 落单保存分账户金额与拦截证据

必须保持的不变量

  • 支付预览与最终结算必须使用相同的受限账户集合。
  • 未选账户不受规则影响,可覆盖整单时不得拦截。
  • 次数只统计对应账户实付字段大于 0 的成功订单。
  • 离线补单当前绕过限制校验,需在发布说明中保留该既有边界。

5. 数据与接口设计

5.1 新增关系表

新增 ydy_consume_limit_account,保持与部门、场景、范围关系表一致的建模方式,不使用逗号字符串或主表 JSON。

字段类型/约束说明
idint unsigned, PK主键
limit_idint unsigned, NOT NULL消费限制 ID
account_typevarchar(16), NOT NULLcash / subsidy / gift
store_idint unsigned, NOT NULL租户隔离字段
create_timedatetime创建时间

索引:UNIQUE(limit_id, account_type)INDEX(store_id, account_type)。拦截日志表补充 account_type

5.2 API 契约

创建/更新入参

accountTypes: ["cash", "subsidy"]

数组、去重后至少一项、仅允许 cash/subsidy/gift 三个受支持枚举。

详情回显

account_types: ["cash", "subsidy"]

接口路由保持不变;当前 PRD 不要求列表展示与筛选。

5.3 历史数据

所有有效历史规则回填 cashsubsidy;不回填 gift。迁移后检查规则覆盖率、未知枚举、重复关系和 store_id 一致性。

6. 实施清单

模块主要文件/对象改动要点
前端addConsumeLimit.vue新增必填复选组、Tooltip、编辑回显;展示个人、餐补和赠送
接口校验api/validate/ConsumeLimit.php校验数组、非空及 cash/subsidy/gift 枚举
CRUDapi/controller/ConsumeLimit.php
api/service/ConsumeLimit.php
事务内同步关系表;详情返回账户集合;删除保持一致性
账户枚举common/enum/account/Type.php让 VWCG-1119 与 VWCG-1121 共用 cash/subsidy/gift 口径
支付规划账户结算服务 / 扣款规划器提供无副作用的 previewDeduction;实际扣款锁内复核预期计划
规则评估api/service/Consume.php 拆分出的评估器两个在线支付入口统一调用;按账户判断范围与次数
DDL目标版本 SQL关系表、日志字段、历史规则回填和核验 SQL
双支付入口必须同时覆盖 当前同步支付与异步快照路径都会调用消费限制。只改其中一条会产生渠道行为不一致。

7. 规则语义

范围限制

沿用现有“配置范围为禁止范围”的语义。档口与餐次命中后仅排除规则所选账户;未选账户继续参与支付,剩余账户不足时才拦截整单。

次数限制

在相同人员、应用场景与时间窗口下,按账户读取成功订单:

  • 个人账户:cash > 0
  • 餐补账户:subsidy > 0
  • 赠送账户:gift > 0

一笔混合支付订单对每个金额大于 0 的账户分别记 1 次,而不是按金额或拆分行数计次。退款、撤销订单是否回退次数属于待确认口径。

8. 验证策略

单元与服务测试

枚举、参数校验、关系表事务、历史回填、纯扣款规划、规则与实际账户交集、分账户计次。

支付集成测试

个人/餐补单账户、混合支付、未选账户、余额临界变化、同步与异步入口、失败不落单。

前端与手工验收

字段位置、必填、Tooltip、个人/餐补/赠送三选项、新增编辑回显、原范围/次数配置不回归。

多租户与权限

不同 store_id 不串数据;现有部门、餐厅与档口数据权限保持有效。

后端至少运行相关 PHPUnit 与支付回归;前端运行 lint、构建及组件级验证。由于表单行为变化,需要人工 UI 验收证据。

9. 发布、依赖与回滚

  1. 先部署 DDL:创建账户关系表、日志字段并回填历史规则。
  2. 部署后端兼容代码;服务端允许并校验 cash/subsidy/gift
  3. 部署前端:个人、餐补和赠送账户均可选。
  4. 观察规则保存失败率、消费拦截量、按账户拦截分布及扣款计划漂移告警。
  5. 赠送账户与个人、餐补同时开放,三者复用相同规则与验证矩阵。

回滚时先回退功能代码并保留关系表和日志字段,避免丢失新规则配置;旧代码恢复“全部账户共同受限”的历史行为。

异步导出联动结论 VWCG-1119 不修改九类业务列表的查询、字段、统计、权限、分页或导出数据源,因此本任务无需修改异步导出。赠送账户引入后的订单与导出字段影响由 VWCG-1121 单独负责。

工作量估算:个人+餐补+赠送完整闭环约 44–62 小时。估算不含大规模历史数据治理。

10. 已确认口径与保留项

P0
次数按账户独立计数

个人、餐补、赠送分别按订单对应实付字段大于 0 计一次;混合订单分别计入。

P0
受限账户命中后改用未受限账户

只排除命中的账户;未受限账户可以覆盖整单时继续支付,不能覆盖时才拦截。

P0
赠送账户与其他账户同时启用

赠送账户不是独立赠送金额配置,只是本条消费限制的第三个受限账户选项。

P1
历史规则是否统一回填个人+餐补?退款/撤销、离线补单如何计次?

本文给出兼容默认值,但需要产品、研发和测试在评审中确认并固化验收样例。

开发结论 已按以上口径进入实现;发布前继续保留历史规则回填、退款计次和离线补单边界的验证证据。
VWCG-1119 · 技术开发文档 V1.1 · 详细证据、代码定位与完整测试矩阵见同目录 Markdown 真源
=======

1. 修订结论

本版不再根据“本单最终扣了哪个账户”决定规则,而是在下单/支付前读取所选受限账户的可用余额。

统一判定
所选账户中任意一个可用余额大于 0,继续执行原有范围/次数限制;全部为 0 时跳过本条规则。

次数口径

继续按当前成功订单数统计,不按 cash/subsidy/gift 最终扣款拆分。

资金来源

规则只决定是否拦截,不修改营销价格、扣款顺序,也不自动换账户。

项目边界

消费机在 store;小程序订餐在 ai_api。只改一个仓库无法闭环。

赠送账户

依赖 VWCG-1121;迁移与全链路验证前保持置灰。

旧数据默认值
历史规则没有受限账户字段,统一默认个人账户 + 餐补账户;赠送账户不默认选中。

2. 业务规则

账户“有钱”定义排除项
cash个人活动本金余额 + 可提现余额;旧数据未拆分时兼容回退 balance透支额度默认不算
subsidy餐补判断时点有效、未删除且大于 0 的补贴余额之和未生效、过期、负数
gift赠送gift_balance 大于 0能力未开放时不可配置
  • 金额使用 BCMath 两位小数比较,禁止浮点直接判断。
  • 未选账户余额不参与规则是否生效。
  • 余额查询失败不能按 0 放行,应返回可重试错误。
  • 范围、月/日/餐次次数阈值保持现状。

3. 三类入口

入口真实调用链接入要求
消费机 HTTP APIConsume controller → orderPayAsync覆盖 Redis 快照与 DB 回退
消费机 MQTTMqttServer → orderPayAsync与 HTTP 共享 Service,不复制规则
小程序点餐MealCheckout / DirectPay → MealPayment / PaySuccess提交早检 + 支付最终复检
代码事实
store 只识别 source=4 的订餐订单,没有小程序提交入口;小程序必须在 ai_api 改造。

4. 目标架构

入口HTTP / MQTT / 小程序
身份映射staff、zhct user、AI user
余额快照cash / subsidy / gift
规则评估余额门禁 + 范围/次数
原支付流程创建订单与扣款

两个项目使用相同的评估输入、输出和契约样例。输入至少包含租户、人员、智慧餐厅用户、场景、档口、餐次、消费时间、余额快照和幂等号。

5. 数据与接口

规则账户关系

新增 ydy_consume_limit_account(limit_id, account_type, store_id, create_time),唯一索引为 (limit_id, account_type)。所有未删除历史规则(包括启用和停用)幂等回填 cash + subsidy,不自动回填 gift。

CRUD

新增/编辑传 accountTypes: ["cash","subsidy"];详情返回 account_types。必须为数组、去重后至少一项,并执行枚举白名单与赠送能力校验。

拦截日志

增加命中账户、脱敏余额快照和渠道,渠道取值 consume_apiconsume_mqttmini_program

四层兼容
① 数据库回填;② 详情缺关系时返回 cash+subsidy;③ 新增和旧数据编辑默认勾选个人+餐补;④ 运行期缺关系按 cash+subsidy 判断并告警。旧规则首次编辑后把默认值真实持久化。

6. store 改造

  1. 新增账户余额解析器,完成 staff_uuid → store user → ai_user_id 映射。
  2. Consume.php 抽出消费限制评估器,按“账户余额交集 → 范围 → 次数”执行。
  3. HTTP 与 MQTT 只传渠道标识,统一在 orderPayAsync() 的支付锁内判断。
  4. 同时覆盖 handleAsyncSnapshotPayment()runAsyncFallback() → orderPay()
  5. 在线设备时间需要漂移校验;补贴有效期、次数边界和日志使用同一消费时间。
Redis 快照风险
当前 TTL 为 300 秒。应在支付锁内刷新账户余额,或严格保证限制判断和后续扣款使用同一份最新快照。

7. 小程序改造

  • MealCheckout::submit():创建订单前早检,避免产生不可支付订单。
  • meal/DirectPay::pay():创建订单和扣款前,在同一事务内最终检查。
  • cashier/MealPayment / meal/PaySuccess:支付前防御复检,覆盖历史待支付订单和旁路调用。
  • 0 元订单必须在自动支付成功前检查。
  • AI 用户映射不到智慧餐厅用户/人员时失败,不得按“无规则”放行。

双检查的目的:提交时尽早反馈,支付时处理余额、规则和时间变化,避免提交后充值或规则调整造成绕过。

8. 隐形 Bug 风险

风险隐形后果防护
只改 HTTP 或 MQTT上报方式不同,限制结果不同共享 Service + 双入口契约测试
300 秒旧快照充值/扣款/补贴变化后误判锁内刷新并复用同快照
只改 store小程序完全不判断ai_api 三个接入点同步改
透支算余额实际 0 余额仍触发规则默认排除透支
账户异常当 0跨库故障时规则绕过失败关闭、提示重试
只在提交检查支付时余额变化后绕过支付前最终复检
次数先查后写并发两单同时通过最后额度跨项目同一用户/场景锁
两个项目算法漂移消费机与小程序行为不一致共享版本化契约样例
0 元自动支付范围/次数被旁路自动支付前检查
只做前端默认API/任务链路仍把旧规则当空配置SQL、详情、前端、运行期四层兼容
并发门禁
锁名统一为 consume_limit:{storeId}:{zhctUserId}:{scenario},覆盖最终复检到成功订单落库。两项目 Redis 不共享时必须采用 mysql_zhct 数据库锁/守卫行。

9. 测试与发布

余额边界

个人拆分/旧值、有效与过期餐补、赠送开关、多选任一有钱、透支、查询失败。

渠道一致性

同一载荷走 HTTP 和 MQTT,除 channel/trace_id 外结果一致。

小程序

submit、0 元自动支付、余额支付、directPay、历史待支付复检。

并发

MQTT 与小程序并发争抢最后一次额度,只允许一单成功。

上线顺序

  1. 完成 VWCG-1121 账户迁移,gift 未就绪则保持禁用。
  2. 创建关系表、日志字段,为全部未删除旧规则幂等回填 cash+subsidy。
  3. 验证重复回填无重复数据,旧规则详情默认勾选个人+餐补,gift 不默认选中。
  4. 部署 store 后端,再部署 ai_api,最后开放 PC 表单。
  5. 内部门店同时验证 HTTP、MQTT、小程序,观察后灰度。

本需求不改变订单列表字段或导出口径,九类异步导出无需同步修改。

10. 待确认项

P0
多选账户触发逻辑

本文按“任一所选账户有可用余额即生效”设计。

P0
透支口径

本文默认透支额度不属于“账户有钱”。

P0
跨项目锁

确认 store 与 ai_api 是否共享 Redis;否则采用 mysql_zhct 数据库锁。

P0
0 元订单

本文默认也执行范围与次数限制。

P1
设备时间与离线补传

确认在线时间漂移窗口;离线补传默认仍不实时拦截。

开发门禁
方案可实现,但属于双项目、高一致性改造。P0 口径、跨项目锁方案和赠送账户依赖未关闭前,不建议直接上线。
>>>>>>> 5d05833ef476571c794ef3016336bc136526872c