zhou zhou
13 小时以前 2f8e173048d22c5b40612c3538b9c1aa5a5397f6
#乐观锁
7个文件已添加
30个文件已修改
1570 ■■■■ 已修改文件
docs/superpowers/plans/2026-03-26-menu-tree-redis-cache.md 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/OptimisticLockUtils.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/CheckDiffItemController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/CheckOrderController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocPreviewController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/MatPreparationController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/TaskController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java 35 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WkOrderController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Loc.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Task.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaitPakin.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WkOrder.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/PakinSchedules.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/ScheduleJobs.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/AsnOrderItemServiceImpl.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/AsnOrderServiceImpl.java 34 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/CheckOrderServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocPreviewServiceImpl.java 51 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocServiceImpl.java 55 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockItemServiceImpl.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java 382 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/test/java/com/vincent/rsf/server/common/utils/OptimisticLockUtilsTest.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/test/java/com/vincent/rsf/server/manager/mapper/MybatisPlusOptimisticLockIntegrationTest.java 474 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/test/java/com/vincent/rsf/server/system/controller/MenuControllerTest.java 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-server/src/test/java/com/vincent/rsf/server/system/entity/MenuJsonTest.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
version/db/20260327_optimistic_lock.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/superpowers/plans/2026-03-26-menu-tree-redis-cache.md
New file
@@ -0,0 +1,55 @@
# Menu Tree Redis Cache Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add Redis caching for `/menu/tree` without changing the endpoint contract.
**Architecture:** Cache the full menu tree in Redis behind a single stable key, then apply the existing `condition` filter in memory on the cached tree result. Evict the cache only after successful menu save, update, and remove operations so reads stay fast and behavior stays aligned with the database.
**Tech Stack:** Spring Boot, MyBatis-Plus, Jedis-based `RedisService`, JUnit 5, Mockito
---
### Task 1: Lock behavior with controller tests
**Files:**
- Create: `rsf-server/src/test/java/com/vincent/rsf/server/system/controller/MenuControllerTest.java`
- Modify: `rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MenuController.java`
- [ ] **Step 1: Write the failing test**
```java
@Test
void treeReturnsCachedTreeBeforeQueryingDatabase() {
    // arrange cached tree
    // call controller.tree(...)
    // assert menuService.list(...) is never called
}
```
- [ ] **Step 2: Run test to verify it fails**
Run: `mvn -pl rsf-server -Dtest=MenuControllerTest test`
Expected: FAIL because `MenuController` does not use Redis for `/menu/tree`
- [ ] **Step 3: Write minimal implementation**
```java
List<Menu> treeData = redisService.get(MENU_TREE_CACHE_FLAG, MENU_TREE_CACHE_KEY);
if (treeData == null) {
    treeData = loadAndBuildTree();
    redisService.set(MENU_TREE_CACHE_FLAG, MENU_TREE_CACHE_KEY, treeData, MENU_TREE_CACHE_TTL_SECONDS);
}
```
- [ ] **Step 4: Run test to verify it passes**
Run: `mvn -pl rsf-server -Dtest=MenuControllerTest test`
Expected: PASS
- [ ] **Step 5: Commit**
```bash
git add docs/superpowers/plans/2026-03-26-menu-tree-redis-cache.md rsf-server/src/main/java/com/vincent/rsf/server/system/controller/MenuController.java rsf-server/src/test/java/com/vincent/rsf/server/system/controller/MenuControllerTest.java
git commit -m "feat: cache menu tree in redis"
```
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/InBoundServiceImpl.java
@@ -275,8 +275,17 @@
            if (!basStationService.updateById(station)) {
                throw new CoolException("站点状态更新失败!!");
            }
            if (!locService.update(new LambdaUpdateWrapper<Loc>().eq(Loc::getCode, task.getTargLoc())
                    .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type).set(Loc::getBarcode, param.getContainerNo()))) {
            Loc taskLoc = locService.getOne(new LambdaQueryWrapper<Loc>()
                    .eq(Loc::getCode, task.getTargLoc())
                    .last("limit 1"), false);
            if (Objects.isNull(taskLoc)) {
                throw new CoolException("库位预约失败!!");
            }
            taskLoc.setUseStatus(LocStsType.LOC_STS_TYPE_S.type)
                    .setBarcode(param.getContainerNo())
                    .setUpdateBy(loginUserId)
                    .setUpdateTime(new Date());
            if (!locService.updateById(taskLoc)) {
                throw new CoolException("库位预约失败!!");
            }
            return R.ok("任务生成完毕!");
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReceiveMsgServiceImpl.java
@@ -980,9 +980,8 @@
                    }
                } else {
                    Double sum = orderItems.stream().mapToDouble(WkOrderItem::getAnfme).sum();
                    if (!asnOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                            .eq(WkOrder::getId, wkOrder.getId())
                            .set(WkOrder::getAnfme, sum))) {
                    wkOrder.setAnfme(sum);
                    if (!asnOrderService.updateById(wkOrder)) {
                        throw new CoolException("主单数量修改失败!!");
                    }
                }
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/ReportMsgServiceImpl.java
@@ -161,14 +161,17 @@
            Map<Long, List<WkOrderItem>> asnIds = wkOrderItems.stream().collect(Collectors.groupingBy(WkOrderItem::getOrderId));
            ids.forEach(id -> {
                long count = Optional.ofNullable(asnOrderMapper.selectCount(new LambdaQueryWrapper<WkOrder>().in(WkOrder::getId, id))).orElse(0L);
                WkOrder order = asnOrderMapper.selectById(id);
                if (Objects.isNull(order)) {
                    throw new CoolException("ASN主单状态修改失败!!");
                }
                if (count == asnIds.get(id).size()) {
                    if (asnOrderMapper.update(null, new LambdaUpdateWrapper<WkOrder>().eq(WkOrder::getId, id).set(WkOrder::getNtyStatus, 1)) <= 0) {
                        throw new CoolException("ASN主单状态修改失败!!");
                    }
                    order.setNtyStatus(1);
                } else {
                    if (asnOrderMapper.update(null, new LambdaUpdateWrapper<WkOrder>().eq(WkOrder::getId, id).set(WkOrder::getNtyStatus, 2)) <= 0) {
                        throw new CoolException("ASN主单状态修改失败!!");
                    order.setNtyStatus(2);
                    }
                if (asnOrderMapper.updateById(order) <= 0) {
                    throw new CoolException("ASN主单状态修改失败!!");
                }
            });
            return true;
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -234,10 +234,17 @@
     * 更新库位状态
     */
    private void updateLocStatus(String locCode, String barcode) {
        boolean updated = locService.update(new LambdaUpdateWrapper<Loc>()
                .eq(Loc::getCode, locCode)
                .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type)
                .set(Loc::getBarcode, barcode));
        Loc current = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, locCode));
        if (Objects.isNull(current)) {
            throw new CoolException("库位预约失败!!");
        }
        Loc update = new Loc();
        update.setId(current.getId());
        update.setVersion(current.getVersion());
        update.setUseStatus(LocStsType.LOC_STS_TYPE_S.type);
        update.setBarcode(barcode);
        boolean updated = locService.update(update, new LambdaUpdateWrapper<Loc>()
                .eq(Loc::getId, current.getId()));
        if (!updated) {
            throw new CoolException("库位预约失败!!");
        }
