# 设备与门店模块业务说明

> 本文档描述设备管理、门店管理、库存管理及设备回调链路。
> 帮助开发人员快速理解"为什么这样设计"以及各环节的特殊处理方式。

---

## 一、设备管理

### 1.1 Device 实体

| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 雪花算法主键 |
| deviceId | String | 设备SN号（唯一标识） |
| shopId | Long | 所属门店ID |
| name | String | 设备名称 |
| authToken | String | 设备认证Token |
| status | Integer | 设备状态 |
| doorStatus | String | 门状态：ERROR / OPENED / CLOSED / ANOTHER(设备繁忙) |
| currentInventoryHash | String | 当前库存哈希（用于同步判断） |
| createTime / updateTime | LocalDateTime | 时间戳 |

**非DB字段**（`@TableField(exist = false)`）：shopName, address, deviceTypeLabel, statusLabel, statusColor, isOnline, temperature, volume, doorCount, layerCount, hasScreen, totalSales, todaySales, orderCount, lastOnlineTime

### 1.2 门状态枚举 (`DeviceDoorStatus`)

| code | 枚举值 | 说明 |
|------|--------|------|
| ERROR | 开门失败 | 远程开门请求失败 |
| OPENED | 门已开 | 门处于打开状态 |
| CLOSED | 门已关 | 门处于关闭状态 |
| ANOTHER | 设备繁忙 | 设备正忙，无法开门 |

#### 为什么门状态用字符串而非数字编码？

门状态直接来自哈哈平台设备端的回调，设备端使用字符串状态码（如 "1"=ERROR, "2"=OPENED, "3"=CLOSED, "4"=ANOTHER）。为避免数字编码与数据库值之间的映射混乱，`DeviceDoorStatus` 枚举在数据库中存储英文状态字符串（ERROR/OPENED/CLOSED/ANOTHER），并在 `convertToDbValue()` 方法中处理原始数字码到字符串的转换。这种设计使得数据库中的值自解释，排查问题时一目了然。

### 1.3 设备服务核心方法 (`DeviceService`)

| 方法 | 说明 |
|------|------|
| getPage() | 分页查询设备列表（支持门店/状态筛选） |
| getDetailById() | 设备详情（含门店名、状态标签填充） |
| getStatistics() | 设备统计数据 |
| openDoor() | 远程开门 |
| scanCodeOpenDoor() | 扫码开门业务（含支付分校验） |
| setTemperature() | 设置温度 |
| setVolume() | 设置音量 |
| getNearbyDevices() | 附近设备查询（经纬度+距离） |
| getDeviceBySn() | 通过SN号查询设备 |
| updateDeviceStatus() | 更新设备状态 |
| updateInventoryHash() | 更新库存哈希 |

---

## 二、设备回调处理 (`HahaCallbackService`)

#### 什么是设备回调？

哈哈平台（智能售卖机设备平台）采用回调机制主动推送设备事件。我们的服务端作为回调接收方，在 `CallbackController` 中接收并分发处理。回调类型包括设备状态变更、AI识别结果、订单信息等。

**为什么用回调而非轮询？**
- 实时性：设备事件立即推送，无需等待轮询周期
- 高效：只有事件发生时才产生网络请求，节省资源
- 可靠：回调失败可重试，配合签名验证保证安全

### 2.1 回调消息类型

| 类型 | 方法 | 说明 |
|------|------|------|
| DEVICE_STATUS | handleDeviceStatus() | 开关门状态通知 |
| ONLINE_STATUS | handleOnlineStatus() | 设备在线/离线通知 |
| VOICE_RESULT | handleVoiceResult() | 音量调节结果 |
| CLIENT_NEW_PRODUCT | handleNewProductAudit() | 新品审核结果 |
| MERGE_PRODUCT | handleMergeProduct() | 商品合并结果 |
| ORC_RESULT | handleOrcResult() | AI识别结果通知 |
| 订单回调 | handleOrderCallback() | 设备端订单信息回调 |

### 2.2 核心回调流程

#### ORC_RESULT（AI识别回调）

