Просмотр исходного кода

上货员模块清理v1版本代码

skyline 1 месяц назад
Родитель
Сommit
ca571896ce

+ 525 - 0
docs/上货员模块.md

@@ -0,0 +1,525 @@
+# 上货员(补货员)模块说明
+
+> 本文档描述系统上货员模块的完整架构,涵盖独立补货员体系、微信绑定流程、补货操作流程及相关实现。
+> 帮助开发人员理解"为什么这样设计"以及各环节的特殊处理方式。
+
+---
+
+## 一、模块概述
+
+### 1.1 什么是上货员/补货员?
+
+上货员(系统内称"补货员",Replenisher)指负责为智能售卖柜补货的工作人员。他们通过手机小程序查看库存、执行补货操作。
+
+### 1.2 独立补货员体系(V2)
+
+系统使用独立补货员体系,核心表:
+
+| 体系 | 数据表 | 说明 |
+|------|--------|------|
+| **独立补货员** | `t_replenisher` + `t_replenisher_device` | 独立账号体系,设备级绑定粒度,微信扫码绑定 |
+
+> **历史背景**:早期存在基于 `t_shop_replenisher` + `t_admin` 的 V1 体系(补货员需是系统管理员),已于 2026年4月 完成迁移清理,V1 表 `t_shop_replenisher` 及所有相关代码已彻底移除。
+
+### 1.3 为什么使用独立补货员体系?
+
+独立补货员体系相比旧的 V1(基于 `t_admin` 管理员表)解决了以下问题:
+- 补货员必须是系统管理员,具备过多后台权限
+- 粒度仅到门店级别,无法精确控制设备权限
+- 全凭管理员账号密码登录,补货员使用体验差
+
+V2 独立补货员体系的核心优势:
+- **独立账号**:`t_replenisher` 表独立管理,不与管理员混合
+- **设备级绑定**:通过 `t_replenisher_device` 表实现设备级粒度绑定
+- **微信扫码绑定**:后台创建账号 → 生成绑定码 → 微信扫码绑定,纯手机端操作
+
+---
+
+## 二、数据库设计
+
+### 2.1 核心表
+
+#### `t_replenisher` — 补货员表
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | BIGINT | 主键,雪花算法 |
+| `name` | VARCHAR(50) | 姓名 |
+| `phone` | VARCHAR(20) | 手机号(可选) |
+| `employee_id` | VARCHAR(50) | 工号(可选) |
+| `wechat_openid` | VARCHAR(100) | 微信 OpenID(扫码绑定时回填) |
+| `avatar` | VARCHAR(500) | 头像 URL |
+| `status` | INT | 状态:1-启用,0-禁用 |
+| `total_tasks` | INT | 累计任务数 |
+| `last_task_time` | DATETIME | 最后任务时间 |
+| `create_time` | DATETIME | 创建时间 |
+| `update_time` | DATETIME | 更新时间 |
+
+#### `t_replenisher_device` — 补货员-设备关联表
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | BIGINT | 主键,雪花算法 |
+| `replenisher_id` | BIGINT | 补货员ID |
+| `device_id` | VARCHAR(50) | 设备SN号 |
+| `source` | VARCHAR(20) | 绑定来源:`MANUAL`-手动,`QR`-扫码绑定,`SHOP_INHERIT`-门店继承 |
+| `create_time` | DATETIME | 创建时间 |
+
+### 2.2 补货记录表
+
+#### `t_stock_record` — 上货/补货记录表
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | BIGINT | 主键,雪花算法 |
+| `device_id` | VARCHAR(50) | 设备ID |
+| `activity_id` | VARCHAR(100) | 哈哈平台活动号 |
+| `stock_type` | INT | 类型:1-上货,2-补货 |
+| `stocker_id` | BIGINT | 上货员ID |
+| `stocker_name` | VARCHAR(50) | 上货员名称 |
+| `stocker_phone` | VARCHAR(20) | 上货员电话 |
+| `total_items` | INT | 上货商品种类数 |
+| `total_quantity` | INT | 上货总数量 |
+| `status` | INT | 状态:1-进行中,2-已完成,3-已取消 |
+| `start_time` | DATETIME | 开始时间 |
+| `end_time` | DATETIME | 结束时间 |
+| `remark` | VARCHAR(500) | 备注 |
+| `create_time` | DATETIME | 创建时间 |
+| `update_time` | DATETIME | 更新时间 |
+
+#### `t_stock_record_item` — 补货明细表
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `id` | BIGINT | 主键,雪花算法 |
+| `record_id` | BIGINT | 上货记录ID |
+| `device_id` | VARCHAR(50) | 设备ID |
+| `product_id` | BIGINT | 商品ID |
+| `product_code` | VARCHAR(50) | 商品编码 |
+| `product_name` | VARCHAR(100) | 商品名称 |
+| `shelf_num` | INT | 货架层号 |
+| `quantity` | INT | 上货数量 |
+| `before_stock` | INT | 上货前库存 |
+| `after_stock` | INT | 上货后库存 |
+| `create_time` | DATETIME | 创建时间 |
+
+### 2.3 数据迁移
+
+`docs/database/migrate_replenisher.sql` 提供了从旧表 `t_stocker` 到 `t_replenisher` 的数据迁移脚本。
+
+> V1 表 `t_shop_replenisher` 及所有相关后端/前端代码已于 2026年4月 彻底清理移除。
+
+---
+
+## 三、后端实现
+
+### 3.1 模块目录结构
+
+```
+haha-entity
+├── Replenisher.java               # 补货员实体
+├── ReplenisherDevice.java          # 补货员-设备关联实体
+├── StockRecord.java                # 补货记录实体
+├── StockRecordItem.java            # 补货明细实体
+├── dto/
+│   ├── ReplenisherCreateDTO.java   # 创建补货员
+│   ├── ReplenisherUpdateDTO.java   # 更新补货员
+│   ├── ReplenisherQueryDTO.java    # 分页查询参数
+│   ├── ReplenisherBindDTO.java     # 微信扫码绑定
+│   ├── BindReplenisherDTO.java     # 绑定补货员到设备/门店
+│   ├── ReplenishDTO.java           # 执行补货操作
+│   ├── BindDeviceDTO.java          # 绑定设备
+│   ├── BatchBindDevicesDTO.java    # 批量绑定设备
+│   ├── StatusDTO.java              # 通用状态更新
+│   └── WechatLoginDTO.java         # 微信登录
+
+haha-mapper
+├── ReplenisherMapper.java          # 补货员 Mapper
+├── ReplenisherDeviceMapper.java    # 关联 Mapper
+├── resources/mapper/
+│   ├── ReplenisherMapper.xml       # 分页带设备绑定数查询
+│   └── ReplenisherDeviceMapper.xml # 批量插入忽略已存在
+
+haha-service
+├── ReplenisherService.java         # 补货员服务接口
+├── impl/
+│   └── ReplenisherServiceImpl.java # 补货员服务实现
+
+haha-admin (运营平台)
+├── controller/
+│   ├── ReplenisherController.java          # 补货员 CRUD 管理
+│   ├── ReplenisherLoginController.java     # 微信登录/绑定
+│   └── ReplenisherOperationController.java # 补货操作(设备查询/库存/补货)
+
+haha-admin-web (前端)
+├── src/api/replenisher.ts                  # API 接口层
+├── src/views/replenisher/
+│   ├── index.vue                           # 补货员管理页面
+│   └── utils/
+│       ├── hook.tsx                        # 逻辑钩子
+│       └── types.ts                        # 类型定义
+
+haha-admin-mp (管理端小程序)
+├── src/api/replenish.ts                    # 补货员 API
+├── src/pages/
+│   ├── login/login.vue                     # 微信登录 + 绑定码入口
+│   └── replenish/
+│       ├── bind.vue                        # 微信绑定页
+│       └── index.vue / ...                 # 补货工作台
+
+haha-mp (用户端小程序)
+├── src/api/replenish.ts                    # 补货员 API
+├── src/pages/replenish/bind.vue            # 微信绑定页
+```
+
+### 3.2 核心实体关系
+
+```
+┌─────────────┐     ┌──────────────────────┐     ┌──────────┐
+│ t_replenisher│────<│ t_replenisher_device │────>│ t_device │
+│  (补货员)    │     │  (补货员-设备关联)    │     │  (设备)   │
+└─────────────┘     └──────────────────────┘     └──────────┘
+       │
+       │ (wechat_openid 绑定)
+       │
+  ┌────┴─────┐
+  │ 微信 OpenID│
+  └──────────┘
+```
+
+### 3.3 API 接口总览
+
+#### 3.3.1 补货员管理(运营平台,需要管理员权限)
+
+| 接口 | 方法 | 路径 | 权限 | 说明 |
+|------|------|------|------|------|
+| 分页列表 | GET | `/replenishers/list` | `replenisher:read` | 关键词+状态筛选,含设备绑定数 |
+| 详情 | GET | `/replenishers/{id}` | `replenisher:read` | 含设备绑定数 |
+| 搜索 | GET | `/replenishers/search` | `replenisher:read` | 仅搜索启用状态,用于下拉选择 |
+| 创建 | POST | `/replenishers` | `replenisher:create` | 姓名必填,手机号可选 |
+| 更新 | PUT | `/replenishers/{id}` | `replenisher:update` | 仅更新非空字段 |
+| 状态 | PUT | `/replenishers/{id}/status` | `replenisher:update` | 启用/禁用 |
+| 删除 | DELETE | `/replenishers/{id}` | `replenisher:delete` | 级联解绑设备 |
+| 绑定设备 | POST | `/replenishers/{id}/devices` | `replenisher:update` | 绑定设备到补货员 |
+| 解绑设备 | DELETE | `/replenishers/{id}/devices/{deviceId}` | `replenisher:update` | 解绑单个设备 |
+| 批量绑定 | POST | `/replenishers/batch-bind` | `replenisher:update` | 多补货员→多设备 |
+| **生成绑定码** | POST | `/replenishers/{id}/binding-code` | `replenisher:update` | **生成微信绑定码** |
+
+#### 3.3.2 门店补货员关联(运营平台)
+
+| 接口 | 方法 | 路径 | 权限 | 说明 |
+|------|------|------|------|------|
+| 获取门店补货员 | GET | `/shops/{id}/replenishers` | `shop:read` | 通过设备绑定反向推导门店关联的补货员 |
+| 绑定到门店 | POST | `/shops/{id}/replenishers` | `shop:update` | 将补货员绑定到门店下的所有设备 |
+
+#### 3.3.3 补货员登录/绑定(免登录)
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 微信静默登录 | POST | `/replenisher/login/wechat` | 已绑定微信的补货员直接登录 |
+| 扫码绑定微信 | POST | `/replenisher/login/bind` | 绑定码 + 微信凭证进行绑定 |
+
+#### 3.3.4 补货员操作(需补货员身份认证)
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 获取个人信息 | GET | `/replenisher/my-info` | 含设备绑定数 |
+| 设备列表 | GET | `/replenisher/device/list` | 含库存概览 |
+| 设备库存 | GET | `/replenisher/device/inventory/{deviceId}` | 详细库存 |
+| 执行补货 | POST | `/replenisher/stock/replenish` | 增加库存 |
+
+---
+
+## 四、微信绑定流程
+
+### 4.1 流程概述
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│                    补货员微信绑定流程                              │
+│                                                                 │
+│  ┌──────────┐    ┌──────────────┐    ┌──────────────────┐      │
+│  │ 管理员后台 │───>│ 生成一次性绑定码│───>│ 展示二维码/绑定码  │      │
+│  │ 创建补货员 │    │ (Redis 24h)  │    │ (前端弹窗展示)   │      │
+│  └──────────┘    └──────────────┘    └────────┬─────────┘      │
+│                                               │                │
+│                                               ▼                │
+│  ┌──────────┐    ┌──────────────┐    ┌──────────────────┐      │
+│  │ 绑定成功  │<───│ 存入openid +  │<───│ 补货员扫码/输入   │      │
+│  │ 自动登录  │    │ 删除绑定码    │    │  + 微信授权      │      │
+│  └──────────┘    └──────────────┘    └──────────────────┘      │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### 4.2 详细步骤
+
+**1. 管理员创建补货员**
+- 运营平台后台 → 补货员管理 → 创建补货员
+- 输入姓名、手机号(可选)、工号(可选)
+- 创建后状态为启用,`wechat_openid` 为空
+
+**2. 管理员生成绑定码**
+- 在补货员列表操作列点击"绑定码"
+- 后端生成 24 位 UUID 大写绑定码,存入 Redis(24 小时 TTL)
+- 前端弹窗展示绑定码和二维码(小程序路径 `pages/replenish/bind?code=xxx`)
+- 同一补货员如已绑定微信则拒绝生成
+
+**3. 补货员扫码绑定**
+- 打开管理端小程序(`haha-admin-mp`)或用户端小程序(`haha-mp`)
+- 扫描二维码或手动输入绑定码
+- 点击"绑定微信" → `uni.login()` 获取微信 code
+- 调用 `POST /replenisher/login/bind` 接口,传入 `bindingCode` + `code`
+- 后端验证 Redis 绑定码 → `jscode2session` 获取 openid → 检查 openid 唯一性 → 存入补货员记录 → 删除 Redis 绑定码 → 自动登录
+
+**4. 后续登录**
+- 已绑定微信的补货员可直接使用微信静默登录 `POST /replenisher/login/wechat`
+- 后端通过 `wechat_openid` 定位补货员 → 检查状态 → Sa-Token 登录
+
+### 4.3 安全机制
+
+| 机制 | 说明 |
+|------|------|
+| **绑定码一次性有效** | 使用后立即从 Redis 删除 |
+| **绑定码过期** | Redis TTL 24 小时 |
+| **openid 唯一性检查** | 绑定和登录时都检查 openid 是否已被其他账号占用 |
+| **禁止重复绑定** | 已绑定微信的补货员不能再生成绑定码 |
+| **账号状态检查** | 禁用状态的补货员无法登录 |
+
+### 4.4 微信配置
+
+补货员使用**管理端微信小程序**的 AppId/Secret:
+
+```yaml
+wechat:
+  admin-miniapp:
+    app-id: xxxxxxxxxxxxxx
+    secret: xxxxxxxxxxxxxx
+```
+
+通过 `jscode2session` 接口获取 openid:
+
+```
+GET https://api.weixin.qq.com/sns/jscode2session
+  ?appid={appid}
+  &secret={secret}
+  &js_code={code}
+  &grant_type=authorization_code
+```
+
+### 4.5 认证方式
+
+补货员登录使用 **Sa-Token 同一域名**(与运营平台共享 `adminAccessToken`),通过 Token Session 中的 `userType` 字段区分身份:
+
+```java
+StpUtil.login(replenisher.getId());
+StpUtil.getTokenSession().set("userType", "REPLENISHER");
+```
+
+登录成功后返回 `LoginVO`:
+
+```json
+{
+  "token": "sa-token-value",
+  "userInfo": {
+    "id": "182734...",
+    "nickname": "张三",
+    "avatar": "https://...",
+    "phone": "138..."
+  }
+}
+```
+
+补货员操作接口通过 `getCurrentReplenisher()` 方法验证身份:
+```java
+String userType = StpUtil.getTokenSession().getString("userType");
+if (!"REPLENISHER".equals(userType)) {
+    throw new BusinessException(403, "无访问权限");
+}
+```
+
+---
+
+## 五、补货操作流程
+
+### 5.1 补货流程
+
+```
+┌──────────┐    ┌──────────────┐    ┌──────────────────┐    ┌──────────┐
+│ 登录小程序 │───>│ 查看绑定设备   │───>│ 查看设备库存详情   │───>│ 执行补货  │
+│ 微信静默   │    │ 列表含库存概览 │    │ 含商品库存明细    │    │ 增加库存  │
+└──────────┘    └──────────────┘    └──────────────────┘    └──────────┘
+```
+
+### 5.2 补货 API
+
+**请求**:
+```json
+POST /replenisher/stock/replenish
+{
+  "deviceId": "B142977",
+  "items": [
+    {
+      "productId": 123456,
+      "productCode": "6921234567890",
+      "productName": "矿泉水550ml",
+      "quantity": 24,
+      "shelfNum": 1,
+      "position": "A1"
+    }
+  ]
+}
+```
+
+**响应**:
+```json
+{
+  "code": 200,
+  "message": "补货完成,成功1项,失败0项",
+  "data": {
+    "total": 1,
+    "success": 1,
+    "details": [
+      {
+        "productId": 123456,
+        "productName": "矿泉水550ml",
+        "quantity": 24,
+        "afterStock": 120,
+        "success": true
+      }
+    ]
+  }
+}
+```
+
+### 5.3 安全校验
+
+补货操作前进行权限验证:
+1. 验证当前用户是否为补货员(通过 `userType` 身份标识)
+2. 验证设备是否绑定到此补货员(从 `t_replenisher_device` 查询)
+3. 逐项执行补货,每项独立事务,失败不影响其他项
+
+---
+
+## 六、前端实现
+
+### 6.1 管理后台(haha-admin-web)
+
+- **页面**:`src/views/replenisher/index.vue` — 补货员管理页面
+- **API**:`src/api/replenisher.ts` — 全部 CRUD + 绑定设备 + 生成绑定码
+- **功能**:
+  - 补货员分页列表(含设备绑定数)
+  - 创建/编辑/删除补货员
+  - 启用/禁用
+  - 绑定/解绑设备
+  - **生成绑定码弹窗**(展示 24 位绑定码 + 二维码)
+- **依赖**:`qrcode` npm 包用于生成二维码图片
+
+### 6.2 管理端小程序(haha-admin-mp)
+
+- **登录页**:`src/pages/login/login.vue`
+  - 微信一键登录(`POST /replenisher/login/wechat`)
+  - 未绑定提示 → 展开绑定码输入区域
+  - 绑定码 + 微信授权 → 绑定登录
+- **绑定页**:`src/pages/replenish/bind.vue`
+  - 从 `options.code` / `scene` 获取绑定码
+  - 绿色清新 UI,绑定流程指引
+  - 微信授权 → 绑定 API → 自动跳转
+- **补货工作台**:`src/pages/replenish/` 目录下
+  - 设备列表
+  - 库存查看
+  - 执行补货
+
+### 6.3 用户端小程序(haha-mp)
+
+- **绑定页**:`src/pages/replenish/bind.vue`
+  - 金色品牌风格 UI,三步骤流程指引
+  - 支持 `options.code` / `options.bindingCode` / `options.scene` 获取绑定码
+  - 绑定成功后保存 token 并跳转首页
+  - 已加入免登录白名单
+
+---
+
+## 七、Redis 键设计
+
+| Key | 格式 | 说明 | TTL |
+|-----|------|------|-----|
+| 补货员绑定码 | `replenisher:bind:code:{bindingCode}` | Value 为补货员 ID | 24 小时 |
+
+定义在 `RedisConstants.java`:
+```java
+public static final String REPLENISHER_BIND_CODE_KEY = "replenisher:bind:code:%s";
+public static final String REPLENISHER_BIND_CODE_KEY_PREFIX = "replenisher:bind:code:";
+```
+
+---
+
+## 八、关键设计决策
+
+### 8.1 为什么不允许主动注册?
+
+**决策**:补货员不允许自主注册,必须由管理员在后台创建后,通过绑定码完成微信绑定。
+
+**原因**:
+- 补货员属于内部人员,需要经过公司审核和培训
+- 设备绑定权限不可随意分配(关系到库存数据和设备安全)
+- 避免未授权的第三方通过扫描二维码自动注册
+
+### 8.2 为什么使用 Redis 存储绑定码而非数据库?
+
+- 绑定码是一次性凭证,无需持久化存储
+- Redis 自动过期(TTL)机制天然适合此类场景
+- 避免数据库表膨胀(绑定码数据量少但频繁生成/删除)
+- 高性能读写,不增加数据库压力
+
+### 8.3 为什么补货员使用管理端小程序的 AppId?
+
+补货员和管理端共用同一套微信小程序(`haha-admin-mp`),使用管理端小程序的 AppId/Secret 进行微信登录。这意味着:
+- 补货员和管理员扫码的是同一个微信小程序
+- 通过 `userType` 区分身份和权限
+- 补货员无法访问管理后台页面
+
+### 8.4 补货操作的事务处理
+
+补货操作的 `increaseStock` 方法内部通过 `synchronized (deviceId.intern())` 实现设备级锁,防止同一设备并发补货导致库存数据不一致。每条商品补货独立执行,失败不影响其他商品。
+
+---
+
+## 九、SQL 参考
+
+### 9.1 补货员分页查询(含设备绑定数)
+
+```xml
+<select id="selectWithDeviceCount" resultMap="BaseResultMap">
+    SELECT r.*,
+           (SELECT COUNT(*) FROM t_replenisher_device rd WHERE rd.replenisher_id = r.id) AS bound_device_count
+    FROM t_replenisher r
+    <where>
+        <if test="keyword != null and keyword != ''">
+            AND (r.name LIKE CONCAT('%', #{keyword}, '%')
+                 OR r.phone LIKE CONCAT('%', #{keyword}, '%')
+                 OR r.employee_id LIKE CONCAT('%', #{keyword}, '%'))
+        </if>
+        <if test="status != null">
+            AND r.status = #{status}
+        </if>
+    </where>
+    ORDER BY r.create_time DESC
+</select>
+```
+
+### 9.2 通过设备查询补货员
+
+```sql
+SELECT DISTINCT r.* FROM t_replenisher r
+INNER JOIN t_replenisher_device rd ON r.id = rd.replenisher_id
+INNER JOIN t_device d ON rd.device_id = d.device_id
+WHERE d.shop_id = #{shopId}
+ORDER BY r.create_time DESC
+```
+
+### 9.3 批量插入关联(忽略已存在)
+
+```sql
+INSERT IGNORE INTO t_replenisher_device (id, replenisher_id, device_id, source, create_time)
+VALUES (?, ?, ?, ?, ?)
+```