@@ -284,11 +291,19 @@
     * 更新组托状态
     */
    private void updateWaitPakinStatus(String barcode, Long loginUserId) {
        boolean updated = waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                .eq(WaitPakin::getBarcode, barcode)
                .set(WaitPakin::getUpdateBy, loginUserId)
                .set(WaitPakin::getCreateBy, loginUserId)
                .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val));
        WaitPakin current = waitPakinService.getOne(new LambdaQueryWrapper<WaitPakin>()
                .eq(WaitPakin::getBarcode, barcode));
        if (Objects.isNull(current)) {
            throw new CoolException("组托状态修改失败!!");
        }
        WaitPakin update = new WaitPakin();
        update.setId(current.getId());
        update.setVersion(current.getVersion());
        update.setUpdateBy(loginUserId);
        update.setCreateBy(loginUserId);
        update.setIoStatus(PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val);
        boolean updated = waitPakinService.update(update, new LambdaUpdateWrapper<WaitPakin>()
                .eq(WaitPakin::getId, current.getId()));
        if (!updated) {
            throw new CoolException("组托状态修改失败!!");
rsf-server/src/main/java/com/vincent/rsf/server/common/utils/OptimisticLockUtils.java
New file
@@ -0,0 +1,15 @@
package com.vincent.rsf.server.common.utils;
import com.vincent.rsf.framework.exception.CoolException;
public final class OptimisticLockUtils {
    private OptimisticLockUtils() {
    }
    public static void requireVersion(String dataName, Integer version) {
        if (version == null) {
            throw new CoolException(dataName + "版本号不能为空,请刷新后重试!!");
        }
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/CheckDiffItemController.java
@@ -102,9 +102,12 @@
                // 仅在有单时更新盘点单(WkOrder)执行状态;无单盘点无盘点单,不更新
                CheckDiff checkDiff = checkDiffService.getById(checkDiffItem.getCheckId());
                if (checkDiff != null && checkDiff.getOrderId() != null) {
                    if (!checkOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                            .eq(WkOrder::getId, checkDiff.getOrderId())
                            .set(WkOrder::getExceStatus, CheckExceStatus.CHECK_ORDER_STATUS_EXCE_DONE.val))) {
                    WkOrder order = checkOrderService.getById(checkDiff.getOrderId());
                    if (order == null) {
                        throw new CoolException("盘点单不存在!!");
                    }
                    order.setExceStatus(CheckExceStatus.CHECK_ORDER_STATUS_EXCE_DONE.val);
                    if (!checkOrderService.updateById(order)) {
                        throw new CoolException("盘点单执行状态修改失败!!");
                    }
                }
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/CheckOrderController.java
@@ -11,6 +11,7 @@
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.ExcelUtil;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.manager.controller.params.*;
import com.vincent.rsf.server.manager.entity.WkOrder;
import com.vincent.rsf.server.manager.entity.WkOrderItem;
@@ -86,6 +87,7 @@
    @OperationLog("Update 字典数据集")
    @PostMapping("/check/update")
    public R update(@RequestBody WkOrder order) {
        OptimisticLockUtils.requireVersion("盘点单", order.getVersion());
        order.setUpdateTime(null);
        order.setUpdateBy(getLoginUserId());
        if (!checkOrderService.updateById(order)) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocController.java
@@ -10,6 +10,7 @@
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.manager.controller.params.LocMastInitParam;
import com.vincent.rsf.server.manager.controller.params.LocModifyParams;
import com.vincent.rsf.server.manager.entity.Loc;
@@ -164,6 +165,7 @@
    @PostMapping("/loc/update")
    @Transactional(rollbackFor = Exception.class)
    public R update(@RequestBody Loc loc) {
        OptimisticLockUtils.requireVersion("库位", loc.getVersion());
        loc.setUpdateBy(getLoginUserId());
        String join = StringUtils.join(loc.getTypeIds(), ",");
        if (!Objects.isNull(loc.getTypeIds())) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/LocPreviewController.java
@@ -10,6 +10,7 @@
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.ExcelUtil;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.manager.controller.params.LocModifyParams;
import com.vincent.rsf.server.manager.entity.Loc;
import com.vincent.rsf.server.manager.enums.LocStsType;
@@ -112,6 +113,7 @@
    @PostMapping("/locPreview/update")
    @Transactional(rollbackFor = Exception.class)
    public R update(@RequestBody Loc loc) {
        OptimisticLockUtils.requireVersion("库位", loc.getVersion());
        loc.setUpdateBy(getLoginUserId());
        String join = StringUtils.join(loc.getTypeIds(), ",");
        loc.setType(join);
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/MatPreparationController.java
@@ -15,6 +15,7 @@
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.ExcelUtil;
import com.vincent.rsf.server.common.utils.FieldsUtils;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.manager.controller.params.AsnOrderAndItemsParams;
import com.vincent.rsf.server.manager.controller.params.GenWaveParams;
import com.vincent.rsf.server.manager.controller.params.OrderOutTaskParam;
@@ -120,6 +121,7 @@
    @PostMapping("/preparation/update")
    @ApiOperation("更新")
    public R update(@RequestBody WkOrder wkOrder) {
        OptimisticLockUtils.requireVersion("备料单", wkOrder.getVersion());
        wkOrder.setType(OrderType.ORDER_OUT.type)
                .setUpdateBy(getLoginUserId())
                .setUpdateTime(new Date());
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/OutStockController.java
@@ -22,6 +22,7 @@
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.ExcelUtil;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.manager.controller.params.AsnOrderAndItemsParams;
import com.vincent.rsf.server.manager.entity.excel.AsnOrderTemplate;
import com.vincent.rsf.server.manager.enums.AsnExceStatus;
@@ -119,6 +120,7 @@
    @PostMapping("/outStock/update")
    @ApiOperation("更新")
    public R update(@RequestBody WkOrder wkOrder) {
        OptimisticLockUtils.requireVersion("出库单", wkOrder.getVersion());
        wkOrder.setType(OrderType.ORDER_OUT.type)
                .setUpdateBy(getLoginUserId())
                .setUpdateTime(new Date());
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/TaskController.java
@@ -15,6 +15,7 @@
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.manager.service.*;
import com.vincent.rsf.server.system.controller.BaseController;
import com.vincent.rsf.server.system.service.impl.ConfigServiceImpl;
@@ -89,6 +90,7 @@
    @OperationLog("Update 任务工作档")
    @PostMapping("/task/update")
    public R update(@RequestBody Task task) {
        OptimisticLockUtils.requireVersion("任务", task.getVersion());
        task.setUpdateBy(getLoginUserId());
        task.setUpdateTime(new Date());
        if (!taskService.updateById(task)) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WaveController.java
@@ -9,6 +9,7 @@
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.common.domain.PageResult;
import com.vincent.rsf.server.common.utils.ExcelUtil;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.common.annotation.OperationLog;
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.KeyValVo;
@@ -91,6 +92,7 @@
    @OperationLog("Update 波次单据")
    @PostMapping("/wave/update")
    public R update(@RequestBody Wave wave) {
        OptimisticLockUtils.requireVersion("波次", wave.getVersion());
        wave.setUpdateBy(getLoginUserId());
        wave.setUpdateTime(new Date());
        if (!waveService.updateById(wave)) {
@@ -172,9 +174,14 @@
    @ApiOperation("暂停下发任务")
    @PostMapping("/wave/pause/pub/{id}")
    public R pausePublicTask(@PathVariable Long id) {
        waveService.update(new LambdaUpdateWrapper<Wave>()
                .eq(Wave::getId, id)
                .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_PAUSE.val));
        Wave wave = waveService.getById(id);
        if (Objects.isNull(wave)) {
            throw new CoolException("波次数据不存在!!");
        }
        wave.setExceStatus(WaveExceStatus.WAVE_EXCE_STATUS_PAUSE.val);
        if (!waveService.updateById(wave)) {
            throw new CoolException("波次状态修改失败!!");
        }
        return R.ok();
    }
@@ -182,9 +189,14 @@
    @ApiOperation("继续下发任务")
    @PostMapping("/wave/continue/pub/{id}")
    public R continuePublicTask(@PathVariable Long id) {
        waveService.update(new LambdaUpdateWrapper<Wave>()
                .eq(Wave::getId, id)
                .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_EXCING.val));
        Wave wave = waveService.getById(id);
        if (Objects.isNull(wave)) {
            throw new CoolException("波次数据不存在!!");
        }
        wave.setExceStatus(WaveExceStatus.WAVE_EXCE_STATUS_EXCING.val);
        if (!waveService.updateById(wave)) {
            throw new CoolException("波次状态修改失败!!");
        }
        return R.ok();
    }
@@ -196,9 +208,14 @@
        if (Objects.isNull(id)) {
            return R.error("参数不能为空!!");
        }
        waveService.update(new LambdaUpdateWrapper<Wave>()
                .eq(Wave::getId, id)
                .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_PAUSE.val));
        Wave wave = waveService.getById(id);
        if (Objects.isNull(wave)) {
            throw new CoolException("波次数据不存在!!");
        }
        wave.setExceStatus(WaveExceStatus.WAVE_EXCE_STATUS_PAUSE.val);
        if (!waveService.updateById(wave)) {
            throw new CoolException("波次状态修改失败!!");
        }
        return waveService.stopPubTask(id);
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/controller/WkOrderController.java
@@ -16,6 +16,7 @@
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.KeyValVo;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.common.utils.OptimisticLockUtils;
import com.vincent.rsf.server.manager.controller.params.AsnOrderAndItemsParams;
import com.vincent.rsf.server.manager.controller.params.BatchUpdateParam;
import com.vincent.rsf.server.manager.entity.excel.AsnOrderTemplate;
@@ -146,6 +147,7 @@
    @PostMapping("/asnOrder/update")
    @ApiOperation("更新")
    public R update(@RequestBody WkOrder wkOrder) {
        OptimisticLockUtils.requireVersion("ASN单据", wkOrder.getVersion());
        wkOrder.setUpdateBy(getLoginUserId());
        wkOrder.setUpdateTime(new Date());
        if (!asnOrderService.updateById(wkOrder)) {
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Loc.java
@@ -218,6 +218,9 @@
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date updateTime;
    @Version
    private Integer version;
    /**
     * 备注
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Task.java
@@ -198,6 +198,9 @@
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date updateTime;
    @Version
    private Integer version;
    /**
     * 备注
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WaitPakin.java
@@ -18,6 +18,7 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -120,6 +121,9 @@
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date updateTime;
    @Version
    private Integer version;
    /**
     * 备注
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/Wave.java
@@ -18,6 +18,7 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@@ -135,6 +136,9 @@
    @TableLogic
    private Integer deleted;
    @Version
    private Integer version;
    /**
     * 备注
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/entity/WkOrder.java
@@ -20,6 +20,7 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import com.vincent.rsf.framework.common.Cools;
@@ -172,6 +173,9 @@
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
    @Version
    private Integer version;
    /**
     * 备注
     */
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/PakinSchedules.java
@@ -15,6 +15,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Component
@@ -79,9 +80,12 @@
                }
                Double sum1 = waitPakinItems.stream().mapToDouble(WaitPakinItem::getAnfme).sum();
                if (sum.compareTo(sum1) == 0) {
                    if (!asnOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                            .eq(WkOrder::getCode, item)
                            .set(WkOrder::getExceStatus, AsnExceStatus.ASN_EXCE_STATUS_TASK_DONE.val))) {
                    WkOrder wkOrder = asnOrderService.getOne(new LambdaQueryWrapper<WkOrder>().eq(WkOrder::getCode, item), false);
                    if (Objects.isNull(wkOrder)) {
                        throw new CoolException("单据不存在!!");
                    }
                    wkOrder.setExceStatus(AsnExceStatus.ASN_EXCE_STATUS_TASK_DONE.val);
                    if (!asnOrderService.updateById(wkOrder)) {
                        throw new CoolException("单据修改失败!!");
                    }
                }
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/ScheduleJobs.java
@@ -135,10 +135,13 @@
//                    throw new CoolException("收货单保存至收货区执行失败!!");
//                }
                if (!asnOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                        .set(WkOrder::getQty, order.getAnfme())
                        .set(WkOrder::getExceStatus, AsnExceStatus.ASN_EXCE_STATUS_RECEIPT_DONE.val)
                        .eq(WkOrder::getId, order.getId()))) {
                WkOrder currentOrder = asnOrderService.getById(order.getId());
                if (Objects.isNull(currentOrder)) {
                    throw new CoolException("收货单不存在!!");
                }
                currentOrder.setQty(order.getAnfme())
                        .setExceStatus(AsnExceStatus.ASN_EXCE_STATUS_RECEIPT_DONE.val);
                if (!asnOrderService.updateById(currentOrder)) {
                    throw new CoolException("收货单状态修改失败!!");
                }
            }
rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/TaskSchedules.java
@@ -941,8 +941,8 @@
                            || task.getTaskType().equals(TaskType.TASK_TYPE_EMPTY_IN.type)
                            || task.getTaskType().equals(TaskType.TASK_TYPE_MERGE_IN.type)
                            || task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                        if (!taskService.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                                .set(Task::getTaskStatus, TaskStsType.WCS_EXECUTE_IN.id))) {
                        task.setTaskStatus(TaskStsType.WCS_EXECUTE_IN.id);
                        if (!taskService.updateById(task)) {
                            throw new CoolException("任务状态修改失败!!");
                        }
                        /**排除移库功能*/
@@ -962,8 +962,8 @@
                            || task.getTaskType().equals(TaskType.TASK_TYPE_MERGE_OUT.type)
                            || task.getTaskType().equals(TaskType.TASK_TYPE_EMPTY_OUT.type)
                            || task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)) {
                        if (!taskService.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                                .set(Task::getTaskStatus, TaskStsType.WCS_EXECUTE_OUT.id))) {
                        task.setTaskStatus(TaskStsType.WCS_EXECUTE_OUT.id);
                        if (!taskService.updateById(task)) {
                            throw new CoolException("任务状态修改失败!!");
                        }
                        /**如果是普通站点,修改站点状态为入库预约*/
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/AsnOrderItemServiceImpl.java
@@ -202,10 +202,12 @@
            if (!items.isEmpty()) {
//                double qty = items.stream().mapToDouble(AsnOrderItem::getQty).sum();
                double anfme = items.stream().mapToDouble(WkOrderItem::getAnfme).sum();
                if (asnOrderMapper.update(null, new LambdaUpdateWrapper<WkOrder>()
//                        .set(AsnOrder::getQty, qty)
                        .set(WkOrder::getAnfme, anfme)
                        .eq(WkOrder::getId, order.getId())) <= 0) {
                WkOrder currentOrder = asnOrderMapper.selectById(order.getId());
                if (Objects.isNull(currentOrder)) {
                    throw new CoolException("单据数量修改失败!!");
                }
                currentOrder.setAnfme(anfme);
                if (asnOrderMapper.updateById(currentOrder) <= 0) {
                    throw new CoolException("单据数量修改失败!!");
                }
            }
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/AsnOrderServiceImpl.java
@@ -203,14 +203,32 @@
        if (Objects.isNull(order)) {
            throw new CoolException("修改参数不能为空!!");
        }
        return this.update(new LambdaUpdateWrapper<WkOrder>()
                .in(WkOrder::getId, params.getIds())
                .set(!Objects.isNull(order.getRleStatus()), WkOrder::getRleStatus, order.getRleStatus())
                .set(!Objects.isNull(order.getNtyStatus()), WkOrder::getNtyStatus, order.getNtyStatus())
                .set(!Objects.isNull(order.getStatus()), WkOrder::getStatus, order.getStatus())
                .set(!Objects.isNull(order.getWkType()), WkOrder::getWkType, order.getWkType())
                .set(!Objects.isNull(order.getExceStatus()), WkOrder::getExceStatus, order.getExceStatus())
                .set(WkOrder::getUpdateBy, userId));
        List<WkOrder> orders = this.listByIds(params.getIds());
        if (orders.isEmpty()) {
            return false;
        }
        for (WkOrder current : orders) {
            if (!Objects.isNull(order.getRleStatus())) {
                current.setRleStatus(order.getRleStatus());
            }
            if (!Objects.isNull(order.getNtyStatus())) {
                current.setNtyStatus(order.getNtyStatus());
            }
            if (!Objects.isNull(order.getStatus())) {
                current.setStatus(order.getStatus());
            }
            if (!Objects.isNull(order.getWkType())) {
                current.setWkType(order.getWkType());
            }
            if (!Objects.isNull(order.getExceStatus())) {
                current.setExceStatus(order.getExceStatus());
            }
            current.setUpdateBy(userId);
            if (!this.updateById(current)) {
                throw new CoolException("批量修改失败!!");
            }
        }
        return true;
    }
    /**
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/CheckOrderServiceImpl.java
@@ -116,9 +116,13 @@
            }
            if (!items.isEmpty()) {
                double purQty = items.stream().mapToDouble(WkOrderItem::getAnfme).sum();
                if (!this.update(new LambdaUpdateWrapper<WkOrder>()
                        .set(WkOrder::getExceStatus, CheckExceStatus.CHECK_ORDER_STATUS_UN_EXCE.val)
                        .set(WkOrder::getAnfme, purQty).eq(WkOrder::getId, order.getId()))) {
                WkOrder savedOrder = this.getById(order.getId());
                if (Objects.isNull(savedOrder)) {
                    throw new CoolException("单据不存在!!");
                }
                savedOrder.setExceStatus(CheckExceStatus.CHECK_ORDER_STATUS_UN_EXCE.val)
                        .setAnfme(purQty);
                if (!this.updateById(savedOrder)) {
                    throw new CoolException("单据数量修改失败!!");
                }
            }
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocPreviewServiceImpl.java
@@ -55,22 +55,47 @@
        if (!Objects.isNull(loc.getTypeIds()) && !loc.getTypeIds().isEmpty()) {
             join = StringUtils.join(loc.getTypeIds(), ",");
        }
        boolean update = this.update(new LambdaUpdateWrapper<Loc>()
        List<Loc> existsLocs = this.list(new LambdaQueryWrapper<Loc>()
                .in(Loc::getId, locs.getId())
                .eq(Loc::getStatus, 1)
                .set(!Objects.isNull(loc.getAreaId()), Loc::getAreaId, loc.getAreaId())
                .set(!Objects.isNull(loc.getWarehouseId()), Loc::getWarehouseId, loc.getWarehouseId())
                .set(!Objects.isNull(loc.getUseStatus()), Loc::getUseStatus, loc.getUseStatus())
                .set(!Objects.isNull(loc.getTypeIds()) && !loc.getTypeIds().isEmpty(), Loc::getType, join)
                .set(!Objects.isNull(loc.getLength()), Loc::getLength, loc.getLength())
                .set(!Objects.isNull(loc.getWidth()), Loc::getWidth, loc.getWidth())
                .set(!Objects.isNull(loc.getHeight()), Loc::getHeight, loc.getHeight())
                .set(!Objects.isNull(loc.getChannel()), Loc::getChannel, loc.getChannel())
                .set(!Objects.isNull(loc.getFlagLabelMange()), Loc::getFlagLabelMange, loc.getFlagLabelMange())
                .set(!Objects.isNull(loc.getStatus()), Loc::getStatus, loc.getStatus()));
        if (!update) {
                .eq(Loc::getStatus, 1));
        if (existsLocs.isEmpty()) {
            throw new CoolException("库位信息修改失败!!");
        }
        for (Loc currentLoc : existsLocs) {
            if (!Objects.isNull(loc.getAreaId())) {
                currentLoc.setAreaId(loc.getAreaId());
            }
            if (!Objects.isNull(loc.getWarehouseId())) {
                currentLoc.setWarehouseId(loc.getWarehouseId());
            }
            if (!Objects.isNull(loc.getUseStatus())) {
                currentLoc.setUseStatus(loc.getUseStatus());
            }
            if (!Objects.isNull(loc.getTypeIds()) && !loc.getTypeIds().isEmpty()) {
                currentLoc.setType(join);
            }
            if (!Objects.isNull(loc.getLength())) {
                currentLoc.setLength(loc.getLength());
            }
            if (!Objects.isNull(loc.getWidth())) {
                currentLoc.setWidth(loc.getWidth());
            }
            if (!Objects.isNull(loc.getHeight())) {
                currentLoc.setHeight(loc.getHeight());
            }
            if (!Objects.isNull(loc.getChannel())) {
                currentLoc.setChannel(loc.getChannel());
            }
            if (!Objects.isNull(loc.getFlagLabelMange())) {
                currentLoc.setFlagLabelMange(loc.getFlagLabelMange());
            }
            if (!Objects.isNull(loc.getStatus())) {
                currentLoc.setStatus(loc.getStatus());
            }
            if (!this.updateById(currentLoc)) {
                throw new CoolException("库位信息修改失败!!");
            }
        }
        return R.ok(loc);
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocServiceImpl.java
@@ -57,23 +57,50 @@
        if (!Objects.isNull(loc.getTypeIds()) && !loc.getTypeIds().isEmpty()) {
            join = StringUtils.join(loc.getTypeIds(), ",");
        }
        boolean update = this.update(new LambdaUpdateWrapper<Loc>()
        List<Loc> existsLocs = this.list(new LambdaQueryWrapper<Loc>()
                .in(Loc::getId, locs.getId())
                .eq(Loc::getStatus, 1)
                .set(!Objects.isNull(loc.getAreaId()), Loc::getAreaId, loc.getAreaId())
                .set(!Objects.isNull(loc.getWarehouseId()), Loc::getWarehouseId, loc.getWarehouseId())
                .set(!Objects.isNull(loc.getUseStatus()), Loc::getUseStatus, loc.getUseStatus())
                .set(!Objects.isNull(loc.getTypeIds()) && !loc.getTypeIds().isEmpty(), Loc::getType, join)
                .set(!Objects.isNull(loc.getLength()), Loc::getLength, loc.getLength())
                .set(!Objects.isNull(loc.getWidth()), Loc::getWidth, loc.getWidth())
                .set(!Objects.isNull(loc.getHeight()), Loc::getHeight, loc.getHeight())
                .set(!Objects.isNull(loc.getChannel()), Loc::getChannel, loc.getChannel())
                .set(!Objects.isNull(loc.getFlagLabelMange()), Loc::getFlagLabelMange, loc.getFlagLabelMange())
                .set(loc.getStatus().equals(CommonStatus.COMMONSTATUS_NO.val), Loc::getUseStatus, LocStsType.LOC_STS_TYPE_X.type)
                .set(!Objects.isNull(loc.getStatus()), Loc::getStatus, loc.getStatus()));
        if (!update) {
                .eq(Loc::getStatus, 1));
        if (existsLocs.isEmpty()) {
            throw new CoolException("库位信息修改失败!!");
        }
        for (Loc currentLoc : existsLocs) {
            if (!Objects.isNull(loc.getAreaId())) {
                currentLoc.setAreaId(loc.getAreaId());
            }
            if (!Objects.isNull(loc.getWarehouseId())) {
                currentLoc.setWarehouseId(loc.getWarehouseId());
            }
            if (!Objects.isNull(loc.getUseStatus())) {
                currentLoc.setUseStatus(loc.getUseStatus());
            }
            if (!Objects.isNull(loc.getTypeIds()) && !loc.getTypeIds().isEmpty()) {
                currentLoc.setType(join);
            }
            if (!Objects.isNull(loc.getLength())) {
                currentLoc.setLength(loc.getLength());
            }
            if (!Objects.isNull(loc.getWidth())) {
                currentLoc.setWidth(loc.getWidth());
            }
            if (!Objects.isNull(loc.getHeight())) {
                currentLoc.setHeight(loc.getHeight());
            }
            if (!Objects.isNull(loc.getChannel())) {
                currentLoc.setChannel(loc.getChannel());
            }
            if (!Objects.isNull(loc.getFlagLabelMange())) {
                currentLoc.setFlagLabelMange(loc.getFlagLabelMange());
            }
            if (!Objects.isNull(loc.getStatus()) && loc.getStatus().equals(CommonStatus.COMMONSTATUS_NO.val)) {
                currentLoc.setUseStatus(LocStsType.LOC_STS_TYPE_X.type);
            }
            if (!Objects.isNull(loc.getStatus())) {
                currentLoc.setStatus(loc.getStatus());
            }
            if (!this.updateById(currentLoc)) {
                throw new CoolException("库位信息修改失败!!");
            }
        }
        return R.ok(loc);
    }
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockItemServiceImpl.java
@@ -147,9 +147,13 @@
            }
            if (!items.isEmpty()) {
                double purQty = items.stream().mapToDouble(WkOrderItem::getAnfme).sum();
                if (asnOrderMapper.update(null, new LambdaUpdateWrapper<WkOrder>()
                                .set(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val)
                        .set(WkOrder::getAnfme, purQty).eq(WkOrder::getId, order.getId())) <= 0) {
                WkOrder currentOrder = asnOrderMapper.selectById(order.getId());
                if (Objects.isNull(currentOrder)) {
                    throw new CoolException("单据数量修改失败!!");
                }
                currentOrder.setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val)
                        .setAnfme(purQty);
                if (asnOrderMapper.updateById(currentOrder) <= 0) {
                    throw new CoolException("单据数量修改失败!!");
                }
            }
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -356,11 +356,10 @@
        }
        for (WkOrder order : orders) {
            Double wkQty = Math.round((order.getWorkQty() + order.getAnfme()) * 10000) / 10000.0;
            if (!this.update(new LambdaUpdateWrapper<WkOrder>()
                    .set(WkOrder::getWaveId, wave.getId())
                    .set(WkOrder::getWorkQty, wkQty)
                    .set(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WAVE.val)
                    .eq(WkOrder::getId, order.getId()))) {
            order.setWaveId(wave.getId())
                    .setWorkQty(wkQty)
                    .setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_WAVE.val);
            if (!this.updateById(order)) {
                throw new CoolException("执行状态修改修改失败!!");
            }
        }
@@ -455,11 +454,10 @@
        }
        for (WkOrder order : orders) {
            Double wkQty = Math.round((order.getWorkQty() + order.getAnfme()) * 10000) / 10000.0;
            if (!this.update(new LambdaUpdateWrapper<WkOrder>()
                    .set(WkOrder::getWaveId, wave.getId())
                    .set(WkOrder::getWorkQty, wkQty)
                    .set(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WAVE.val)
                    .eq(WkOrder::getId, order.getId()))) {
            order.setWaveId(wave.getId())
                    .setWorkQty(wkQty)
                    .setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_WAVE.val);
            if (!this.updateById(order)) {
                throw new CoolException("执行状态修改修改失败!!");
            }
        }
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/TaskServiceImpl.java
@@ -169,10 +169,7 @@
            if (!this.save(task)) {
                throw new CoolException("任务保存失败!!");
            }
            if (!locService.update(new LambdaUpdateWrapper<Loc>().eq(Loc::getCode, task.getTargLoc())
                    .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type).set(Loc::getBarcode, pakin.getBarcode()))) {
                throw new CoolException("库位预约失败!!");
            }
            updateLocByCode(task.getTargLoc(), LocStsType.LOC_STS_TYPE_S.type, pakin.getBarcode(), null, "库位预约失败!!", true);
            /**获取组拖明细**/
            List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, pakin.getId()));
            if (waitPakinItems.isEmpty()) {
@@ -212,13 +209,7 @@
            });
        });
        if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                .in(WaitPakin::getId, pakins.getId())
                .set(WaitPakin::getUpdateBy, loginUserId)
                .set(WaitPakin::getCreateBy, loginUserId)
                .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val))) {
            throw new CoolException("组托状态修改失败!!");
        }
        updateWaitPakinsStatus(waitPakins, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val, loginUserId, true, "组托状态修改失败!!");
        return R.ok("任务生成完毕!");
    }