```
设备AI识别完成
    ↓
解析 sku_list: [{code, quantity}]
    ↓
创建/查找订单 createOrderFromRecognition()
    - 幂等：通过 activityId 防重复
    - 订单金额初始化为0（等待设备端回调）
    ↓
解析视频URL等资源信息
    ↓
创建 DoorRecord（doorStatus=CLOSED）
```

#### 订单回调

```
设备端计算好订单金额和商品明细后回调
    ↓
查找或创建本地订单
    ↓
updateOrderFromCallback()
    - 写入 orderNo, totalAmount, items
    - 初始化 discountAmount=0, paidAmount=totalAmount
    ↓
saveOrderGoods() — 保存订单商品明细
    ↓
processCouponDiscount() — 自动优惠券扣减
    ↓
processPayScorePayment() — 支付分扣费
```

---

## 三、开关门记录 (DoorRecord)

### 3.1 实体字段

| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 雪花算法主键 |
| activityId | String | 哈哈平台活动ID |
| deviceId | String | 设备ID |
| userId | Long | 用户ID |
| doorIndex | String | 门索引（A/B/C/D） |
| openType | String | 开门类型：IN-上货，OUT-消费 |
| doorStatus | String | 门状态：OPENED-已开，CLOSED-已关 |
| nobuy | Integer | 是否无消费：0-有消费，1-无消费 |
| orderId | Long | 关联订单ID（有消费时） |
| openTime | LocalDateTime | 开门时间 |
| closeTime | LocalDateTime | 关门时间 |
| duration | Integer | 持续时长（秒） |
| source | String | 来源：MINIAPP / ADMIN |
| remark | String | 备注 |

### 3.2 开门类型 (`OpenType`)

| code | 说明 |
|------|------|
| IN | 上货开门（运营人员补货） |
| OUT | 消费开门（用户购物） |

---

## 四、门店管理

### 4.1 Shop 实体

| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 雪花算法主键 |
| shopCode | String | 门店编码（唯一标识） |
| name | String | 门店名称 |
| address | String | 门店地址 |
| province / city / district | String | 省市区 |
| longitude / latitude | Double | 经纬度 |
| contactName / contactPhone | String | 联系人/电话 |
| businessHours | String | 营业时间 |
| status | Integer | 状态：0-禁用 1-启用 |
| remark | String | 备注 |

**非DB字段**：deviceCount（设备总数）, onlineCount（在线设备数）, statusLabel, statusColor

### 4.2 门店服务核心方法 (`ShopService`)

| 方法 | 说明 |
|------|------|
| getPage() | 分页查询门店 |
| getDetailById() | 门店详情（含设备数/在线数） |
| getStatistics() | 门店统计 |
| getNearbyShops() | 附近门店查询（小程序端） |

### 4.3 门店补货员 (ShopReplenisher)

关联门店和补货人员，用于补货权限管理。

---

## 五、库存管理

### 5.1 DeviceInventory 实体（设备商品库存）

| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 雪花算法主键 |
| deviceId | String | 设备ID（SN号） |
| productId | Long | 商品ID（本地） |
| productCode | String | 商品编码（哈哈平台code） |
| productName | String | 商品名称（冗余） |
| stock | Integer | 当前库存数量 |
| shelfNum | Integer | 货架层号 |
| position | String | 货道位置（left/right） |
| warningThreshold | Integer | 库存预警阈值 |
| lastRestockTime | LocalDateTime | 最后补货时间 |

### 5.2 库存服务 (`DeviceInventoryService`)

| 方法 | 说明 |
|------|------|
| getInventoryByDevice() | 查询设备库存列表 |
| syncInventory() | 同步设备库存（从设备端获取最新数据） |
| deductStock() | 扣减库存（订单完成后） |
| checkLowStock() | 低库存预警检查 |

### 5.3 库存同步流程

```
设备端上报库存变更
    ↓
HahaCallbackService 接收通知
    ↓
比对 currentInventoryHash
    ↓
如不一致 → 调用哈哈SDK获取最新库存
    ↓
更新 DeviceInventory 记录
    ↓
更新 currentInventoryHash
    ↓
检查低库存 → 触发告警
```

#### 库存哈希机制的设计考量

