From 7b8b2456bb15aa733b8599fce2ada5d9549ba881 Mon Sep 17 00:00:00 2001
From: huminmin <mac@MacBook-Pro.local>
Date: 星期四, 11 六月 2026 13:21:20 +0800
Subject: [PATCH] 销售台账,工艺路线配置设置是否完成;导出工艺路线

---
 src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java |  304 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 298 insertions(+), 6 deletions(-)

diff --git a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
index e209855..7fdb825 100644
--- a/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
+++ b/src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -53,6 +53,7 @@
 import com.ruoyi.sales.mapper.*;
 import com.ruoyi.sales.pojo.*;
 import com.ruoyi.sales.service.ISalesLedgerProcessRouteService;
+import com.ruoyi.sales.service.ISalesLedgerProcessRouteRecordService;
 import com.ruoyi.sales.service.ISalesLedgerProductProcessBindService;
 import com.ruoyi.sales.service.ISalesLedgerProductProcessService;
 import com.ruoyi.sales.service.ISalesLedgerService;
@@ -152,6 +153,7 @@
     private final ISalesLedgerProductProcessBindService salesLedgerProductProcessBindService;
 
     private final ISalesLedgerProcessRouteService salesLedgerProcessRouteService;
+    private final ISalesLedgerProcessRouteRecordService salesLedgerProcessRouteRecordService;
 
     private final StockInventoryService stockInventoryService;
     private final StockInRecordMapper stockInRecordMapper;
