熟食消费模式实现方案
1. 方案结论
在现有 SellWeight 称重和结算能力上新增独立的 CookedFoodActivity,复用底层称重、支付和订单能力;不继承麻辣烫 WeightActivity,不改变服务端订单协议,不复用 categories 保存熟食明细。
核心原则:
- 称重硬件复用,业务状态独立。
- 熟食商品本地维护,价格以 500g 为固定基准。
- “加入购物车”是显式命令,不是重量变化事件。
- 购物车按商品标识合并,订单行保存价格快照。
- 本地展示明细与服务端上传摘要物理分离。
2. 当前代码依据
| 能力 | 当前位置 | 影响 |
|---|---|---|
| 首页进入称重 | HomeActivity | 增加熟食入口或模式参数 |
| 称重主流程 | CookedFoodActivity | 复用称重回调,独立维护熟食状态机 |
| 商品模型 | ProductBean | 增加计价基准和熟食类型 |
| 商品数据库 | ProductDatabaseHelper | 增加熟食商品字段和迁移 |
| 设置页 | WeightSetActivity | 增加“熟食设置”同级页签 |
| 支付请求 | OrderPaymentRequest | 仍只构造一条 custom_dish |
| 本地订单 | Order、OrderDao | 新增独立熟食明细字段 |
| 离线补传 | UploadOrderWorker | 只从 categories 读取单条上传摘要 |
| 稳定重量 | ScaleProtocolParser | 加入操作必须检查稳定标志 |
3. 推荐数据模型
3.1 本地商品
在现有商品模型基础上增加:
type = TYPE_COOKED_FOOD
basisGrams = 500
enabled
sort
商品表保留现有字段,并新增 basis_grams、enabled、sort;熟食商品固定保存 basis_grams=500,设置页只读展示计价基准。
数据库版本从 1 升级到 2 时使用 ALTER TABLE 增加字段并设置默认值,禁止沿用当前“删除整表后重建”的升级方式,以免丢失已配置的汤底和其他本地商品。
3.2 购物车行
CookedFoodCartLine
- productId
- nameSnapshot
- unitPriceSnapshot
- basisGrams = 500
- weightGrams
- amount
合并键为 productId。同一商品再次加入时只累加 weightGrams,再按快照单价重新计算 amount。
3.3 本地订单明细
推荐在 Order 增加 cookedFoodDetails 字段,以版本化 JSON 保存熟食明细;由 OrderDao 独立序列化到订单表的新列。字段名可采用 cooked_food_details,与现有 categories 并列。
CookedFoodOrderDetails
- version
- lines[]
- customAmount
- cookedFoodAmount
- totalAmount
保存时写入已合并的购物车行和价格快照,不保存实时秤重引用。读取失败时不影响旧订单上传,但详情页应给出降级展示或空明细提示。
4. 结算与离线补传
4.1 在线支付
结算时使用统一金额汇总器计算总额,然后构造:
{
"dishes": [
{
"uuid": "custom_dish",
"number": "1",
"money": "totalAmount"
}
]
}
服务端不感知每个熟食商品,熟食商品明细只用于本地订单、显示和审计。
4.2 离线订单
本地保存时同时保存:
categories:单条DishItem兼容 JSON,供现有UploadOrderWorker补传。cooked_food_details:完整熟食明细 JSON,供本地查询和订单展示。
UploadOrderWorker 保持从 categories 读取上传摘要的职责,不读取 cooked_food_details 构造上传菜品,避免熟食明细被拆分上传。
现有代码中不同工具对金额字段存在 price 与 money 的历史差异。新熟食链路必须以 OrderPaymentRequest.DishItem 的序列化格式为准,统一生成 money 字段,并增加离线回归测试。
5. 页面与状态机
5.1 状态
IDLE_ZERO_WEIGHT
├─ 先选商品 → PRODUCT_SELECTED → WEIGHT_UNSTABLE → WEIGHT_STABLE
└─ 先放商品 → WEIGHT_UNSTABLE → WEIGHT_STABLE → PRODUCT_SELECTED_STABLE
↓
ADDED_TO_CART
↓
IDLE_ZERO_WEIGHT
异常状态包括 NO_PRODUCT、ZERO_WEIGHT、UNSTABLE_WEIGHT 和 INVALID_EDIT_WEIGHT。这些状态只阻止当前动作,不清空已有购物车。
5.2 关键动作
| 动作 | 前置条件 | 结果 |
|---|---|---|
| 选择商品 | 当前重量为 0,或当前重量已稳定且尚未绑定商品 | 设置当前商品,不入车 |
| 加入购物车 | 当前商品存在、重量大于 0、重量稳定 | 新增或合并购物车行 |
| 归零 | 无 | 清除当前称重重量,保留购物车 |
| 编辑重量 | 购物车行存在 | 更新行重量和金额 |
| 删除 | 购物车行存在 | 删除行并重算金额 |
| 结算 | 至少存在有效金额 | 先显示对账单,确认后保存本地订单并发起支付 |
6. 设置实现
在 WeightSetActivity 的页签和 Fragment 列表中增加 FragmentCookedFood 或等价页面。页面沿用现有汤底设置的 CRUD 交互,但商品保存到 TYPE_COOKED_FOOD,并将 500g 作为只读计价基准。
当前订单已加入的购物车行必须保存商品名称和单价快照;设置页修改商品后,只影响后续新加入的行。
7. 金额与精度
- 内部重量统一使用整数克。
- 单价使用可精确表示金额的类型或字符串转高精度数值,不使用二进制浮点直接累计金额。
- 计算公式为
weightGrams / 500 × unitPrice。 - 展示、支付和本地保存金额统一两位小数。
- 计算结果采用项目既有金额舍入规则;若现有规则未明确,第一期采用半入方式并补充测试。
8. 实施步骤
- 增加熟食商品类型、字段、DAO 和数据库安全迁移。
- 增加熟食设置页,实现本地商品 CRUD 和启停。
- 增加熟食称重模式入口及“归零—选择—稳定—主动加入”状态机。
- 实现购物车同品合并、删除、重量编辑和金额汇总。
- 增加本地熟食明细字段及独立序列化/反序列化。
- 统一在线支付和离线保存使用单条
custom_dish上传摘要。 - 补齐订单详情、离线补传和旧功能回归测试。
9. 风险与控制
| 风险 | 控制措施 |
|---|---|
| 秤重变化误入车 | 只有点击加入才提交快照 |
| 未稳定重量计入 | 加入前校验 isWeightStable() |
| 商品设置改价影响历史订单 | 购物车和订单保存价格快照 |
| 离线补传拆分熟食明细 | 明细与 categories 分字段保存 |
| 数据库升级丢配置 | 使用版本迁移,不删除整表 |
| 金额字段不兼容 | 以 DishItem.money 为上传唯一格式并测试 |
| 旧麻辣烫流程受影响 | 熟食类型、入口和状态分支隔离,做回归测试 |
10. 定值商品点选补充方案
10.1 决策
熟食商品继续统一在本地维护,但新增 saleMode 区分两类商品:SALE_MODE_WEIGHT = 1 为按重量计价、单价单位固定为 500g;SALE_MODE_FIXED = 2 为定值商品、单价单位固定为份。历史熟食商品迁移后默认按重量计价,商品表新增 sale_mode,数据库版本升至 3,使用增列迁移保留既有配置。
10.2 收银交互
- 右侧商品区增加“称重熟食 / 定值商品”切换。
- 称重熟食沿用先选后称或先称后选,重量稳定后由收银员主动加入购物车。
- 定值商品点击卡片立即加入购物车;重复点击同一商品时数量加一并合并为同一行。
- 存在待加入称重商品、非零重量或归零锁定时,定值商品点选被阻止并提示先完成称重和归零。
10.3 购物车与本地明细
| 商品类型 | 合并方式 | 金额公式 | 可编辑字段 |
|---|---|---|---|
| 称重熟食 | 同商品累计重量 | unitPrice × weightGrams ÷ 500 | 重量(g) |
| 定值商品 | 同商品累计数量 | unitPrice × quantity | 数量(份) |
CookedFoodCartLine 保存 saleMode、weightGrams、quantity 与价格快照;CookedFoodOrderDetails 升级为版本 2。上传仍只构造一个 custom_dish,不改变 categories 与 cooked_food_details 的职责分离。