@@ -261,14 +252,7 @@
            if (!this.save(task)) {
                throw new CoolException("任务保存失败!!");
            }
            if (!locService.update(new LambdaUpdateWrapper<Loc>()
                    .eq(Loc::getCode, task.getTargLoc())
                    .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type)
                    .set(Loc::getUpdateBy, loginUserId)
                    .set(Loc::getUpdateTime, new Date())
                    .set(Loc::getBarcode, pakin.getBarcode()))) {
                throw new CoolException("库位预约失败!!");
            }
            updateLocByCode(task.getTargLoc(), LocStsType.LOC_STS_TYPE_S.type, pakin.getBarcode(), loginUserId, "库位预约失败!!", true);
            /**获取组拖明细**/
            List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, pakin.getId()));
            if (waitPakinItems.isEmpty()) {
@@ -303,13 +287,7 @@
            });
        });
        if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                .in(WaitPakin::getId, pakins.getId())
                .set(WaitPakin::getUpdateBy, loginUserId)
                .set(WaitPakin::getCreateBy, loginUserId)
                .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val))) {
            throw new CoolException("组拖状态修改失败!!");
        }
        updateWaitPakinsStatus(waitPakins, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val, loginUserId, true, "组拖状态修改失败!!");
        return R.ok("任务生成完毕!");
    }
