chen.lin
昨天 98d88ac8caf7f0991d741079474c262f1e252927
rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/PdaOutStockServiceImpl.java
@@ -5,26 +5,48 @@
import com.vincent.rsf.framework.common.R;
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.api.entity.dto.ContainerWaveDto;
import com.vincent.rsf.server.api.entity.dto.ContainerWaveItemDto;
import com.vincent.rsf.server.api.entity.dto.QuickPickOrderModuleDto;
import com.vincent.rsf.server.api.entity.params.ContainerWaveParam;
import com.vincent.rsf.server.api.entity.params.WavePickItemsParams;
import com.vincent.rsf.server.api.service.PdaOutStockService;
import com.vincent.rsf.server.common.utils.FieldsUtils;
import com.vincent.rsf.server.manager.entity.*;
import com.vincent.rsf.server.manager.enums.AsnExceStatus;
import com.vincent.rsf.server.manager.enums.TaskResouceType;
import com.vincent.rsf.server.manager.enums.TaskStsType;
import com.vincent.rsf.server.manager.enums.WaveExceStatus;
import com.vincent.rsf.server.manager.enums.TaskType;
import com.vincent.rsf.server.manager.service.*;
import com.vincent.rsf.server.manager.service.impl.LocItemWorkingServiceImpl;
import com.vincent.rsf.server.manager.service.impl.StockItemServiceImpl;
import com.vincent.rsf.server.manager.service.impl.StockServiceImpl;
import com.vincent.rsf.server.system.constant.GlobalConfigCode;
import com.vincent.rsf.server.system.constant.SerialRuleCode;
import com.vincent.rsf.server.system.entity.Config;
import com.vincent.rsf.server.system.entity.Fields;
import com.vincent.rsf.server.system.entity.FieldsItem;
import com.vincent.rsf.server.system.service.FieldsItemService;
import com.vincent.rsf.server.system.service.FieldsService;
import com.vincent.rsf.server.system.service.impl.ConfigServiceImpl;
import com.vincent.rsf.server.system.service.impl.FieldsItemServiceImpl;
import com.vincent.rsf.server.system.service.impl.FieldsServiceImpl;
import com.vincent.rsf.server.system.utils.SerialRuleUtils;
import com.vincent.rsf.server.common.constant.Constants;
import com.vincent.rsf.server.system.utils.SystemAuthUtils;
import lombok.Synchronized;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Date;
@Service
public class PdaOutStockServiceImpl implements PdaOutStockService {
@@ -39,55 +61,158 @@
    private AsnOrderService asnOrderService;
    @Autowired
    private AsnOrderItemService asnOrderItemService;
    @Autowired
    private WaveOrderRelaService waveOrderRelaService;
    @Autowired
    private FieldsItemService fieldsItemService;
    @Autowired
    private FieldsService fieldsService;
    @Autowired
    private StockService stockService;
    @Autowired
    private StockItemServiceImpl stockItemService;
    @Autowired
    private LocItemService locItemService;
    @Autowired
    private LocItemWorkingService locItemWorkingService;
    @Autowired
    private ConfigServiceImpl configService;
    /**
     * 快速拣货查询:同一箱码可能有多条任务,仅 RCS 出库回调后变为 199 的才展示;该箱码下仍不是 199 的 PDA 不显示。
     * 返回:orders 按出库单分模块、list/taskItems 该箱码下 199 任务明细。
     */
    @Override
    public R getOutStockTaskItem(String barcode) {
        LambdaQueryWrapper<Task> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Task::getBarcode, barcode);
        Task task = taskService.getOne(lambdaQueryWrapper);
        if (null == task){
            return R.error("未查询到相关任务");
        // 只查 199(WAVE_SEED)/AWAIT:已确认变成 200 的绝不能扫出来,明确排除 200 避免第二次扫到
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .eq(Task::getBarcode, barcode)
                .in(Task::getTaskStatus, Arrays.asList(TaskStsType.WAVE_SEED.id, TaskStsType.AWAIT.id))
                .ne(Task::getTaskStatus, TaskStsType.UPDATED_OUT.id)
                .orderByAsc(Task::getId));
        if (tasks == null || tasks.isEmpty()) {
            return R.error("未查询到待确认任务");
        }
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
        if (null == taskItems || taskItems.size() <= 0){
        List<Long> taskIds = tasks.stream().map(Task::getId).collect(Collectors.toList());
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().in(TaskItem::getTaskId, taskIds));
        if (taskItems == null || taskItems.isEmpty()) {
            return R.error("任务出错,未查询到相关任务明细");
        }
        return R.ok(taskItems);
        // 同一箱码下可能有多条(多个出库单),按出库单分组;仅返回尚未拣完的订单模块
        String nullKey = "__none__";
        Map<String, List<TaskItem>> byOrder = taskItems.stream()
                .collect(Collectors.groupingBy(ti -> ti.getOrderId() != null ? "o_" + ti.getOrderId() : (StringUtils.isNotBlank(ti.getSourceCode()) ? "s_" + ti.getSourceCode() : nullKey)));
        List<QuickPickOrderModuleDto> orders = new ArrayList<>();
        for (Map.Entry<String, List<TaskItem>> e : byOrder.entrySet()) {
            List<TaskItem> items = e.getValue();
            boolean allPicked = items.stream().allMatch(ti -> ti.getQty() != null && ti.getAnfme() != null && ti.getQty().compareTo(ti.getAnfme()) >= 0);
            if (allPicked) continue;
            TaskItem first = items.get(0);
            orders.add(new QuickPickOrderModuleDto()
                    .setOrderId(first.getOrderId())
                    .setOrderCode(StringUtils.isNotBlank(first.getSourceCode()) ? first.getSourceCode() : ("单号:" + (first.getOrderId() != null ? first.getOrderId() : "—")))
                    .setItems(items));
        }
        R r = orders.isEmpty() ? R.ok("全部拣货已完成") : R.ok();
        r.put("orders", orders);
        r.put("taskItems", taskItems);
        r.put("list", taskItems); // 同一箱码下多条明细,便于直接展示
        return r;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Synchronized
    public R saveOutTaskSts(String barcode) {
        LambdaQueryWrapper<Task> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(Task::getBarcode, barcode);
        Task task = taskService.getOne(lambdaQueryWrapper);
        if (null == task){
            throw new CoolException("未找到容器号对应任务");
        // 只统计当前「待确认」任务:出库单有3单但只下发了2个任务时,2个任务都拣完即可确认并生成拣料入库;有任务被取消则只处理剩余任务
        List<Task> tasks = taskService.list(new LambdaQueryWrapper<Task>()
                .eq(Task::getBarcode, barcode)
                .in(Task::getTaskStatus, Arrays.asList(TaskStsType.WAVE_SEED.id, TaskStsType.AWAIT.id))
                .orderByAsc(Task::getId));
        if (tasks == null || tasks.isEmpty()) {
            throw new CoolException("未找到料箱码对应任务或任务状态不是等待确认");
        }
        if (!task.getTaskStatus().equals(TaskStsType.AWAIT.id)){
            return R.error("任务状态不是等待确认");
        Long loginUserId = SystemAuthUtils.getLoginUserId();
        if (loginUserId == null) {
            loginUserId = 1L;
        }
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
        Map<Long, List<TaskItem>> maps = taskItems.stream().collect(Collectors.groupingBy(TaskItem::getSource));
        maps.keySet().forEach(key -> {
                AsnOrderItem orderItem = asnOrderItemService.getById(key);
                if (Objects.isNull(orderItem)) {
                    throw new CoolException("单据明细不存在!!");
        try {
            Task first = tasks.get(0);
            if (first.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)) {
                // 确认前该箱码下已有 200 的(例如第一次已确认的):本次只把当前 199 置为 200,不生成拣料入库,避免“第二次误确认”导致错误扣减和生成入库
                long already200 = taskService.count(new LambdaQueryWrapper<Task>()
                        .eq(Task::getBarcode, barcode)
                        .eq(Task::getTaskType, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
                        .eq(Task::getTaskStatus, TaskStsType.UPDATED_OUT.id));
                // 确认即已确认:当前 199 任务全部置为 200,并回写已拣数量(qty);仅当本次确认前没有任何 200 且确认后全部 200 时才统一扣减并生成拣料入库
                for (Task task : tasks) {
                    task.setTaskStatus(TaskStsType.UPDATED_OUT.id)
                            .setUpdateBy(loginUserId)
                            .setUpdateTime(new Date());
                    if (!taskService.updateById(task)) {
                        return R.error("更新任务状态失败");
                    }
                    List<TaskItem> items = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
                    for (TaskItem ti : items) {
                        if (ti.getQty() == null || ti.getQty().compareTo(0.0) <= 0) {
                            ti.setQty(ti.getAnfme() != null ? ti.getAnfme() : 0.0);
                            ti.setUpdateBy(loginUserId);
                            ti.setUpdateTime(new Date());
                            taskItemService.updateById(ti);
                        }
                    }
                }
        });
        task.setTaskStatus(TaskStsType.COMPLETE_OUT.id);
        if (!taskService.updateById(task)){
            return R.error("更新任务状态失败");
                long not200 = taskService.count(new LambdaQueryWrapper<Task>()
                        .eq(Task::getBarcode, barcode)
                        .ne(Task::getTaskStatus, TaskStsType.UPDATED_OUT.id));
                if (not200 > 0) {
                    return R.ok("确认成功");
                }
                // 本次确认前该箱码下已有 200 的,不在此处生成拣料入库,由定时任务在“全部 200”时统一处理
                if (already200 > 0) {
                    return R.ok("确认成功;同箱已有过确认任务,扣减与拣料入库由系统在全部200后统一处理");
                }
                // 本次确认前没有任何 200,且确认后同箱码已全部 200:统一扣减、有余量才生成拣料入库单
                List<Task> all200 = taskService.list(new LambdaQueryWrapper<Task>()
                        .eq(Task::getBarcode, barcode)
                        .eq(Task::getTaskType, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
                        .eq(Task::getTaskStatus, TaskStsType.UPDATED_OUT.id)
                        .orderByAsc(Task::getId));
                for (Task task : all200) {
                    taskService.pickOrCheckTask(task.getId(), "");
                }
                return R.ok("确认成功,已统一扣减并生成拣料入库任务(有余量时)");
            }
            if (first.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type)) {
                for (Task task : tasks) {
                    taskService.pickOrCheckTask(task.getId(), Constants.TASK_TYPE_OUT_CHECK);
                }
                return R.ok("确认成功,已创建盘点入库任务");
            }
            if (first.getTaskType().equals(TaskType.TASK_TYPE_OUT.type)) {
                for (Task task : tasks) {
                    taskService.completeFullOutStock(task.getId(), loginUserId);
                }
                return R.ok("确认成功,全版出库已完成");
            }
            for (Task task : tasks) {
                task.setTaskStatus(TaskStsType.UPDATED_OUT.id)
                        .setUpdateBy(loginUserId)
                        .setUpdateTime(new Date());
                if (!taskService.updateById(task)) {
                    return R.error("更新任务状态失败");
                }
            }
            return R.ok("确认成功");
        } catch (Exception e) {
            throw new CoolException("快速拣货确认失败:" + e.getMessage());
        }
        return R.ok("确认成功");
    }
    @Override
    public R getWaveListItem(String barcode) {
        LambdaQueryWrapper<Wave> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(!Cools.isEmpty(barcode),Wave::getCode,barcode);
        lambdaQueryWrapper.eq(!Cools.isEmpty(barcode), Wave::getCode, barcode);
        List<Wave> waveList = waveService.list(lambdaQueryWrapper);
        return R.ok(waveList);
    }
@@ -95,131 +220,460 @@
    @Override
    public R getContainerWaveList(Map<String, String> map) {
        String barcode = map.get("barcode");
        if (Cools.isEmpty(barcode) ){
        if (Cools.isEmpty(barcode)) {
            throw new CoolException("参数有误");
        }
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, barcode));
        if (null == task){
            throw new CoolException("未找到容器号对应任务");
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, barcode)
                .orderByDesc(Task::getId)
                .last("limit 1"));
        if (null == task) {
            throw new CoolException("未找到料箱码对应任务");
        }
        if (!task.getTaskStatus().equals(TaskStsType.AWAIT.id)){
            return R.error("任务状态不是等待确认");
        if (!task.getTaskStatus().equals(TaskStsType.WAVE_SEED.id)) {
            return R.error("任务状态不是揀料狀態");
        }
        ArrayList<ContainerWaveDto> containerWaveDtos = new ArrayList<>();
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
        for (TaskItem taskItem : taskItems) {
            ContainerWaveDto containerWaveDto = new ContainerWaveDto();
            containerWaveDto.setTaskItem(taskItem);
            Wave wave = waveService.getById(taskItem.getSourceId());
            if (null == wave){
                throw new CoolException("未找到容器号对应波次");
        // 当前料箱对应库位下所有处于「预约出库/拣货中」的任务(含可追加的后续订单)
        String orgLoc = task.getOrgLoc();
        List<Integer> pickingStatuses = Arrays.asList(TaskStsType.GENERATE_OUT.id, TaskStsType.WAVE_SEED.id);
        List<Task> sameLocTasks = taskService.list(new LambdaQueryWrapper<Task>()
                .eq(Task::getOrgLoc, orgLoc)
                .in(Task::getTaskStatus, pickingStatuses));
        Set<Long> waveIds = new java.util.HashSet<>();
        Set<String> matnrCodes = new java.util.HashSet<>();
        for (Task t : sameLocTasks) {
            List<TaskItem> items = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, t.getId()));
            for (TaskItem ti : items) {
                if (ti.getSourceId() != null) waveIds.add(ti.getSourceId());
                if (StringUtils.isNotBlank(ti.getMatnrCode())) matnrCodes.add(ti.getMatnrCode());
            }
            ArrayList<AsnOrderItem> list = new ArrayList<>();
            List<AsnOrder> asnOrderList = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().eq(AsnOrder::getWaveId, wave.getId()));
            for (AsnOrder asnOrder : asnOrderList) {
                AsnOrderItem orderItem = asnOrderItemService.getOne(new LambdaQueryWrapper<AsnOrderItem>()
                        .eq(AsnOrderItem::getAsnId, asnOrder.getId())
                        .eq(AsnOrderItem::getMatnrCode, taskItem.getMatnrCode())
                        .eq(AsnOrderItem::getSplrBatch, taskItem.getBatch())
                );
                if (null != orderItem){
                    list.add(orderItem);
                }
            }
            containerWaveDto.setAsnOrderItems(list);
            containerWaveDtos.add(containerWaveDto);
        }
        if (waveIds.isEmpty()) {
            throw new CoolException("波次对应关联单未找到");
        }
        List<WaveOrderRela> waveOrderRelas = waveOrderRelaService.list(new LambdaQueryWrapper<WaveOrderRela>()
                .in(WaveOrderRela::getWaveId, waveIds));
        Set<Long> orderIds = waveOrderRelas.stream().map(WaveOrderRela::getOrderId).collect(Collectors.toSet());
        List<WkOrder> wkOrders = asnOrderService.listByIds(orderIds);
        if (wkOrders.isEmpty()) {
            throw new CoolException("单据不存在!!");
        }
        // 按订单创建时间排序,先创建的为主订单,后续为可追加
        wkOrders.sort(Comparator.comparing(WkOrder::getCreateTime, Comparator.nullsLast(Comparator.naturalOrder())));
        Set<String> codes = matnrCodes.isEmpty() ? taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()))
                .stream().map(TaskItem::getMatnrCode).filter(StringUtils::isNotBlank).collect(Collectors.toSet()) : matnrCodes;
        List<WkOrderItem> orderItems = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>()
                .in(WkOrderItem::getMatnrCode, codes)
                .in(WkOrderItem::getOrderId, orderIds));
        List<ContainerWaveItemDto> result = new ArrayList<>();
        Long firstOrderId = wkOrders.isEmpty() ? null : wkOrders.get(0).getId();
        for (WkOrderItem item : orderItems) {
            boolean appendable = firstOrderId != null && !firstOrderId.equals(item.getOrderId());
            result.add(new ContainerWaveItemDto().setOrderItem(item).setAppendable(appendable));
        }
        R r = R.ok("查询成功");
        r.put("list", result);
        return r;
        return R.ok(containerWaveDtos);
//        ArrayList<ContainerWaveDto> containerWaveDtos = new ArrayList<>();
////        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
//        for (TaskItem taskItem : taskItems) {
//            ContainerWaveDto containerWaveDto = new ContainerWaveDto();
//            containerWaveDto.setTaskItem(taskItem);
//            Wave wave = waveService.getById(taskItem.getSourceId());
//            if (null == wave) {
//                throw new CoolException("未找到料箱码对应波次");
//            }
//            List<WaveOrderRela> waveOrderRelas = waveOrderRelaService.list(new LambdaQueryWrapper<WaveOrderRela>()
//                    .eq(WaveOrderRela::getWaveId, wave.getId()));
//            if (Cools.isEmpty(waveOrderRelas)) {
//                throw new CoolException("波次对应关联单未找到");
//            }
//            Set<Long> ids = waveOrderRelas.stream().map(WaveOrderRela::getOrderId).collect(Collectors.toSet());
//            ArrayList<WkOrderItem> list = new ArrayList<>();
//            List<WkOrder> wkOrderList = asnOrderService.list(new LambdaQueryWrapper<WkOrder>().in(WkOrder::getId, ids));
//            for (WkOrder wkOrder : wkOrderList) {
//                List<WkOrderItem> orderItem = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>()
//                        .eq(WkOrderItem::getOrderId, wkOrder.getId())
//                        .eq(StringUtils.isNotEmpty(taskItem.getMatnrCode()), WkOrderItem::getMatnrCode, taskItem.getMatnrCode())
//                        .eq(StringUtils.isNotEmpty(taskItem.getBatch()), WkOrderItem::getSplrBatch, taskItem.getBatch()));
//                if (null != orderItem) {
//                    list.addAll(orderItem);
//                }
//            }
//            containerWaveDto.setWkOrderItems(list);
//            containerWaveDtos.add(containerWaveDto);
//        }
//        return R.ok("查询成功").add(wkOrders);
    }
    /**
     * @author Ryan
     * @date 2025/11/13
     * @description: 获取波次拣货明细
     * @version 1.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Synchronized
    public R saveWavePick(ContainerWaveParam containerWaveParam, Long loginUserId) {
        if (null == containerWaveParam || containerWaveParam.getContainerWaveDtos().size() <= 0){
            return R.error("参数错误");
    public R getWaveOrderItems(Map<String, Object> param) {
        if (Objects.isNull(param)) {
            return R.error("参数不能为空!!");
        }
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode,containerWaveParam.getContainer()));
        if (null == task){
            return R.error("未找到托盘对应的任务");
        // 票号暂不使用,注释校验
        // if (Objects.isNull(param.get("fieldsIndex"))) {
        //     return R.error("票号不能为空!!");
        // }
        if (Objects.isNull(param.get("barcode"))) {
            return R.error("料箱码不能为空!!");
        }
        if (!task.getTaskStatus().equals(TaskStsType.AWAIT.id)){
            return R.error("任务状态不是等待确认");
        if (Objects.isNull(param.get("orderId"))) {
            return R.error("订单ID不能为空!!");
        }
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, param.get("barcode").toString())
                .orderByDesc(Task::getId)
                .last("limit 1"));
        if (Objects.isNull(task)) {
            throw new CoolException("数据错误,任务档已不存在!!");
        }
        // 票号暂不使用,按任务取第一条明细
        // FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>()
        //         .eq(FieldsItem::getValue, param.get("fieldsIndex").toString())
        //         .last("limit 1"));
        // if (Objects.isNull(fieldsItem)) {
        //     return R.error("数据错误,票号不存在!!");
        // }
        TaskItem taskItem = null;
        FieldsItem fieldsItem = null;
        if (param.get("fieldsIndex") != null && StringUtils.isNotBlank(param.get("fieldsIndex").toString())) {
            fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>()
                    .eq(FieldsItem::getValue, param.get("fieldsIndex").toString())
                    .last("limit 1"));
            if (fieldsItem != null) {
                taskItem = taskItemService.getOne(new LambdaQueryWrapper<TaskItem>()
                        .eq(TaskItem::getFieldsIndex, fieldsItem.getUuid())
                        .eq(TaskItem::getTaskId, task.getId()));
            }
        }
        if (taskItem == null) {
            taskItem = taskItemService.getOne(new LambdaQueryWrapper<TaskItem>()
                    .eq(TaskItem::getTaskId, task.getId())
                    .last("limit 1"));
        }
        if (Objects.isNull(taskItem)) {
            return R.error("数据错误,任务档明细不存在!!");
        }
        // 票号暂不使用:仅当有 fieldsItem 时设置 extendFields
        if (fieldsItem != null) {
            Fields fields1 = fieldsService.getById(fieldsItem.getFieldsId());
            if (fields1 != null) {
                Map<String, String> fields = new HashMap<>();
                fields.put(fields1.getFields(), fieldsItem.getValue());
                taskItem.setExtendFields(fields);
            }
        }
        for (ContainerWaveDto containerWaveDto : containerWaveParam.getContainerWaveDtos()) {
            //做一次校验,判断前端所有出库数量是否超过本托出库数量
            double sum = containerWaveDto.getAsnOrderItems().stream().mapToDouble(AsnOrderItem::getDemandQty).sum();
            BigDecimal total = new BigDecimal(String.valueOf(sum));
            BigDecimal anfme = new BigDecimal(containerWaveDto.getTaskItem().getAnfme().toString());
            if (!anfme.equals(total)){
                throw new CoolException("播种数量不等于容器出库数量,请检查");
        return R.ok().add(taskItem);
    }
    /**
     * @author Ryan
     * @date 2025/11/13
     * @description: 波次明细拣货
     * @version 1.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public synchronized R wavePickItems(WavePickItemsParams params) {
        if (Objects.isNull(params.getBarcode())) {
            return R.error("料箱码不能为空!!");
        }
        if (Objects.isNull(params.getOrderId())) {
            return R.error("订单ID不能为空!!");
        }
        if (Objects.isNull(params.getTaskItems()) || params.getTaskItems().isEmpty()) {
            return R.error("拣货明细不能为空!");
        }
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, params.getBarcode())
                .orderByDesc(Task::getId)
                .last("limit 1"));
        if (null == task) {
            return R.error("未找到料箱对应的任务");
        }
        if (!task.getTaskStatus().equals(TaskStsType.WAVE_SEED.id)) {
            return R.error("任务状态不是揀料狀態");
        }
        WkOrder order = asnOrderService.getById(params.getOrderId());
        if (Objects.isNull(order)) {
            return R.error("单据信息不存在!!");
        }
        List<TaskItem> taskItems = params.getTaskItems();
        Map<String, List<TaskItem>> listMap = taskItems.stream().collect(Collectors.groupingBy(TaskItem::getMatnrCode));
        // 拣货完成仅扣减库位数量并累加 TaskItem.qty,不更新出库单/订单;待托盘全部拣完在 saveWavePick 再按顺序更新库存并校验
        Config config = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.ALLOW_OVER_CHANGE));
        listMap.keySet().forEach(code -> {
            List<TaskItem> items = listMap.get(code);
            WkOrderItem orderItem = asnOrderItemService.getOne(new LambdaQueryWrapper<WkOrderItem>()
                    .eq(WkOrderItem::getMatnrCode, code)
                    .eq(WkOrderItem::getOrderId, order.getId()));
            if (Objects.isNull(orderItem)) {
                throw new CoolException("数据错误,拣料不在单据需求中!!");
            }
            for (AsnOrderItem oldOrderItem : containerWaveDto.getAsnOrderItems()) {
                AsnOrderItem orderItem = asnOrderItemService.getById(oldOrderItem.getId());
                if (Double.compare(orderItem.getDemandQty(), 0.0) == 0) {
                    continue;
            Double summed = items.stream().mapToDouble(ti -> ti.getAnfme() != null ? ti.getAnfme() : 0.0).sum();
            Double pickQty = Math.round((orderItem.getQty() != null ? orderItem.getQty() : 0.0) + summed) * 1000000.0 / 1000000.0;
            if (!Objects.isNull(config) && !Boolean.parseBoolean(config.getVal())) {
                if (pickQty.compareTo(orderItem.getAnfme()) > 0.0) {
                    throw new CoolException("播种数量不能超出订单需求数量");
                }
                BigDecimal num = new BigDecimal(orderItem.getWorkQty().toString()).subtract(new BigDecimal(orderItem.getQty().toString()));
                BigDecimal orderDemandQty = new BigDecimal(orderItem.getDemandQty().toString());
                if (num.compareTo(orderDemandQty) < 0){
                    throw new CoolException("播种数量大于单据出库数量,请检查");
            }
            items.forEach(taskItem -> {
                TaskItem item = taskItemService.getById(taskItem.getId());
                if (Objects.isNull(item)) {
                    throw new CoolException("任务明细不存在!!");
                }
                orderItem.setQty(new BigDecimal(orderItem.getQty().toString()).add(orderDemandQty).doubleValue());
                if (!asnOrderItemService.updateById(orderItem)){
                    throw new CoolException("单据明细更新失败");
                }
                //检查单据是否完成
                Boolean orderChecked = checkOrderComplete(orderItem);
                if (orderChecked){
                    AsnOrder asnOrder = asnOrderService.getById(orderItem.getAsnId());
                    if (Cools.isEmpty(asnOrder)){
                        throw new CoolException("出库单主单未找到");
                    }
                    asnOrder.setExceStatus(AsnExceStatus.OUT_STOCK_STATUS_TASK_DONE.val);
                    if (!asnOrderService.updateById(asnOrder)){
                        throw new CoolException("出库单更新状态失败");
                if (!Objects.isNull(config) && !Boolean.parseBoolean(config.getVal())) {
                    Double v = Math.round(((item.getQty() != null ? item.getQty() : 0.0) + (taskItem.getAnfme() != null ? taskItem.getAnfme() : 0.0)) * 1000000.0) / 1000000.0;
                    if (item.getAnfme() != null && item.getAnfme().compareTo(v) < 0.0) {
                        throw new CoolException("当前物料已超出可拣范围,请核对后再操作!!");
                    }
                }
            }
            //检查波次是否完成
            Boolean waveChecked = checkWaveComplete(containerWaveDto.getTaskItem());
            if (waveChecked){
                Wave wave = waveService.getById(containerWaveDto.getTaskItem().getSourceId());
                if (null == wave){
                    throw new CoolException("未找到容器号对应波次");
                Double picQty = Math.round(((item.getQty() != null ? item.getQty() : 0.0) + (taskItem.getAnfme() != null ? taskItem.getAnfme() : 0.0)) * 1000000.0) / 1000000.0;
                item.setQty(picQty).setOrderId(order.getId()).setOrderItemId(orderItem.getId());
                if (!taskItemService.updateById(item)) {
                    throw new CoolException("拣货数量更新失败!!");
                }
                wave.setExceStatus(WaveExceStatus.WAVE_EXCE_STATUS_DONE.val);
                if (!waveService.updateById(wave)){
                    throw new CoolException("波次单更新状态失败");
                if (StringUtils.isNotBlank(task.getOrgLoc())) {
                    LocItem locItem = locItemService.getOne(new LambdaQueryWrapper<LocItem>()
                            .eq(LocItem::getLocCode, task.getOrgLoc())
                            .eq(LocItem::getMatnrId, item.getMatnrId())
                            .eq(StringUtils.isNotBlank(item.getBatch()), LocItem::getBatch, item.getBatch())
                            .eq(StringUtils.isNotBlank(item.getFieldsIndex()), LocItem::getFieldsIndex, item.getFieldsIndex()));
                    if (Objects.nonNull(locItem)) {
                        Double pickAmt = taskItem.getAnfme() != null ? taskItem.getAnfme() : 0.0;
                        Double newAnfme = Math.round((locItem.getAnfme() - pickAmt) * 1000000.0) / 1000000.0;
                        if (newAnfme.compareTo(0.0) <= 0) {
                            locItemService.removeById(locItem.getId());
                        } else {
                            locItem.setAnfme(newAnfme)
                                    .setUpdateBy(SystemAuthUtils.getLoginUserId())
                                    .setUpdateTime(new Date());
                            if (!locItemService.updateById(locItem)) {
                                throw new CoolException("库位明细数量扣减失败!!");
                            }
                        }
                    }
                }
            });
        });
        return R.ok();
    }
    /**
     * @author Ryan
     * @date 2025/11/19
     * @description: 获取出库任务拣货明细
     * @version 1.0
     */
    @Override
    public R getTaskItems(Map<String, String> params) {
        if (Objects.isNull(params.get("barcode"))) {
            throw new CoolException("拖盘码不能为空!!");
        }
        List<Integer> integers = Arrays.asList(TaskType.TASK_TYPE_OUT.type, TaskType.TASK_TYPE_EMPITY_OUT.type, TaskType.TASK_TYPE_PICK_AGAIN_OUT.type, TaskType.TASK_TYPE_CHECK_OUT.type);
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>()
                .in(Task::getTaskType, integers)
                .eq(Task::getBarcode, params.get("barcode")), false);
        if (Objects.isNull(task)) {
            return R.error("料箱所在任务不存在!!");
        }
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
        taskItems.forEach(taskItem -> {
            if (!Objects.isNull(taskItem.getFieldsIndex())) {
                Map<String, String> fields = FieldsUtils.getFields(taskItem.getFieldsIndex());
                taskItem.setExtendFields(fields);
            }
        });
        return R.ok().add(taskItems);
    }
    /**
     * @author Ryan
     * @date 2025/11/19
     * @description: 修改出库任务档明细票号
     * @version 1.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R modifyTaskItem(List<TaskItem> items) {
        if (Objects.isNull(items) || items.isEmpty()) {
            return R.error("参数不能为空!!");
        }
        for (TaskItem item : items) {
            // 票号暂不使用,跳过修改出库任务档明细票号逻辑
            continue;
            /*
            if (Objects.isNull(item.getCrushNo())) {
                continue;
            }
            TaskItem byId = taskItemService.getById(item.getId());
            if (!Objects.isNull(byId.getFieldsIndex())) {
                Map<String, String> fields = FieldsUtils.getFields(byId.getFieldsIndex());
                byId.setExtendFields(fields);
            }
            if (byId.getExtendFields() != null && byId.getExtendFields().get("crushNo") != null && byId.getExtendFields().get("crushNo").equals(item.getCrushNo())) {
                continue;
            }
            FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>()
                    .eq(FieldsItem::getValue, item.getCrushNo())
                    .last("limit 1"));
            if (Objects.isNull(fieldsItem)) {
                throw new CoolException("库存不存在!!");
            }
            String uuid = fieldsItem.getUuid();
            item.setFieldsIndex(uuid).setExtendFields(null);
            if (!taskItemService.updateById(item)) {
               throw new CoolException("任务明细修改失败");
            }
            LocItemWorking oldOne = locItemWorkingService.getOne(new LambdaQueryWrapper<LocItemWorking>()
                    .eq(LocItemWorking::getTaskId, byId.getTaskId())
                    .eq(LocItemWorking::getMatnrCode, byId.getMatnrCode())
                    .eq(LocItemWorking::getFieldsIndex, byId.getFieldsIndex()));
            if (Objects.isNull(oldOne)) {
                throw new CoolException("明细不存在或已出库!!");
            }
            LocItemWorking one = locItemWorkingService.getOne(new LambdaQueryWrapper<LocItemWorking>()
                    .eq(LocItemWorking::getTaskId, byId.getTaskId())
                    .eq(LocItemWorking::getMatnrCode, byId.getMatnrCode())
                    .eq(LocItemWorking::getFieldsIndex, uuid));
            if (Objects.isNull(one)) {
                throw new CoolException("明细不存在或已出库!!");
            }
            one.setWorkQty(oldOne.getWorkQty());
            oldOne.setWorkQty(0.0);
        task.setTaskStatus(TaskStsType.COMPLETE_OUT.id);
        if (!taskService.updateById(task)){
            throw new CoolException("任务状态更新失败");
            //更新库位信息
            locItemWorkingService.updateById(oldOne);
            locItemWorkingService.updateById(one);
            */
        }
        return R.ok();
    }
    /**
     * @author Ryan
     * @date 2025/11/5
     * @description: 波次拣货
     * @version 1.0
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @Synchronized
    public R saveWavePick(ContainerWaveParam containerWaveParam, Long loginUserId) {
        if (null == containerWaveParam ) {
            return R.error("参数错误");
        }
        List<WkOrderItem> orderItems = containerWaveParam.getContainerWaveDtos();
        if (Objects.isNull(orderItems) || orderItems.isEmpty()) {
            return R.error("数据错误!!");
        }
        Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, containerWaveParam.getContainer())
                .orderByDesc(Task::getId)
                .last("limit 1"));
        if (null == task) {
            return R.error("未找到料箱对应的任务");
        }
        if (!task.getTaskStatus().equals(TaskStsType.WAVE_SEED.id)) {
            return R.error("任务状态不是待揀狀態");
        }
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId()));
        // 必须当前托盘关联出库单全部拣货完成才允许确认
        for (TaskItem ti : taskItems) {
            Double q = ti.getQty() != null ? ti.getQty() : 0.0;
            Double a = ti.getAnfme() != null ? ti.getAnfme() : 0.0;
            if (q.compareTo(a) < 0) {
                throw new CoolException("有单据物料未拣完,请完成该托盘下所有订单拣货后再确认!!");
            }
        }
        // 按顺序更新出库单明细、订单及库存流水(与 wavePickItems 原逻辑一致,在全部拣完后统一执行)
        Map<Long, List<TaskItem>> byOrder = taskItems.stream()
                .filter(ti -> ti.getOrderId() != null)
                .collect(Collectors.groupingBy(TaskItem::getOrderId));
        List<Long> orderIds = new ArrayList<>(byOrder.keySet());
        orderIds.sort(Long::compareTo);
        for (Long orderId : orderIds) {
            WkOrder order = asnOrderService.getById(orderId);
            if (order == null) continue;
            List<TaskItem> items = byOrder.get(orderId);
            Map<String, List<TaskItem>> byMatnr = items.stream().collect(Collectors.groupingBy(TaskItem::getMatnrCode));
            for (String code : byMatnr.keySet()) {
                List<TaskItem> matItems = byMatnr.get(code);
                WkOrderItem orderItem = asnOrderItemService.getOne(new LambdaQueryWrapper<WkOrderItem>()
                        .eq(WkOrderItem::getMatnrCode, code)
                        .eq(WkOrderItem::getOrderId, orderId));
                if (orderItem == null) continue;
                Double summed = matItems.stream().mapToDouble(t -> t.getQty() != null ? t.getQty() : 0.0).sum();
                orderItem.setQty(summed);
                asnOrderItemService.updateById(orderItem);
                String ruleCode = SerialRuleUtils.generateRuleCode(SerialRuleCode.SYS_STOCK_CODE, null);
                if (StringUtils.isBlank(ruleCode)) continue;
                Stock stock = new Stock();
                stock.setCode(ruleCode)
                        .setUpdateBy(loginUserId)
                        .setBarcode(task.getBarcode())
                        .setLocCode(task.getOrgLoc())
                        .setType(order.getType())
                        .setWkType(Short.parseShort(order.getWkType()))
                        .setSourceId(orderItem.getOrderId())
                        .setSourceCode(orderItem.getOrderCode())
                        .setUpdateTime(new Date())
                        .setAnfme(summed);
                if (!stockService.save(stock)) continue;
                List<StockItem> stockItems = new ArrayList<>();
                for (TaskItem ti : matItems) {
                    StockItem si = new StockItem();
                    BeanUtils.copyProperties(ti, si);
                    si.setStockId(stock.getId()).setAnfme(ti.getQty()).setStockCode(stock.getCode()).setSourceItemId(orderItem.getId());
                    stockItems.add(si);
                }
                stockItemService.saveBatch(stockItems);
            }
            List<WkOrderItem> ois = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>().eq(WkOrderItem::getOrderId, orderId));
            Double total = ois.stream().mapToDouble(oi -> oi.getQty() != null ? oi.getQty() : 0.0).sum();
            Double wkQty = ois.stream().mapToDouble(oi -> oi.getWorkQty() != null ? oi.getWorkQty() : 0.0).sum();
            double v = (order.getWorkQty() != null && order.getWorkQty().compareTo(wkQty) < 0) ? 0.0 : Math.round((total - wkQty) * 1000000.0) / 1000000.0;
            order.setQty(total).setWorkQty(v);
            asnOrderService.updateById(order);
        }
        try {
            if (task.getTaskType().equals(TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)) {
                taskService.pickOrCheckTask(task.getId(), "");
            } else if (task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_OUT.type)) {
                taskService.pickOrCheckTask(task.getId(), Constants.TASK_TYPE_OUT_CHECK);
            } else {
                task.setTaskStatus(TaskStsType.UPDATED_OUT.id);
                if (!taskService.updateById(task)) {
                    throw new CoolException("任务状态更新失败");
                }
            }
        } catch (Exception e) {
            throw new CoolException("分拣失败:" + e.getMessage());
        }
        return R.ok();
    }
    private Boolean checkWaveComplete(TaskItem taskItem) {
        Wave wave = waveService.getById(taskItem.getSourceId());
        List<AsnOrder> asnOrderList = asnOrderService.list(new LambdaQueryWrapper<AsnOrder>().eq(AsnOrder::getWaveId, wave.getId()));
        return asnOrderList.stream().allMatch(item -> new BigDecimal(item.getAnfme().toString()).equals(new BigDecimal(item.getQty().toString())));
        List<WkOrder> wkOrderList = asnOrderService.list(new LambdaQueryWrapper<WkOrder>().eq(WkOrder::getWaveId, wave.getId()));
        return wkOrderList.stream().allMatch(item -> new BigDecimal(item.getAnfme().toString()).equals(new BigDecimal(item.getQty().toString())));
    }
    private Boolean checkOrderComplete(AsnOrderItem orderItem) {
        List<AsnOrderItem> asnOrderItems = asnOrderItemService.list(new LambdaQueryWrapper<AsnOrderItem>().eq(AsnOrderItem::getAsnCode, orderItem.getAsnCode()));
        return asnOrderItems.stream().allMatch(item -> new BigDecimal(item.getAnfme().toString()).equals(new BigDecimal(item.getQty().toString())));
    private Boolean checkOrderComplete(WkOrderItem orderItem) {
        List<WkOrderItem> wkOrderItems = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>().eq(WkOrderItem::getOrderCode, orderItem.getOrderCode()));
        return wkOrderItems.stream().allMatch(item -> new BigDecimal(item.getAnfme().toString()).equals(new BigDecimal(item.getQty().toString())));
    }
}