From db06b944e7886832d20b8e3ae62b2cb70bcba30f Mon Sep 17 00:00:00 2001
From: 1 <1@123>
Date: 星期五, 17 四月 2026 13:59:50 +0800
Subject: [PATCH] lsh#订单完结

---
 rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/phyz/impl/ErpReportServiceImpl.java |  806 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 801 insertions(+), 5 deletions(-)

diff --git a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/phyz/impl/ErpReportServiceImpl.java b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/phyz/impl/ErpReportServiceImpl.java
index 3e070c8..d62a7be 100644
--- a/rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/phyz/impl/ErpReportServiceImpl.java
+++ b/rsf-open-api/src/main/java/com/vincent/rsf/openApi/service/phyz/impl/ErpReportServiceImpl.java
@@ -3,18 +3,25 @@
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.cfg.CoercionAction;
 import com.fasterxml.jackson.databind.cfg.CoercionInputShape;
 import com.vincent.rsf.framework.exception.CoolException;
 import com.vincent.rsf.openApi.config.PlatformProperties;
+import com.vincent.rsf.openApi.entity.app.OpenApiOrder;
+import com.vincent.rsf.openApi.entity.app.OpenApiOrderItem;
+import com.vincent.rsf.openApi.entity.app.OpenApiOrderItemMap;
 import com.vincent.rsf.openApi.entity.constant.WmsConstant;
 import com.vincent.rsf.openApi.entity.dto.CommonResponse;
-import com.vincent.rsf.openApi.entity.phyz.ErpReportParams;
-import com.vincent.rsf.openApi.entity.phyz.Material;
-import com.vincent.rsf.openApi.entity.phyz.Order;
-import com.vincent.rsf.openApi.entity.phyz.Warehouse;
+import com.vincent.rsf.openApi.entity.phyz.*;
+import com.vincent.rsf.openApi.entity.params.ReportDataParam;
+import com.vincent.rsf.openApi.entity.params.ReportParams;
+import com.vincent.rsf.openApi.mapper.OpenApiOrderItemMapMapper;
+import com.vincent.rsf.openApi.mapper.OpenApiOrderItemMapper;
+import com.vincent.rsf.openApi.mapper.OpenApiOrderMapper;
 import com.vincent.rsf.openApi.service.WmsErpService;
 import com.vincent.rsf.openApi.service.phyz.ErpReportService;
 import com.vincent.rsf.openApi.utils.ParamsMapUtils;
@@ -45,9 +52,12 @@
 import java.security.cert.X509Certificate;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.*;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -65,6 +75,14 @@
     private WmsErpService wmsErpService;
     @Resource
     private RestTemplate restTemplate;
+    @Resource
+    private OpenApiOrderMapper openApiOrderMapper;
+    @Resource
+    private OpenApiOrderItemMapper openApiOrderItemMapper;
+    @Resource
+    private OpenApiOrderItemMapMapper openApiOrderItemMapMapper;
+
+    private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
 
 
     @PostConstruct
@@ -129,6 +147,7 @@
             map.put("spec", material.getSpec());
             map.put("describle", material.getDescribe());
             map.put("unit", material.getUnit());
+            map.put("operateType", material.getOperateType());
             mappedList.add(map);
         }
         return mappedList;
@@ -170,6 +189,71 @@
             }
         }
     }