@@ -415,10 +393,7 @@
             }
            if (!locService.update(new LambdaUpdateWrapper<Loc>().eq(Loc::getCode, task.getTargLoc())
                    .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type).set(Loc::getBarcode, pakin.getBarcode()))) {
                throw new CoolException("库位预约失败!!");
            }
            updateLocByCode(task.getTargLoc(), LocStsType.LOC_STS_TYPE_S.type, pakin.getBarcode(), null, "库位预约失败!!", true);
            /**获取组拖明细**/
            List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, pakin.getId()));
            if (waitPakinItems.isEmpty()) {
@@ -461,13 +436,7 @@
            });
        });
        if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                .in(WaitPakin::getId, ids)
                .set(WaitPakin::getUpdateBy, loginUserId)
                .set(WaitPakin::getCreateBy, loginUserId)
                .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val))) {
            throw new CoolException("组拖状态修改失败!!");
        }
        updateWaitPakinsStatus(waitPakins, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val, loginUserId, true, "组拖状态修改失败!!");
        return R.ok("任务生成完毕!");
    }
@@ -691,10 +660,7 @@
                    throw new CoolException("任务保存失败!!");
                }
                if (!locService.update(new LambdaUpdateWrapper<Loc>().eq(Loc::getCode, task.getTargLoc())
                        .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type).set(Loc::getBarcode, pakin.getBarcode()))) {
                    throw new CoolException("库位预约失败!!");
                }
                updateLocByCode(task.getTargLoc(), LocStsType.LOC_STS_TYPE_S.type, pakin.getBarcode(), null, "库位预约失败!!", true);
                /**获取组拖明细**/
                List<WaitPakinItem> waitPakinItems = waitPakinItemService.list(new LambdaQueryWrapper<WaitPakinItem>().eq(WaitPakinItem::getPakinId, pakin.getId()));
                if (waitPakinItems.isEmpty()) {
@@ -736,13 +702,7 @@
                continue;
            }
            if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                    .in(WaitPakin::getId, ids)
                    .set(WaitPakin::getUpdateBy, loginUserId)
                    .set(WaitPakin::getCreateBy, loginUserId)
                    .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val))) {
                throw new CoolException("组拖状态修改失败!!");
            }
            updateWaitPakinsStatus(waitPakins, PakinIOStatus.PAKIN_IO_STATUS_TASK_EXCE.val, loginUserId, true, "组拖状态修改失败!!");
            return R.ok("任务生成完毕!");
        }
        String msg = "未找库位" + Arrays.toString(errMsg);
