From c67e3d0295858a61122354a15dec8835044bac0d Mon Sep 17 00:00:00 2001
From: chen.lin <1442464845@qq.com>
Date: 星期二, 24 二月 2026 15:54:53 +0800
Subject: [PATCH] 库位拣料出库数量调整

---
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java |  241 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 237 insertions(+), 4 deletions(-)

diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
index 3b301d7..e2cf9c4 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/OutStockServiceImpl.java
+++ b/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,125 @@
     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) {
+        // 鍦� getMatnrPage 涔嬪墠鍙栧嚭 locUseStatus锛歡etMatnrPage 浼氫粠 where.map 涓� remove 鎺夎閿紙涓� params 鍚屽紩鐢級锛屽鑷村悗缁彇涓嶅埌
+        String locUseStatus = params.get("locUseStatus") != null ? params.get("locUseStatus").toString().trim() : null;
+        if (locUseStatus != null && locUseStatus.isEmpty()) locUseStatus = null;
+
+        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());
+        List<Map<String, Object>> stockByLocList = locItemMapper.listStockByMatnrIdsGroupByStatusAndLoc(matnrIds, locUseStatus);
+        Map<Long, List<Map<String, Object>>> rowsByMatnr = buildRowsByMatnrFromPerLoc(stockByLocList);
+        List<Matnr> expanded = new ArrayList<>();
+        for (Matnr record : records) {
+            List<Map<String, Object>> statusRows = rowsByMatnr.get(record.getId());
+            if (statusRows == null || statusRows.isEmpty()) {
+                record.setStockQty(0d);
+                record.setLocUseStatus$(null);
+                record.setLocCodes$(null);
+                expanded.add(record);
+                continue;
+            }
+            for (Map<String, Object> row : statusRows) {
+                double v = row.get("stockQty") instanceof Number ? ((Number) row.get("stockQty")).doubleValue() : 0d;
+                String useStatus = getStr(row, "useStatus", "usestatus");
+                String statusDesc = useStatus != null ? LocStsType.getDescByType(useStatus) : null;
+                String locCodesWithQty = getStr(row, "locCodes$", "loccodes$");
+                Matnr copy = cloneMatnrForRow(record);
+                copy.setStockQty(v);
+                copy.setLocUseStatus$(statusDesc);
+                copy.setLocCodes$(locCodesWithQty);
+                expanded.add(copy);
+            }
+        }
+        page.setRecords(expanded);
+        return page;
+    }
+
+    /** 澶嶅埗鐗╂枡鐢ㄤ簬鎸夌姸鎬佸睍寮�琛岋紙浠呭鍒跺睍绀虹敤瀛楁锛宨d 淇濇寔鍘熸牱渚涘墠绔�夎鐢級 */
+    private static Matnr cloneMatnrForRow(Matnr source) {
+        Matnr copy = new Matnr();
+        BeanUtils.copyProperties(source, copy, "stockQty", "locUseStatus$", "locCodes$");
+        return copy;
+    }
+
+    /**
+     * 浠呯敤銆屾寜搴撲綅鏄庣粏銆嶆煡璇㈢粨鏋滃湪鍐呭瓨涓垎缁勬眹鎬伙細鎸� (matnrId, useStatus) 鑱氬悎锛�
+     * 寰楀埌 stockQty銆乴ocCodes$锛堝簱浣�(鏁伴噺),...锛夛紝鍑忚交鏁版嵁搴撳帇鍔涖��
+     */
+    private static Map<Long, List<Map<String, Object>>> buildRowsByMatnrFromPerLoc(List<Map<String, Object>> stockByLocList) {
+        Map<String, List<Map<String, Object>>> perLocByMatnrAndStatus = new HashMap<>();
+        for (Map<String, Object> locRow : stockByLocList) {
+            Long mid = getLong(locRow, "matnrId", "matnrid");
+            String us = getStr(locRow, "useStatus", "usestatus");
+            if (mid == null || us == null) continue;
+            String key = mid + ":" + us;
+            perLocByMatnrAndStatus.computeIfAbsent(key, k -> new ArrayList<>()).add(locRow);
+        }
+        Map<Long, List<Map<String, Object>>> rowsByMatnr = new HashMap<>();
+        for (Map.Entry<String, List<Map<String, Object>>> e : perLocByMatnrAndStatus.entrySet()) {
+            String[] parts = e.getKey().split(":", 2);
+            if (parts.length != 2) continue;
+            Long matnrId;
+            try { matnrId = Long.parseLong(parts[0]); } catch (NumberFormatException ex) { continue; }
+            String useStatus = parts[1];
+            List<Map<String, Object>> locs = e.getValue();
+            double stockQty = 0d;
+            StringBuilder sb = new StringBuilder();
+            for (Map<String, Object> locRow : locs) {
+                Object q = getAny(locRow, "locQty", "locqty");
+                double qty = q instanceof Number ? ((Number) q).doubleValue() : 0d;
+                stockQty += qty;
+                String code = getStr(locRow, "locCode", "loccode");
+                if (sb.length() > 0) sb.append(",");
+                sb.append(code != null ? code : "").append("(").append(formatQtyForLoc(qty)).append(")");
+            }
+            Map<String, Object> statusRow = new HashMap<>();
+            statusRow.put("matnrId", matnrId);
+            statusRow.put("useStatus", useStatus);
+            statusRow.put("stockQty", stockQty);
+            statusRow.put("locCodes$", sb.toString());
+            rowsByMatnr.computeIfAbsent(matnrId, k -> new ArrayList<>()).add(statusRow);
+        }
+        return rowsByMatnr;
+    }
+
+    private static String formatQtyForLoc(double qty) {
+        if (qty == (long) qty) return String.valueOf((long) qty);
+        String s = BigDecimal.valueOf(qty).setScale(6, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString();
+        return s;
+    }
+
+    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 +541,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 +556,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("璇ユ槑缁嗗凡鐢熸垚宸ヤ綔妗o紝涓嶈兘鍒犻櫎");
+            }
+        }
+        // 宸茬敓鎴愬伐浣滄。鐨勬槑缁嗕笉鍏佽淇敼
+        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("璇ユ槑缁嗗凡鐢熸垚宸ヤ綔妗o紝涓嶈兘淇敼");
+            }
+        }
         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();
