From 0005d49a697ce934c6fc2a61ecb75d881b9a76f2 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 23 四月 2026 11:48:31 +0800
Subject: [PATCH] feat(production): 完善生产订单管理功能

---
 src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java |  234 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 227 insertions(+), 7 deletions(-)

diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
index 6113736..6ba0eb0 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionPlanServiceImpl.java
@@ -1,56 +1,176 @@
 package com.ruoyi.production.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.production.bean.dto.ProductionPlanDto;
+import com.ruoyi.production.bean.dto.ProductionOrderDto;
 import com.ruoyi.production.bean.vo.ProductionPlanVo;
 import com.ruoyi.production.mapper.ProductionPlanMapper;
+import com.ruoyi.production.pojo.ProductionOrder;
 import com.ruoyi.production.pojo.ProductionPlan;
+import com.ruoyi.production.service.ProductionOrderService;
 import com.ruoyi.production.service.ProductionPlanService;
+import com.ruoyi.sales.mapper.SalesLedgerMapper;
+import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
+import com.ruoyi.sales.pojo.SalesLedger;
+import com.ruoyi.sales.pojo.SalesLedgerProduct;
 import jakarta.servlet.http.HttpServletResponse;
+import lombok.AllArgsConstructor;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 @Service
 @RequiredArgsConstructor