`currentInventoryHash` 是设备当前库存的哈希摘要。每次设备端回调库存变更时，先比对哈希值：
- 哈希一致 → 库存未变化，跳过同步，减少不必要的SDK调用
- 哈希不一致 → 库存有变化，拉取最新数据并更新

这种设计避免了频繁全量同步的性能开销，只在库存确实变化时才触发同步。

### 5.4 补货记录 (StockRecord / StockRecordItem)

| 实体 | 说明 |
|------|------|
| StockRecord | 补货记录主表（操作人、设备、时间） |
| StockRecordItem | 补货明细（商品、数量变化） |

---

## 六、设备告警体系

### 6.1 DeviceAlertRecord 实体

| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 雪花算法主键 |
| deviceId | String | 设备ID |
| alertType | String | 告警类型 |
| alertLevel | Integer | 告警级别 |
| title | String | 告警标题 |
| content | String | 告警内容 |
| status | Integer | 处理状态 |

### 6.2 告警类型 (`DeviceAlertType`)

| code | 说明 |
|------|------|
| OFFLINE | 设备离线 |
| LOW_STOCK | 低库存 |
| DOOR_ERROR | 开门异常 |

### 6.3 告警通知

告警通过企业微信 Webhook 推送，配置在 `haha-service/.../notify/` 中。

---

## 七、商品管理

### 7.1 Product 实体

| 字段 | 类型 | 说明 |
|------|------|------|
| id | Long | 雪花算法主键 |
| productId | String | 哈哈平台商品ID |
| code | String | 商品编码（哈哈平台code） |
| barcode | String | 商品条码 |
| name | String | 商品名称 |
| type | String | 商品分类 |
| pic | String | 商品图片URL |
| retailPrice | Double | 零售价 |
| costPrice | Double | 成本价 |
| applyStatus | String | 适用设备：全部柜/静态柜/动态柜 |
| syncStatus | Integer | 同步状态：0-未同步 1-同步中 2-已同步 3-同步失败 |

### 7.2 商品同步

商品数据从哈哈平台（SDK）同步到本地，`DataSyncServiceImpl` 处理全量/增量同步。

---

## 八、核心代码文件索引

| 文件 | 职责 |
|------|------|
| `haha-entity/.../Device.java` | 设备实体 |
| `haha-entity/.../Shop.java` | 门店实体 |
| `haha-entity/.../Product.java` | 商品实体 |
| `haha-entity/.../DoorRecord.java` | 开关门记录 |
| `haha-entity/.../DeviceInventory.java` | 设备库存 |
| `haha-entity/.../StockRecord.java` | 补货记录 |
| `haha-entity/.../DeviceAlertRecord.java` | 设备告警记录 |
| `haha-service/.../DeviceServiceImpl.java` | 设备服务 |
| `haha-service/.../ShopServiceImpl.java` | 门店服务 |
| `haha-service/.../ProductServiceImpl.java` | 商品服务 |
| `haha-service/.../DoorRecordServiceImpl.java` | 开关门记录服务 |
| `haha-service/.../DeviceInventoryServiceImpl.java` | 库存服务 |
| `haha-service/.../DeviceAlertServiceImpl.java` | 告警服务 |
| `haha-service/.../HahaCallbackServiceImpl.java` | 设备回调处理 |
| `haha-service/.../DataSyncServiceImpl.java` | 数据同步服务 |
| `haha-admin/.../DeviceController.java` | 运营平台-设备API |
| `haha-admin/.../ShopController.java` | 运营平台-门店API |
| `haha-admin/.../InventoryController.java` | 运营平台-库存API |
| `haha-admin/.../DeviceAlertController.java` | 运营平台-告警API |
| `haha-miniapp/.../DeviceController.java` | 小程序-设备API |
| `haha-common/.../enums/DeviceDoorStatus.java` | 门状态枚举 |
| `haha-common/.../enums/DeviceAlertType.java` | 告警类型枚举 |
| `haha-common/.../enums/DeviceOnlineStatus.java` | 在线状态枚举 |
| `haha-common/.../constant/DeviceConstants.java` | 设备常量 |
