xiaoyi
3 天以前 1dfdc3b0611211feba0570ec6d8cfad078c37135
feat 功能修正
已添加3个文件
已修改26个文件
280 ■■■■ 文件已修改
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastHistorySaveReqVO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiForecastDataMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java 50 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/PurchaseOrderApi.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/PurchasePlanApi.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApiImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/ErpPurchaseStatisticsController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductServiceImpl.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductServiceImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/DecisionForecastController.java
@@ -2,6 +2,7 @@
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastHistorySaveReqVO;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastReqVO;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastRespVO;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastWorkOrderCreateReqVO;
@@ -39,6 +40,13 @@
        return success(forecastService.getForecastList(reqVO.getForecastCode(), beginTime, endTime));
    }
    @PostMapping("/history")
    @Operation(summary = "新增历史预测数据")
    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:update')")
    public CommonResult<Long> createForecastHistory(@Valid @RequestBody DecisionForecastHistorySaveReqVO reqVO) {
        return success(forecastService.createForecastHistory(reqVO));
    }
    @PostMapping("/generate")
    @Operation(summary = "生成预测(占位)")
    @PreAuthorize("@ss.hasPermission('bi:decision:kpi:query')")
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/controller/admin/decision/vo/DecisionForecastHistorySaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.bi.controller.admin.decision.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - è´Ÿè·é¢„测历史数据新增 Request VO")
@Data
public class DecisionForecastHistorySaveReqVO {
    @Schema(description = "预测编码", requiredMode = Schema.RequiredMode.REQUIRED)
    @Size(max = 64)
    @jakarta.validation.constraints.NotBlank
    private String forecastCode;
    @Schema(description = "预测名称", requiredMode = Schema.RequiredMode.REQUIRED)
    @Size(max = 128)
    @jakarta.validation.constraints.NotBlank
    private String forecastName;
    @Schema(description = "预测时间点", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull
    private LocalDateTime pointTime;
    @Schema(description = "预测值", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull
    @DecimalMin(value = "0", inclusive = true)
    private BigDecimal forecastValue;
    @Schema(description = "实际值")
    @DecimalMin(value = "0", inclusive = true)
    private BigDecimal actualValue;
    @Schema(description = "维度")
    @Size(max = 32)
    private String dimension;
    @Schema(description = "维度值")
    @Size(max = 128)
    private String dimensionValue;
    @Schema(description = "备注")
    @Size(max = 500)
    private String remark;
}
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/dal/mysql/decision/BiForecastDataMapper.java
@@ -19,4 +19,14 @@
                .between(BiForecastDataDO::getPointTime, beginTime, endTime)
                .orderByAsc(BiForecastDataDO::getPointTime));
    }
    /**
     * æŸ¥è¯¢æŒ‡å®šé¢„测编码下最近 n æ¡é¢„测数据(按时间倒序,用于移动平均)
     */
    default List<BiForecastDataDO> selectRecentByCode(String forecastCode, int limit) {
        return selectList(new LambdaQueryWrapper<BiForecastDataDO>()
                .eq(BiForecastDataDO::getForecastCode, forecastCode)
                .orderByDesc(BiForecastDataDO::getPointTime)
                .last("LIMIT " + limit));
    }
}
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/enums/ErrorCodeConstants.java
@@ -39,5 +39,6 @@
    // ========== å†³ç­–支持 - è´Ÿè·é¢„测 1_080_221 ~ 1_080_240 ==========
    ErrorCode BI_FORECAST_NOT_EXISTS = new ErrorCode(1_080_221, "负荷预测数据不存在");
    ErrorCode BI_FORECAST_NO_HISTORY = new ErrorCode(1_080_222, "该预测编码下暂无历史数据,无法生成预测");
}
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bi.service.decision;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastHistorySaveReqVO;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastWorkOrderCreateReqVO;
import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
@@ -17,6 +18,8 @@
    void generateForecast(String forecastCode, LocalDateTime pointTime);
    Long createForecastHistory(DecisionForecastHistorySaveReqVO reqVO);
    /**
     * è´Ÿè·é¢„测联动创建生产工单(运维/发电/检修类)
     *
yudao-module-bi/src/main/java/cn/iocoder/yudao/module/bi/service/decision/BiForecastServiceImpl.java
@@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.bi.service.decision;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastHistorySaveReqVO;
import cn.iocoder.yudao.module.bi.controller.admin.decision.vo.DecisionForecastWorkOrderCreateReqVO;
import cn.iocoder.yudao.module.bi.dal.dataobject.decision.BiForecastDataDO;
import cn.iocoder.yudao.module.bi.dal.mysql.decision.BiForecastDataMapper;
@@ -12,10 +14,12 @@
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bi.enums.ErrorCodeConstants.BI_FORECAST_NO_HISTORY;
import static cn.iocoder.yudao.module.bi.enums.ErrorCodeConstants.BI_FORECAST_NOT_EXISTS;
/**
@@ -44,21 +48,51 @@
        return forecastDataMapper.selectListByCodeAndTimeRange(forecastCode, beginTime, endTime);
    }
    /**
     * ç§»åŠ¨å¹³å‡çª—å£å¤§å°
     */
    private static final int SMA_WINDOW = 12;
    @Override
    public Long createForecastHistory(DecisionForecastHistorySaveReqVO reqVO) {
        BiForecastDataDO data = BiForecastDataDO.builder()
                .forecastCode(reqVO.getForecastCode())
                .forecastName(reqVO.getForecastName())
                .pointTime(reqVO.getPointTime())
                .forecastValue(reqVO.getForecastValue())
                .actualValue(reqVO.getActualValue())
                .dimension(reqVO.getDimension())
                .dimensionValue(reqVO.getDimensionValue())
                .remark(reqVO.getRemark())
                .modelVersion("manual-history")
                .build();
        forecastDataMapper.insert(data);
        return data.getId();
    }
    @Override
    public void generateForecast(String forecastCode, LocalDateTime pointTime) {
        // æœ¬æœŸå ä½ï¼šç®€å•移动平均预测
        // å®žé™…项目应集成 ML æ¨¡åž‹ï¼ˆARIMA/Prophet/LSTM)
        log.info("[generateForecast][生成预测] forecastCode={}, pointTime={}, å½“前为占位实现", forecastCode, pointTime);
        // åŸºäºŽåŒé¢„测编码的历史预测数据做简单移动平均;无历史数据时不伪造零值
        List<BiForecastDataDO> history = forecastDataMapper.selectRecentByCode(forecastCode, SMA_WINDOW);
        if (CollUtil.isEmpty(history)) {
            throw exception(BI_FORECAST_NO_HISTORY);
        }
        BigDecimal sum = history.stream()
                .map(BiForecastDataDO::getForecastValue)
                .filter(value -> value != null)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal avg = sum.divide(BigDecimal.valueOf(history.size()), 4, RoundingMode.HALF_UP);
        BigDecimal bound = avg.multiply(new BigDecimal("0.10"));
        BiForecastDataDO forecast = BiForecastDataDO.builder()
                .forecastCode(forecastCode)
                .forecastName("负荷预测(占位)")
                .forecastName(history.get(0).getForecastName())
                .pointTime(pointTime)
                .forecastValue(BigDecimal.ZERO)
                .lowerBound(BigDecimal.ZERO)
                .upperBound(BigDecimal.ZERO)
                .forecastValue(avg)
                .lowerBound(avg.subtract(bound))
                .upperBound(avg.add(bound))
                .confidenceLevel(new BigDecimal("95.00"))
                .modelVersion("v1.0-placeholder")
                .modelVersion("v1.0-sma")
                .build();
        forecastDataMapper.insert(forecast);
    }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -252,6 +252,7 @@
            }
            // 2.8 è®¾ç½®æŒ‡ä»¤ä¸‹å‘数量
            contractVO.setCommandCount(contractCommandService.countByContractId(contractVO.getId()));
            contractVO.setAuditStatusName(CrmAuditStatusEnum.getNameByStatus(contractVO.getAuditStatus()));
        });
    }
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/PurchaseOrderApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package cn.iocoder.yudao.module.erp.api.purchase;
/**
 * é‡‡è´­è®¢å• API æŽ¥å£ã€‚
 *
 * <p>采购业务对外契约,不暴露 ERP ä¸šåŠ¡è·¯ç”±ï¼›æ—§ ERP API ä½œä¸ºå…¼å®¹å¥‘约保留。</p>
 */