@@ -1060,30 +1020,11 @@
        }
        /**修改库位状态为F.在库*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>()
                .set(Loc::getBarcode, task.getBarcode())
                .set(Loc::getUseStatus, type)
                .set(Loc::getUpdateBy, loginUserId)
                .set(Loc::getUpdateTime, new Date())
                .eq(Loc::getCode, task.getTargLoc()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        updateLocByCode(task.getTargLoc(), type, task.getBarcode(), loginUserId, "库位状态修改失败!!", true);
        if (!locService.update(new LambdaUpdateWrapper<Loc>()
                .set(Loc::getBarcode, null)
                .set(Loc::getUpdateBy, loginUserId)
                .set(Loc::getUpdateTime, new Date())
                .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                .eq(Loc::getCode, task.getOrgLoc()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        updateLocByCode(task.getOrgLoc(), LocStsType.LOC_STS_TYPE_O.type, null, loginUserId, "库位状态修改失败!!", true);
        if (!this.update(new LambdaUpdateWrapper<Task>().eq(Task::getId, task.getId())
                .set(Task::getUpdateBy, loginUserId)
                .set(Task::getUpdateTime, new Date())
                .set(Task::getTaskStatus, TaskStsType.UPDATED_IN.id))) {
            throw new CoolException("任务状态修改失败!!");
        }
        updateTaskSnapshot(task, TaskStsType.UPDATED_IN.id, loginUserId, "任务状态修改失败!!");
    }
@@ -1324,16 +1265,8 @@
        for (Task task : tasks) {
            //取消移库任务
            if (task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type) && task.getTaskStatus().equals(TaskStsType.GENERATE_IN.id)) {
                if (!locService.update(new LambdaUpdateWrapper<Loc>()
                        .eq(Loc::getCode, task.getOrgLoc())
                        .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type))) {
                    throw new CoolException("源库位状态修改失败!!");
                }
                if (!locService.update(new LambdaUpdateWrapper<Loc>()
                        .eq(Loc::getCode, task.getTargLoc())
                        .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type))) {
                    throw new CoolException("移库目标库位状态修改失败!!");
                }
                updateLocByCode(task.getOrgLoc(), LocStsType.LOC_STS_TYPE_F.type, null, null, "源库位状态修改失败!!", false);
                updateLocByCode(task.getTargLoc(), LocStsType.LOC_STS_TYPE_O.type, null, null, "移库目标库位状态修改失败!!", false);
                Task outTask = this.getById(task.getParentId());
                if (!Objects.isNull(outTask)) {
@@ -1360,11 +1293,7 @@
                            throw new CoolException("明细数量修改失败!!");
                        }
                        if (!checkOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                                .eq(WkOrder::getId, taskItem.getOrderId())
                                .setSql("work_qty = work_qty - " + taskItem.getAnfme()))) {
                            throw new CoolException("盘点单执行数量修改失败!!");
                        }
                        updateWkOrderWorkQty(taskItem.getOrderId(), -taskItem.getAnfme(), null, "盘点单执行数量修改失败!!");
                        CheckDiffItem serviceOne = checkDiffItemService.getOne(new LambdaQueryWrapper<CheckDiffItem>().eq(CheckDiffItem::getTaskItemId, taskItem.getId()));
@@ -1383,17 +1312,11 @@
                            .distinct()
                            .collect(Collectors.toList());
                    for (Long orderId : orderIds) {
                        checkOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                                .eq(WkOrder::getId, orderId)
                                .set(WkOrder::getExceStatus, CheckExceStatus.CHECK_ORDER_STATUS_UN_EXCE.val));
                        updateWkOrderExceStatus(orderId, CheckExceStatus.CHECK_ORDER_STATUS_UN_EXCE.val, null, "盘点单状态修改失败!!");
                    }
                }
                if (!locService.update(new LambdaUpdateWrapper<Loc>()
                        .eq(Loc::getCode, task.getOrgLoc())
                        .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type))) {
                    throw new CoolException("源库位状态修改失败!!");
                }
                updateLocByCode(task.getOrgLoc(), LocStsType.LOC_STS_TYPE_F.type, null, null, "源库位状态修改失败!!", false);
            }
            // 出库类任务取消时,回退路径规划阶段占用的目标站点(S→O)
@@ -1804,22 +1727,9 @@
        }
        /**修改为库位状态为O.空库*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>()
                .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                .set(Loc::getBarcode, null)
                .set(Loc::getUpdateBy, loginUserId)
                .set(Loc::getUpdateTime, new Date())
                .eq(Loc::getId, loc.getId()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        updateLocSnapshot(loc, LocStsType.LOC_STS_TYPE_O.type, null, loginUserId, "库位状态修改失败!!", true);
        if (!this.update(new LambdaUpdateWrapper<Task>()
                .eq(Task::getId, task.getId())
                .set(Task::getUpdateBy, loginUserId)
                .set(Task::getUpdateTime, new Date())
                .set(Task::getTaskStatus, TaskStsType.WAVE_SEED.id))) {
            throw new CoolException("库存状态更新失败!!");
        }
        updateTaskSnapshot(task, TaskStsType.WAVE_SEED.id, loginUserId, "库存状态更新失败!!");
//        if (task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type) || task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type)) {
//            if (!this.update(new LambdaUpdateWrapper<Task>()
@@ -1952,22 +1862,9 @@
        }
        /**修改为库位状态为O.空库*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>()
                .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                .set(Loc::getBarcode, null)
                .set(Loc::getUpdateBy, loginUserId)
                .set(Loc::getUpdateTime, new Date())
                .eq(Loc::getId, loc.getId()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        updateLocSnapshot(loc, LocStsType.LOC_STS_TYPE_O.type, null, loginUserId, "库位状态修改失败!!", true);
        if (!this.update(new LambdaUpdateWrapper<Task>()
                .eq(Task::getId, task.getId())
                .set(Task::getUpdateBy, loginUserId)
                .set(Task::getUpdateTime, new Date())
                .set(Task::getTaskStatus, TaskStsType.UPDATED_OUT.id))) {
            throw new CoolException("库存状态更新失败!!");
        }
        updateTaskSnapshot(task, TaskStsType.UPDATED_OUT.id, loginUserId, "库存状态更新失败!!");
//        if (task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type) || task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type)) {
//            if (!this.update(new LambdaUpdateWrapper<Task>()
@@ -2068,22 +1965,24 @@
        }
        /**修改为库位状态为O.空库*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>()
                .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_S.type)
                .set(Loc::getBarcode, loc.getBarcode())
                .set(Loc::getUpdateBy, loginUserId)
                .set(Loc::getUpdateTime, new Date())
                .eq(Loc::getId, loc.getId()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        updateLocSnapshot(loc, LocStsType.LOC_STS_TYPE_S.type, loc.getBarcode(), loginUserId, "库位状态修改失败!!", true);
        if (!this.update(new LambdaUpdateWrapper<Task>()
                .eq(Task::getId, task.getId())
                .set(Task::getUpdateBy, loginUserId)
                .set(Task::getUpdateTime, new Date())
                .set(Task::getTargLoc, task.getOrgLoc())
                .set(Task::getTaskStatus, TaskStsType.COMPLETE_IN.id))) {
        Task update = new Task();
        update.setId(task.getId());
        update.setVersion(task.getVersion());
        update.setUpdateBy(loginUserId);
        update.setUpdateTime(new Date());
        update.setTargLoc(task.getOrgLoc());
        update.setTaskStatus(TaskStsType.COMPLETE_IN.id);
        if (!this.updateById(update)) {
            throw new CoolException("库存状态更新失败!!");
        }
        task.setUpdateBy(loginUserId);
        task.setUpdateTime(update.getUpdateTime());
        task.setTargLoc(task.getOrgLoc());
        task.setTaskStatus(TaskStsType.COMPLETE_IN.id);
        if (update.getVersion() != null) {
            task.setVersion(update.getVersion());
        }
    }
@@ -2108,22 +2007,9 @@
        }
        /**修改为库位状态为O.空库*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>()
                .set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_O.type)
                .set(Loc::getBarcode, null)
                .set(Loc::getUpdateBy, loginUserId)
                .set(Loc::getUpdateTime, new Date())
                .eq(Loc::getId, loc.getId()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        updateLocSnapshot(loc, LocStsType.LOC_STS_TYPE_O.type, null, loginUserId, "库位状态修改失败!!", true);
        if (!this.update(new LambdaUpdateWrapper<Task>()
                .eq(Task::getId, task.getId())
                .set(Task::getUpdateBy, loginUserId)
                .set(Task::getUpdateTime, new Date())
                .set(Task::getTaskStatus, TaskStsType.UPDATED_OUT.id))) {
            throw new CoolException("库存状态更新失败!!");
        }
        updateTaskSnapshot(task, TaskStsType.UPDATED_OUT.id, loginUserId, "库存状态更新失败!!");
    }
    /**
@@ -2357,10 +2243,7 @@
                                throw new CoolException("站点不存在!!");
                            }
                            if (!this.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                                    .set(Task::getTaskStatus, TaskStsType.WCS_EXECUTE_IN.id))) {
                                throw new CoolException("任务状态修改失败!!");
                            }
                            updateTaskSnapshot(task, TaskStsType.WCS_EXECUTE_IN.id, null, "任务状态修改失败!!");
                            /**排除移库功能*/
                            if (!task.getTaskType().equals(TaskType.TASK_TYPE_LOC_MOVE.type)) {
                                /**如果是普通站点,修改站点状态为出库预约*/
@@ -2381,10 +2264,7 @@
                                throw new CoolException("站点不存在!!");
                            }
                            if (!this.update(new LambdaUpdateWrapper<Task>().eq(Task::getTaskCode, task.getTaskCode())
                                    .set(Task::getTaskStatus, TaskStsType.WCS_EXECUTE_OUT.id))) {
                                throw new CoolException("任务状态修改失败!!");
                            }
                            updateTaskSnapshot(task, TaskStsType.WCS_EXECUTE_OUT.id, null, "任务状态修改失败!!");
                            /**如果是普通站点,修改站点状态为入库预约*/
                            if (curSta.getType().equals(StationTypeEnum.STATION_TYPE_NORMAL.type)) {
                                curSta.setUseStatus(LocStsType.LOC_STS_TYPE_S.type);
@@ -2462,12 +2342,8 @@
            removeReceiptStock(pakinItem, loginUserId);
        });
        /**修改库位状态为F.在库*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>().set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_F.type).eq(Loc::getCode, task.getTargLoc()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        if (!this.update(new LambdaUpdateWrapper<Task>().eq(Task::getId, task.getId()).set(Task::getTaskStatus, TaskStsType.UPDATED_IN.id))) {
            throw new CoolException("任务状态修改失败!!");
        }
        updateLocByCode(task.getTargLoc(), LocStsType.LOC_STS_TYPE_F.type, null, null, "库位状态修改失败!!", false);
        updateTaskSnapshot(task, TaskStsType.UPDATED_IN.id, null, "任务状态修改失败!!");
    }
    /**
@@ -2492,12 +2368,8 @@
        }
        /**修改库位状态为"D", "空板"*/
        if (!locService.update(new LambdaUpdateWrapper<Loc>().set(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_D.type).eq(Loc::getCode, task.getTargLoc()))) {
            throw new CoolException("库位状态修改失败!!");
        }
        if (!this.update(new LambdaUpdateWrapper<Task>().eq(Task::getId, task.getId()).set(Task::getTaskStatus, TaskStsType.UPDATED_IN.id))) {
            throw new CoolException("任务状态修改失败!!");
        }
        updateLocByCode(task.getTargLoc(), LocStsType.LOC_STS_TYPE_D.type, null, null, "库位状态修改失败!!", false);
        updateTaskSnapshot(task, TaskStsType.UPDATED_IN.id, null, "任务状态修改失败!!");
    }
    /**
@@ -2511,12 +2383,7 @@
    @Transactional(rollbackFor = Exception.class)
    public synchronized void removeReceiptStock(WaitPakinItem pakinItem, Long loginUserId) {
        if (pakinItem.getType().equals(OrderType.ORDER_PRE.type)){
            if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                    .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_DONE.val)
                    .set(WaitPakin::getUpdateBy, loginUserId)
                    .eq(WaitPakin::getId, pakinItem.getPakinId()))) {
                throw new CoolException("组拖状态修改失败!!");
            }
            updateWaitPakinById(pakinItem.getPakinId(), PakinIOStatus.PAKIN_IO_STATUS_TASK_DONE.val, loginUserId, "组拖状态修改失败!!");
        }else {
            WarehouseAreasItem itemServiceOne = warehouseAreasItemService.getOne(new LambdaQueryWrapper<WarehouseAreasItem>()
                    .eq(WarehouseAreasItem::getId, pakinItem.getSource()));
@@ -2530,12 +2397,7 @@
            Double qty = Math.round((itemServiceOne.getQty() + pakinItem.getAnfme()) * 1000000) / 1000000.0;
            itemServiceOne.setWorkQty(workQty).setQty(qty);
            if (!waitPakinService.update(new LambdaUpdateWrapper<WaitPakin>()
                    .set(WaitPakin::getIoStatus, PakinIOStatus.PAKIN_IO_STATUS_TASK_DONE.val)
                    .set(WaitPakin::getUpdateBy, loginUserId)
                    .eq(WaitPakin::getId, pakinItem.getPakinId()))) {
                throw new CoolException("组拖状态修改失败!!");
            }
            updateWaitPakinById(pakinItem.getPakinId(), PakinIOStatus.PAKIN_IO_STATUS_TASK_DONE.val, loginUserId, "组拖状态修改失败!!");
            if (qty.compareTo(itemServiceOne.getAnfme()) == 0.00) {
                if (!warehouseAreasItemService.removeById(itemServiceOne.getId())) {
@@ -2665,4 +2527,156 @@
            }
        }
    }
    private void updateLocByCode(String locCode, String useStatus, String barcode, Long loginUserId, String errorMessage, boolean updateBarcode) {
        Loc loc = locService.getOne(new LambdaQueryWrapper<Loc>().eq(Loc::getCode, locCode), false);
        if (Objects.isNull(loc)) {
            throw new CoolException("库位不存在!!");
        }
        updateLocSnapshot(loc, useStatus, barcode, loginUserId, errorMessage, updateBarcode);
    }
    private void updateLocSnapshot(Loc loc, String useStatus, String barcode, Long loginUserId, String errorMessage, boolean updateBarcode) {
        Loc update = new Loc();
        update.setId(loc.getId());
        update.setVersion(loc.getVersion());
        update.setUseStatus(useStatus);
        if (updateBarcode) {
            update.setBarcode(barcode);
        }
        if (loginUserId != null) {
            update.setUpdateBy(loginUserId);
            update.setUpdateTime(new Date());
        }
        if (!locService.updateById(update)) {
            throw new CoolException(errorMessage);
        }
        loc.setUseStatus(useStatus);
        if (updateBarcode) {
            loc.setBarcode(barcode);
        }
        if (loginUserId != null) {
            loc.setUpdateBy(loginUserId);
            loc.setUpdateTime(update.getUpdateTime());
        }
        if (update.getVersion() != null) {
            loc.setVersion(update.getVersion());
        }
    }
    private void updateWaitPakinById(Long pakinId, Short ioStatus, Long loginUserId, String errorMessage) {
        WaitPakin waitPakin = waitPakinService.getById(pakinId);
        if (Objects.isNull(waitPakin)) {
            throw new CoolException("组拖不存在!!");
        }
        updateWaitPakinSnapshot(waitPakin, ioStatus, loginUserId, false, errorMessage);
    }
    private void updateWaitPakinsStatus(List<WaitPakin> waitPakins, Short ioStatus, Long loginUserId, boolean updateCreateBy, String errorMessage) {
        for (WaitPakin waitPakin : waitPakins) {
            updateWaitPakinSnapshot(waitPakin, ioStatus, loginUserId, updateCreateBy, errorMessage);
        }
    }
    private void updateWaitPakinSnapshot(WaitPakin waitPakin, Short ioStatus, Long loginUserId, boolean updateCreateBy, String errorMessage) {
        WaitPakin update = new WaitPakin();
        update.setId(waitPakin.getId());
        update.setVersion(waitPakin.getVersion());
        update.setIoStatus(ioStatus);
        if (updateCreateBy && loginUserId != null) {
            update.setCreateBy(loginUserId);
        }
        if (loginUserId != null) {
            update.setUpdateBy(loginUserId);
            update.setUpdateTime(new Date());
        }
        if (!waitPakinService.updateById(update)) {
            throw new CoolException(errorMessage);
        }
        waitPakin.setIoStatus(ioStatus);
        if (loginUserId != null) {
            waitPakin.setUpdateBy(loginUserId);
            waitPakin.setUpdateTime(update.getUpdateTime());
        }
        if (update.getVersion() != null) {
            waitPakin.setVersion(update.getVersion());
        }
    }
    private void updateWkOrderExceStatus(Long orderId, Short exceStatus, Long loginUserId, String errorMessage) {
        WkOrder order = checkOrderService.getById(orderId);
        if (Objects.isNull(order)) {
            return;
        }
        WkOrder update = new WkOrder();
        update.setId(order.getId());
        update.setVersion(order.getVersion());
        update.setExceStatus(exceStatus);
        if (loginUserId != null) {
            update.setUpdateBy(loginUserId);
            update.setUpdateTime(new Date());
        }
        if (!checkOrderService.updateById(update)) {
            throw new CoolException(errorMessage);
        }
        order.setExceStatus(exceStatus);
        if (loginUserId != null) {
            order.setUpdateBy(loginUserId);
            order.setUpdateTime(update.getUpdateTime());
        }
        if (update.getVersion() != null) {
            order.setVersion(update.getVersion());
        }
    }
    private void updateWkOrderWorkQty(Long orderId, Double delta, Long loginUserId, String errorMessage) {
        WkOrder order = checkOrderService.getById(orderId);
        if (Objects.isNull(order)) {
            throw new CoolException("盘点单不存在!!");
        }
        double currentWorkQty = Objects.isNull(order.getWorkQty()) ? 0.0 : order.getWorkQty();
        double nextWorkQty = Math.round((currentWorkQty + delta) * 10000) / 10000.0;
        WkOrder update = new WkOrder();
        update.setId(order.getId());
        update.setVersion(order.getVersion());
        update.setWorkQty(nextWorkQty);
        if (loginUserId != null) {
            update.setUpdateBy(loginUserId);
            update.setUpdateTime(new Date());
        }
        if (!checkOrderService.updateById(update)) {
            throw new CoolException(errorMessage);
        }
        order.setWorkQty(nextWorkQty);
        if (loginUserId != null) {
            order.setUpdateBy(loginUserId);
            order.setUpdateTime(update.getUpdateTime());
        }
        if (update.getVersion() != null) {
            order.setVersion(update.getVersion());
        }
    }
    private void updateTaskSnapshot(Task task, Integer taskStatus, Long loginUserId, String errorMessage) {
        Task update = new Task();
        update.setId(task.getId());
        update.setVersion(task.getVersion());
        update.setTaskStatus(taskStatus);
        if (loginUserId != null) {
            update.setUpdateBy(loginUserId);
            update.setUpdateTime(new Date());
        }
        if (!this.updateById(update)) {
            throw new CoolException(errorMessage);
        }
        task.setTaskStatus(taskStatus);
        if (loginUserId != null) {
            task.setUpdateBy(loginUserId);
            task.setUpdateTime(update.getUpdateTime());
        }
        if (update.getVersion() != null) {
            task.setVersion(update.getVersion());
        }
    }
}
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/WaveServiceImpl.java
@@ -96,11 +96,7 @@
        }
        List<Long> orderIds = waveItems.stream().map(WaveItem::getOrderId).collect(Collectors.toList());
        /**修改出库单状态*/
        if (!asnOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                .set(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val)
                .in(WkOrder::getId, orderIds))) {
            throw new CoolException("出库单据状态修改失败!!");
        }
        updateAsnOrders(orderIds, null, AsnExceStatus.OUT_STOCK_STATUS_TASK_WORKING.val, null, "出库单据状态修改失败!!");
        return R.ok();
    }