@@ -754,21 +756,22 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void saleProcessBind(SalesLedgerProcessRoute salesLedgerProcessRoute) {
-        if (salesLedgerProcessRoute == null) {
+    public void saleProcessBind(SalesLedgerProcessRouteDto salesLedgerProcessRouteDto) {
+        if (salesLedgerProcessRouteDto == null) {
             throw new ServiceException("缁戝畾澶辫触,鏁版嵁涓嶈兘涓虹┖");
         }
 
-        SalesLedger salesLedger = baseMapper.selectById(salesLedgerProcessRoute.getSalesLedgerId());
+        SalesLedger salesLedger = baseMapper.selectById(salesLedgerProcessRouteDto.getSalesLedgerId());
         if (salesLedger == null) {
             throw new ServiceException("缁戝畾澶辫触,閿�鍞鍗曚笉瀛樺湪");
         }
-        ProcessRoute processRoute = processRouteMapper.selectById(salesLedgerProcessRoute.getProcessRouteId());
+        ProcessRoute processRoute = processRouteMapper.selectById(salesLedgerProcessRouteDto.getProcessRouteId());
         if (processRoute == null) {
             throw new ServiceException("缁戝畾澶辫触,宸ヨ壓璺嚎涓嶅瓨鍦�");
         }
         //  娓呴櫎宸茬粦瀹氱殑鏁版嵁
         salesLedgerProcessRouteService.remove(new LambdaQueryWrapper<SalesLedgerProcessRoute>().eq(SalesLedgerProcessRoute::getSalesLedgerId, salesLedger.getId()));
+        salesLedgerProcessRouteRecordService.remove(new LambdaQueryWrapper<SalesLedgerProcessRouteRecord>().eq(SalesLedgerProcessRouteRecord::getSalesLedgerId, salesLedger.getId()));
 
         //  灏嗘暟鎹縼绉诲埌sales_ledger_process_route
         List<ProcessRouteItem> routeItems = processRouteItemMapper.selectList(new LambdaQueryWrapper<ProcessRouteItem>().eq(ProcessRouteItem::getRouteId, processRoute.getId()));
@@ -783,6 +786,39 @@
             salesLedgerProcessRouteList.add(ledgerProcessRoute);
         }
         salesLedgerProcessRouteService.saveBatch(salesLedgerProcessRouteList);
+
+        List<SalesLedgerProcessRoute> savedRoutes = salesLedgerProcessRouteService.list(new LambdaQueryWrapper<SalesLedgerProcessRoute>()
+            .eq(SalesLedgerProcessRoute::getSalesLedgerId, salesLedger.getId())
+            .eq(SalesLedgerProcessRoute::getProcessRouteId, processRoute.getId()));
+        Map<Long, SalesLedgerProcessRoute> routeMap = savedRoutes.stream()
+            .filter(item -> item.getProcessRouteItemId() != null)
+            .collect(Collectors.toMap(SalesLedgerProcessRoute::getProcessRouteItemId, item -> item, (a, b) -> a));
+
+        Map<Long, SalesLedgerProcessRouteRecord> inputRecordMap = new HashMap<>();
+        if (CollectionUtils.isNotEmpty(salesLedgerProcessRouteDto.getRecordList())) {
+            for (SalesLedgerProcessRouteRecord record : salesLedgerProcessRouteDto.getRecordList()) {
+                if (record != null && record.getProcessRouteItemId() != null) {
+                    inputRecordMap.put(record.getProcessRouteItemId(), record);
+                }
+            }
+        }
+
+        List<SalesLedgerProcessRouteRecord> routeRecordList = new ArrayList<>();
+        for (ProcessRouteItem routeItem : routeItems) {
+            SalesLedgerProcessRoute route = routeMap.get(routeItem.getId());
+            if (route == null || route.getId() == null) {
+                continue;
+            }
+            SalesLedgerProcessRouteRecord inputRecord = inputRecordMap.get(routeItem.getId());
+            SalesLedgerProcessRouteRecord record = new SalesLedgerProcessRouteRecord();
+            record.setSalesLedgerId(salesLedger.getId());
+            record.setSalesLedgerProcessRouteId(route.getId());
+            Integer isCompleted = inputRecord != null && inputRecord.getIsCompleted() != null ? inputRecord.getIsCompleted() : 0;
+            record.setIsCompleted(isCompleted);
+            record.setCompletedTime(Objects.equals(isCompleted, 1) ? LocalDateTime.now() : null);
+            routeRecordList.add(record);
+        }
+        salesLedgerProcessRouteRecordService.saveBatch(routeRecordList);
     }
 
     /**
@@ -1175,6 +1211,11 @@
     public SalesLedgerProcessRouteDto salesProcess(Long salesLedgerId) {
         SalesLedgerProcessRouteDto dto = new SalesLedgerProcessRouteDto();
         List<SalesLedgerProcessRoute> list = baseMapper.selectSalesProcess(salesLedgerId);
+        List<SalesLedgerProcessRouteRecord> recordList = salesLedgerProcessRouteRecordService.list(
+            new LambdaQueryWrapper<SalesLedgerProcessRouteRecord>()
+                .eq(SalesLedgerProcessRouteRecord::getSalesLedgerId, salesLedgerId)
+                .orderByAsc(SalesLedgerProcessRouteRecord::getId)
+        );
         if (CollectionUtils.isNotEmpty(list)) {
             Long processRouteId = list.get(0).getProcessRouteId();
             ProcessRoute processRoute = processRouteMapper.selectById(processRouteId);
@@ -1201,6 +1242,17 @@
             }
         }
         dto.setList(list);
+        if (CollectionUtils.isNotEmpty(list) && CollectionUtils.isNotEmpty(recordList)) {
+            Map<Long, Long> routeItemIdMap = list.stream()
+                .filter(item -> item.getId() != null && item.getProcessRouteItemId() != null)
+                .collect(Collectors.toMap(SalesLedgerProcessRoute::getId, SalesLedgerProcessRoute::getProcessRouteItemId, (a, b) -> a));
+            recordList.forEach(record -> {
+                if (record != null && record.getSalesLedgerProcessRouteId() != null) {
+                    record.setProcessRouteItemId(routeItemIdMap.get(record.getSalesLedgerProcessRouteId()));
+                }
+            });
+        }
+        dto.setRecordList(recordList);
         return dto;
     }
 
@@ -3740,7 +3792,8 @@
                     .or().isNull(SalesLedger::getReviewStatus));
             }
 
-            List<SalesLedger> ledgerList = salesLedgerMapper.selectList(page, queryWrapper);
+            IPage<SalesLedger> ledgerPage = salesLedgerMapper.selectPage(page, queryWrapper);
+            List<SalesLedger> ledgerList = ledgerPage.getRecords();
 
             // 2. 鏀堕泦鏁版嵁
             List<SalesLedgerExportDto> ledgerExportList = new ArrayList<>();
@@ -3821,6 +3874,245 @@
             log.error("瀵煎嚭閿�鍞彴璐﹀け璐�", e);
             throw new ServiceException("瀵煎嚭澶辫触锛�" + e.getMessage());
         }
+    }
+
+    @Override
+    public void exportProcessRoute(HttpServletResponse response, List<Long> salesLedgerIds, String completedTimeStart, String completedTimeEnd) {
+        try {
+            if (CollectionUtils.isEmpty(salesLedgerIds)) {
+                throw new ServiceException("璇烽�夋嫨瑕佸鍑虹殑閿�鍞彴璐�");
+            }
+            LocalDateTime startTime = parseCompletedTime(completedTimeStart);
+            LocalDateTime endTime = parseCompletedTime(completedTimeEnd);
+            if (startTime == null && endTime == null) {
+                startTime = LocalDate.now().atStartOfDay();
+                endTime = LocalDateTime.now();
+            } else {
+                if (startTime == null) {
+                    startTime = LocalDate.now().atStartOfDay();
+                }
+                if (endTime == null) {
+                    endTime = LocalDateTime.now();
+                }
+            }
+
+            LambdaQueryWrapper<SalesLedgerProcessRouteRecord> queryWrapper = Wrappers.<SalesLedgerProcessRouteRecord>lambdaQuery()
+                .eq(SalesLedgerProcessRouteRecord::getIsCompleted, 1)
+                .ge(SalesLedgerProcessRouteRecord::getCompletedTime, startTime)
+                .le(SalesLedgerProcessRouteRecord::getCompletedTime, endTime)
+                .orderByAsc(SalesLedgerProcessRouteRecord::getCompletedTime)
+                .orderByAsc(SalesLedgerProcessRouteRecord::getId);
+
+            if (CollectionUtils.isNotEmpty(salesLedgerIds)) {
+                queryWrapper.in(SalesLedgerProcessRouteRecord::getSalesLedgerId, salesLedgerIds);
+            }
+
+            List<SalesLedgerProcessRouteRecord> completedRoutes = salesLedgerProcessRouteRecordService.list(queryWrapper);
+            Map<Long, SalesLedger> salesLedgerMap = Collections.emptyMap();
+            Map<Long, SalesLedgerProcessRoute> routeMap = Collections.emptyMap();
+            Map<Long, ProcessRouteItem> processRouteItemMap = Collections.emptyMap();
+            Map<Long, List<SalesLedgerProduct>> productMap = Collections.emptyMap();
+
+            if (CollectionUtils.isNotEmpty(completedRoutes)) {
+                List<Long> routeSalesLedgerIds = completedRoutes.stream()
+                    .map(SalesLedgerProcessRouteRecord::getSalesLedgerId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+                List<Long> salesLedgerProcessRouteIds = completedRoutes.stream()
+                    .map(SalesLedgerProcessRouteRecord::getSalesLedgerProcessRouteId)
+                    .filter(Objects::nonNull)
+                    .distinct()
+                    .collect(Collectors.toList());
+
+                if (CollectionUtils.isNotEmpty(routeSalesLedgerIds)) {
+                    salesLedgerMap = salesLedgerMapper.selectBatchIds(routeSalesLedgerIds).stream()
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toMap(SalesLedger::getId, item -> item, (a, b) -> a));
+                    List<SalesLedgerProduct> products = salesLedgerProductMapper.selectList(new LambdaQueryWrapper<SalesLedgerProduct>()
+                        .in(SalesLedgerProduct::getSalesLedgerId, routeSalesLedgerIds)
+                        .eq(SalesLedgerProduct::getType, 1)
+                        .orderByAsc(SalesLedgerProduct::getSalesLedgerId)
+                        .orderByAsc(SalesLedgerProduct::getId));
+                    productMap = products.stream().collect(Collectors.groupingBy(
+                        SalesLedgerProduct::getSalesLedgerId,
+                        LinkedHashMap::new,
+                        Collectors.toList()
+                    ));
+                }
+
+                if (CollectionUtils.isNotEmpty(salesLedgerProcessRouteIds)) {
+                    routeMap = salesLedgerProcessRouteService.listByIds(salesLedgerProcessRouteIds).stream()
+                        .filter(Objects::nonNull)
+                        .collect(Collectors.toMap(SalesLedgerProcessRoute::getId, item -> item, (a, b) -> a));
+                    List<Long> processRouteItemIds = routeMap.values().stream()
+                        .map(SalesLedgerProcessRoute::getProcessRouteItemId)
+                        .filter(Objects::nonNull)
+                        .distinct()
+                        .collect(Collectors.toList());
+                    if (CollectionUtils.isNotEmpty(processRouteItemIds)) {
+                        processRouteItemMap = processRouteItemMapper.selectBatchIds(processRouteItemIds).stream()
+                            .filter(Objects::nonNull)
+                            .collect(Collectors.toMap(ProcessRouteItem::getId, item -> item, (a, b) -> a));
+                    }
+                }
+            }
+
+            Map<Long, List<SalesLedgerProcessRouteRecord>> routeGroupMap = new LinkedHashMap<>();
+            for (SalesLedgerProcessRouteRecord record : completedRoutes) {
+                SalesLedgerProcessRoute route = routeMap.get(record.getSalesLedgerProcessRouteId());
+                if (route == null || route.getProcessRouteItemId() == null) {
+                    continue;
+                }
+                routeGroupMap.computeIfAbsent(route.getProcessRouteItemId(), k -> new ArrayList<>()).add(record);
+            }
+
+            final Map<Long, ProcessRouteItem> finalProcessRouteItemMap = processRouteItemMap;
+            LinkedHashMap<String, List<List<Object>>> sheetMap = new LinkedHashMap<>();
+            List<Long> orderedProcessRouteItemIds = routeGroupMap.keySet().stream()
+                .sorted((left, right) -> compareProcessRouteItem(left, right, finalProcessRouteItemMap))
+                .collect(Collectors.toList());
+
+            for (Long processRouteItemId : orderedProcessRouteItemIds) {
+                ProcessRouteItem processRouteItem = finalProcessRouteItemMap.get(processRouteItemId);
+                String sheetName = buildUniqueSheetName(sheetMap, processRouteItem, processRouteItemId);
+                List<List<Object>> sheetData = new ArrayList<>();
+                sheetData.add(buildProcessRouteHeader());
+
+                for (SalesLedgerProcessRouteRecord route : routeGroupMap.getOrDefault(processRouteItemId, Collections.emptyList())) {
+                    SalesLedger salesLedger = salesLedgerMap.get(route.getSalesLedgerId());
+                    if (salesLedger == null) {
+                        continue;
+                    }
+                    List<SalesLedgerProduct> products = productMap.getOrDefault(salesLedger.getId(), Collections.emptyList());
+                    if (CollectionUtils.isEmpty(products)) {
+                        sheetData.add(buildProcessRouteRow(salesLedger, null, route));
+                        continue;
+                    }
+                    for (SalesLedgerProduct product : products) {
+                        sheetData.add(buildProcessRouteRow(salesLedger, product, route));
+                    }
+                }
+
+                if (sheetData.size() == 1) {
+                    sheetData.add(Arrays.asList("", "", "", "", "", "", "", "", ""));
+                }
+                sheetMap.put(sheetName, sheetData);
+            }
+
+            if (sheetMap.isEmpty()) {
+                List<List<Object>> sheetData = new ArrayList<>();
+                sheetData.add(buildProcessRouteHeader());
+                sheetData.add(Arrays.asList("", "", "", "", "", "", ""));
+                sheetMap.put("宸ヨ壓璺嚎", sheetData);
+            }
+
+            com.ruoyi.common.utils.excel.ExcelUtils.exportManySheet(response, "閿�鍞彴璐﹀伐鑹鸿矾绾垮鍑�", sheetMap);
+        } catch (Exception e) {
+            log.error("瀵煎嚭鍞悗鍙拌处宸ヨ壓璺嚎澶辫触", e);
+            throw new ServiceException("瀵煎嚭鍞悗鍙拌处宸ヨ壓璺嚎澶辫触锛�" + e.getMessage());
+        }
+    }
+
+    private LocalDateTime parseCompletedTime(String value) {
+        if (!StringUtils.hasText(value)) {
+            return null;
+        }
+        String text = value.trim();
+        try {
+            return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+        } catch (Exception ex) {
+            try {
+                return LocalDateTime.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
+            } catch (Exception ignored) {
+                return LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd")).atStartOfDay();
+            }
+        }
+    }
+
+    private int compareProcessRouteItem(Long left, Long right, Map<Long, ProcessRouteItem> processRouteItemMap) {
+        ProcessRouteItem leftItem = processRouteItemMap.get(left);
+        ProcessRouteItem rightItem = processRouteItemMap.get(right);
+        int leftSort = leftItem != null && leftItem.getDragSort() != null ? leftItem.getDragSort() : Integer.MAX_VALUE;
+        int rightSort = rightItem != null && rightItem.getDragSort() != null ? rightItem.getDragSort() : Integer.MAX_VALUE;
+        if (leftSort != rightSort) {
+            return Integer.compare(leftSort, rightSort);
+        }
+        String leftName = leftItem != null && StringUtils.hasText(leftItem.getProcessName()) ? leftItem.getProcessName() : "";
+        String rightName = rightItem != null && StringUtils.hasText(rightItem.getProcessName()) ? rightItem.getProcessName() : "";
+        int nameCompare = leftName.compareTo(rightName);
+        if (nameCompare != 0) {
+            return nameCompare;
+        }
+        return Long.compare(left, right);
+    }
+
+    private List<Object> buildProcessRouteHeader() {
+        return Arrays.asList("鏃ユ湡", "璁㈠崟缂栧彿", "瀹㈡埛鍚嶇О", "瑙勬牸", "鏁伴噺", "闈㈢Н", "鏄惁鏄伐绋�");
+    }
+
+    private List<Object> buildProcessRouteRow(SalesLedger salesLedger, SalesLedgerProduct product, SalesLedgerProcessRouteRecord route) {
+        List<Object> row = new ArrayList<>();
+        row.add(salesLedger.getEntryDate() == null ? "" : DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, salesLedger.getEntryDate()));
+        row.add(salesLedger.getSalesContractNo());
+        row.add(salesLedger.getCustomerName());
+        row.add(product == null ? "" : product.getSpecificationModel());
+        row.add(product == null ? "" : product.getQuantity());
+        row.add(product == null ? "" : resolveExportArea(product));
+        row.add("");
+        return row;
+    }
+
+    private BigDecimal resolveExportArea(SalesLedgerProduct product) {
+        if (product == null) {
+            return BigDecimal.ZERO;
+        }
+        if (product.getSettleTotalArea() != null) {
+            return product.getSettleTotalArea();
+        }
+        if (product.getActualTotalArea() != null) {
+            return product.getActualTotalArea();
+        }
+        BigDecimal qty = product.getQuantity() == null ? BigDecimal.ONE : product.getQuantity();
+        if (product.getSettlePieceArea() != null) {
+            return product.getSettlePieceArea().multiply(qty).setScale(2, RoundingMode.HALF_UP);
+        }
+        if (product.getActualPieceArea() != null) {
+            return product.getActualPieceArea().multiply(qty).setScale(2, RoundingMode.HALF_UP);
+        }
+        if (product.getWidth() != null && product.getHeight() != null) {
+            BigDecimal area = product.getWidth().multiply(product.getHeight())
+                .divide(new BigDecimal("1000000"), 2, RoundingMode.HALF_UP);
+            return area.multiply(qty).setScale(2, RoundingMode.HALF_UP);
+        }
+        return BigDecimal.ZERO;
+    }
+
+    private String buildUniqueSheetName(Map<String, List<List<Object>>> sheetMap, ProcessRouteItem processRouteItem, Long processRouteItemId) {
+        String baseName = processRouteItem != null && StringUtils.hasText(processRouteItem.getProcessName())
+            ? processRouteItem.getProcessName()
+            : "宸ュ簭" + processRouteItemId;
+        baseName = sanitizeSheetName(baseName);
+        String sheetName = baseName;
+        int suffix = 2;
+        while (sheetMap.containsKey(sheetName)) {
+            String suffixText = "_" + suffix++;
+            int maxBaseLength = 31 - suffixText.length();
+            String trimmedBase = baseName.length() > maxBaseLength ? baseName.substring(0, maxBaseLength) : baseName;
+            sheetName = trimmedBase + suffixText;
+        }
+        return sheetName;
+    }
+
+    private String sanitizeSheetName(String sheetName) {
+        if (!StringUtils.hasText(sheetName)) {
+            return "宸ヨ壓璺嚎";
+        }
+        String sanitized = sheetName.replaceAll("[\\\\/?*\\[\\]:]", "_").trim();
+        if (sanitized.isEmpty()) {
+            sanitized = "宸ヨ壓璺嚎";
+        }
+        return sanitized.length() > 31 ? sanitized.substring(0, 31) : sanitized;
     }
 
     /**
@@ -3927,4 +4219,4 @@
             default: return "杩涜涓�";
         }
     }
-}
\ No newline at end of file
+}

--
Gitblit v1.9.3