+@Transactional(rollbackFor = Exception.class)
 public class ProductionPlanServiceImpl extends ServiceImpl<ProductionPlanMapper, ProductionPlan> implements ProductionPlanService {
+
+    private final ProductionOrderService productionOrderService;
+    private final SalesLedgerMapper salesLedgerMapper;
+    private final SalesLedgerProductMapper salesLedgerProductMapper;
+
     @Override
     public IPage<ProductionPlanVo> listPage(Page<ProductionPlanDto> page, ProductionPlanDto productionPlanDto) {
-        return null;
+        Page<ProductionPlan> entityPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal());
+        return this.page(entityPage, buildQueryWrapper(productionPlanDto))
+                .convert(item -> BeanUtil.copyProperties(item, ProductionPlanVo.class));
     }
 
     @Override
     public void loadProdData() {
-
+        // 鐢ㄩ攢鍞槑缁嗕綔涓烘潵婧愬悓姝ョ敓浜ц鍒掞紝source 瀛楁鎵挎媴骞傜瓑鍘婚噸浣滅敤銆�
+        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(
+                Wrappers.<SalesLedgerProduct>lambdaQuery()
+                        .isNotNull(SalesLedgerProduct::getProductModelId)
+                        .gt(SalesLedgerProduct::getQuantity, BigDecimal.ZERO));
+        for (SalesLedgerProduct salesLedgerProduct : salesLedgerProducts) {
+            String source = buildSalesSource(salesLedgerProduct.getId());
+            long exists = this.count(Wrappers.<ProductionPlan>lambdaQuery().eq(ProductionPlan::getSource, source));
+            if (exists > 0) {
+                continue;
+            }
+            SalesLedger salesLedger = salesLedgerMapper.selectById(salesLedgerProduct.getSalesLedgerId());
+            ProductionPlan productionPlan = new ProductionPlan();
+            productionPlan.setMpsNo(generateNextPlanNo());
+            productionPlan.setProductModelId(salesLedgerProduct.getProductModelId());
+            productionPlan.setQtyRequired(defaultDecimal(salesLedgerProduct.getQuantity()));
+            productionPlan.setRequiredDate(resolveDeliveryTime(salesLedger));
+            productionPlan.setPromisedDeliveryDate(resolveDeliveryTime(salesLedger));
+            productionPlan.setSource(source);
+            productionPlan.setIssued(Boolean.FALSE);
+            productionPlan.setState("1");
+            this.save(productionPlan);
+        }
     }
 
     @Override
     public void syncProdDataJob() {
-
+        loadProdData();
     }
 
     @Override
     public boolean combine(ProductionPlanDto productionPlanDto) {
-        return false;
+        // 澶氫釜璁″垝鍚堝苟杞崟鍚庯紝浠嶇粺涓�璧扮敓浜ц鍗曚繚瀛橀�昏緫锛岄伩鍏嶄袱濂椾笅鍗曟祦绋嬩笉涓�鑷淬��
+        if (productionPlanDto == null || productionPlanDto.getIds() == null || productionPlanDto.getIds().isEmpty()) {
+            throw new ServiceException("璇烽�夋嫨鐢熶骇璁″垝");
+        }
+        List<ProductionPlan> productionPlans = this.listByIds(productionPlanDto.getIds());
+        if (productionPlans.size() != productionPlanDto.getIds().size()) {
+            throw new ServiceException("閮ㄥ垎鐢熶骇璁″垝涓嶅瓨鍦�");
+        }
+        if (productionPlans.stream().anyMatch(item -> Boolean.TRUE.equals(item.getIssued()))) {
+            throw new ServiceException("宸蹭笅鍙戠殑鐢熶骇璁″垝涓嶈兘閲嶅杞崟");
+        }
+        Long productModelId = productionPlans.stream()
+                .map(ProductionPlan::getProductModelId)
+                .distinct()
+                .reduce((left, right) -> {
+                    throw new ServiceException("浠呮敮鎸佺浉鍚屼骇鍝佽鏍肩殑鐢熶骇璁″垝鍚堝苟杞崟");
+                })
+                .orElseThrow(() -> new ServiceException("鐢熶骇璁″垝缂哄皯浜у搧瑙勬牸"));
+        BigDecimal totalRequiredQuantity = productionPlans.stream()
+                .map(ProductionPlan::getQtyRequired)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        BigDecimal assignedQuantity = defaultDecimal(productionPlanDto.getTotalAssignedQuantity());
+        if (assignedQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            assignedQuantity = totalRequiredQuantity;
+        }
+        if (assignedQuantity.compareTo(totalRequiredQuantity) > 0) {
+            throw new ServiceException("涓嬪彂鏁伴噺涓嶈兘澶т簬璁″垝鎬婚渶姹傛暟閲�");
+        }
+
+        ProductionOrder productionOrder = new ProductionOrder();
+        productionOrder.setProductModelId(productModelId);
+        productionOrder.setQuantity(assignedQuantity);
+        productionOrder.setProductionPlanIds(formatPlanIds(productionPlans));
+        productionOrder.setPlanCompleteTime(productionPlanDto.getPlanCompleteTime() != null
+                ? productionPlanDto.getPlanCompleteTime()
+                : resolveEarliestPlanDate(productionPlans));
+        productionOrder.setStrength(productionPlanDto.getStrength());
+        return productionOrderService.saveProductionOrder(productionOrder);
     }
 
     @Override
     public boolean add(ProductionPlanDto productionPlanDto) {
-        return false;
+        // 鎵嬪伐寤鸿鍒掓椂琛ラ綈榛樿缂栧彿鍜岀姸鎬侊紝淇濊瘉鍚庣画鍙互鐩存帴涓嬪彂杞崟銆�
+        ProductionPlan productionPlan = BeanUtil.copyProperties(productionPlanDto, ProductionPlan.class);
+        validateProductionPlan(productionPlan, false);
+        if (productionPlan.getMpsNo() == null || productionPlan.getMpsNo().trim().isEmpty()) {
+            productionPlan.setMpsNo(generateNextPlanNo());
+        }
+        if (productionPlan.getIssued() == null) {
+            productionPlan.setIssued(Boolean.FALSE);
+        }
+        if (productionPlan.getState() == null || productionPlan.getState().trim().isEmpty()) {
+            productionPlan.setState("1");
+        }
+        return this.save(productionPlan);
     }
 
     @Override
     public boolean update(ProductionPlanDto productionPlanDto) {
-        return false;
+        // 宸蹭笅鍙戣鍒掔殑鏍稿績瀛楁涓嶈兘鍐嶅彉鏇达紝鍚﹀垯浼氬拰宸茬敓鎴愯鍗曡劚鑺傘��
+        if (productionPlanDto == null || productionPlanDto.getId() == null) {
+            throw new ServiceException("鐢熶骇璁″垝ID涓嶈兘涓虹┖");
+        }
+        ProductionPlan current = this.getById(productionPlanDto.getId());
+        if (current == null) {
+            throw new ServiceException("鐢熶骇璁″垝涓嶅瓨鍦�");
+        }
+        if (Boolean.TRUE.equals(current.getIssued()) && hasPlanCoreChanges(current, productionPlanDto)) {
+            throw new ServiceException("宸蹭笅鍙戠殑鐢熶骇璁″垝涓嶅厑璁镐慨鏀逛骇鍝併�佹暟閲忓拰浜ゆ湡");
+        }
+        ProductionPlan update = BeanUtil.copyProperties(productionPlanDto, ProductionPlan.class);
+        validateProductionPlan(update, true);
+        return this.updateById(update);
     }
 
     @Override
     public boolean delete(List<Long> ids) {
-        return false;
+        // 鍒犻櫎鍓嶆牎楠� issued锛岄伩鍏嶆妸宸茶浆鍗曡鍒掔洿鎺ヤ粠婧愬ご鍒犳帀銆�
+        if (ids == null || ids.isEmpty()) {
+            return false;
+        }
+        List<ProductionPlan> productionPlans = this.listByIds(ids);
+        if (productionPlans.stream().anyMatch(item -> Boolean.TRUE.equals(item.getIssued()))) {
+            throw new ServiceException("宸蹭笅鍙戠殑鐢熶骇璁″垝涓嶅厑璁稿垹闄�");
+        }
+        return this.removeByIds(ids);
     }
 
     @Override
@@ -62,4 +182,104 @@
     public void exportProdData(HttpServletResponse response, List<Long> ids) {
 
     }
