次数口径
继续按当前成功订单数统计,不按 cash/subsidy/gift 最终扣款拆分。
HEAD
TECHNICAL DEVELOPMENT DESIGN · VWCG-1119
<<<<<<< HEAD基于云效 PRD、交互原型、正式代码基线与 VWCG-1121 依赖关系形成的技术开发评审稿。
覆盖 store 消费机 HTTP API、MQTT,以及 ai_api 小程序订餐的双项目技术方案。
这不是一个只加表单字段的需求。要真正做到“未勾选账户不受限制”,限制校验、支付预览和最终扣款必须使用同一受限账户集合。
cashsubsidygift 全链路统一口径。
本期页面同时启用个人、餐补和赠送账户;三者复用同一范围/次数规则。
按订单支付字段金额大于 0 分账户计次;混合支付对每个实际账户分别计一次。
历史规则回填个人与餐补,不自动扩大旧规则到赠送账户。
account-type-consume-limit-20260713 是更宽的扩展方案。当前实现必须以云效 PRD 的收敛版本为准,不能顺带实现三维规则、金额限制或复杂组合。
| 层级 | 当前行为 | 本需求需要 |
|---|---|---|
| 数据 | 规则没有账户关系 | 支持一条规则关联多个账户类型 |
| 支付时序 | 限制校验早于价格计算与扣款拆分 | 最终价格后排除受限账户并重算扣款计划 |
| 范围校验 | 人员命中后整单拦截 | 只排除规则选中的受限账户 |
| 次数统计 | 统计人员全部成功订单 | 按 cash/subsidy/gift > 0 分账户统计 |
| 日志 | 不记录触发账户 | 落库 account_type 便于追溯 |
| 赠送账户 | 当前分支已具备余额、扣款、退款和订单字段 | 页面启用且后端允许保存 gift |
pay_price新增 ydy_consume_limit_account,保持与部门、场景、范围关系表一致的建模方式,不使用逗号字符串或主表 JSON。
| 字段 | 类型/约束 | 说明 |
|---|---|---|
id | int unsigned, PK | 主键 |
limit_id | int unsigned, NOT NULL | 消费限制 ID |
account_type | varchar(16), NOT NULL | cash / subsidy / gift |
store_id | int unsigned, NOT NULL | 租户隔离字段 |
create_time | datetime | 创建时间 |
索引:UNIQUE(limit_id, account_type)、INDEX(store_id, account_type)。拦截日志表补充 account_type。
accountTypes: ["cash", "subsidy"]
数组、去重后至少一项、仅允许 cash/subsidy/gift 三个受支持枚举。
account_types: ["cash", "subsidy"]
接口路由保持不变;当前 PRD 不要求列表展示与筛选。
所有有效历史规则回填 cash 与 subsidy;不回填 gift。迁移后检查规则覆盖率、未知枚举、重复关系和 store_id 一致性。
| 模块 | 主要文件/对象 | 改动要点 |
|---|---|---|
| 前端 | addConsumeLimit.vue | 新增必填复选组、Tooltip、编辑回显;展示个人、餐补和赠送 |
| 接口校验 | api/validate/ConsumeLimit.php | 校验数组、非空及 cash/subsidy/gift 枚举 |
| CRUD | api/controller/ConsumeLimit.phpapi/service/ConsumeLimit.php | 事务内同步关系表;详情返回账户集合;删除保持一致性 |
| 账户枚举 | common/enum/account/Type.php | 让 VWCG-1119 与 VWCG-1121 共用 cash/subsidy/gift 口径 |
| 支付规划 | 账户结算服务 / 扣款规划器 | 提供无副作用的 previewDeduction;实际扣款锁内复核预期计划 |
| 规则评估 | api/service/Consume.php 拆分出的评估器 | 两个在线支付入口统一调用;按账户判断范围与次数 |
| DDL | 目标版本 SQL | 关系表、日志字段、历史规则回填和核验 SQL |
沿用现有“配置范围为禁止范围”的语义。档口与餐次命中后仅排除规则所选账户;未选账户继续参与支付,剩余账户不足时才拦截整单。
在相同人员、应用场景与时间窗口下,按账户读取成功订单:
cash > 0subsidy > 0gift > 0一笔混合支付订单对每个金额大于 0 的账户分别记 1 次,而不是按金额或拆分行数计次。退款、撤销订单是否回退次数属于待确认口径。
枚举、参数校验、关系表事务、历史回填、纯扣款规划、规则与实际账户交集、分账户计次。
个人/餐补单账户、混合支付、未选账户、余额临界变化、同步与异步入口、失败不落单。
字段位置、必填、Tooltip、个人/餐补/赠送三选项、新增编辑回显、原范围/次数配置不回归。
不同 store_id 不串数据;现有部门、餐厅与档口数据权限保持有效。
后端至少运行相关 PHPUnit 与支付回归;前端运行 lint、构建及组件级验证。由于表单行为变化,需要人工 UI 验收证据。
cash/subsidy/gift。回滚时先回退功能代码并保留关系表和日志字段,避免丢失新规则配置;旧代码恢复“全部账户共同受限”的历史行为。
工作量估算:个人+餐补+赠送完整闭环约 44–62 小时。估算不含大规模历史数据治理。
个人、餐补、赠送分别按订单对应实付字段大于 0 计一次;混合订单分别计入。
只排除命中的账户;未受限账户可以覆盖整单时继续支付,不能覆盖时才拦截。
赠送账户不是独立赠送金额配置,只是本条消费限制的第三个受限账户选项。
本文给出兼容默认值,但需要产品、研发和测试在评审中确认并固化验收样例。
本版不再根据“本单最终扣了哪个账户”决定规则,而是在下单/支付前读取所选受限账户的可用余额。
继续按当前成功订单数统计,不按 cash/subsidy/gift 最终扣款拆分。
规则只决定是否拦截,不修改营销价格、扣款顺序,也不自动换账户。
消费机在 store;小程序订餐在 ai_api。只改一个仓库无法闭环。
依赖 VWCG-1121;迁移与全链路验证前保持置灰。
| 账户 | “有钱”定义 | 排除项 |
|---|---|---|
| cash个人 | 活动本金余额 + 可提现余额;旧数据未拆分时兼容回退 balance | 透支额度默认不算 |
| subsidy餐补 | 判断时点有效、未删除且大于 0 的补贴余额之和 | 未生效、过期、负数 |
| gift赠送 | gift_balance 大于 0 | 能力未开放时不可配置 |
| 入口 | 真实调用链 | 接入要求 |
|---|---|---|
| 消费机 HTTP API | Consume controller → orderPayAsync | 覆盖 Redis 快照与 DB 回退 |
| 消费机 MQTT | MqttServer → orderPayAsync | 与 HTTP 共享 Service,不复制规则 |
| 小程序点餐 | MealCheckout / DirectPay → MealPayment / PaySuccess | 提交早检 + 支付最终复检 |
source=4 的订餐订单,没有小程序提交入口;小程序必须在 ai_api 改造。两个项目使用相同的评估输入、输出和契约样例。输入至少包含租户、人员、智慧餐厅用户、场景、档口、餐次、消费时间、余额快照和幂等号。
新增 ydy_consume_limit_account(limit_id, account_type, store_id, create_time),唯一索引为 (limit_id, account_type)。所有未删除历史规则(包括启用和停用)幂等回填 cash + subsidy,不自动回填 gift。
新增/编辑传 accountTypes: ["cash","subsidy"];详情返回 account_types。必须为数组、去重后至少一项,并执行枚举白名单与赠送能力校验。
增加命中账户、脱敏余额快照和渠道,渠道取值 consume_api、consume_mqtt、mini_program。
staff_uuid → store user → ai_user_id 映射。Consume.php 抽出消费限制评估器,按“账户余额交集 → 范围 → 次数”执行。orderPayAsync() 的支付锁内判断。handleAsyncSnapshotPayment() 和 runAsyncFallback() → orderPay()。MealCheckout::submit():创建订单前早检,避免产生不可支付订单。meal/DirectPay::pay():创建订单和扣款前,在同一事务内最终检查。cashier/MealPayment / meal/PaySuccess:支付前防御复检,覆盖历史待支付订单和旁路调用。双检查的目的:提交时尽早反馈,支付时处理余额、规则和时间变化,避免提交后充值或规则调整造成绕过。
| 风险 | 隐形后果 | 防护 |
|---|---|---|
| 只改 HTTP 或 MQTT | 上报方式不同,限制结果不同 | 共享 Service + 双入口契约测试 |
| 300 秒旧快照 | 充值/扣款/补贴变化后误判 | 锁内刷新并复用同快照 |
| 只改 store | 小程序完全不判断 | ai_api 三个接入点同步改 |
| 透支算余额 | 实际 0 余额仍触发规则 | 默认排除透支 |
| 账户异常当 0 | 跨库故障时规则绕过 | 失败关闭、提示重试 |
| 只在提交检查 | 支付时余额变化后绕过 | 支付前最终复检 |
| 次数先查后写 | 并发两单同时通过最后额度 | 跨项目同一用户/场景锁 |
| 两个项目算法漂移 | 消费机与小程序行为不一致 | 共享版本化契约样例 |
| 0 元自动支付 | 范围/次数被旁路 | 自动支付前检查 |
| 只做前端默认 | API/任务链路仍把旧规则当空配置 | SQL、详情、前端、运行期四层兼容 |
consume_limit:{storeId}:{zhctUserId}:{scenario},覆盖最终复检到成功订单落库。两项目 Redis 不共享时必须采用 mysql_zhct 数据库锁/守卫行。个人拆分/旧值、有效与过期餐补、赠送开关、多选任一有钱、透支、查询失败。
同一载荷走 HTTP 和 MQTT,除 channel/trace_id 外结果一致。
submit、0 元自动支付、余额支付、directPay、历史待支付复检。
MQTT 与小程序并发争抢最后一次额度,只允许一单成功。
本需求不改变订单列表字段或导出口径,九类异步导出无需同步修改。
本文按“任一所选账户有可用余额即生效”设计。
本文默认透支额度不属于“账户有钱”。
确认 store 与 ai_api 是否共享 Redis;否则采用 mysql_zhct 数据库锁。
本文默认也执行范围与次数限制。
确认在线时间漂移窗口;离线补传默认仍不实时拦截。