+    @Override
+    public String syncCustomer(List<Customer> customerList){
+        if (customerList.isEmpty()) {
+            throw new CoolException("鍙傛暟涓嶈兘涓虹┖锛侊紒");
+        }
+        /**WMS鍩虹閰嶇疆閾炬帴*/
+        String wmsUrl = wmsApi.getHost() + ":" + wmsApi.getPort() + WmsConstant.SYNC_COMPANIES;
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Content-Type", "application/json");
+        headers.add("api-version", "v2.0");
+        // Customer -> CompaniesParam 瀛楁涓�涓�瀵瑰簲鍚庡啀涓嬪彂
+        List<Map<String, Object>> companiesParamsList = customerToCompaniesParams(customerList);
+        HttpEntity httpEntity2 = new HttpEntity<>(companiesParamsList, headers);//cs
+        ResponseEntity<String> exchange = restTemplate.exchange(wmsUrl, HttpMethod.POST, httpEntity2, String.class);//cs
+        log.info("鍚屾瀹㈡埛淇℃伅杩斿洖缁撴灉锛� {}", exchange);
+        if (Objects.isNull(exchange.getBody())) {
+            throw new CoolException("鏌ヨ澶辫触锛侊紒");
+        } else {
+            ObjectMapper objectMapper = new ObjectMapper();
+            objectMapper.coercionConfigDefaults().setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
+            try {
+                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
+                if (result.getCode() == 200) {
+                    return "200";
+                } else {
+                    return result.getMsg();
+                }
+            } catch (JsonProcessingException e) {
+                return e.getMessage();
+            }
+        }
+    }
+
+    @Override
+    public String syncSupplier(List<Supplier> supplierList){
+        if (supplierList.isEmpty()) {
+            throw new CoolException("鍙傛暟涓嶈兘涓虹┖锛侊紒");
+        }
+        /**WMS鍩虹閰嶇疆閾炬帴*/
+        String wmsUrl = wmsApi.getHost() + ":" + wmsApi.getPort() + WmsConstant.SYNC_COMPANIES;
+        HttpHeaders headers = new HttpHeaders();
+        headers.add("Content-Type", "application/json");
+        headers.add("api-version", "v2.0");
+        // Supplier -> supplierParams 瀛楁涓�涓�瀵瑰簲鍚庡啀涓嬪彂
+        List<Map<String, Object>> supplierParamsList = customerToSupplierParams(supplierList);
+        HttpEntity httpEntity2 = new HttpEntity<>(supplierParamsList, headers);//cs
+        ResponseEntity<String> exchange = restTemplate.exchange(wmsUrl, HttpMethod.POST, httpEntity2, String.class);//cs
+        log.info("鍚屾瀹㈡埛淇℃伅杩斿洖缁撴灉锛� {}", exchange);
+        if (Objects.isNull(exchange.getBody())) {
+            throw new CoolException("鏌ヨ澶辫触锛侊紒");
+        } else {
+            ObjectMapper objectMapper = new ObjectMapper();
+            objectMapper.coercionConfigDefaults().setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsEmpty);
+            try {
+                CommonResponse result = objectMapper.readValue(exchange.getBody(), CommonResponse.class);
+                if (result.getCode() == 200) {
+                    return "200";
+                } else {
+                    return result.getMsg();
+                }
+            } catch (JsonProcessingException e) {
+                return e.getMessage();
+            }
+        }
+    }
 
     /**
      * Warehouse 瀛楁鏄犲皠涓� server 绔� WarehouseParams 瀛楁
@@ -182,12 +266,80 @@
             }
             Map<String, Object> map = new HashMap<>();
             map.put("name", warehouse.getWareHouseName());
-            map.put("code", warehouse.getUseOrgId());
+            map.put("code", warehouse.getWareHouseId());
             map.put("factory",  warehouse.getUseOrgName());
+            map.put("factoryId",  warehouse.getUseOrgId());
             map.put("address", warehouse.getAddress());
             map.put("type", null);
             map.put("longitude", null);
             map.put("latitude", null);
+            map.put("operateType", warehouse.getOperateType());
+            mappedList.add(map);
+        }
+        return mappedList;
+    }
+
+    /**
+     * Customer 瀛楁鏄犲皠涓� server 绔� CompaniesParam 瀛楁
+     */
+    private List<Map<String, Object>> customerToCompaniesParams(List<Customer> customerList) {
+        List<Map<String, Object>> mappedList = new ArrayList<>();
+        int rowNum = 0;
+        for (Customer customer : customerList) {
+            rowNum++;
+            if (Objects.isNull(customer)) {
+                continue;
+            }
+            if (StringUtils.isBlank(customer.getCustomerId())) {
+                throw new CoolException("瀹㈡埛鍚屾澶辫触锛氱" + rowNum + "琛屽鎴风紪鐮佷负绌�");
+            }
+            if (StringUtils.isBlank(customer.getCustomerName())) {
+                throw new CoolException("瀹㈡埛鍚屾澶辫触锛氱" + rowNum + "琛屽鎴峰悕绉颁负绌�");
+            }
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", customer.getCustomerName());
+            map.put("nameEn", null);
+            map.put("breifCode", customer.getCustomerNickName());
+            // server 绔被鍨嬭浆鎹娇鐢ㄤ腑鏂囨弿杩�
+            map.put("type", "瀹㈡埛");
+            map.put("contact", customer.getContact());
+            map.put("tel", customer.getTelephone());
+            map.put("email", customer.getEmail());
+            map.put("pcode", null);
+            map.put("city", customer.getCustomerGroup());
+            map.put("province", null);
+            map.put("address", customer.getAddress());
+            map.put("code", customer.getCustomerId());
+            map.put("operateType", customer.getOperateType());
+            mappedList.add(map);
+        }
+        return mappedList;
+    }
+
+    /**
+     * Customer 瀛楁鏄犲皠涓� server 绔� CompaniesParam 瀛楁
+     */
+    private List<Map<String, Object>> customerToSupplierParams(List<Supplier> supplierList) {
+        List<Map<String, Object>> mappedList = new ArrayList<>();
+        for (Supplier supplier : supplierList) {
+            if (Objects.isNull(supplier)) {
+                continue;
+            }
+            Map<String, Object> map = new HashMap<>();
+            map.put("name", supplier.getSupplierName());
+            map.put("nameEn", null);
+            map.put("breifCode", supplier.getSupplierName());
+            // server 绔被鍨嬭浆鎹娇鐢ㄤ腑鏂囨弿杩�
+            map.put("type", supplier.getType());
+            map.put("contact", supplier.getContact());
+            map.put("tel", supplier.getTelephone());
+            map.put("email", supplier.getEmail());
+            map.put("pcode", null);
+            map.put("city", supplier.getSupplierGroup());
+            map.put("province", null);
+            map.put("address", supplier.getAddress());
+            map.put("code", supplier.getSupplierId());
+            map.put("operateType", supplier.getOperateType());
             mappedList.add(map);
         }
         return mappedList;
@@ -258,6 +410,650 @@
 //        asnOrderService.saveOrderAndItems(params, getLoginUserId());
     }
 