+
+    private LambdaQueryWrapper<ProductionPlan> buildQueryWrapper(ProductionPlanDto dto) {
+        ProductionPlan query = dto == null ? new ProductionPlan() : dto;
+        return Wrappers.<ProductionPlan>lambdaQuery()
+                .eq(query.getId() != null, ProductionPlan::getId, query.getId())
+                .eq(query.getProductModelId() != null, ProductionPlan::getProductModelId, query.getProductModelId())
+                .eq(query.getIssued() != null, ProductionPlan::getIssued, query.getIssued())
+                .eq(query.getState() != null && !query.getState().trim().isEmpty(), ProductionPlan::getState, query.getState())
+                .eq(query.getIsAudit() != null && !query.getIsAudit().trim().isEmpty(), ProductionPlan::getIsAudit, query.getIsAudit())
+                .like(query.getMpsNo() != null && !query.getMpsNo().trim().isEmpty(), ProductionPlan::getMpsNo, query.getMpsNo())
+                .like(query.getSource() != null && !query.getSource().trim().isEmpty(), ProductionPlan::getSource, query.getSource())
+                .orderByDesc(ProductionPlan::getId);
+    }
+
+    private void validateProductionPlan(ProductionPlan productionPlan, boolean update) {
+        if (productionPlan == null) {
+            throw new ServiceException("鐢熶骇璁″垝涓嶈兘涓虹┖");
+        }
+        if (update && productionPlan.getId() == null) {
+            throw new ServiceException("鐢熶骇璁″垝ID涓嶈兘涓虹┖");
+        }
+        if (productionPlan.getProductModelId() == null) {
+            throw new ServiceException("productModelId涓嶈兘涓虹┖");
+        }
+        if (defaultDecimal(productionPlan.getQtyRequired()).compareTo(BigDecimal.ZERO) <= 0) {
+            throw new ServiceException("qtyRequired蹇呴』澶т簬0");
+        }
+    }
+
+    private boolean hasPlanCoreChanges(ProductionPlan current, ProductionPlanDto update) {
+        // 杩欎簺瀛楁浼氱洿鎺ュ奖鍝嶈浆鍗曠粨鏋滐紝鐢ㄦ潵鍒ゆ柇鏄惁灞炰簬鏍稿績鍙樻洿銆�
+        return !Objects.equals(current.getProductModelId(), update.getProductModelId())
+                || defaultDecimal(current.getQtyRequired()).compareTo(defaultDecimal(update.getQtyRequired())) != 0
+                || !Objects.equals(toLocalDate(current.getPromisedDeliveryDate()), update.getPlanCompleteTime())
+                || !Objects.equals(toLocalDate(current.getRequiredDate()), toLocalDate(update.getRequiredDate()));
+    }
+
+    private String generateNextPlanNo() {
+        // 缂栧彿鎸夋棩鏈熼�掑鐢熸垚锛屾柟渚垮拰璁㈠崟銆佸伐鍗曠粺涓�杩借釜銆�
+        String datePrefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
+        String prefix = "MPS" + datePrefix;
+        ProductionPlan latestPlan = this.getOne(Wrappers.<ProductionPlan>lambdaQuery()
+                .likeRight(ProductionPlan::getMpsNo, prefix)
+                .orderByDesc(ProductionPlan::getMpsNo)
+                .last("limit 1"));
+        int sequence = 1;
+        if (latestPlan != null && latestPlan.getMpsNo() != null && latestPlan.getMpsNo().startsWith(prefix)) {
+            try {
+                sequence = Integer.parseInt(latestPlan.getMpsNo().substring(prefix.length())) + 1;
+            } catch (NumberFormatException ignored) {
+                sequence = 1;
+            }
+        }
+        return prefix + String.format("%04d", sequence);
+    }
+
+    private String formatPlanIds(List<ProductionPlan> productionPlans) {
+        return productionPlans.stream()
+                .map(ProductionPlan::getId)
+                .distinct()
+                .map(String::valueOf)
+                .collect(Collectors.joining(",", "[", "]"));
+    }
+
+    private LocalDate resolveEarliestPlanDate(List<ProductionPlan> productionPlans) {
+        return productionPlans.stream()
+                .map(this::resolvePlanDate)
+                .filter(Objects::nonNull)
+                .min(Comparator.naturalOrder())
+                .orElse(null);
+    }
+
+    private LocalDate resolvePlanDate(ProductionPlan productionPlan) {
+        if (productionPlan.getPromisedDeliveryDate() != null) {
+            return productionPlan.getPromisedDeliveryDate().toLocalDate();
+        }
+        if (productionPlan.getRequiredDate() != null) {
+            return productionPlan.getRequiredDate().toLocalDate();
+        }
+        return null;
+    }
+
+    private LocalDateTime resolveDeliveryTime(SalesLedger salesLedger) {
+        if (salesLedger == null || salesLedger.getDeliveryDate() == null) {
+            return null;
+        }
+        return salesLedger.getDeliveryDate().atStartOfDay();
+    }
+
+    private String buildSalesSource(Long salesLedgerProductId) {
+        return "salesLedgerProduct:" + salesLedgerProductId;
+    }
+
+    private LocalDate toLocalDate(LocalDateTime value) {
+        return value == null ? null : value.toLocalDate();
+    }
+
+    private BigDecimal defaultDecimal(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
+    }
 }

--
Gitblit v1.9.3