public interface PurchaseOrderApi extends ErpPurchaseOrderApi {
    String PREFIX = "/rpc-api/purchase/order";
}
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/PurchasePlanApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package cn.iocoder.yudao.module.erp.api.purchase;
/**
 * é‡‡è´­è®¡åˆ’ API æŽ¥å£ã€‚
 *
 * <p>采购业务对外契约,旧 ERP API ä½œä¸ºå…¼å®¹å¥‘约保留。</p>
 */
public interface PurchasePlanApi extends ErpPurchasePlanApi {
    String PREFIX = "/rpc-api/purchase/plan";
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
@@ -28,9 +28,9 @@
 * @author è¶…级管理员
 */
@RestController
@RequestMapping(ErpPurchaseOrderApi.PREFIX)
@RequestMapping({ErpPurchaseOrderApi.PREFIX, PurchaseOrderApi.PREFIX})
@Validated
public class ErpPurchaseOrderApiImpl implements ErpPurchaseOrderApi {
public class ErpPurchaseOrderApiImpl implements PurchaseOrderApi {
    @Resource
    private ErpPurchaseOrderService purchaseOrderService;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchasePlanApiImpl.java
@@ -16,7 +16,7 @@
 */
@Service
@Validated
public class ErpPurchasePlanApiImpl implements ErpPurchasePlanApi {
public class ErpPurchasePlanApiImpl implements PurchasePlanApi {
    @Resource
    private ErpPurchasePlanService purchasePlanService;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java
@@ -51,7 +51,7 @@
@Tag(name = "管理后台 - ERP é‡‡è´­æ¥ç¥¨")
@RestController
@RequestMapping("/erp/purchase-invoice")
@RequestMapping({"/erp/purchase-invoice", "/purchase/invoice"})
@Validated
public class ErpPurchaseInvoiceController {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -47,7 +47,7 @@
@Tag(name = "管理后台 - ERP é‡‡è´­è®¢å•")
@RestController
@RequestMapping("/erp/purchase-order")
@RequestMapping({"/erp/purchase-order", "/purchase/order"})
@Validated
public class ErpPurchaseOrderController {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchasePlanController.java
@@ -48,7 +48,7 @@
@Tag(name = "管理后台 - ERP é‡‡è´­è®¡åˆ’")
@RestController
@RequestMapping("/erp/purchase-plan")
@RequestMapping({"/erp/purchase-plan", "/purchase/plan"})
@Validated
public class ErpPurchasePlanController {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -47,7 +47,7 @@
@Tag(name = "管理后台 - ERP é‡‡è´­ç”³è¯·")
@RestController
@RequestMapping("/erp/purchase-request")
@RequestMapping({"/erp/purchase-request", "/purchase/request"})
@Validated
public class ErpPurchaseRequestController {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierController.java
@@ -33,7 +33,7 @@
@Tag(name = "管理后台 - ERP ä¾›åº”商")
@RestController
@RequestMapping("/erp/supplier")
@RequestMapping({"/erp/supplier", "/purchase/supplier"})
@Validated
public class ErpSupplierController {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/statistics/ErpPurchaseStatisticsController.java
@@ -26,7 +26,7 @@
@Tag(name = "管理后台 - ERP é‡‡è´­ç»Ÿè®¡")
@RestController
@RequestMapping("/erp/purchase-statistics")
@RequestMapping({"/erp/purchase-statistics", "/purchase/statistics"})
@Validated
public class ErpPurchaseStatisticsController {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
@@ -20,7 +20,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
@@ -71,7 +71,7 @@
    @Resource
    private MesWmMaterialStockMapper materialStockMapper;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
    private PurchaseOrderApi purchaseOrderApi;
    @PostMapping("/create")
    @Operation(summary = "创建工单 BOM")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
@@ -13,8 +13,8 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArrivalNoticeSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
@@ -48,7 +48,7 @@
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
    private PurchaseOrderApi purchaseOrderApi;
    @PostMapping("/create")
    @Operation(summary = "创建到货通知单")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -265,6 +265,7 @@
    // ========== MES ç”Ÿäº§ç®¡ç†-工艺路线产品(1-040-501-200) ==========
    ErrorCode PRO_ROUTE_PRODUCT_NOT_EXISTS = new ErrorCode(1_040_501_200, "工艺路线产品不存在");
    ErrorCode PRO_ROUTE_PRODUCT_ITEM_DUPLICATE = new ErrorCode(1_040_501_201, "此产品已配置了工艺路线");
    ErrorCode PRO_ROUTE_PRODUCT_ITEM_TYPE_INVALID = new ErrorCode(1_040_501_202, "工艺路线只能关联产品类型物料");
    // ========== MES ç”Ÿäº§ç®¡ç†-工艺路线产品BOM(1-040-501-300) ==========
    ErrorCode PRO_ROUTE_PRODUCT_BOM_NOT_EXISTS = new ErrorCode(1_040_501_300, "工艺路线产品 BOM ä¸å­˜åœ¨");
    ErrorCode PRO_ROUTE_PRODUCT_BOM_DUPLICATE = new ErrorCode(1_040_501_301, "当前 BOM ç‰©æ–™åœ¨æ­¤å·¥åºå·²ç»é…ç½®è¿‡");
@@ -274,6 +275,7 @@
    ErrorCode PRO_WORK_ORDER_CODE_DUPLICATE = new ErrorCode(1_040_502_001, "生产工单编码已存在");
    ErrorCode PRO_WORK_ORDER_NOT_PREPARE = new ErrorCode(1_040_502_002, "只有草稿状态的工单才能执行此操作");
    ErrorCode PRO_WORK_ORDER_NOT_CONFIRMED = new ErrorCode(1_040_502_003, "只有已确认状态的工单才能执行此操作");
    ErrorCode PRO_WORK_ORDER_PRODUCTION_NOT_COMPLETE = new ErrorCode(1_040_502_007, "工单实际产出未达到计划数量,不能完成");
    ErrorCode PRO_WORK_ORDER_HAS_CHILDREN = new ErrorCode(1_040_502_004, "存在子工单,无法删除");
    ErrorCode PRO_WORK_ORDER_PRODUCT_MISMATCH = new ErrorCode(1_040_502_005, "当前产品物料与生产工单产品不一致");
    ErrorCode PRO_WORK_ORDER_SCHEDULE_QUANTITY_EXCEED = new ErrorCode(1_040_502_006, "排产数量超限:工单{}(计划{},已排产{},本次排产{})");
@@ -356,6 +358,7 @@
    ErrorCode PRO_MPS_CODE_DUPLICATE = new ErrorCode(1_040_509_006, "MPS ç¼–码已存在");
    ErrorCode PRO_MPS_HAS_WORK_ORDER = new ErrorCode(1_040_509_007, "MPS å·²å…³è”生产工单,无法删除");
    ErrorCode PRO_MPS_MES_PRODUCT_NOT_EXISTS = new ErrorCode(1_040_509_008, "MES äº§å“ä¸å­˜åœ¨ï¼Œæ— æ³•创建 MPS,产品:{}");
    ErrorCode PRO_MPS_PRODUCT_TYPE_INVALID = new ErrorCode(1_040_509_009, "排程产品必须是产品类型,禁止使用物料");
    // ========== MES è´¨é‡ç®¡ç†-质检方案(1-040-600-000) ==========
    ErrorCode QC_TEMPLATE_NOT_EXISTS = new ErrorCode(1_040_600_000, "质检方案不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/product/MesPdProductServiceImpl.java
@@ -9,6 +9,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.product.MesPdProductDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pd.product.MesPdProductMapper;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
import cn.iocoder.yudao.module.mes.enums.pd.MesPdAuditStatusEnum;
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.enums.pd.MesPdAuditStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
@@ -144,8 +145,10 @@
    private void fillDictName(MesPdProductRespVO respVO) {
        respVO.setArchiveTypeName(DictFrameworkUtils.parseDictDataLabel(
                DictTypeConstants.MES_PD_PRODUCT_ARCHIVE_TYPE, respVO.getArchiveType()));
        respVO.setAuditStatusName(DictFrameworkUtils.parseDictDataLabel(
                DictTypeConstants.MES_PD_PRODUCT_AUDIT_STATUS, respVO.getAuditStatus()));
        String auditStatusName = DictFrameworkUtils.parseDictDataLabel(
                DictTypeConstants.MES_PD_PRODUCT_AUDIT_STATUS, respVO.getAuditStatus());
        respVO.setAuditStatusName(auditStatusName != null
                ? auditStatusName : MesPdAuditStatusEnum.getNameByStatus(respVO.getAuditStatus()));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/maintenanceplan/MesProMaintenancePlanServiceImpl.java
@@ -4,7 +4,7 @@
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchasePlanApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchasePlanApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchasePlanCreateReqDTO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.maintenanceplan.vo.MesProMaintenancePlanSaveReqVO;
@@ -74,7 +74,7 @@
    private MesMdItemService itemService;
    @Resource
    private ErpPurchasePlanApi erpPurchasePlanApi;
    private PurchasePlanApi purchasePlanApi;
    @Resource
    private MesProWorkOrderService workOrderService;
@@ -199,7 +199,7 @@
        createReqDTO.setPlanDate(plan.getPlanDate());
        createReqDTO.setRemark("来源计划: " + plan.getCode());
        createReqDTO.setItems(items);
        return erpPurchasePlanApi.createPurchasePlan(createReqDTO);
        return purchasePlanApi.createPurchasePlan(createReqDTO);
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -17,6 +17,10 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import cn.iocoder.yudao.module.mes.enums.md.MesMdItemTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
@@ -74,6 +78,8 @@
    @Resource
    private MesMdItemService itemService;
    @Resource
    private MesMdItemTypeService itemTypeService;
    @Resource
    private MesMdAutoCodeRecordService autoCodeRecordService;
    @Resource
    private CrmCustomerApi customerApi;
@@ -94,11 +100,12 @@
                          Long mdmItemId, String productName, BigDecimal quantity, Long clientId,
                          String requestDate, String remark) {
        // 1. æ ¹æ® MDM ç‰©æ–™ ID æŸ¥æ‰¾ MES äº§å“
        cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO mesItem = itemService.getItemByMdmItemId(mdmItemId);
        MesMdItemDO mesItem = itemService.getItemByMdmItemId(mdmItemId);
        if (mesItem == null) {
            // MES äº§å“ä¸å­˜åœ¨ï¼ŒæŠ›å‡ºå¼‚常并包含产品名称提示
            throw exception(PRO_MPS_MES_PRODUCT_NOT_EXISTS, productName != null ? productName : mdmItemId);
        }
        validateProductItem(mesItem.getId());
        // 2. ç”Ÿæˆ MPS ç¼–码
        String code = generateMpsCode();
@@ -138,8 +145,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createMps(MesProMpsSaveReqVO createReqVO) {
        // æ ¡éªŒäº§å“å­˜åœ¨
        itemService.validateItemExists(createReqVO.getProductId());
        // æ ¡éªŒäº§å“å­˜åœ¨ã€å¯ç”¨ä¸”为产品类型
        validateProductItem(createReqVO.getProductId());
        // å¦‚果未填写编码,自动生成
        if (StrUtil.isBlank(createReqVO.getCode())) {
@@ -165,6 +172,8 @@
    public void updateMps(MesProMpsSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateMpsExistsAndDraft(updateReqVO.getId());
        // æ ¡éªŒäº§å“å­˜åœ¨ã€å¯ç”¨ä¸”为产品类型
        validateProductItem(updateReqVO.getProductId());
        // æ ¡éªŒç¼–码唯一
        validateMpsCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
@@ -509,6 +518,17 @@
    }
    /**
     * æ ¡éªŒäº§å“å­˜åœ¨ã€å¯ç”¨ä¸”为产品类型
     */
    private void validateProductItem(Long itemId) {
        MesMdItemDO item = itemService.validateItemExistsAndEnable(itemId);
        MesMdItemTypeDO itemType = itemTypeService.getItemType(item.getItemTypeId());
        if (itemType == null || !MesMdItemTypeEnum.isProduct(itemType.getItemOrProduct())) {
            throw exception(PRO_MPS_PRODUCT_TYPE_INVALID);
        }
    }
    /**
     * æ ¹æ® MPS åˆ›å»ºç”Ÿäº§å·¥å•
     */
    private Long createWorkOrderFromMps(MesProMpsDO mps, BigDecimal quantity) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductServiceImpl.java
@@ -4,7 +4,12 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.product.MesProRouteProductSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.route.MesProRouteProductMapper;
import cn.iocoder.yudao.module.mes.enums.md.MesMdItemTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@@ -34,12 +39,18 @@
    @Resource
    @Lazy
    private MesProRouteProductBomService routeProductBomService;
    @Resource
    private MesMdItemService itemService;
    @Resource
    private MesMdItemTypeService itemTypeService;
    @Override
    public Long createRouteProduct(MesProRouteProductSaveReqVO createReqVO) {
        // 1.0 å·²å¯ç”¨çš„工艺路线,不允许操作
        routeService.validateRouteNotEnable(createReqVO.getRouteId());
        // 1.1 æ ¡éªŒäº§å“å”¯ä¸€æ€§ï¼ˆä¸€ä¸ªäº§å“åªèƒ½å…³è”一条工艺路线)
        // 1.1 æ ¡éªŒäº§å“å­˜åœ¨ã€å¯ç”¨ä¸”类型正确
        validateProductItem(createReqVO.getItemId());
        // 1.2 æ ¡éªŒäº§å“å”¯ä¸€æ€§ï¼ˆä¸€ä¸ªäº§å“åªèƒ½å…³è”一条工艺路线)
        validateItemUnique(null, createReqVO.getItemId());
        // 2. æ’å…¥
@@ -54,7 +65,9 @@
        routeService.validateRouteNotEnable(updateReqVO.getRouteId());
        // 1.1 æ ¡éªŒå­˜åœ¨
        validateRouteProductExists(updateReqVO.getId());
        // 1.2 æ ¡éªŒäº§å“å”¯ä¸€æ€§
        // 1.2 æ ¡éªŒäº§å“å­˜åœ¨ã€å¯ç”¨ä¸”类型正确
        validateProductItem(updateReqVO.getItemId());
        // 1.3 æ ¡éªŒäº§å“å”¯ä¸€æ€§
        validateItemUnique(updateReqVO.getId(), updateReqVO.getItemId());
        // 2. æ›´æ–°
@@ -89,6 +102,14 @@
        }
    }
    private void validateProductItem(Long itemId) {
        MesMdItemDO item = itemService.validateItemExistsAndEnable(itemId);
        MesMdItemTypeDO itemType = itemTypeService.getItemType(item.getItemTypeId());
        if (itemType == null || !MesMdItemTypeEnum.isProduct(itemType.getItemOrProduct())) {
            throw exception(PRO_ROUTE_PRODUCT_ITEM_TYPE_INVALID);
        }
    }
    private void validateItemUnique(Long id, Long itemId) {
        MesProRouteProductDO existing = routeProductMapper.selectByItemId(itemId);
        if (existing == null) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderServiceImpl.java
@@ -225,6 +225,20 @@
        if (ObjUtil.notEqual(workOrder.getStatus(), MesProWorkOrderStatusEnum.CONFIRMED.getStatus())) {
            throw exception(PRO_WORK_ORDER_NOT_CONFIRMED);
        }
        if (workOrder.getQuantityProduced() == null
                || workOrder.getQuantity() == null
                || workOrder.getQuantityProduced().compareTo(workOrder.getQuantity()) < 0) {
            throw exception(PRO_WORK_ORDER_PRODUCTION_NOT_COMPLETE);
        }
        // æ ¡éªŒæ¯ä¸ªä»»åŠ¡çš„å®žé™…äº§å‡ºæ˜¯å¦å·²è¾¾æˆè®¡åˆ’æ•°é‡ï¼ˆä»»åŠ¡çŠ¶æ€åªæœ‰åœ¨å·¥å•å®Œæˆæ—¶ç”±çº§è”æ–¹æ³•ç½®ä¸ºå·²å®Œæˆï¼Œä¸èƒ½åœ¨æ­¤æ ¡éªŒçŠ¶æ€ï¼‰
        List<MesProTaskDO> tasks = taskService.getTaskListByWorkOrderIds(Collections.singletonList(id));
        boolean hasUnfinishedTask = CollUtil.isEmpty(tasks) || tasks.stream().anyMatch(task ->
                task.getProducedQuantity() == null
                        || task.getQuantity() == null
                        || task.getProducedQuantity().compareTo(task.getQuantity()) < 0);
        if (hasUnfinishedTask) {
            throw exception(PRO_WORK_ORDER_PRODUCTION_NOT_COMPLETE);
        }
        // 2. çº§è”完成所有关联任务
        taskService.finishTaskByOrderId(id);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -11,7 +11,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
@@ -51,7 +51,7 @@
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
    private PurchaseOrderApi purchaseOrderApi;
    @Resource
    private MesMdmItemService mdmItemService;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
@@ -25,7 +25,7 @@
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -81,7 +81,7 @@
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
    private PurchaseOrderApi purchaseOrderApi;
    @Resource
    private MdmItemApi mdmItemApi;
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
@@ -3,7 +3,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderCreateReqDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -61,7 +61,7 @@
    private MdmItemApi mdmItemApi;
    @Resource
    private ErpPurchaseOrderApi erpPurchaseOrderApi;
    private PurchaseOrderApi purchaseOrderApi;
    @Resource
    private SrmSupplierApplyService supplierApplyService;
@@ -233,12 +233,12 @@
        createReq.setItems(orderItems);
        createReq.setRemark("SRM å®šæ ‡è‡ªåŠ¨ç”Ÿæˆ, å®šæ ‡ç¼–号: " + award.getAwardNo());
        Long purchaseOrderId = erpPurchaseOrderApi.createPurchaseOrder(createReq).getCheckedData();
        Long purchaseOrderId = purchaseOrderApi.createPurchaseOrder(createReq).getCheckedData();
        // èŽ·å–é‡‡è´­è®¢å•å·
        String purchaseOrderNo = null;
        try {
            purchaseOrderNo = erpPurchaseOrderApi.getPurchaseOrder(purchaseOrderId)
            purchaseOrderNo = purchaseOrderApi.getPurchaseOrder(purchaseOrderId)
                    .getCheckedData().getNo();
        } catch (Exception e) {
            log.warn("[generatePurchaseOrder] èŽ·å–é‡‡è´­è®¢å•å·å¤±è´¥ï¼ŒpurchaseOrderId={}", purchaseOrderId, e);
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
@@ -3,7 +3,7 @@
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.PurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderCreateReqDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
@@ -66,7 +66,7 @@
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private ErpPurchaseOrderApi erpPurchaseOrderApi;
    private PurchaseOrderApi purchaseOrderApi;
    @Resource
    private SrmSupplierApplyService supplierApplyService;
    @Resource
@@ -376,12 +376,12 @@
        createReq.setItems(orderItems);
        createReq.setRemark("SRM ç®€æ˜“招投标自动生成, å®šæ ‡ç¼–号: " + award.getAwardNo());
        Long purchaseOrderId = erpPurchaseOrderApi.createPurchaseOrder(createReq).getCheckedData();
        Long purchaseOrderId = purchaseOrderApi.createPurchaseOrder(createReq).getCheckedData();
        // èŽ·å–é‡‡è´­è®¢å•å·
        String purchaseOrderNo = null;
        try {
            purchaseOrderNo = erpPurchaseOrderApi.getPurchaseOrder(purchaseOrderId)
            purchaseOrderNo = purchaseOrderApi.getPurchaseOrder(purchaseOrderId)
                    .getCheckedData().getNo();
        } catch (Exception e) {
            log.warn("[generatePurchaseOrderForSimple] èŽ·å–é‡‡è´­è®¢å•å·å¤±è´¥ï¼ŒpurchaseOrderId={}", purchaseOrderId, e);