@@ -203,25 +199,9 @@
            Double sum = taskItems.stream().mapToDouble(TaskItem::getAnfme).sum();
            Double v = Math.round((wave.getWorkQty() + sum) * 1000000) / 1000000.0;
            if (wave.getAnfme().compareTo(v) == 0) {
                if (!this.update(new LambdaUpdateWrapper<Wave>()
                        .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK.val)
                        .set(Wave::getWorkQty, v)
                        .set(Wave::getUpdateBy, loginUserId)
                        .set(Wave::getMemo, null)
                        .set(Wave::getUpdateTime, new Date())
                        .eq(Wave::getId, wave.getId()))) {
                    throw new CoolException("波次状态修改失败!!");
                }
                updateWaveSnapshot(wave, WaveExceStatus.WAVE_EXCE_STATUS_TASK.val, v, loginUserId, null, "波次状态修改失败!!");
            } else {
                if (!this.update(new LambdaUpdateWrapper<Wave>()
                        .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_EXCING.val)
                        .set(Wave::getWorkQty, v)
                        .set(Wave::getUpdateBy, loginUserId)
                        .set(Wave::getMemo, null)
                        .set(Wave::getUpdateTime, new Date())
                        .eq(Wave::getId, wave.getId()))) {
                    throw new CoolException("波次状态修改失败!!");
                }
                updateWaveSnapshot(wave, WaveExceStatus.WAVE_EXCE_STATUS_EXCING.val, v, loginUserId, null, "波次状态修改失败!!");
            }
        }
