chen.lin
1 天以前 bb7dd1f513149ecd2887895c807861fdd06a43f6
rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
@@ -8,12 +8,16 @@
import com.vincent.rsf.framework.exception.CoolException;
import com.vincent.rsf.server.api.utils.LocUtils;
import com.vincent.rsf.server.common.constant.Constants;
import com.vincent.rsf.server.common.domain.BaseParam;
import com.vincent.rsf.server.common.domain.PageParam;
import com.vincent.rsf.server.manager.controller.dto.ExistDto;
import com.vincent.rsf.server.manager.controller.dto.OrderOutItemDto;
import com.vincent.rsf.server.manager.controller.params.*;
import com.vincent.rsf.server.manager.enums.*;
import com.vincent.rsf.server.manager.entity.Matnr;
import com.vincent.rsf.server.manager.entity.*;
import com.vincent.rsf.server.manager.mapper.AsnOrderMapper;
import com.vincent.rsf.server.manager.mapper.LocItemMapper;
import com.vincent.rsf.server.manager.service.*;
import com.vincent.rsf.server.manager.utils.LocManageUtil;
import com.vincent.rsf.server.manager.utils.OptimalAlgorithmUtil;
@@ -81,7 +85,73 @@
    private LocService locService;
    @Autowired
    private WaveOrderRelaServiceImpl waveOrderRelaService;
    @Autowired
    private TaskItemService taskItemService;
    @Autowired
    private LocItemMapper locItemMapper;
    @Override
    public PageParam<Matnr, BaseParam> pageMatnrForOutStock(PageParam<Matnr, BaseParam> pageParam, Map<String, Object> params) {
        PageParam<Matnr, BaseParam> page = matnrService.getMatnrPage(pageParam, params);
        List<Matnr> records = page.getRecords();
        if (records == null || records.isEmpty()) {
            return page;
        }
        List<Long> matnrIds = records.stream().map(Matnr::getId).collect(Collectors.toList());
        String locUseStatus = params.get("locUseStatus") != null ? params.get("locUseStatus").toString() : null;
        List<Map<String, Object>> stockList = locItemMapper.listStockByMatnrIds(matnrIds, locUseStatus);
        Map<Long, Double> stockQtyMap = new HashMap<>();
        Map<Long, String> locStatusDescMap = new HashMap<>();
        Map<Long, String> locCodesMap = new HashMap<>();
        for (Map<String, Object> row : stockList) {
            Long matnrId = getLong(row, "matnrId", "matnrid");
            if (matnrId == null) continue;
            Object qty = getAny(row, "stockQty", "stockqty");
            double v = qty instanceof Number ? ((Number) qty).doubleValue() : 0d;
            stockQtyMap.put(matnrId, v);
            String locCodes = getStr(row, "locCodes", "loccodes");
            if (locCodes != null && !locCodes.isEmpty()) {
                locCodesMap.put(matnrId, locCodes);
            }
            String locStatuses = getStr(row, "locStatuses", "locstatuses");
            if (locStatuses != null && !locStatuses.isEmpty()) {
                String desc = Arrays.stream(locStatuses.split(","))
                        .map(String::trim)
                        .map(LocStsType::getDescByType)
                        .collect(Collectors.joining(","));
                locStatusDescMap.put(matnrId, desc);
            } else if (locUseStatus != null && !locUseStatus.isEmpty()) {
                locStatusDescMap.put(matnrId, LocStsType.getDescByType(locUseStatus));
            }
        }
        for (Matnr record : records) {
            record.setStockQty(stockQtyMap.getOrDefault(record.getId(), 0d));
            record.setLocUseStatus$(locStatusDescMap.get(record.getId()));
            record.setLocCodes$(locCodesMap.get(record.getId()));
        }
        return page;
    }
    private static Long getLong(Map<String, Object> map, String... keys) {
        Object v = getAny(map, keys);
        if (v == null) return null;
        if (v instanceof Long) return (Long) v;
        if (v instanceof Number) return ((Number) v).longValue();
        try { return Long.parseLong(v.toString()); } catch (NumberFormatException e) { return null; }
    }
    private static String getStr(Map<String, Object> map, String... keys) {
        Object v = getAny(map, keys);
        return v != null ? v.toString() : null;
    }
    private static Object getAny(Map<String, Object> map, String... keys) {
        for (String key : keys) {
            Object v = map.get(key);
            if (v != null) return v;
        }
        return null;
    }
    /**
     * @param
@@ -419,6 +489,7 @@
     * @time 2025/4/29 13:47
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public R updateOrderItem(AsnOrderAndItemsParams params, Long loginUserId) {
        WkOrder orders = params.getOrders();
        if (Objects.isNull(orders)) {
@@ -433,12 +504,102 @@
        if (Objects.isNull(params.getItems()) || params.getItems().isEmpty()) {
            throw new CoolException("明细参数不能为空!!");
        }
        // 删除/修改明细前收集当前单据下的明细 id,用于校验与同步库位状态
        List<Long> existingIds = asnOrderItemService.list(
                new LambdaQueryWrapper<WkOrderItem>().eq(WkOrderItem::getOrderId, orders.getId()))
                .stream().map(WkOrderItem::getId).collect(Collectors.toList());
        Set<Long> requestedIds = params.getItems().stream()
                .map(item -> item.get("id"))
                .filter(Objects::nonNull)
                .map(id -> Long.valueOf(id.toString()))
                .collect(Collectors.toSet());
        // 已生成工作档的明细不允许删除
        for (Long existingId : existingIds) {
            if (!requestedIds.contains(existingId) && hasGeneratedTask(orders.getId(), existingId)) {
                throw new CoolException("该明细已生成工作档,不能删除");
            }
        }
        // 已生成工作档的明细不允许修改
        for (Map<String, Object> item : params.getItems()) {
            Object idObj = item.get("id");
            if (idObj != null && hasGeneratedTask(orders.getId(), Long.valueOf(idObj.toString()))) {
                throw new CoolException("该明细已生成工作档,不能修改");
            }
        }
        try {
            svaeOrUpdateOrderItem(params, loginUserId);
        } catch (Exception e) {
            throw new CoolException(e.getMessage());
        }
        // 对本次被删除的明细(仅初始化状态):先同步库位状态,再删除明细记录
        for (Long existingId : existingIds) {
            if (!requestedIds.contains(existingId)) {
                syncLocStatusOnOrderItemRemoved(orders.getId(), existingId, loginUserId);
                outStockItemService.removeById(existingId);
            }
        }
        // 重新汇总主单数量(删除明细后)
        List<WkOrderItem> afterItems = asnOrderItemService.list(new LambdaQueryWrapper<WkOrderItem>()
                .eq(WkOrderItem::getOrderId, orders.getId()));
        Double sum = afterItems.stream().mapToDouble(WkOrderItem::getAnfme).sum();
        orders.setAnfme(sum);
        this.updateById(orders);
        return R.ok();
    }
    /**
     * 判断出库单明细是否已生成工作档(存在关联的任务明细)
     */
    private boolean hasGeneratedTask(Long orderId, Long orderItemId) {
        return taskItemService.count(new LambdaQueryWrapper<TaskItem>()
                .eq(TaskItem::getSourceId, orderId)
                .eq(TaskItem::getOrderItemId, orderItemId)) > 0;
    }
    /**
     * 出库单明细被删除时同步库位状态:释放该明细关联的库位预约、回滚 LocItem.workQty、恢复库位为在库(F)
     */
    private void syncLocStatusOnOrderItemRemoved(Long orderId, Long orderItemId, Long loginUserId) {
        List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>()
                .eq(TaskItem::getSourceId, orderId)
                .eq(TaskItem::getOrderItemId, orderItemId));
        if (taskItems.isEmpty()) {
            return;
        }
        Set<Long> affectedLocIds = new HashSet<>();
        Date now = new Date();
        for (TaskItem taskItem : taskItems) {
            if (taskItem.getSource() == null) {
                continue;
            }
            LocItem locItem = locItemService.getById(taskItem.getSource());
            if (locItem == null) {
                continue;
            }
            Double anfme = taskItem.getAnfme() != null ? taskItem.getAnfme() : 0.0;
            Double newWorkQty = Math.round((locItem.getWorkQty() - anfme) * 1000000) / 1000000.0;
            locItem.setWorkQty(newWorkQty >= 0 ? newWorkQty : 0)
                    .setOrderId(null)
                    .setOrderItemId(null)
                    .setUpdateBy(loginUserId)
                    .setUpdateTime(now);
            locItemService.updateById(locItem);
            affectedLocIds.add(locItem.getLocId());
        }
        for (Long locId : affectedLocIds) {
            long stillReserved = locItemService.count(new LambdaQueryWrapper<LocItem>()
                    .eq(LocItem::getLocId, locId)
                    .isNotNull(LocItem::getOrderId));
            if (stillReserved == 0) {
                Loc loc = locService.getById(locId);
                if (loc != null && LocStsType.LOC_STS_TYPE_R.type.equals(loc.getUseStatus())) {
                    loc.setUseStatus(LocStsType.LOC_STS_TYPE_F.type)
                            .setUpdateBy(loginUserId)
                            .setUpdateTime(now);
                    locService.updateById(loc);
                }
            }
        }
    }
    @Override