+ 1 - 52
haha-admin-web/src/api/shop.ts

@@ -86,57 +86,6 @@ export const getShopReplenishers = (id: number) => {
   return http.request<Result>("get", `/shops/${id}/replenishers`);
 };
 
-export const addReplenisher = (id: number, data: { adminId: number }) => {
+export const addReplenisherToShop = (id: number, data: { replenisherId: number }) => {
   return http.request<Result>("post", `/shops/${id}/replenishers`, { data });
 };
-
-export const batchAddReplenishers = (
-  id: number,
-  data: { adminIds: number[] }
-) => {
-  return http.request<Result>("post", `/shops/${id}/replenishers/batch`, {
-    data
-  });
-};
-
-export const removeReplenisher = (shopId: number, adminId: number) => {
-  return http.request<Result>(
-    "delete",
-    `/shops/${shopId}/replenishers/${adminId}`
-  );
-};
-
-export const batchRemoveReplenishers = (
-  id: number,
-  data: { adminIds: number[] }
-) => {
-  return http.request<Result>("delete", `/shops/${id}/replenishers/batch`, {
-    data
-  });
-};
-
-export const updateReplenisherStatus = (
-  id: number,
-  data: { status: number }
-) => {
-  return http.request<Result>("put", `/shops/replenishers/${id}/status`, {
-    data
-  });
-};
-
-export const getAvailableUsers = (keyword?: string) => {
-  return http.request<Result>(
-    "get",
-    `/shops/available-users${keyword ? `?keyword=${keyword}` : ""}`
-  );
-};
-
-// ==================== V2 补货员管理(独立补货员体系) ====================
-
-export const getShopReplenishersV2 = (id: number) => {
-  return http.request<Result>("get", `/shops/${id}/replenishers/v2`);
-};
-
-export const addReplenisherV2 = (id: number, data: { replenisherId: number }) => {
-  return http.request<Result>("post", `/shops/${id}/replenishers/v2`, { data });
-};

+ 7 - 10
haha-admin-web/src/views/shop/utils/hook.tsx

@@ -14,12 +14,9 @@ import {
   toggleShopStatus,
   getShopDevices,
   getShopReplenishers,
-  getShopReplenishersV2,
-  addReplenisherV2,
+  addReplenisherToShop,
   linkDevice,
   unlinkDevice,
-  addReplenisher,
-  removeReplenisher,
   getUnlinkedDevices
 } from "@/api/shop";
 import { getAllRoles } from "@/api/role";
@@ -37,7 +34,7 @@ import {
   ElMessageBox,
   ElTransfer
 } from "element-plus";
-import type { ShopItem, ShopSearchForm, DeviceItem, ReplenisherItem } from "./types";
+import type { ShopItem, ShopSearchForm, DeviceItem } from "./types";
 
 export function useShop(tableRef: Ref) {
   const form = reactive<ShopSearchForm>({
@@ -652,7 +649,7 @@ export function useShop(tableRef: Ref) {
 
   // 补货员管理 V2
   async function handleReplenishers(row: ShopItem) {
-    const replenishersRes = await getShopReplenishersV2(row.id);
+    const replenishersRes = await getShopReplenishers(row.id);
     const currentReplenishers = ref<any[]>(replenishersRes.data || []);
     const searchKeyword = ref("");
     const searchResults = ref<any[]>([]);
@@ -679,10 +676,10 @@ export function useShop(tableRef: Ref) {
 
     async function doAdd(replenisher: any) {
       try {
-        const res = await addReplenisherV2(row.id, { replenisherId: replenisher.id });
+        const res = await addReplenisherToShop(row.id, { replenisherId: replenisher.id });
         if (res.code === 0 || res.code === 200) {
           message(`已添加补货员 ${replenisher.name}`, { type: "success" });
-          const updateRes = await getShopReplenishersV2(row.id);
+          const updateRes = await getShopReplenishers(row.id);
           currentReplenishers.value = updateRes.data || [];
           searchKeyword.value = "";
           searchResults.value = [];
@@ -707,7 +704,7 @@ export function useShop(tableRef: Ref) {
           await unbindDevice(replenisher.id, device.deviceId);
         }
         message(`已移除补货员 ${replenisher.name}`, { type: "success" });
-        const updateRes = await getShopReplenishersV2(row.id);
+        const updateRes = await getShopReplenishers(row.id);
         currentReplenishers.value = updateRes.data || [];
       } catch {
         message("操作失败", { type: "error" });
@@ -715,7 +712,7 @@ export function useShop(tableRef: Ref) {
     }
 
     addDialog({
-      title: `管理 ${row.name} 的补货员(V2)`,
+      title: `管理 ${row.name} 的补货员`,
       width: "500px",
       draggable: true,
       closeOnClickModal: false,

+ 0 - 9
haha-admin-web/src/views/shop/utils/types.ts

@@ -31,12 +31,3 @@ export interface DeviceItem {
   deviceName: string;
   status: number;
 }
-
-// 补货员信息类型
-export interface ReplenisherItem {
-  id: number;
-  adminId: number;
-  adminName: string;
-  adminPhone: string;
-  status: number;
-}

+ 2 - 2
haha-admin/src/main/java/com/haha/admin/controller/DeviceController.java

@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.haha.admin.annotation.RequirePermission;
 import com.haha.entity.dto.DeviceQueryDTO;
 import com.haha.entity.dto.DoorOpenDTO;
-import com.haha.entity.dto.ReplenisherBindDTO;
+import com.haha.entity.dto.BindReplenisherDTO;
 import com.haha.entity.dto.TemperatureDTO;
 import com.haha.entity.dto.VolumeDTO;
 import com.haha.common.annotation.Log;
@@ -196,7 +196,7 @@ public class DeviceController {
     @RequirePermission("device:update")
     @Log(module = "设备管理", operation = OperationType.UPDATE, summary = "绑定补货员到设备")
     @PostMapping("/{deviceId}/replenishers")
-    public Result<Void> bindReplenisher(@PathVariable String deviceId, @RequestBody ReplenisherBindDTO dto) {
+    public Result<Void> bindReplenisher(@PathVariable String deviceId, @RequestBody BindReplenisherDTO dto) {
         replenisherService.bindDevices(dto.getReplenisherId(), List.of(deviceId));
         return Result.success("绑定成功");
     }

+ 26 - 119
haha-admin/src/main/java/com/haha/admin/controller/ShopController.java

@@ -2,7 +2,11 @@ package com.haha.admin.controller;
 
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.haha.admin.annotation.RequirePermission;
-import com.haha.entity.dto.*;
+import com.haha.entity.dto.BindReplenisherDTO;
+import com.haha.entity.dto.ShopQueryDTO;
+import com.haha.entity.dto.StatusDTO;
+import com.haha.entity.dto.LinkDeviceDTO;
+import com.haha.entity.dto.BatchIdsDTO;
 import com.haha.common.annotation.Log;
 import com.haha.common.constant.ResponseEnum;
 import com.haha.common.enums.OperationType;
@@ -11,12 +15,9 @@ import com.haha.common.vo.Result;
 import com.haha.entity.Shop;
 import com.haha.entity.Device;
 import com.haha.entity.Replenisher;
-import com.haha.entity.ShopReplenisher;
-import com.haha.entity.Admin;
 import com.haha.service.ShopService;
 import com.haha.service.DeviceService;
-import com.haha.service.ShopReplenisherService;
-import com.haha.service.AdminService;
+import com.haha.service.ReplenisherService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
@@ -24,6 +25,7 @@ import org.springframework.web.bind.annotation.*;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 门店管理控制器
@@ -36,8 +38,7 @@ public class ShopController {
 
     private final ShopService shopService;
     private final DeviceService deviceService;
-    private final ShopReplenisherService shopReplenisherService;
-    private final AdminService adminService;
+    private final ReplenisherService replenisherService;
 
     /**
      * 分页查询门店列表
@@ -263,135 +264,41 @@ public class ShopController {
         return Result.success("查询成功", devices);
     }
 
-    // ==================== 补货员管理 ====================
+    // ==================== 补货员管理(V2 独立补货员体系) ====================
 
     /**
-     * 获取门店补货员列表
-     * @param id 门店ID
-     * @return 补货员列表
-     */
-    @RequirePermission("shop:read")
-    @GetMapping("/{id}/replenishers")
-    public Result<List<ShopReplenisher>> getReplenishers(@PathVariable Long id) {
-        List<ShopReplenisher> replenishers = shopReplenisherService.getReplenishersByShopId(id);
-        return Result.success("查询成功", replenishers);
-    }
-
-    /**
-     * 添加补货员
-     * @param id 门店ID
-     * @param dto 管理员ID参数
-     * @return 操作结果
-     */
-    @RequirePermission("shop:update")
-    @Log(module = "门店管理", operation = OperationType.INSERT, summary = "添加门店补货员")
-    @PostMapping("/{id}/replenishers")
-    public Result<Void> addReplenisher(@PathVariable Long id, @RequestBody AddReplenisherDTO dto) {
-        return shopReplenisherService.addReplenisher(id, dto.getAdminId());
-    }
-
-    /**
-     * 批量添加补货员
-     * @param id 门店ID
-     * @param dto 管理员ID列表参数
-     * @return 操作结果
-     */
-    @RequirePermission("shop:update")
-    @Log(module = "门店管理", operation = OperationType.INSERT, summary = "批量添加门店补货员")
-    @PostMapping("/{id}/replenishers/batch")
-    public Result<Map<String, Object>> batchAddReplenishers(@PathVariable Long id, @RequestBody BatchIdsDTO dto) {
-        int count = shopReplenisherService.batchAddReplenishers(id, dto.getIds());
-
-        Map<String, Object> result = new HashMap<>();
-        result.put("success", count);
-        result.put("total", dto.getIds().size());
-
-        return Result.success("批量添加完成", result);
-    }
-
-    /**
-     * 移除补货员
-     * @param id 门店ID
-     * @param adminId 管理员ID
-     * @return 操作结果
-     */
-    @RequirePermission("shop:update")
-    @Log(module = "门店管理", operation = OperationType.DELETE, summary = "移除门店补货员")
-    @DeleteMapping("/{id}/replenishers/{adminId}")
-    public Result<Void> removeReplenisher(@PathVariable Long id, @PathVariable Long adminId) {
-        return shopReplenisherService.removeReplenisher(id, adminId);
-    }
-
-    /**
-     * 批量移除补货员
-     * @param id 门店ID
-     * @param dto 管理员ID列表参数
-     * @return 操作结果
-     */
-    @RequirePermission("shop:update")
-    @Log(module = "门店管理", operation = OperationType.DELETE, summary = "批量移除门店补货员")
-    @DeleteMapping("/{id}/replenishers/batch")
-    public Result<Map<String, Object>> batchRemoveReplenishers(@PathVariable Long id, @RequestBody BatchIdsDTO dto) {
-        int count = shopReplenisherService.batchRemoveReplenishers(id, dto.getIds());
-
-        Map<String, Object> result = new HashMap<>();
-        result.put("success", count);
-        result.put("total", dto.getIds().size());
-
-        return Result.success("批量移除完成", result);
-    }
-
-    /**
-     * 更新补货员状态
-     * @param id 补货员关联ID
-     * @param dto 状态参数
-     * @return 操作结果
-     */
-    @RequirePermission("shop:update")
-    @Log(module = "门店管理", operation = OperationType.UPDATE, summary = "更新补货员状态")
-    @PutMapping("/replenishers/{id}/status")
-    public Result<Void> updateReplenisherStatus(@PathVariable Long id, @RequestBody StatusDTO dto) {
-        return shopReplenisherService.updateStatus(id, dto.getStatus());
-    }
-
-    /**
-     * 获取可选的管理员列表(用于添加补货员)
-     * @return 管理员列表
-     */
-    @RequirePermission("shop:read")
-    @GetMapping("/available-users")
-    public Result<List<Admin>> getAvailableUsers(@RequestParam(required = false) String keyword) {
-        List<Admin> admins = adminService.getAvailableAdmins(keyword);
-        return Result.success("查询成功", admins);
-    }
-
-    // ==================== V2 补货员管理(独立补货员体系) ====================
-
-    /**
-     * V2:获取门店关联的补货员列表(通过设备绑定反向推导)
+     * 获取门店关联的补货员列表(通过设备绑定反向推导)
      *
      * @param id 门店ID
      * @return 补货员列表(来自独立补货员表)
      */
     @RequirePermission("shop:read")
-    @GetMapping("/{id}/replenishers/v2")
-    public Result<List<Replenisher>> getReplenishersV2(@PathVariable Long id) {
-        List<Replenisher> replenishers = shopReplenisherService.getReplenishersByShopIdV2(id);
+    @GetMapping("/{id}/replenishers")
+    public Result<List<Replenisher>> getReplenishers(@PathVariable Long id) {
+        List<Replenisher> replenishers = replenisherService.getReplenishersByShopId(id);
         return Result.success("查询成功", replenishers);
     }
 
     /**
-     * V2:将独立补货员绑定到门店下的所有设备
+     * 将独立补货员绑定到门店下的所有设备
      *
      * @param id  门店ID
      * @param dto 补货员ID
      * @return 操作结果
      */
     @RequirePermission("shop:update")
-    @Log(module = "门店管理", operation = OperationType.INSERT, summary = "V2绑定补货员到门店")
-    @PostMapping("/{id}/replenishers/v2")
-    public Result<Void> addReplenisherV2(@PathVariable Long id, @RequestBody ReplenisherBindDTO dto) {
-        shopReplenisherService.addReplenisherToShop(id, dto.getReplenisherId());
+    @Log(module = "门店管理", operation = OperationType.INSERT, summary = "绑定补货员到门店")
+    @PostMapping("/{id}/replenishers")
+    public Result<Void> addReplenisherToShop(@PathVariable Long id, @RequestBody BindReplenisherDTO dto) {
+        // 查询门店下的所有设备
+        List<Device> devices = deviceService.lambdaQuery().eq(Device::getShopId, id).list();
+        if (devices.isEmpty()) {
+            return Result.error(400, "该门店下没有设备,无法绑定补货员");
+        }
+        List<String> deviceIds = devices.stream().map(Device::getDeviceId).collect(Collectors.toList());
+        replenisherService.bindDevices(dto.getReplenisherId(), deviceIds);
+        log.info("将补货员绑定到门店所有设备: replenisherId={}, shopId={}, deviceCount={}",
+                dto.getReplenisherId(), id, deviceIds.size());
         return Result.success("绑定成功");
     }
 }

+ 0 - 81
haha-entity/src/main/java/com/haha/entity/ShopReplenisher.java

@@ -1,81 +0,0 @@
-package com.haha.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableField;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableName;
-import lombok.Data;
-import java.io.Serializable;
-import java.time.LocalDateTime;
-
-/**
- * 门店补货员关联实体
- */
-@Data
-@TableName("t_shop_replenisher")
-public class ShopReplenisher implements Serializable {
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 主键ID
-     */
-    @TableId(type = IdType.ASSIGN_ID)
-    private Long id;
-
-    /**
-     * 门店ID
-     */
-    private Long shopId;
-
-    /**
-     * 管理员ID(补货员)
-     */
-    private Long adminId;
-
-    /**
-     * 状态:0-禁用,1-启用
-     */
-    private Integer status;
-
-    /**
-     * 创建时间
-     */
-    private LocalDateTime createTime;
-
-    /**
-     * 更新时间
-     */
-    private LocalDateTime updateTime;
-
-    // ========== 以下为非数据库字段,用于前端展示 ==========
-
-    /**
-     * 管理员姓名
-     */
-    @TableField(exist = false)
-    private String nickname;
-
-    /**
-     * 管理员手机号
-     */
-    @TableField(exist = false)
-    private String phone;
-
-    /**
-     * 管理员头像
-     */
-    @TableField(exist = false)
-    private String avatar;
-
-    /**
-     * 状态标签
-     */
-    @TableField(exist = false)
-    private String statusLabel;
-
-    /**
-     * 状态颜色
-     */
-    @TableField(exist = false)
-    private String statusColor;
-}

+ 0 - 15
haha-entity/src/main/java/com/haha/entity/dto/AddReplenisherDTO.java

@@ -1,15 +0,0 @@
-package com.haha.entity.dto;
-
-import lombok.Data;
-
-/**
- * 添加补货员DTO
- */
-@Data
-public class AddReplenisherDTO {
-
-    /**
-     * 管理员ID
-     */
-    private Long adminId;
-}

+ 17 - 0
haha-entity/src/main/java/com/haha/entity/dto/BindReplenisherDTO.java

@@ -0,0 +1,17 @@
+package com.haha.entity.dto;
+
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+/**
+ * 绑定补货员请求DTO
+ */
+@Data
+public class BindReplenisherDTO {
+
+    /**
+     * 补货员ID
+     */
+    @NotNull(message = "补货员ID不能为空")
+    private Long replenisherId;
+}

+ 0 - 40
haha-mapper/src/main/java/com/haha/mapper/ShopReplenisherMapper.java

@@ -1,40 +0,0 @@
-package com.haha.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.haha.entity.ShopReplenisher;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-import org.apache.ibatis.annotations.Select;
-
-import java.util.List;
-
-/**
- * 门店补货员关联Mapper
- */
-@Mapper
-public interface ShopReplenisherMapper extends BaseMapper<ShopReplenisher> {
-
-    /**
-     * 获取门店补货员列表(带管理员信息)
-     */
-    @Select("SELECT sr.*, a.real_name as nickname, a.phone, a.avatar " +
-            "FROM t_shop_replenisher sr " +
-            "LEFT JOIN t_admin a ON sr.admin_id = a.id " +
-            "WHERE sr.shop_id = #{shopId} " +
-            "ORDER BY sr.create_time DESC")
-    List<ShopReplenisher> selectByShopId(@Param("shopId") Long shopId);
-
-    /**
-     * 检查管理员是否已是某门店的补货员
-     */
-    @Select("SELECT COUNT(*) FROM t_shop_replenisher WHERE shop_id = #{shopId} AND admin_id = #{adminId}")
-    int countByShopIdAndAdminId(@Param("shopId") Long shopId, @Param("adminId") Long adminId);
-
-    /**
-     * 获取管理员关联的所有门店补货员记录
-     */
-    @Select("SELECT sr.*, s.name as shop_name FROM t_shop_replenisher sr " +
-            "LEFT JOIN t_shop s ON sr.shop_id = s.id " +
-            "WHERE sr.admin_id = #{adminId}")
-    List<ShopReplenisher> selectByAdminId(@Param("adminId") Long adminId);
-}

+ 0 - 87
haha-service/src/main/java/com/haha/service/ShopReplenisherService.java

@@ -1,87 +0,0 @@
-package com.haha.service;
-
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.haha.common.vo.Result;
-import com.haha.entity.Replenisher;
-import com.haha.entity.ShopReplenisher;
-
-import java.util.List;
-
-/**
- * 门店补货员服务接口
- */
-public interface ShopReplenisherService extends IService<ShopReplenisher> {
-
-    /**
-     * 获取门店补货员列表
-     * @param shopId 门店ID
-     * @return 补货员列表
-     */
-    List<ShopReplenisher> getReplenishersByShopId(Long shopId);
-
-    /**
-     * 添加补货员
-     * @param shopId 门店ID
-     * @param adminId 管理员ID
-     * @return 操作结果
-     */
-    Result<Void> addReplenisher(Long shopId, Long adminId);
-
-    /**
-     * 批量添加补货员
-     * @param shopId 门店ID
-     * @param adminIds 管理员ID列表
-     * @return 成功添加数量
-     */
-    int batchAddReplenishers(Long shopId, List<Long> adminIds);
-
-    /**
-     * 移除补货员
-     * @param shopId 门店ID
-     * @param adminId 管理员ID
-     * @return 操作结果
-     */
-    Result<Void> removeReplenisher(Long shopId, Long adminId);
-
-    /**
-     * 批量移除补货员
-     * @param shopId 门店ID
-     * @param adminIds 管理员ID列表
-     * @return 成功移除数量
-     */
-    int batchRemoveReplenishers(Long shopId, List<Long> adminIds);
-
-    /**
-     * 更新补货员状态
-     * @param id 补货员关联ID
-     * @param status 状态
-     * @return 操作结果
-     */
-    Result<Void> updateStatus(Long id, Integer status);
-
-    /**
-     * 检查管理员是否是某门店的补货员
-     * @param shopId 门店ID
-     * @param adminId 管理员ID
-     * @return 是否是补货员
-     */
-    boolean isReplenisher(Long shopId, Long adminId);
-
-    // ========== V2 方法:使用独立补货员体系(t_replenisher + t_replenisher_device) ==========
-
-    /**
-     * V2:获取门店关联的补货员列表(通过设备绑定反向推导)
-     *
-     * @param shopId 门店ID
-     * @return 补货员列表
-     */
-    List<Replenisher> getReplenishersByShopIdV2(Long shopId);
-
-    /**
-     * V2:将补货员绑定到门店的所有设备
-     *
-     * @param shopId        门店ID
-     * @param replenisherId 补货员ID
-     */
-    void addReplenisherToShop(Long shopId, Long replenisherId);
-}

+ 0 - 203
haha-service/src/main/java/com/haha/service/impl/ShopReplenisherServiceImpl.java

@@ -1,203 +0,0 @@
-package com.haha.service.impl;
-
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.haha.common.exception.BusinessException;
-import com.haha.common.vo.Result;
-import com.haha.entity.ShopReplenisher;
-import com.haha.entity.Admin;
-import com.haha.entity.Device;
-import com.haha.entity.Replenisher;
-import com.haha.mapper.ShopReplenisherMapper;
-import com.haha.service.AdminService;
-import com.haha.service.DeviceService;
-import com.haha.service.ReplenisherService;
-import com.haha.service.ShopReplenisherService;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 门店补货员服务实现类
- */
-@Slf4j
-@Service
-@RequiredArgsConstructor
-public class ShopReplenisherServiceImpl extends ServiceImpl<ShopReplenisherMapper, ShopReplenisher> implements ShopReplenisherService {
-
-    private final ShopReplenisherMapper shopReplenisherMapper;
-    private final AdminService adminService;
-    private final ReplenisherService replenisherService;
-    private final DeviceService deviceService;
-
-    @Override
-    public List<ShopReplenisher> getReplenishersByShopId(Long shopId) {
-        List<ShopReplenisher> list = shopReplenisherMapper.selectByShopId(shopId);
-        // 填充状态标签
-        for (ShopReplenisher sr : list) {
-            if (sr.getStatus() != null) {
-                if (sr.getStatus() == 1) {
-                    sr.setStatusLabel("正常");
-                    sr.setStatusColor("success");
-                } else {
-                    sr.setStatusLabel("禁用");
-                    sr.setStatusColor("danger");
-                }
-            }
-        }
-        return list;
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Result<Void> addReplenisher(Long shopId, Long adminId) {
-        // 检查管理员是否存在
-        Admin admin = adminService.getById(adminId);
-        if (admin == null) {
-            throw new BusinessException(404, "管理员不存在");
-        }
-
-        // 检查是否已存在
-        int count = shopReplenisherMapper.countByShopIdAndAdminId(shopId, adminId);
-        if (count > 0) {
-            throw new BusinessException(400, "该管理员已是此门店的补货员");
-        }
-
-        // 创建关联
-        ShopReplenisher replenisher = new ShopReplenisher();
-        replenisher.setShopId(shopId);
-        replenisher.setAdminId(adminId);
-        replenisher.setStatus(1);
-        replenisher.setCreateTime(LocalDateTime.now());
-        replenisher.setUpdateTime(LocalDateTime.now());
-
-        boolean success = this.save(replenisher);
-        return success ? Result.success("添加成功") : Result.error(500, "添加失败");
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public int batchAddReplenishers(Long shopId, List<Long> adminIds) {
-        if (adminIds == null || adminIds.isEmpty()) {
-            return 0;
-        }
-        
-        List<ShopReplenisher> toAdd = new ArrayList<>();
-        LocalDateTime now = LocalDateTime.now();
-        
-        for (Long adminId : adminIds) {
-            // 检查管理员是否存在
-            Admin admin = adminService.getById(adminId);
-            if (admin == null) {
-                log.warn("管理员不存在: adminId={}", adminId);
-                continue;
-            }
-            
-            // 检查是否已存在
-            int count = shopReplenisherMapper.countByShopIdAndAdminId(shopId, adminId);
-            if (count > 0) {
-                log.warn("该管理员已是此门店的补货员: shopId={}, adminId={}", shopId, adminId);
-                continue;
-            }
-            
-            ShopReplenisher replenisher = new ShopReplenisher();
-            replenisher.setShopId(shopId);
-            replenisher.setAdminId(adminId);
-            replenisher.setStatus(1);
-            replenisher.setCreateTime(now);
-            replenisher.setUpdateTime(now);
-            toAdd.add(replenisher);
-        }
-        
-        if (!toAdd.isEmpty()) {
-            this.saveBatch(toAdd);
-        }
-        
-        log.info("批量添加补货员完成: shopId={}, 总数={}, 成功={}", shopId, adminIds.size(), toAdd.size());
-        return toAdd.size();
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Result<Void> removeReplenisher(Long shopId, Long adminId) {
-        LambdaQueryWrapper<ShopReplenisher> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(ShopReplenisher::getShopId, shopId)
-                .eq(ShopReplenisher::getAdminId, adminId);
-
-        boolean success = this.remove(wrapper);
-        return success ? Result.success("移除成功") : Result.error(500, "移除失败");
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public int batchRemoveReplenishers(Long shopId, List<Long> adminIds) {
-        if (adminIds == null || adminIds.isEmpty()) {
-            return 0;
-        }
-
-        LambdaQueryWrapper<ShopReplenisher> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(ShopReplenisher::getShopId, shopId)
-                .in(ShopReplenisher::getAdminId, adminIds);
-
-        long count = this.count(wrapper);
-        this.remove(wrapper);
-        
-        log.info("批量移除补货员完成: shopId={}, 总数={}, 成功={}", shopId, adminIds.size(), count);
-        return (int) count;
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Result<Void> updateStatus(Long id, Integer status) {
-        ShopReplenisher replenisher = this.getById(id);
-        if (replenisher == null) {
-            throw new BusinessException(404, "记录不存在");
-        }
-
-        replenisher.setStatus(status);
-        replenisher.setUpdateTime(LocalDateTime.now());
-
-        boolean success = this.updateById(replenisher);
-        return success ? Result.success("状态更新成功") : Result.error(500, "状态更新失败");
-    }
-
-    @Override
-    public boolean isReplenisher(Long shopId, Long adminId) {
-        return shopReplenisherMapper.countByShopIdAndAdminId(shopId, adminId) > 0;
-    }
-
-    // ========== V2 方法实现 ==========
-
-    @Override
-    public List<Replenisher> getReplenishersByShopIdV2(Long shopId) {
-        return replenisherService.getReplenishersByShopId(shopId);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void addReplenisherToShop(Long shopId, Long replenisherId) {
-        // 检查补货员是否存在
-        Replenisher replenisher = replenisherService.getById(replenisherId);
-        if (replenisher == null) {
-            throw new BusinessException(404, "补货员不存在");
-        }
-
-        // 查询门店下的所有设备
-        List<Device> devices = deviceService.lambdaQuery().eq(Device::getShopId, shopId).list();
-        if (devices.isEmpty()) {
-            throw new BusinessException(400, "该门店下没有设备,无法绑定补货员");
-        }
-
-        List<String> deviceIds = devices.stream().map(Device::getDeviceId).collect(Collectors.toList());
-        int count = replenisherService.bindDevices(replenisherId, deviceIds);
-
-        log.info("将补货员绑定到门店所有设备: replenisherId={}, shopId={}, deviceCount={}, boundCount={}",
-                replenisherId, shopId, deviceIds.size(), count);
-    }
-}