+    @Override
+    public String addOrderToServerNew(Order order) {
+        if (Objects.isNull(order) || StringUtils.isBlank(order.getOrderNo())) {
+            throw new CoolException("璁㈠崟鍙蜂笉鑳戒负绌猴紒锛�");
+        }
+        if (Objects.isNull(order.getOrderItems()) || order.getOrderItems().isEmpty()) {
+            throw new CoolException("璁㈠崟鏄庣粏涓嶈兘涓虹┖锛侊紒");
+        }
+
+        OpenApiOrder persistedOrder = saveOpenApiOrder(order);
+        List<OpenApiOrderItem> persistedItems = saveOpenApiOrderItems(persistedOrder, order.getOrderItems());
+        saveOpenApiOrderItemMappings(persistedOrder, persistedItems);
+
+        Order mergedOrder = buildMergedOrder(order);
+        String dispatchResult = addOrderToServer(mergedOrder);
+        if (!"200".equals(dispatchResult)) {
+            return dispatchResult;
+        }
+        return "200";
+    }
+
+    @Override
+    public CommonResponse reportOrderNew(Object params) {
+        if (Objects.isNull(params)) {
+            throw new CoolException("鍙傛暟涓嶈兘涓虹┖锛侊紒");
+        }
+        JSONObject root = JSONObject.parseObject(JSON.toJSONString(params));
+
+        String orderNo = pickString(root, "orderNo", "OrderNo", "WMSNO", "wmsno");
+        if (StringUtils.isBlank(orderNo)) {
+            return CommonResponse.error("orderNo涓嶈兘涓虹┖");
+        }
+
+        // 瑙f瀽鏄庣粏锛氫紭鍏堝彇 orderItems锛圤rder鏍煎紡锛夛紝鍏煎 Data/data锛圧eportParams鏍煎紡锛�
+        JSONArray dataArray = pickArray(root, "orderItems", "orderItems");
+        if (Objects.isNull(dataArray) || dataArray.isEmpty()) {
+            dataArray = pickArray(root, "Data", "data");
+        }
+        if (Objects.isNull(dataArray) || dataArray.isEmpty()) {
+            return CommonResponse.error("鍥炰紶鏄庣粏涓虹┖");
+        }
+
+        // 瀵瑰洖浼犳暟鎹仛鍙嶆槧灏勶紙WMS瀛楁鍚� 鈫� ERP瀛楁鍚嶏級锛屽吋瀹规棫鏍煎紡
+        JSONObject mappedRoot = ParamsMapUtils.reverseApiMaps("erp", "orderId", root);
+        JSONArray mappedDataArray = pickArray(mappedRoot, "orderItems", "orderItems");
+        if (Objects.isNull(mappedDataArray) || mappedDataArray.isEmpty()) {
+            mappedDataArray = pickArray(mappedRoot, "Data", "data");
+        }
+        if (Objects.isNull(mappedDataArray) || mappedDataArray.isEmpty()) {
+            mappedDataArray = dataArray;
+        }
+
+        int allocateCount = 0;
+        List<String> errors = new ArrayList<>();
+        Set<String> affectedOrderNos = new HashSet<>();
+        Map<String, OpenApiOrder> orderCache = new HashMap<>();
+
+        for (int i = 0; i < mappedDataArray.size(); i++) {
+            JSONObject row = mappedDataArray.getJSONObject(i);
+            String matnrCode = pickString(row, "matNr", "MatNr", "ItemCode", "itemCode");
+            String batch = pickString(row, "batch", "Batch", "GoodsNO", "goodsNo");
+            String lineId = pickString(row, "lineId", "LineId", "sourceLineId");
+            BigDecimal doneQty = pickDecimal(row, "doneQty", "anfme", "qty", "InQty", "inQty", "OutQty", "outQty", "pdqty", "PDQty");
+            if (StringUtils.isBlank(matnrCode) || doneQty.compareTo(ZERO) <= 0) {
+                errors.add("绗�" + (i + 1) + "琛岀己灏戝繀瑕佸瓧娈碉紙matNr/鏁伴噺锛�");
+                continue;
+            }
+            doneQty = doneQty.setScale(2, RoundingMode.HALF_UP);
+
+            try {
+                int singleAllocated = allocateMergedDoneQty(orderNo, matnrCode, batch, lineId, doneQty, orderCache);
+                allocateCount += singleAllocated;
+                affectedOrderNos.add(orderNo);
+            } catch (Exception e) {
+                log.error("澶勭悊鍥炰紶澶辫触锛宱rderNo={}, matnr={}, batch={}, lineId={}", orderNo, matnrCode, batch, lineId, e);
+                errors.add("绗�" + (i + 1) + "琛屽鐞嗗け璐ワ細" + e.getMessage());
+            }
+        }
+
+        // 寰幆缁撴潫鍚庯紝姣忎釜鍙楀奖鍝嶈鍗曞彧鍒锋柊涓�娆$姸鎬�
+        for (String no : affectedOrderNos) {
+            refreshOrderFinishStatus(no);
+        }
+
+        if (!errors.isEmpty()) {
+            return CommonResponse.error("澶勭悊瀹屾垚锛屼絾瀛樺湪寮傚父锛�" + String.join(" | ", errors));
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("allocatedCount", allocateCount);
+        return CommonResponse.ok(result);
+    }
+
+    /**
+     * 鏌ヨ宸插畬鎴愪絾鏈笂鎶RP鐨勮鍗曪紝缁勮ReportParams涓婃姤ERP
+     * 鐢盿srs-schedule瀹氭椂浠诲姟瑙﹀彂
+     */
+    @Override
+    public CommonResponse reportOrdersToErp() {
+        List<OpenApiOrder> pendingOrders = openApiOrderMapper.selectList(new LambdaQueryWrapper<OpenApiOrder>()
+                .eq(OpenApiOrder::getExceStatus, 2)
+                .eq(OpenApiOrder::getNtyStatus, 0)
+                .last("limit 50"));
+        if (pendingOrders.isEmpty()) {
+            return CommonResponse.ok("鏃犲緟涓婃姤ERP鐨勮鍗�");
+        }
+
+        List<String> errors = new ArrayList<>();
+        int successCount = 0;
+        for (OpenApiOrder order : pendingOrders) {
+            try {
+                List<OpenApiOrderItem> items = openApiOrderItemMapper.selectList(new LambdaQueryWrapper<OpenApiOrderItem>()
+                        .eq(OpenApiOrderItem::getOrderId, order.getId()));
+                if (items.isEmpty()) {
+                    log.warn("璁㈠崟鏃犳槑缁嗭紝璺宠繃涓婃姤ERP锛宱rderNo={}", order.getCode());
+                    continue;
+                }
+
+                ReportParams erpParams = buildReportParams(order, items);
+                CommonResponse erpResp = wmsErpService.reportOrders(erpParams);
+                if (Objects.nonNull(erpResp) && Objects.equals(erpResp.getCode(), 200)) {
+                    order.setNtyStatus(1);
+                    openApiOrderMapper.updateById(order);
+                    successCount++;
+                    log.info("涓婃姤ERP鎴愬姛锛宱rderNo={}", order.getCode());
+                } else {
+                    String msg = Objects.isNull(erpResp) ? "ERP鍝嶅簲涓虹┖" : erpResp.getMsg();
+                    log.warn("涓婃姤ERP澶辫触锛宱rderNo={}锛宮sg={}", order.getCode(), msg);
+                    errors.add(order.getCode() + ":" + msg);
+                }
+            } catch (Exception e) {
+                log.error("涓婃姤ERP寮傚父锛宱rderNo={}", order.getCode(), e);
+                errors.add(order.getCode() + ":" + e.getMessage());
+            }
+        }
+
+        if (!errors.isEmpty()) {
+            Map<String, Object> result = new HashMap<>();
+            result.put("successCount", successCount);
+            result.put("totalCount", pendingOrders.size());
+            return CommonResponse.error("閮ㄥ垎璁㈠崟涓婃姤ERP澶辫触锛�" + String.join(" | ", errors));
+        }
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("successCount", successCount);
+        result.put("totalCount", pendingOrders.size());
+        return CommonResponse.ok(result);
+    }
+
+    /**
+     * 浠嶰penApiOrder+OpenApiOrderItem缁勮ReportParams
+     */
+    private ReportParams buildReportParams(OpenApiOrder order, List<OpenApiOrderItem> items) {
+        String erpOrderType = resolveErpOrderType(order.getType(), order.getWkType());
+
+        List<ReportDataParam> reportDataList = new ArrayList<>();
+        for (OpenApiOrderItem item : items) {
+            BigDecimal qty = defaultQty(item.getQty());
+            if (qty.compareTo(ZERO) <= 0) {
+                continue;
+            }
+
+            ReportDataParam dataParam = new ReportDataParam()
+                    .setWMSNO(order.getCode())
+                    .setPONO(order.getPoCode())
+                    .setOrderNO(order.getCode())
+                    .setGoodsNO(item.getBatch())
+                    .setItemCode(item.getMatnrCode())
+                    .setEditUser("schedule")
+                    .setEditDate(new Date())
+                    .setMemoDtl(item.getMemo());
+
+            if ("in".equalsIgnoreCase(order.getType())) {
+                dataParam.setInQty(qty.doubleValue());
+            } else {
+                dataParam.setOutQty(qty.doubleValue());
+            }
+
+            reportDataList.add(dataParam);
+        }
+
+        return new ReportParams()
+                .setOrderType(erpOrderType)
+                .setAction("Update")
+                .setData(reportDataList);
+    }
+
+    /**
+     * 鏍规嵁type鍜寃kType瑙f瀽ERP涓婃姤鐨勮鍗曠被鍨�
+     */
+    private String resolveErpOrderType(String type, String wkType) {
+        if (StringUtils.isBlank(wkType)) {
+            return "in".equalsIgnoreCase(type) ? "PO_Instock" : "WO_Outstock";
+        }
+        switch (wkType) {
+            case "PUR_ReceiveBill": return "PO_Instock";
+            case "PUR_MRB": return "PR_Outstock";
+            case "PRD_ReturnMtrl": return "WR_Instock";
+            case "PRD_PickMtrl": return "WO_Outstock";
+            case "PRD_FeedMtrl": return "WR_Instock_BL";
+            case "PRD_INSTOCK":
+            case "PRD_MORPT": return "WO_Outstock";
+            case "STK_MISCELLANEOUS": return "In_Instock";
+            case "STK_MisDelivery": return "Io_Outstock";
+            default:
+                return "in".equalsIgnoreCase(type) ? "PO_Instock" : "WO_Outstock";
+        }
+    }
+
+    private OpenApiOrder saveOpenApiOrder(Order order) {
+        OpenApiOrder existing = openApiOrderMapper.selectOne(new LambdaQueryWrapper<OpenApiOrder>()
+                .eq(OpenApiOrder::getCode, order.getOrderNo())
+                .last("limit 1"));
+
+        BigDecimal totalQty = sumOrderItemQty(order.getOrderItems());
+        Date businessTime = toDate(order.getBusinessTime());
+        OpenApiOrder target = Objects.isNull(existing) ? new OpenApiOrder() : existing;
+        target.setCode(order.getOrderNo())
+                .setType(order.getType())
+                .setWkType(order.getWkType())
+                .setAnfme(totalQty)
+                .setQty(ZERO)
+                .setWorkQty(ZERO)
+                .setExceStatus(0)
+                .setNtyStatus(0)
+                .setStatus(1)
+                .setBusinessTime(businessTime)
+                .setOrderInternalCode(order.getOrderInternalCode())
+                .setStationId(order.getStationId())
+                .setStockDirect(order.getStockDirect())
+                .setCustomerId(order.getCustomerId())
+                .setCustomerName(order.getCustomerName())
+                .setSupplierId(order.getSupplierId())
+                .setSupplierName(order.getSupplierName())
+                .setStockOrgId(order.getStockOrgId())
+                .setStockOrgName(order.getStockOrgName())
+                .setPurchaseOrgId(order.getPurchaseOrgId())
+                .setPurchaseOrgName(order.getPurchaseOrgName())
+                .setPurchaseUserId(order.getPurchaseUserId())
+                .setPurchaseUserName(order.getPurchaseUserName())
+                .setPrdOrgId(order.getPrdOrgId())
+                .setPrdOrgName(order.getPrdOrgName())
+                .setSaleOrgId(order.getSaleOrgId())
+                .setSaleOrgName(order.getSaleOrgName())
+                .setSaleUserId(order.getSaleUserId())
+                .setSaleUserName(order.getSaleUserName());
+
+        if (Objects.isNull(existing)) {
+            openApiOrderMapper.insert(target);
+        } else {
+            openApiOrderMapper.updateById(target);
+            openApiOrderItemMapper.delete(new LambdaQueryWrapper<OpenApiOrderItem>().eq(OpenApiOrderItem::getOrderId, target.getId()));
+            openApiOrderItemMapMapper.delete(new LambdaQueryWrapper<OpenApiOrderItemMap>().eq(OpenApiOrderItemMap::getOrderId, target.getId()));
+        }
+        return target;
+    }
+
+    private List<OpenApiOrderItem> saveOpenApiOrderItems(OpenApiOrder order, List<OrderItem> orderItems) {
+        List<OpenApiOrderItem> saved = new ArrayList<>();
+        for (OrderItem src : orderItems) {
+            OpenApiOrderItem target = new OpenApiOrderItem()
+                    .setOrderId(order.getId())
+                    .setOrderCode(order.getCode())
+                    .setPlatItemId(src.getLineId())
+                    .setPlatWorkCode(src.getPlanNo())
+                    .setMatnrCode(src.getMatNr())
+                    .setMaktx(src.getMakTx())
+                    .setModel(src.getModel())
+                    .setSpec(src.getSpec())
+                    .setAnfme(toDecimal(src.getAnfme()))
+                    .setStockUnit(src.getUnit())
+                    .setWorkQty(ZERO)
+                    .setQty(ZERO)
+                    .setBatch(src.getBatch())
+                    .setMemo(src.getMemo())
+                    .setTargetWarehouseId(src.getTargetWarehouseId())
+                    .setSourceWarehouseId(src.getSourceWarehouseId())
+                    .setOwnerId(src.getOwnerId())
+                    .setBaseUnit(src.getBaseUnit())
+                    .setUseOrgId(src.getUseOrgId())
+                    .setUseOrgName(src.getUseOrgName())
+                    .setErpClsId(src.getErpClsId())
+                    .setPriceUnitId(src.getPriceUnitId())
+                    .setInStockType(src.getInStockType())
+                    .setOwnerTypeId(src.getOwnerTypeId())
+                    .setOwnerName(src.getOwnerName())
+                    .setKeeperTypeId(src.getKeeperTypeId())
+                    .setKeeperId(src.getKeeperId())
+                    .setKeeperName(src.getKeeperName())
+                    .setStatus(1)
+                    .setDeleted(0);
+            openApiOrderItemMapper.insert(target);
+            saved.add(target);
+        }
+        return saved;
+    }
+
+    private void saveOpenApiOrderItemMappings(OpenApiOrder order, List<OpenApiOrderItem> items) {
+        Map<String, List<OpenApiOrderItem>> grouped = items.stream().collect(Collectors.groupingBy(i -> mergeKey(i.getMatnrCode(), i.getBatch())));
+        for (List<OpenApiOrderItem> sameGroupItems : grouped.values()) {
+            sameGroupItems.sort(Comparator.comparing(OpenApiOrderItem::getId));
+            for (int i = 0; i < sameGroupItems.size(); i++) {
+                OpenApiOrderItem item = sameGroupItems.get(i);
+                OpenApiOrderItemMap map = new OpenApiOrderItemMap()
+                        .setOrderId(order.getId())
+                        .setOrderCode(order.getCode())
+                        .setSourceItemId(item.getId())
+                        .setSourceLineId(item.getPlatItemId())
+                        .setMergeMatnrCode(defaultString(item.getMatnrCode()))
+                        .setMergeBatch(defaultString(item.getBatch()))
+                        .setSeqNo(i + 1)
+                        .setSourceQty(defaultQty(item.getAnfme()))
+                        .setAllocatedQty(ZERO)
+                        .setReportedQty(ZERO)
+                        .setStatus(1)
+                        .setDeleted(0);
+                openApiOrderItemMapMapper.insert(map);
+            }
+        }
+    }
+
+    private Order buildMergedOrder(Order source) {
+        Order merged = new Order()
+                .setOrderNo(source.getOrderNo())
+                .setOrderInternalCode(source.getOrderInternalCode())
+                .setWkType(source.getWkType())
+                .setCreateTime(source.getCreateTime())
+                .setBusinessTime(source.getBusinessTime())
+                .setStockDirect(source.getStockDirect())
+                .setStationId(source.getStationId())
+                .setCustomerId(source.getCustomerId())
+                .setCustomerName(source.getCustomerName())
+                .setSupplierId(source.getSupplierId())
+                .setSupplierName(source.getSupplierName())
+                .setStockOrgId(source.getStockOrgId())
+                .setStockOrgName(source.getStockOrgName())
+                .setPurchaseOrgId(source.getPurchaseOrgId())
+                .setPurchaseOrgName(source.getPurchaseOrgName())
+                .setPurchaseUserId(source.getPurchaseUserId())
+                .setPurchaseUserName(source.getPurchaseUserName())
+                .setPrdOrgId(source.getPrdOrgId())
+                .setPrdOrgName(source.getPrdOrgName())
+                .setSaleOrgId(source.getSaleOrgId())
+                .setSaleOrgName(source.getSaleOrgName())
+                .setSaleUserId(source.getSaleUserId())
+                .setSaleUserName(source.getSaleUserName());
+        merged.setType(source.getType());
+        Map<String, List<OrderItem>> grouped = source.getOrderItems().stream().collect(Collectors.groupingBy(i -> mergeKey(i.getMatNr(), i.getBatch())));
+        List<OrderItem> mergedItems = new ArrayList<>();
+        int idx = 1;
+        for (List<OrderItem> groupItems : grouped.values()) {
+            OrderItem first = groupItems.get(0);
+            BigDecimal qty = groupItems.stream()
+                    .map(i -> toDecimal(i.getAnfme()))
+                    .reduce(ZERO, BigDecimal::add)
+                    .setScale(2, RoundingMode.HALF_UP);
+            OrderItem mergedItem = new OrderItem()
+                    .setLineId("M" + idx++)
+                    .setPlanNo(first.getPlanNo())
+                    .setMatNr(first.getMatNr())
+                    .setMakTx(first.getMakTx())
+                    .setModel(first.getModel())
+                    .setAnfme(qty.doubleValue())
+                    .setBatch(first.getBatch())
+                    .setUnit(first.getUnit())
+                    .setBaseUnit(first.getBaseUnit())
+                    .setPriceUnitId(first.getPriceUnitId())
+                    .setPalletId(first.getPalletId())
+                    .setTargetWarehouseId(first.getTargetWarehouseId())
+                    .setSourceWarehouseId(first.getSourceWarehouseId())
+                    .setInStockType(first.getInStockType())
+                    .setOwnerTypeId(first.getOwnerTypeId())
+                    .setOwnerId(first.getOwnerId())
+                    .setOwnerName(first.getOwnerName())
+                    .setKeeperTypeId(first.getKeeperTypeId())
+                    .setKeeperId(first.getKeeperId())
+                    .setKeeperName(first.getKeeperName())
+                    .setMemo(first.getMemo())
+                    .setUseOrgId(first.getUseOrgId())
+                    .setUseOrgName(first.getUseOrgName())
+                    .setErpClsId(first.getErpClsId());
+            mergedItem.setSpec(first.getSpec());
+            mergedItems.add(mergedItem);
+        }
+        merged.setOrderItems(mergedItems);
+        return merged;
+    }
+
+    private int allocateMergedDoneQty(String orderNo, String matnrCode, String batch, String lineId, BigDecimal mergedDoneQty, Map<String, OpenApiOrder> orderCache) {
+        OpenApiOrder order = orderCache.computeIfAbsent(orderNo, k ->
+                openApiOrderMapper.selectOne(new LambdaQueryWrapper<OpenApiOrder>()
+                        .eq(OpenApiOrder::getCode, k)
+                        .last("limit 1")));
+        if (Objects.isNull(order)) {
+            throw new CoolException("鏈壘鍒拌鍗曪細" + orderNo);
+        }
+
+        LambdaQueryWrapper<OpenApiOrderItemMap> queryWrapper = new LambdaQueryWrapper<OpenApiOrderItemMap>()
+                .eq(OpenApiOrderItemMap::getOrderId, order.getId())
+                .orderByAsc(OpenApiOrderItemMap::getSeqNo)
+                .orderByAsc(OpenApiOrderItemMap::getId);
+
+        // 浼樺厛鐢╨ineId绮剧‘鍖归厤鍘熷琛�
+        if (StringUtils.isNotBlank(lineId)) {
+            queryWrapper.eq(OpenApiOrderItemMap::getSourceLineId, lineId);
+        } else {
+            queryWrapper.eq(OpenApiOrderItemMap::getMergeMatnrCode, defaultString(matnrCode));
+            if (StringUtils.isNotBlank(batch)) {
+                queryWrapper.eq(OpenApiOrderItemMap::getMergeBatch, batch);
+            }
+        }
+
+        List<OpenApiOrderItemMap> mappingRows = openApiOrderItemMapMapper.selectList(queryWrapper);
+        if (mappingRows.isEmpty() && StringUtils.isNotBlank(lineId)) {
+            // lineId鍖归厤涓嶅埌鏃讹紝鍥為��鍒癿atNr+batch鍖归厤
+            queryWrapper = new LambdaQueryWrapper<OpenApiOrderItemMap>()
+                    .eq(OpenApiOrderItemMap::getOrderId, order.getId())
+                    .eq(OpenApiOrderItemMap::getMergeMatnrCode, defaultString(matnrCode))
+                    .orderByAsc(OpenApiOrderItemMap::getSeqNo)
+                    .orderByAsc(OpenApiOrderItemMap::getId);
+            if (StringUtils.isNotBlank(batch)) {
+                queryWrapper.eq(OpenApiOrderItemMap::getMergeBatch, batch);
+            }
+            mappingRows = openApiOrderItemMapMapper.selectList(queryWrapper);
+        }
+        if (mappingRows.isEmpty()) {
+            throw new CoolException("鏈壘鍒版槧灏勫叧绯伙紝璁㈠崟锛�" + orderNo + "锛岀墿鏂欙細" + matnrCode);
+        }
+
+        BigDecimal remaining = mergedDoneQty;
+        int allocateRows = 0;
+        for (OpenApiOrderItemMap row : mappingRows) {
+            if (remaining.compareTo(ZERO) <= 0) {
+                break;
+            }
+            BigDecimal sourceQty = defaultQty(row.getSourceQty());
+            BigDecimal allocatedQty = defaultQty(row.getAllocatedQty());
+            BigDecimal canAllocate = sourceQty.subtract(allocatedQty);
+            if (canAllocate.compareTo(ZERO) <= 0) {
+                continue;
+            }
+            BigDecimal toAllocate = remaining.min(canAllocate).setScale(2, RoundingMode.HALF_UP);
+            if (toAllocate.compareTo(ZERO) <= 0) {
+                continue;
+            }
+
+            row.setAllocatedQty(allocatedQty.add(toAllocate).setScale(2, RoundingMode.HALF_UP));
+            openApiOrderItemMapMapper.updateById(row);
+            openApiOrderItemMapper.update(null, new LambdaUpdateWrapper<OpenApiOrderItem>()
+                    .eq(OpenApiOrderItem::getId, row.getSourceItemId())
+                    .setSql("qty = IFNULL(qty,0) + " + toAllocate.toPlainString()));
+            remaining = remaining.subtract(toAllocate).setScale(2, RoundingMode.HALF_UP);
+            allocateRows++;
+        }
+        if (remaining.compareTo(ZERO) > 0) {
+            log.warn("鍥炰紶鏁伴噺瓒呰繃鏈垎閰嶆暟閲忥紝orderNo={}, matnr={}, batch={}, overflow={}", orderNo, matnrCode, batch, remaining);
+        }
+        return allocateRows;
+    }
+
+    private int reportReadyLinesToErp(String orderNo) {
+        OpenApiOrder order = openApiOrderMapper.selectOne(new LambdaQueryWrapper<OpenApiOrder>()
+                .eq(OpenApiOrder::getCode, orderNo)
+                .last("limit 1"));
+        if (Objects.isNull(order)) {
+            return 0;
+        }
+
+        List<OpenApiOrderItemMap> mappings = openApiOrderItemMapMapper.selectList(new LambdaQueryWrapper<OpenApiOrderItemMap>()
+                .eq(OpenApiOrderItemMap::getOrderId, order.getId())
+                .orderByAsc(OpenApiOrderItemMap::getSeqNo)
+                .orderByAsc(OpenApiOrderItemMap::getId));
+
+        // 鎵归噺鏌ヨ鎵�鏈� sourceItemId 瀵瑰簲鐨� OpenApiOrderItem锛屾秷闄� N+1
+        List<Long> itemIds = mappings.stream()
+                .map(OpenApiOrderItemMap::getSourceItemId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .toList();
+        Map<Long, OpenApiOrderItem> itemMap = new HashMap<>();
+        if (!itemIds.isEmpty()) {
+            List<OpenApiOrderItem> items = openApiOrderItemMapper.selectBatchIds(itemIds);
+            for (OpenApiOrderItem item : items) {
+                itemMap.put(item.getId(), item);
+            }
+        }
+
+        int successCount = 0;
+        for (OpenApiOrderItemMap mapping : mappings) {
+            BigDecimal allocatedQty = defaultQty(mapping.getAllocatedQty());
+            BigDecimal reportedQty = defaultQty(mapping.getReportedQty());
+            BigDecimal delta = allocatedQty.subtract(reportedQty).setScale(2, RoundingMode.HALF_UP);
+            if (delta.compareTo(ZERO) <= 0) {
+                continue;
+            }
+
+            OpenApiOrderItem item = itemMap.get(mapping.getSourceItemId());
+            if (Objects.isNull(item)) {
+                continue;
+            }
+
+            ReportParams params = buildLineReportParams(order, item, delta);
+            CommonResponse erpResp = wmsErpService.reportOrders(params);
+            if (Objects.nonNull(erpResp) && Objects.equals(erpResp.getCode(), 200)) {
+                mapping.setReportedQty(reportedQty.add(delta).setScale(2, RoundingMode.HALF_UP));
+                openApiOrderItemMapMapper.updateById(mapping);
+                successCount++;
+            } else {
+                String msg = Objects.isNull(erpResp) ? "ERP鍝嶅簲涓虹┖" : erpResp.getMsg();
+                log.warn("ERP琛屼笂鎶ュけ璐ワ紝orderNo={}, itemId={}, msg={}", orderNo, item.getId(), msg);
+            }
+        }
+        return successCount;
+    }
+
+    private ReportParams buildLineReportParams(OpenApiOrder order, OpenApiOrderItem item, BigDecimal deltaQty) {
+        ReportDataParam dataParam = new ReportDataParam()
+                .setWMSNO(order.getCode())
+                .setPONO(order.getPoCode())
+                .setOrderNO(item.getPlatWorkCode())
+                .setGoodsNO(item.getBatch())
+                .setItemCode(item.getMatnrCode())
+                .setEditUser("open-api")
+                .setEditDate(new Date())
+                .setMemoDtl("lineId=" + defaultString(item.getPlatItemId()));
+
+        if ("in".equalsIgnoreCase(order.getType())) {
+            dataParam.setInQty(deltaQty.doubleValue());
+        } else {
+            dataParam.setOutQty(deltaQty.doubleValue());
+        }
+
+        return new ReportParams().setOrderType(order.getType()).setAction("Update").setData(Collections.singletonList(dataParam));
+    }
+
+    private void refreshOrderFinishStatus(String orderNo) {
+        OpenApiOrder order = openApiOrderMapper.selectOne(new LambdaQueryWrapper<OpenApiOrder>()
+                .eq(OpenApiOrder::getCode, orderNo)
+                .last("limit 1"));
+        if (Objects.isNull(order)) {
+            return;
+        }
+        List<OpenApiOrderItem> items = openApiOrderItemMapper.selectList(new LambdaQueryWrapper<OpenApiOrderItem>()
+                .eq(OpenApiOrderItem::getOrderId, order.getId()));
+        BigDecimal totalPlan = items.stream().map(i -> defaultQty(i.getAnfme())).reduce(ZERO, BigDecimal::add);
+        BigDecimal totalDone = items.stream().map(i -> defaultQty(i.getQty())).reduce(ZERO, BigDecimal::add);
+
+        int exceStatus = 0;
+        if (totalDone.compareTo(ZERO) > 0 && totalDone.compareTo(totalPlan) < 0) {
+            exceStatus = 1;
+        } else if (totalDone.compareTo(totalPlan) >= 0 && totalPlan.compareTo(ZERO) > 0) {
+            exceStatus = 2;
+        }
+
+        order.setQty(totalDone.setScale(2, RoundingMode.HALF_UP))
+                .setWorkQty(totalDone.setScale(2, RoundingMode.HALF_UP))
+                .setExceStatus(exceStatus);
+        openApiOrderMapper.updateById(order);
+    }
+
+    private BigDecimal sumOrderItemQty(List<OrderItem> orderItems) {
+        return orderItems.stream()
+                .filter(Objects::nonNull)
+                .map(i -> toDecimal(i.getAnfme()))
+                .reduce(ZERO, BigDecimal::add)
+                .setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal toDecimal(Double value) {
+        if (Objects.isNull(value)) {
+            return ZERO;
+        }
+        return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal defaultQty(BigDecimal value) {
+        return Objects.isNull(value) ? ZERO : value.setScale(2, RoundingMode.HALF_UP);
+    }
+
+    private Date toDate(Long timestampSeconds) {
+        if (Objects.isNull(timestampSeconds) || timestampSeconds <= 0L) {
+            return null;
+        }
+        return new Date(timestampSeconds * 1000);
+    }
+
+    private String mergeKey(String matnr, String batch) {
+        return defaultString(matnr) + "@@" + defaultString(batch);
+    }
+
+    private String defaultString(String value) {
+        return StringUtils.trimToEmpty(value);
+    }
+
+    private String pickString(JSONObject object, String... candidates) {
+        if (Objects.isNull(object) || Objects.isNull(candidates)) {
+            return "";
+        }
+        for (String key : candidates) {
+            Object value = object.get(key);
+            if (Objects.nonNull(value) && StringUtils.isNotBlank(String.valueOf(value))) {
+                return String.valueOf(value).trim();
+            }
+        }
+        return "";
+    }
+
+    private JSONArray pickArray(JSONObject object, String... candidates) {
+        if (Objects.isNull(object) || Objects.isNull(candidates)) {
+            return new JSONArray();
+        }
+        for (String key : candidates) {
+            Object value = object.get(key);
+            if (value instanceof JSONArray) {
+                return (JSONArray) value;
+            }
+            if (value instanceof List) {
+                return JSONArray.parseArray(JSON.toJSONString(value));
+            }
+            if (value instanceof String && ((String) value).trim().startsWith("[")) {
+                return JSONArray.parseArray((String) value);
+            }
+        }
+        return new JSONArray();
+    }
+
+    private BigDecimal pickDecimal(JSONObject object, String... candidates) {
+        if (Objects.isNull(object) || Objects.isNull(candidates)) {
+            return ZERO;
+        }
+        for (String key : candidates) {
+            Object value = object.get(key);
+            if (Objects.isNull(value)) {
+                continue;
+            }
+            try {
+                return new BigDecimal(String.valueOf(value)).abs().setScale(2, RoundingMode.HALF_UP);
+            } catch (Exception ignore) {
+                // ignore invalid value
+            }
+        }
+        return ZERO;
+    }
+
     public static Map<String, Object> objectToMap(Object obj) {
         Map<String, Object> map = new HashMap<>();
         Field[] fields = obj.getClass().getDeclaredFields();

--
Gitblit v1.9.1