设备故障与库存信号上传:门店经营通知的数据入口

设备故障与库存信号上传:门店经营通知的数据入口

功能定位:门店经营通知系统的一线"感官"——售卖机故障/恢复信号与食材库存信号的统一接入层。设备、网关、店员的每一次上报都会被识别、去重、落流水,并驱动故障事件与低库存事件,最终触发分钟级告警。


一、为什么需要信号上传

门店经营里最怕"发现滞后":

  • 售卖机故障:机器坏了没人知道,顾客到店才发现无法出餐;
  • 食材短缺:缺货直到下单失败或临时下架才暴露,补采不及时造成拒单。

这两类信息过去依赖线下口头沟通和人工巡检。本版功能把它们变成结构化的信号:设备/网关按接口上报,店员在后台手工兜底,两条路径汇入同一套识别与留痕机制,事件一经识别即触发通知配置里的短信/邮件告警。

信号上传只负责"把事实送进来、识别清楚、留下证据",控制类能力(设备远程控制、维修工单)明确不在范围内。


二、三个接入端点

接口位于 /api/operation/ 前缀下,均为 POST,需携带管理员/机器账号的 JWT(IsShopAdmin 按店授权):

端点 用途
/api/operation/device-signals/fault/ 售卖机故障信号上报
/api/operation/device-signals/recovery/ 售卖机恢复信号上报
/api/operation/stock-signals/ 食材库存信号批量上报

完整入参/出参见 Swagger UI:/api/schema/swagger-ui/(生产环境 https://freerain.cloud/api/schema/swagger-ui/),标签为「门店经营通知-设备接入」。

2.1 故障上报

POST /api/operation/device-signals/fault/
{
  "device_no": "SM-2026-0001",
  "fault_type": "heater_overheat",
  "signal_time": "2026-10-30T09:15:00+08:00",
  "remark": "加热模块过温保护",
  "payload": {"temp": 96, "code": "E05"},
  "idempotency_key": "evt-20261030-0915-0001"
}

2.2 恢复上报

请求体与故障上报一致,fault_type 可为空——为空表示恢复该设备的全部活跃故障:

POST /api/operation/device-signals/recovery/
{
  "device_no": "SM-2026-0001",
  "fault_type": "heater_overheat",
  "idempotency_key": "evt-20261030-0940-0001"
}

2.3 库存批量上报

一次提交一个门店的多条库存条目,items 至少 1 条:

POST /api/operation/stock-signals/
{
  "shop": 3,
  "signal_source": "api",
  "signal_time": "2026-10-30T21:00:00+08:00",
  "items": [
    {"shop_product": 101, "stock_num": 12, "idempotency_key": "it-0001"},
    {"shop_product": 102, "stock_num": 0,  "remark": "米粉告急", "idempotency_key": "it-0002"}
  ]
}

signal_source 由调用方声明:manual(人工录入)、stocktake(扫码盘点)、inbound(入库更新)、api(外部系统/设备对接)。未声明按 manual 记,对接方请显式声明 api。


三、识别动作:每条信号都有明确去向

系统不会"收到什么算什么",而是给出统一的 action 回执,与信号流水的 action 字段同源,方便上报方与排查者对齐口径。

设备信号:

action 含义
fault_opened 新故障:生成故障事件,设备置为故障态
fault_duplicate 同设备同故障类型未恢复,去重不重复告警
recovered 对应故障事件已关闭,设备恢复正常
no_active_fault 设备无对应活跃故障,仅留痕
device_not_found 设备不在台账:拒绝并留痕,不自动建档
device_inactive 设备已停用:信号忽略
idempotent_replay 命中幂等键的重放(仅回执,不新增流水)

库存信号(逐条独立处理):

action 含义
low_stock_opened 首次跌破预警阈值,生成低库存事件
low_stock_daily_remind 事件持续中跨自然日,当日首次提醒
low_stock_duplicate 当日已提醒,去重不重复
recovered 补货后高于阈值,事件恢复
stock_updated 库存充足,仅更新账面库存
ignored_inactive 商品已下架,不预警
product_not_found / shop_mismatch 条目级拒绝,不影响同批次其他条目

回执示例(故障上报):

{
  "signal_uuid": "6f1c1d2e-8a4b-4c6d-9f10-2b3c4d5e6f70",
  "action": "fault_opened",
  "message": "已生成故障事件并置设备为故障态",
  "event_id": 12,
  "device_no": "SM-2026-0001",
  "device_status": "fault"
}

回执里的 signal_uuid 是信号流水的全局唯一回执 ID,event_id 关联故障事件/低库存事件,两者共同构成"一条信号 → 一个事件"的可追溯链路。


四、三个关键设计

4.1 幂等:重复上报不重复处理

设备网络抖动、网关重试是常态。上报方传入 idempotency_key(自身事件 ID),系统命中后直接返回 idempotent_replay,不新增流水、不重复生成事件、不重复告警。库存条目支持条目级幂等键,批量上报里单条重放不影响其余条目。

4.2 留痕:所有信号落流水

无论识别结果是通过还是拒绝,每一次上报都会写入信号流水(DeviceSignal / StockSignal),包含原始 payload、来源、幂等键、识别动作与处理消息。未知设备不会被自动建档,而是拒绝并留痕——设备必须先进入台账,避免脏数据自动蔓延。

4.3 门店隔离:按店授权

接口使用 IsShopAdmin 权限:设备/门店超出调用者的授权范围直接 403 且不留流水(未知设备放行给服务层拒绝留痕)。超级管理员不受范围限制,普通管理员与机器账号只能操作本店数据。


五、人工兜底与管理端

自动化上报覆盖不到的场景(网关离线、临时发现),门店人员可以在 Admin 页面手工登记故障、人工确认恢复——人工动作同样落 signal_source=manual 的信号流水,与自动信号走同一套事件识别逻辑,保证口径一致。

管理端(/api/admin/operation/)提供只读查询与台账维护:

  • 设备台账:设备编号、所属门店、启用状态;status、last_signal_at 为只读字段,仅由信号驱动;
  • 信号流水 / 库存信号:按门店范围检索全部上报记录;
  • 故障事件 / 低库存事件:活跃与已恢复事件、持续时长、阈值快照与库存快照;
  • 通知配置与通知记录:接收人配置、投递状态与重试留痕。

六、端到端链路

设备/网关 ──┐
店员手工登记 ─┼─▶ 信号接入(鉴权 / 幂等 / 门店隔离)
外部库存系统 ─┘         │
                        ▼
              信号流水(全部留痕)
                        │
                        ▼
         识别动作:生成事件 / 去重 / 拒绝留痕
                        │
                        ▼
        故障事件 · 低库存事件 ─▶ 通知投递(短信 + 邮件抄送)

一条故障信号从上报到接收人收到短信是分钟级的;一条库存信号跌破阈值即触发补货提醒,并在每日缺货汇总中再次汇总。事后,流水与事件可以按 signal_uuid / event_id 完整回溯。


七、小结

本版落地的是门店经营通知的数据入口:

  1. 三个接入端点覆盖设备故障、设备恢复、库存批量上报,统一 JWT + 按店授权;
  2. 幂等键 + 全量流水保证重复不炸、拒绝留痕,事件链路可追溯;
  3. 识别动作与回执同源,上报方能明确知道每条信号被如何处理;
  4. 人工兜底与自动上报共用同一套识别逻辑,管理端可查台账、流水、事件与通知记录。

后续计划(P1)包括通知发送统计报表、日报按周/按店铺汇总;设备离线检测信号接入也在台账的 offline 状态上预留了位置。