@@ -579,6 +740,8 @@
                locItem.setOutQty(param.getOutQty())
                        .setBatch(param.getBatch())
                        .setOrderId(outId)
                        .setOrderItemId(orderItem.getId())
                        .setSourceId(outId)
                        .setSourceCode(orderItem.getOrderCode())
                        .setSource(orderItem.getId());
@@ -636,7 +799,12 @@
                TaskType.TASK_TYPE_OUT.type,
                TaskType.TASK_TYPE_MERGE_OUT.type,
                TaskType.TASK_TYPE_PICK_AGAIN_OUT.type);
        List<DeviceSite> sites = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>().in(DeviceSite::getType, list).groupBy(DeviceSite::getSite));
        // 先查全部再按 site 去重,避免 GROUP BY 与 only_full_group_by 冲突
        List<DeviceSite> all = deviceSiteService.list(new LambdaQueryWrapper<DeviceSite>().in(DeviceSite::getType, list));
        List<DeviceSite> sites = all.stream()
                .collect(Collectors.toMap(DeviceSite::getSite, d -> d, (a, b) -> a))
                .values().stream()
                .collect(Collectors.toList());
        return R.ok(sites);
    }
@@ -709,10 +877,13 @@
                }
                // 该库位可分配数量:取本行待分配与库位库存的较小值
                double allocatable = Math.min(issued.doubleValue(), locItem.getAnfme() != null ? locItem.getAnfme() : 0);
                // 当分配量等于库位库存时,使用库位库存精度作为出库数量,避免截断导致界面显示/库存校验不一致(如库存15.123457被截成15.123)
                double outQtyToSet = (locItem.getAnfme() != null && Math.abs(allocatable - locItem.getAnfme()) < 1e-6)
                        ? locItem.getAnfme() : allocatable;
                ExistDto existDto = new ExistDto().setBatch(locItem.getBatch()).setMatnr(locItem.getMatnrCode()).setLocNo(locItem.getLocCode());
                if (existDtos.add(existDto)) {
                    // 首次使用该库位:加入列表并扣减 issued
                    locItem.setOutQty(allocatable);
                    locItem.setOutQty(outQtyToSet);
                    locItem.setBarcode(loc.getBarcode());
                    OrderOutItemDto orderOutItemDto = new OrderOutItemDto();
                    orderOutItemDto.setLocItem(locItem);