@@ -263,20 +243,71 @@
        });
        List<Long> orders = orderRelas.stream().map(WaveOrderRela::getOrderId).collect(Collectors.toList());
        if (!asnOrderService.update(new LambdaUpdateWrapper<WkOrder>()
                .set(WkOrder::getWorkQty, 0.0)
                .set(WkOrder::getExceStatus, AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val)
                .in(WkOrder::getId, orders))) {
            throw new CoolException("单据更新失败!!");
        }
        updateAsnOrders(orders, 0.0, AsnExceStatus.OUT_STOCK_STATUS_TASK_INIT.val, null, "单据更新失败!!");
        this.update(new LambdaUpdateWrapper<Wave>()
                .eq(Wave::getId, id)
                .set(Wave::getExceStatus, WaveExceStatus.WAVE_EXCE_STATUS_TASK.val));
        updateWaveSnapshot(wave, WaveExceStatus.WAVE_EXCE_STATUS_TASK.val, null, null, null, "波次状态修改失败!!");
        return R.ok();
    }
    private void updateWaveSnapshot(Wave wave, Short exceStatus, Double workQty, Long loginUserId, String memo, String errorMessage) {
        Wave update = new Wave();
        update.setId(wave.getId());
        update.setVersion(wave.getVersion());
        update.setExceStatus(exceStatus);
        update.setWorkQty(workQty);
        update.setUpdateBy(loginUserId);
        update.setMemo(memo);
        update.setUpdateTime(loginUserId == null ? null : new Date());
        if (!this.updateById(update)) {
            throw new CoolException(errorMessage);
        }
        wave.setExceStatus(exceStatus);
        wave.setWorkQty(workQty);
        wave.setUpdateBy(loginUserId);
        if (loginUserId != null) {
            wave.setUpdateTime(update.getUpdateTime());
        }
        wave.setMemo(memo);
        if (update.getVersion() != null) {
            wave.setVersion(update.getVersion());
        }
    }
    private void updateAsnOrders(List<Long> orderIds, Double workQty, Short exceStatus, Long loginUserId, String errorMessage) {
        if (Cools.isEmpty(orderIds)) {
            return;
        }
        List<WkOrder> orders = asnOrderService.listByIds(orderIds.stream().distinct().collect(Collectors.toList()));
        for (WkOrder order : orders) {
            WkOrder update = new WkOrder();
            update.setId(order.getId());
            update.setVersion(order.getVersion());
            if (workQty != null) {
                update.setWorkQty(workQty);
            }
            update.setExceStatus(exceStatus);
            if (loginUserId != null) {
                update.setUpdateBy(loginUserId);
                update.setUpdateTime(new Date());
            }
            if (!asnOrderService.updateById(update)) {
                throw new CoolException(errorMessage);
            }
            if (workQty != null) {
                order.setWorkQty(workQty);
            }
            order.setExceStatus(exceStatus);
            if (loginUserId != null) {
                order.setUpdateBy(loginUserId);
                order.setUpdateTime(update.getUpdateTime());
            }
            if (update.getVersion() != null) {
                order.setVersion(update.getVersion());
            }
        }
    }
    /**
     * @param
     * @param loginUserId
rsf-server/src/test/java/com/vincent/rsf/server/common/utils/OptimisticLockUtilsTest.java
New file
@@ -0,0 +1,23 @@
package com.vincent.rsf.server.common.utils;
import com.vincent.rsf.framework.exception.CoolException;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
class OptimisticLockUtilsTest {
    @Test
    void requireVersionRejectsMissingVersion() {
        CoolException exception = assertThrows(CoolException.class,
                () -> OptimisticLockUtils.requireVersion("任务", null));
        assertEquals("任务版本号不能为空,请刷新后重试!!", exception.getMessage());
    }
    @Test
    void requireVersionAllowsExistingVersion() {
        assertDoesNotThrow(() -> OptimisticLockUtils.requireVersion("任务", 0));
    }
}
rsf-server/src/test/java/com/vincent/rsf/server/manager/mapper/MybatisPlusOptimisticLockIntegrationTest.java
New file
@@ -0,0 +1,474 @@
package com.vincent.rsf.server.manager.mapper;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.vincent.rsf.server.manager.entity.Loc;
import com.vincent.rsf.server.manager.entity.Task;
import com.vincent.rsf.server.manager.entity.WaitPakin;
import com.vincent.rsf.server.manager.entity.WkOrder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.sql.DataSource;
import java.util.Date;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = MybatisPlusOptimisticLockIntegrationTest.TestConfig.class)
class MybatisPlusOptimisticLockIntegrationTest {
    @Autowired
    private WaitPakinMapper waitPakinMapper;
    @Autowired
    private LocMapper locMapper;
    @Autowired
    private AsnOrderMapper asnOrderMapper;
    @Autowired
    private TaskMapper taskMapper;
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @BeforeEach
    void setUpSchema() {
        jdbcTemplate.execute("drop table if exists man_wait_pakin");
        jdbcTemplate.execute("drop table if exists man_loc");
        jdbcTemplate.execute("drop table if exists man_asn_order");
        jdbcTemplate.execute("drop table if exists man_task");
        jdbcTemplate.execute("""
                create table man_wait_pakin (
                    id bigint auto_increment primary key,
                    code varchar(64),
                    barcode varchar(64),
                    anfme double,
                    io_status smallint,
                    flag_defect smallint,
                    status integer,
                    deleted integer,
                    tenant_id integer,
                    create_by bigint,
                    create_time timestamp,
                    update_by bigint,
                    update_time timestamp,
                    memo varchar(255),
                    version integer not null default 0
                )
                """);
        jdbcTemplate.execute("""
                create table man_loc (
                    id bigint auto_increment primary key,
                    area_id bigint,
                    code varchar(64),
                    warehouse_id bigint,
                    `type` varchar(64),
                    flag_logic smallint,
                    fuc_atrrs varchar(255),
                    barcode varchar(64),
                    unit varchar(64),
                    `length` double,
                    height double,
                    width double,
                    `row` integer,
                    device_no integer,
                    col integer,
                    lev integer,
                    `channel` integer,
                    max_parts integer,
                    max_pack integer,
                    use_status varchar(8),
                    flag_label_mange smallint,
                    loc_attrs varchar(255),
                    status integer,
                    deleted integer,
                    tenant_id integer,
                    create_by bigint,
                    create_time timestamp,
                    update_by bigint,
                    update_time timestamp,
                    memo varchar(255),
                    version integer not null default 0
                )
                """);
        jdbcTemplate.execute("""
                create table man_asn_order (
                    id bigint auto_increment primary key,
                    code varchar(64),
                    po_code varchar(64),
                    po_id bigint,
                    `type` varchar(64),
                    wk_type varchar(64),
                    check_type integer,
                    anfme double,
                    work_qty double,
                    qty double,
                    logis_no varchar(64),
                    wave_id bigint,
                    arr_time timestamp,
                    nty_status integer,
                    report_once integer,
                    rle_status smallint,
                    exce_status smallint,
                    status integer,
                    deleted integer,
                    tenant_id integer,
                    create_by bigint,
                    create_time timestamp,
                    update_by bigint,
                    update_time timestamp,
                    version integer not null default 0,
                    memo varchar(255),
                    warehouse_id bigint,
                    ware_area_id bigint,
                    business_time timestamp,
                    station_id varchar(64),
                    shipper_id bigint,
                    order_internal_code varchar(64),
                    stock_direct varchar(64),
                    customer_id varchar(64),
                    customer_name varchar(255),
                    supplier_id varchar(64),
                    supplier_name varchar(255),
                    stock_org_id varchar(64),
                    stock_org_name varchar(255),
                    purchase_org_id varchar(64),
                    purchase_org_name varchar(255),
                    purchase_user_id varchar(64),
                    purchase_user_name varchar(255),
                    prd_org_id varchar(64),
                    prd_org_name varchar(255),
                    sale_org_id varchar(64),
                    sale_org_name varchar(255),
                    sale_user_id varchar(64),
                    sale_user_name varchar(255)
                )
                """);
        jdbcTemplate.execute("""
                create table man_task (
                    id bigint auto_increment primary key,
                    task_code varchar(64),
                    resource smallint,
                    task_status integer,
                    parent_id bigint,
                    wareh_type varchar(64),
                    task_type integer,
                    org_loc varchar(64),
                    targ_loc varchar(64),
                    org_site varchar(64),
                    targ_site varchar(64),
                    barcode varchar(64),
                    robot_code varchar(64),
                    exce_status smallint,
                    exp_desc varchar(255),
                    sort integer,
                    exp_code varchar(64),
                    start_time timestamp,
                    end_time timestamp,
                    status integer,
                    task_origin varchar(64),
                    deleted integer,
                    tenant_id integer,
                    create_by bigint,
                    create_time timestamp,
                    update_by bigint,
                    update_time timestamp,
                    version integer not null default 0,
                    memo varchar(255),
                    device_site_id bigint,
                    sou_step varchar(64),
                    end_step varchar(64),
                    targ_site_area varchar(255),
                    targ_loc_area varchar(255),
                    targ_site_area_now varchar(255)
                )
                """);
    }
    @Test
    void updateByIdRejectsStaleSnapshot() {
        WaitPakin seed = waitPakin();
        waitPakinMapper.insert(seed);
        WaitPakin firstSnapshot = waitPakinMapper.selectById(seed.getId());
        WaitPakin secondSnapshot = waitPakinMapper.selectById(seed.getId());
        firstSnapshot.setIoStatus((short) 1);
        firstSnapshot.setUpdateBy(2L);
        firstSnapshot.setUpdateTime(new Date());
        assertEquals(1, waitPakinMapper.updateById(firstSnapshot));
        assertEquals(1, firstSnapshot.getVersion());
        secondSnapshot.setIoStatus((short) 2);
        secondSnapshot.setUpdateBy(3L);
        secondSnapshot.setUpdateTime(new Date());
        assertEquals(0, waitPakinMapper.updateById(secondSnapshot));
        WaitPakin latest = waitPakinMapper.selectById(seed.getId());
        assertEquals((short) 1, latest.getIoStatus());
        assertEquals(1, latest.getVersion());
    }
    @Test
    void updateWithEntityAndWrapperRejectsStaleSnapshot() {
        WaitPakin seed = waitPakin();
        waitPakinMapper.insert(seed);
        WaitPakin firstSnapshot = waitPakinMapper.selectById(seed.getId());
        WaitPakin secondSnapshot = waitPakinMapper.selectById(seed.getId());
        WaitPakin firstUpdate = new WaitPakin();
        firstUpdate.setId(firstSnapshot.getId());
        firstUpdate.setVersion(firstSnapshot.getVersion());
        firstUpdate.setIoStatus((short) 1);
        firstUpdate.setUpdateBy(2L);
        firstUpdate.setUpdateTime(new Date());
        assertEquals(1, waitPakinMapper.update(firstUpdate,
                new LambdaUpdateWrapper<WaitPakin>().eq(WaitPakin::getId, firstSnapshot.getId())));
        assertEquals(1, firstUpdate.getVersion());
        WaitPakin secondUpdate = new WaitPakin();
        secondUpdate.setId(secondSnapshot.getId());
        secondUpdate.setVersion(secondSnapshot.getVersion());
        secondUpdate.setIoStatus((short) 2);
        secondUpdate.setUpdateBy(3L);
        secondUpdate.setUpdateTime(new Date());
        assertEquals(0, waitPakinMapper.update(secondUpdate,
                new LambdaUpdateWrapper<WaitPakin>().eq(WaitPakin::getId, secondSnapshot.getId())));
        WaitPakin latest = waitPakinMapper.selectById(seed.getId());
        assertEquals((short) 1, latest.getIoStatus());
        assertEquals(1, latest.getVersion());
    }
    @Test
    void locUpdateWithEntityAndWrapperRejectsStaleSnapshot() {
        Loc seed = loc();
        locMapper.insert(seed);
        Loc firstSnapshot = locMapper.selectById(seed.getId());
        Loc secondSnapshot = locMapper.selectById(seed.getId());
        Loc firstUpdate = new Loc();
        firstUpdate.setId(firstSnapshot.getId());
        firstUpdate.setVersion(firstSnapshot.getVersion());
        firstUpdate.setUseStatus("S");
        firstUpdate.setBarcode("BC-LOC-001");
        firstUpdate.setUpdateBy(2L);
        firstUpdate.setUpdateTime(new Date());
        assertEquals(1, locMapper.update(firstUpdate,
                new LambdaUpdateWrapper<Loc>().eq(Loc::getId, firstSnapshot.getId())));
        assertEquals(1, firstUpdate.getVersion());
        Loc secondUpdate = new Loc();
        secondUpdate.setId(secondSnapshot.getId());
        secondUpdate.setVersion(secondSnapshot.getVersion());
        secondUpdate.setUseStatus("F");
        secondUpdate.setBarcode("BC-LOC-002");
        secondUpdate.setUpdateBy(3L);
        secondUpdate.setUpdateTime(new Date());
        assertEquals(0, locMapper.update(secondUpdate,
                new LambdaUpdateWrapper<Loc>().eq(Loc::getId, secondSnapshot.getId())));
        Loc latest = locMapper.selectById(seed.getId());
        assertEquals("S", latest.getUseStatus());
        assertEquals("BC-LOC-001", latest.getBarcode());
        assertEquals(1, latest.getVersion());
    }
    @Test
    void wkOrderUpdateByIdRejectsStaleSnapshot() {
        WkOrder seed = wkOrder();
        asnOrderMapper.insert(seed);
        WkOrder firstSnapshot = asnOrderMapper.selectById(seed.getId());
        WkOrder secondSnapshot = asnOrderMapper.selectById(seed.getId());
        firstSnapshot.setExceStatus((short) 1);
        firstSnapshot.setUpdateBy(2L);
        firstSnapshot.setUpdateTime(new Date());
        assertEquals(1, asnOrderMapper.updateById(firstSnapshot));
        assertEquals(1, firstSnapshot.getVersion());
        secondSnapshot.setExceStatus((short) 2);
        secondSnapshot.setUpdateBy(3L);
        secondSnapshot.setUpdateTime(new Date());
        assertEquals(0, asnOrderMapper.updateById(secondSnapshot));
        WkOrder latest = asnOrderMapper.selectById(seed.getId());
        assertEquals((short) 1, latest.getExceStatus());
        assertEquals(1, latest.getVersion());
    }
    @Test
    void wkOrderWorkQtyUpdateRejectsStaleSnapshot() {
        WkOrder seed = wkOrder();
        asnOrderMapper.insert(seed);
        WkOrder firstSnapshot = asnOrderMapper.selectById(seed.getId());
        WkOrder secondSnapshot = asnOrderMapper.selectById(seed.getId());
        firstSnapshot.setWorkQty(2.5);
        firstSnapshot.setUpdateBy(2L);
        firstSnapshot.setUpdateTime(new Date());
        assertEquals(1, asnOrderMapper.updateById(firstSnapshot));
        assertEquals(1, firstSnapshot.getVersion());
        secondSnapshot.setWorkQty(1.0);
        secondSnapshot.setUpdateBy(3L);
        secondSnapshot.setUpdateTime(new Date());
        assertEquals(0, asnOrderMapper.updateById(secondSnapshot));
        WkOrder latest = asnOrderMapper.selectById(seed.getId());
        assertEquals(2.5, latest.getWorkQty());
        assertEquals(1, latest.getVersion());
    }
    @Test
    void taskUpdateByIdRejectsStaleSnapshot() {
        Task seed = task();
        taskMapper.insert(seed);
        Task firstSnapshot = taskMapper.selectById(seed.getId());
        Task secondSnapshot = taskMapper.selectById(seed.getId());
        firstSnapshot.setTaskStatus(1);
        firstSnapshot.setUpdateBy(2L);
        firstSnapshot.setUpdateTime(new Date());
        assertEquals(1, taskMapper.updateById(firstSnapshot));
        assertEquals(1, firstSnapshot.getVersion());
        secondSnapshot.setTaskStatus(2);
        secondSnapshot.setUpdateBy(3L);
        secondSnapshot.setUpdateTime(new Date());
        assertEquals(0, taskMapper.updateById(secondSnapshot));
        Task latest = taskMapper.selectById(seed.getId());
        assertEquals(1, latest.getTaskStatus());
        assertEquals(1, latest.getVersion());
    }
    private WaitPakin waitPakin() {
        return new WaitPakin()
                .setCode("PK-001")
                .setBarcode("BC-001")
                .setAnfme(1.0)
                .setIoStatus((short) 0)
                .setFlagDefect((short) 0)
                .setStatus(1)
                .setDeleted(0)
                .setTenantId(1)
                .setCreateBy(1L)
                .setCreateTime(new Date())
                .setUpdateBy(1L)
                .setUpdateTime(new Date())
                .setMemo("seed");
    }
    private Loc loc() {
        return new Loc()
                .setCode("LOC-001")
                .setBarcode(null)
                .setUseStatus("O")
                .setStatus(1)
                .setDeleted(0)
                .setTenantId(1)
                .setCreateBy(1L)
                .setCreateTime(new Date())
                .setUpdateBy(1L)
                .setUpdateTime(new Date())
                .setMemo("seed");
    }
    private WkOrder wkOrder() {
        return new WkOrder()
                .setCode("ASN-001")
                .setType("CHECK")
                .setWkType("IN")
                .setAnfme(1.0)
                .setWorkQty(0.0)
                .setQty(0.0)
                .setRleStatus((short) 0)
                .setExceStatus((short) 0)
                .setStatus(1)
                .setDeleted(0)
                .setTenantId(1)
                .setCreateBy(1L)
                .setCreateTime(new Date())
                .setUpdateBy(1L)
                .setUpdateTime(new Date())
                .setMemo("seed");
    }
    private Task task() {
        return new Task()
                .setTaskCode("TASK-001")
                .setTaskStatus(0)
                .setTaskType(1)
                .setOrgLoc("ORG-001")
                .setTargLoc("TARG-001")
                .setBarcode("BC-TASK-001")
                .setExceStatus((short) 0)
                .setStatus(1)
                .setDeleted(0)
                .setTenantId(1)
                .setCreateBy(1L)
                .setCreateTime(new Date())
                .setUpdateBy(1L)
                .setUpdateTime(new Date())
                .setMemo("seed");
    }
    @Configuration
    @MapperScan(basePackageClasses = {WaitPakinMapper.class, LocMapper.class, AsnOrderMapper.class, TaskMapper.class})
    static class TestConfig {
        @Bean
        DataSource dataSource() {
            DriverManagerDataSource dataSource = new DriverManagerDataSource();
            dataSource.setDriverClassName("org.h2.Driver");
            dataSource.setUrl("jdbc:h2:mem:optimistic-lock;MODE=MySQL;DB_CLOSE_DELAY=-1");
            dataSource.setUsername("sa");
            dataSource.setPassword("");
            return dataSource;
        }
        @Bean
        JdbcTemplate jdbcTemplate(DataSource dataSource) {
            return new JdbcTemplate(dataSource);
        }
        @Bean
        MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            return interceptor;
        }
        @Bean
        MybatisSqlSessionFactoryBean sqlSessionFactory(DataSource dataSource,
                                                       MybatisPlusInterceptor mybatisPlusInterceptor) {
            MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
            factoryBean.setDataSource(dataSource);
            factoryBean.setPlugins(mybatisPlusInterceptor);
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setMapUnderscoreToCamelCase(true);
            factoryBean.setConfiguration(configuration);
            return factoryBean;
        }
    }
}
rsf-server/src/test/java/com/vincent/rsf/server/system/controller/MenuControllerTest.java
New file
@@ -0,0 +1,125 @@
package com.vincent.rsf.server.system.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.server.common.service.RedisService;
import com.vincent.rsf.server.system.entity.Menu;
import com.vincent.rsf.server.system.service.MenuService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class MenuControllerTest {
    private static final String MENU_TREE_CACHE_FLAG = "MENU_TREE";
    private static final String MENU_TREE_CACHE_KEY = "FULL_TREE";
    @Mock
    private MenuService menuService;
    @Mock
    private RedisService redisService;
    private MenuController menuController;
    @BeforeEach
    void setUp() {
        menuController = new MenuController();
        ReflectionTestUtils.setField(menuController, "menuService", menuService);
        ReflectionTestUtils.setField(menuController, "redisService", redisService);
    }
    @Test
    void treeReturnsCachedTreeBeforeQueryingDatabase() {
        List<Menu> cachedTree = List.of(menu(1L, 0L, "System"));
        when(redisService.get(MENU_TREE_CACHE_FLAG, MENU_TREE_CACHE_KEY)).thenReturn(cachedTree);
        R result = menuController.tree(new HashMap<>());
        assertEquals(cachedTree, result.get("data"));
        verify(menuService, never()).list(any(LambdaQueryWrapper.class));
    }
    @Test
    void treeLoadsFromDatabaseAndCachesFullTreeWhenCacheMissing() {
        when(redisService.get(MENU_TREE_CACHE_FLAG, MENU_TREE_CACHE_KEY)).thenReturn(null);
        when(menuService.list(any(LambdaQueryWrapper.class))).thenReturn(List.of(
                menu(1L, 0L, "System"),
                menu(2L, 1L, "User")
        ));
        R result = menuController.tree(new HashMap<>());
        List<Menu> tree = cast(result.get("data"));
        assertEquals(1, tree.size());
        assertNotNull(tree.get(0).getChildren());
        assertEquals(1, tree.get(0).getChildren().size());
        ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
        verify(redisService).set(eq(MENU_TREE_CACHE_FLAG), eq(MENU_TREE_CACHE_KEY), captor.capture(), anyInt());
        List<Menu> cachedTree = cast(captor.getValue());
        assertEquals(1, cachedTree.size());
        assertEquals(1L, cachedTree.get(0).getId());
    }
    @Test
    void saveEvictsMenuTreeCacheAfterSuccessfulSave() {
        Menu menu = menu(1L, 0L, "System");
        when(menuService.save(menu)).thenReturn(true);
        menuController.save(menu);
        verify(redisService).delete(MENU_TREE_CACHE_FLAG, MENU_TREE_CACHE_KEY);
    }
    @Test
    void updateEvictsMenuTreeCacheAfterSuccessfulUpdate() {
        Menu menu = menu(1L, 0L, "System");
        when(menuService.updateById(menu)).thenReturn(true);
        menuController.update(menu);
        verify(redisService).delete(MENU_TREE_CACHE_FLAG, MENU_TREE_CACHE_KEY);
    }
    @Test
    void removeEvictsMenuTreeCacheAfterSuccessfulRemove() {
        when(menuService.removeByIds(List.of(1L, 2L))).thenReturn(true);
        menuController.remove(new Long[]{1L, 2L});
        verify(redisService).delete(MENU_TREE_CACHE_FLAG, MENU_TREE_CACHE_KEY);
    }
    private Menu menu(Long id, Long parentId, String name) {
        Menu menu = new Menu();
        menu.setId(id);
        menu.setParentId(parentId);
        menu.setName(name);
        menu.setChildren(new ArrayList<>());
        return menu;
    }
    @SuppressWarnings("unchecked")
    private List<Menu> cast(Object value) {
        return (List<Menu>) value;
    }
}
rsf-server/src/test/java/com/vincent/rsf/server/system/entity/MenuJsonTest.java
New file
@@ -0,0 +1,30 @@
package com.vincent.rsf.server.system.entity;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertFalse;
class MenuJsonTest {
    private final ObjectMapper objectMapper = new ObjectMapper();
    @Test
    void serializationShouldIgnoreDatabaseBackedDisplayGetters() {
        Menu menu = new Menu();
        menu.setId(1L);
        menu.setTenantId(2L);
        menu.setCreateBy(3L);
        menu.setUpdateBy(4L);
        Map<String, Object> json = objectMapper.convertValue(menu, new TypeReference<Map<String, Object>>() {
        });
        assertFalse(json.containsKey("tenantId$"));
        assertFalse(json.containsKey("createBy$"));
        assertFalse(json.containsKey("updateBy$"));
    }
}
version/db/20260327_optimistic_lock.sql
New file
@@ -0,0 +1,14 @@
ALTER TABLE `man_task`
    ADD COLUMN `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本';
ALTER TABLE `man_loc`
    ADD COLUMN `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本';
ALTER TABLE `man_wait_pakin`
    ADD COLUMN `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本';
ALTER TABLE `man_wave`
    ADD COLUMN `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本';
ALTER TABLE `man_asn_order`
    ADD COLUMN `version` int NOT NULL DEFAULT 0 COMMENT '乐观锁版本';