+    }
+
+    /**
+     * 鍒ゆ柇鍑哄簱鍗曟槑缁嗘槸鍚﹀凡鐢熸垚宸ヤ綔妗o紙瀛樺湪鍏宠仈鐨勪换鍔℃槑缁嗭級
+     */
+    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,17 +792,21 @@
 
                 locItem.setOutQty(param.getOutQty())
                         .setBatch(param.getBatch())
+                        .setOrderId(outId)
+                        .setOrderItemId(orderItem.getId())
                         .setSourceId(outId)
                         .setSourceCode(orderItem.getOrderCode())
                         .setSource(orderItem.getId());
                 locItems.add(locItem);
 
                 LocToTaskParams taskParams = new LocToTaskParams();
+                // 鍑哄簱鍗曚笅鍙戜换鍔℃椂锛屽嚭搴撳彛鏈紶鍒欓粯璁� 1001
+                String siteNo = StringUtils.isNotBlank(param.getSiteNo()) ? param.getSiteNo() : "1001";
                 taskParams.setType(Constants.TASK_TYPE_ORDER_OUT_STOCK)
                         .setOrgLoc(loc.getCode())
                         .setItems(locItems)
                         .setSourceId(outId)
-                        .setSiteNo(param.getSiteNo());
+                        .setSiteNo(siteNo);
                 try {
                     //鐢熸垚鍑哄簱浠诲姟
                     locItemService.generateTask(TaskResouceType.TASK_RESOUCE_ORDER_TYPE.val, taskParams, loginUserId);
@@ -636,7 +853,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 +931,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);
@@ -721,6 +946,14 @@
                             .eq(DeviceSite::getChannel, loc.getChannel())
                             .eq(DeviceSite::getType, issued.doubleValue() >= locItem.getAnfme() && itemList.size() == 1 ? TaskType.TASK_TYPE_OUT.type : TaskType.TASK_TYPE_PICK_AGAIN_OUT.type)
                     );
+                    // 鍑哄簱鍙e垪琛ㄦ帓搴忥細1001 鎺掔涓�锛屼綔涓洪粯璁�
+                    deviceSites.sort((a, b) -> {
+                        boolean a1001 = "1001".equals(a.getSite());
+                        boolean b1001 = "1001".equals(b.getSite());
+                        if (a1001 && !b1001) return -1;
+                        if (!a1001 && b1001) return 1;
+                        return 0;
+                    });
 
                     if (!deviceSites.isEmpty()) {
                         List<OrderOutItemDto.staListDto> maps = new ArrayList<>();
@@ -733,7 +966,7 @@
                         orderOutItemDto.setStaNos(maps);
                         //榛樿鑾峰彇绗竴绔欑偣
                         DeviceSite deviceSite = deviceSites.stream().findFirst().get();
-                        orderOutItemDto.setSiteNo(deviceSite.getSite());
+                        orderOutItemDto.setSitesNo(deviceSite.getSite());
                     }
 
                     list.add(orderOutItemDto);

--
Gitblit v1.9.1