6 天以前 ffe7921a138331185186fd100846fe50dcaf841c
1.采购管理-采购申请恢复
已添加12个文件
已修改17个文件
1699 ■■■■■ 文件已修改
docs/erp_purchase_qc_check_flag.md 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java 114 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java 430 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/erp_purchase_qc_check_flag.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
# ERP é‡‡è´­è®¢å•质检字段与 MES æ¥æ–™æ£€éªŒè‡ªåŠ¨åˆ›å»º - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ERP é‡‡è´­è®¢å•列表页
- ERP é‡‡è´­è®¢å•新增/编辑页
- ERP é‡‡è´­è®¢å•详情页
- MES åˆ°è´§é€šçŸ¥å•列表页
- MES åˆ°è´§é€šçŸ¥å•详情页
- MES æ¥æ–™æ£€éªŒï¼ˆIQC)列表页
## ä¸šåŠ¡æµç¨‹
### 1. é‡‡è´­è®¢å•新增质检标识
1. ç”¨æˆ·åœ¨ ERP é‡‡è´­è®¢å•新增/编辑页面,可以为每个产品明细勾选"是否需要来料检验"
2. ä¿å­˜æ—¶ï¼Œ`qcCheckFlag` å­—段随订单明细一起保存
### 2. ä»Žé‡‡è´­è®¢å•生成到货通知单
1. ç”¨æˆ·ç‚¹å‡»"生成到货通知单"按钮
2. ç³»ç»Ÿè‡ªåŠ¨å°†é‡‡è´­è®¢å•æ˜Žç»†çš„ `qcCheckFlag` å¸¦å…¥åˆ°è´§é€šçŸ¥å•行的 `iqcCheckFlag`
3. ç”¨æˆ·å¯åœ¨åˆ°è´§é€šçŸ¥å•详情页修改每行的质检标识
### 3. åˆ°è´§é€šçŸ¥å•提交自动创建 IQC
1. ç”¨æˆ·æäº¤åˆ°è´§é€šçŸ¥å•
2. å¦‚果有行标记为需要质检(`iqcCheckFlag = true`):
   - åˆ°è´§é€šçŸ¥å•状态变为"待质检"
   - ç³»ç»Ÿè‡ªåŠ¨ä¸ºæ¯ä¸ªéœ€è¦è´¨æ£€çš„è¡Œåˆ›å»ºä¸€æ¡ IQC æ¥æ–™æ£€éªŒå•
   - è¡Œä¸Šçš„ `iqcId` å…³è”到创建的检验单
3. å¦‚果没有需要质检的行,状态直接变为"待入库"
## API å˜æ›´
### ERP é‡‡è´­è®¢å•
#### è¯·æ±‚参数变更
**ErpPurchaseOrderSaveReqVO.Item æ–°å¢žå­—段:**
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| qcCheckFlag | Boolean | å¦ | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒ |
#### å“åº”参数变更
**ErpPurchaseOrderRespVO.Item æ–°å¢žå­—段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| qcCheckFlag | Boolean | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒ |
### MES åˆ°è´§é€šçŸ¥å•
#### å“åº”参数变更
**到货通知单行新增字段(已存在,强调带入逻辑):**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| iqcCheckFlag | Boolean | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒï¼ˆä»Žé‡‡è´­è®¢å•带入) |
| iqcId | Long | å…³è”çš„ IQC æ£€éªŒå• ID(提交后自动创建) |
## å­—段展示规则
### ERP é‡‡è´­è®¢å•
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| æ˜¯å¦è´¨æ£€ | è®¢å•明细表格 | ä½¿ç”¨ Checkbox æˆ– Switch å±•示,可编辑 |
### MES åˆ°è´§é€šçŸ¥å•
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| æ˜¯å¦è´¨æ£€ | è¡Œæ˜Žç»†è¡¨æ ¼ | ä½¿ç”¨ Tag æˆ– Checkbox å±•示,提交后不可修改 |
| IQC æ£€éªŒå• | è¡Œæ˜Žç»†è¡¨æ ¼ | å…³è”的检验单编号,点击可跳转(提交后自动关联) |
### MES æ¥æ–™æ£€éªŒ
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| æ¥æºå•据 | è¯¦æƒ…页 | æ˜¾ç¤ºåˆ°è´§é€šçŸ¥å•编号,点击可跳转 |
| æ¥æºå•据行 | è¯¦æƒ…页 | æ˜¾ç¤ºåˆ°è´§é€šçŸ¥å•行编号 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| é‡‡è´­è®¢å•保存 | `qcCheckFlag` é»˜è®¤ä¸º false(不质检) |
| ç”Ÿæˆåˆ°è´§é€šçŸ¥å• | ä»Žé‡‡è´­è®¢å•明细带入 `qcCheckFlag` åˆ° `iqcCheckFlag` |
| åˆ°è´§é€šçŸ¥å•提交 | å¦‚有需要质检的行,自动创建 IQC æ£€éªŒå•并关联 |
| IQC åˆ›å»ºæ¡ä»¶ | ç‰©æ–™å¿…须配置了 IQC æ£€éªŒæ¨¡æ¿ï¼Œå¦åˆ™è·³è¿‡å¹¶è®°å½•日志 |
| IQC å•据状态 | è‡ªåŠ¨åˆ›å»ºçš„ IQC ä¸ºè‰ç¨¿çŠ¶æ€ï¼Œéœ€è¦æ£€éªŒå‘˜å¡«å†™æ£€éªŒç»“æžœ |
## æ³¨æ„äº‹é¡¹
1. **检验模板配置**:物料必须先在 MES è´¨é‡æ¨¡å—配置 IQC æ£€éªŒæ¨¡æ¿ï¼Œå¦åˆ™è‡ªåŠ¨åˆ›å»º IQC ä¼šå¤±è´¥
2. **数据带入**:从采购订单生成到货通知单时,质检标识会自动带入,但用户可在到货通知单中修改
3. **状态流转**:到货通知单提交后,需要质检的行会进入"待质检"状态,所有检验完成后才可入库
4. **检验单关联**:提交后行上会关联 IQC æ£€éªŒå• ID,点击可跳转查看检验详情
5. **异常处理**:如果某行创建 IQC å¤±è´¥ï¼ˆå¦‚缺少模板),不会中断整个提交流程,会记录日志继续处理其他行
yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
@@ -62,6 +62,9 @@
    @Override
    public List<ProcessDefinition> getProcessDefinitionList(Set<String> ids) {
        if (CollUtil.isEmpty(ids)) {
            return emptyList();
        }
        return repositoryService.createProcessDefinitionQuery().processDefinitionIds(ids).list();
    }
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderItemRespDTO.java
@@ -36,4 +36,7 @@
    @Schema(description = "备注", example = "备注信息")
    private String remark;
    @Schema(description = "是否需要来料检验", example = "true")
    private Boolean qcCheckFlag;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -39,6 +39,7 @@
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP é‡‡è´­è®¢å•")
@RestController
@@ -80,6 +81,23 @@
        return success(true);
    }
    @PostMapping("/submit")
    @Operation(summary = "提交采购订单审批")
    @PreAuthorize("@ss.hasPermission('erp:purchase-order:update-status')")
    public CommonResult<Boolean> submitPurchaseOrder(@RequestParam("id") Long id,
                                                     @RequestParam("processDefinitionKey") String processDefinitionKey) {
        Long userId = getLoginUserId();
        purchaseOrderService.submitPurchaseOrder(id, processDefinitionKey, userId);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获取采购订单审批流程列表")
    @PreAuthorize("@ss.hasPermission('erp:purchase-order:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessList() {
        return success(purchaseOrderService.getPurchaseOrderApproveProcessDefinitionList());
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除采购订单")
    @Parameter(name = "ids", description = "编号数组", required = true)
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,182 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestRespVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - ERP é‡‡è´­ç”³è¯·")
@RestController
@RequestMapping("/erp/purchase-request")
@Validated
public class ErpPurchaseRequestController {
    @Resource
    private ErpPurchaseRequestService purchaseRequestService;
    @Resource
    private ErpSupplierService supplierService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private DeptApi deptApi;
    @PostMapping("/create")
    @Operation(summary = "创建采购申请")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:create')")
    public CommonResult<Long> createPurchaseRequest(@Valid @RequestBody ErpPurchaseRequestSaveReqVO createReqVO) {
        return success(purchaseRequestService.createPurchaseRequest(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新采购申请")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:update')")
    public CommonResult<Boolean> updatePurchaseRequest(@Valid @RequestBody ErpPurchaseRequestSaveReqVO updateReqVO) {
        purchaseRequestService.updatePurchaseRequest(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除采购申请")
    @Parameter(name = "ids", description = "编号数组", required = true)
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:delete')")
    public CommonResult<Boolean> deletePurchaseRequest(@RequestParam("ids") List<Long> ids) {
        purchaseRequestService.deletePurchaseRequest(ids);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获得采购申请")
    @Parameter(name = "id", description = "编号", required = true, example = "1024")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
    public CommonResult<ErpPurchaseRequestRespVO> getPurchaseRequest(@RequestParam("id") Long id) {
        ErpPurchaseRequestDO purchaseRequest = purchaseRequestService.getPurchaseRequest(id);
        if (purchaseRequest == null) {
            return success(null);
        }
        List<ErpPurchaseRequestItemDO> purchaseRequestItemList = purchaseRequestService.getPurchaseRequestItemListByRequestId(id);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
        return success(BeanUtils.toBean(purchaseRequest, ErpPurchaseRequestRespVO.class, purchaseRequestVO ->
                purchaseRequestVO.setItems(BeanUtils.toBean(purchaseRequestItemList, ErpPurchaseRequestRespVO.Item.class, item -> {
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                }))));
    }
    @GetMapping("/page")
    @Operation(summary = "获得采购申请分页")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
    public CommonResult<PageResult<ErpPurchaseRequestRespVO>> getPurchaseRequestPage(@Valid ErpPurchaseRequestPageReqVO pageReqVO) {
        PageResult<ErpPurchaseRequestDO> pageResult = purchaseRequestService.getPurchaseRequestPage(pageReqVO);
        return success(buildPurchaseRequestVOPageResult(pageResult));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出采购申请 Excel")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:export')")
    @ApiAccessLog(operateType = EXPORT)
    public void exportPurchaseRequestExcel(@Valid ErpPurchaseRequestPageReqVO pageReqVO,
                                           HttpServletResponse response) throws IOException {
        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
        List<ErpPurchaseRequestRespVO> list = buildPurchaseRequestVOPageResult(purchaseRequestService.getPurchaseRequestPage(pageReqVO)).getList();
        ExcelUtils.write(response, "采购申请.xls", "数据", ErpPurchaseRequestRespVO.class, list);
    }
    @PutMapping("/submit")
    @Operation(summary = "提交采购申请审批")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:submit')")
    public CommonResult<Boolean> submitPurchaseRequest(@RequestParam("id") Long id,
                                                       @RequestParam("processDefinitionKey") String processDefinitionKey) {
        Long userId = getLoginUserId();
        purchaseRequestService.submitPurchaseRequest(id, processDefinitionKey, userId);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获取采购申请审批流程列表")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:query')")
    public CommonResult<List<Map<String, Object>>> getApproveProcessList() {
        return success(purchaseRequestService.getPurchaseRequestApproveProcessDefinitionList());
    }
    @PostMapping("/generate-order")
    @Operation(summary = "生成采购订单")
    @PreAuthorize("@ss.hasPermission('erp:purchase-request:generate-order')")
    public CommonResult<Long> generatePurchaseOrder(@RequestParam("id") Long id,
                                                    @RequestParam("supplierId") Long supplierId) {
        return success(purchaseRequestService.generatePurchaseOrder(id, supplierId));
    }
    private PageResult<ErpPurchaseRequestRespVO> buildPurchaseRequestVOPageResult(PageResult<ErpPurchaseRequestDO> pageResult) {
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty(pageResult.getTotal());
        }
        // 1.1 ç”³è¯·æ˜Žç»†
        List<ErpPurchaseRequestItemDO> purchaseRequestItemList = purchaseRequestService.getPurchaseRequestItemListByRequestIds(
                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getId));
        Map<Long, List<ErpPurchaseRequestItemDO>> purchaseRequestItemMap = convertMultiMap(purchaseRequestItemList, ErpPurchaseRequestItemDO::getRequestId);
        // 1.2 ç‰©æ–™ä¿¡æ¯
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
        // 1.3 ä¾›åº”商信息
        Map<Long, ErpSupplierDO> supplierMap = supplierService.getSupplierMap(
                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getSupplierId));
        // 1.4 ç”¨æˆ·ä¿¡æ¯
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getRequestUserId));
        // 1.5 éƒ¨é—¨ä¿¡æ¯
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
                convertSet(pageResult.getList(), ErpPurchaseRequestDO::getRequestDeptId));
        // 2. å¼€å§‹æ‹¼æŽ¥
        return BeanUtils.toBean(pageResult, ErpPurchaseRequestRespVO.class, purchaseRequest -> {
            purchaseRequest.setItems(BeanUtils.toBean(purchaseRequestItemMap.get(purchaseRequest.getId()), ErpPurchaseRequestRespVO.Item.class,
                    item -> MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()))));
            purchaseRequest.setProductNames(CollUtil.join(purchaseRequest.getItems(), ",", ErpPurchaseRequestRespVO.Item::getProductName));
            MapUtils.findAndThen(supplierMap, purchaseRequest.getSupplierId(), supplier -> purchaseRequest.setSupplierName(supplier.getName()));
            MapUtils.findAndThen(userMap, purchaseRequest.getRequestUserId(), user -> purchaseRequest.setRequestUserName(user.getNickname()));
            MapUtils.findAndThen(deptMap, purchaseRequest.getRequestDeptId(), dept -> purchaseRequest.setRequestDeptName(dept.getName()));
        });
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
@@ -145,8 +145,13 @@
        private String productUnitName;
        @Schema(description = "库存数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
        private BigDecimal stockCount; // è¯¥å­—段仅仅在“详情”和“编辑”时使用
        private BigDecimal stockCount; // è¯¥å­—段仅仅在详情和编辑时使用
        // ========== è´¨æ£€ ==========
        @Schema(description = "是否需要来料检验", example = "true")
        private Boolean qcCheckFlag;
    }
}
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
@@ -68,6 +68,9 @@
        @Schema(description = "备注", example = "随便")
        private String remark;
        @Schema(description = "是否需要来料检验", example = "true")
        private Boolean qcCheckFlag;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - ERP é‡‡è´­ç”³è¯·åˆ†é¡µ Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ErpPurchaseRequestPageReqVO extends PageParam {
    @Schema(description = "采购申请单号", example = "CGSQ001")
    private String no;
    @Schema(description = "申请人编号", example = "1")
    private Long requestUserId;
    @Schema(description = "申请部门编号", example = "1")
    private Long requestDeptId;
    @Schema(description = "供应商编号", example = "1")
    private Long supplierId;
    @Schema(description = "状态", example = "0")
    private Integer status;
    @Schema(description = "申请时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDateTime[] requestTime;
    @Schema(description = "产品编号", example = "1")
    private Long productId;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - ERP é‡‡è´­ç”³è¯· Response VO")
@Data
public class ErpPurchaseRequestRespVO {
    @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long id;
    @Schema(description = "采购申请单号", requiredMode = Schema.RequiredMode.REQUIRED, example = "CGSQ001")
    private String no;
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "申请人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long requestUserId;
    @Schema(description = "申请人姓名", example = "张三")
    private String requestUserName;
    @Schema(description = "申请部门编号", example = "1")
    private Long requestDeptId;
    @Schema(description = "申请部门名称", example = "生产部")
    private String requestDeptName;
    @Schema(description = "申请时间", requiredMode = Schema.RequiredMode.REQUIRED)
    private LocalDateTime requestTime;
    @Schema(description = "申请理由", example = "生产需要")
    private String requestReason;
    @Schema(description = "供应商编号", example = "1")
    private Long supplierId;
    @Schema(description = "供应商名称", example = "供应商A")
    private String supplierName;
    @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED)
    private BigDecimal totalCount;
    @Schema(description = "合计价格", requiredMode = Schema.RequiredMode.REQUIRED)
    private BigDecimal totalPrice;
    @Schema(description = "产品名称列表", example = "产品A,产品B")
    private String productNames;
    @Schema(description = "生成的采购订单编号", example = "1")
    private Long orderId;
    @Schema(description = "生成的采购订单号", example = "CGDD001")
    private String orderNo;
    @Schema(description = "附件地址", example = "https://xxx.com/xxx.pdf")
    private String fileUrl;
    @Schema(description = "备注", example = "备注")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "申请明细列表")
    private java.util.List<Item> items;
    @Schema(description = "申请明细项")
    @Data
    public static class Item {
        @Schema(description = "编号", example = "1")
        private Long id;
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private Long productId;
        @Schema(description = "产品名称", example = "产品A")
        private String productName;
        @Schema(description = "产品条码", example = "12345678")
        private String productBarCode;
        @Schema(description = "产品单位编号", example = "1")
        private Long productUnitId;
        @Schema(description = "产品单位名称", example = "个")
        private String productUnitName;
        @Schema(description = "产品参考单价", example = "100.00")
        private BigDecimal productPrice;
        @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
        private BigDecimal count;
        @Schema(description = "总价", example = "1000.00")
        private BigDecimal totalPrice;
        @Schema(description = "税率,百分比", example = "13")
        private BigDecimal taxPercent;
        @Schema(description = "税额", example = "130.00")
        private BigDecimal taxPrice;
        @Schema(description = "需求日期")
        private LocalDateTime demandTime;
        @Schema(description = "备注", example = "备注")
        private String remark;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - ERP é‡‡è´­ç”³è¯·æ–°å¢ž/修改 Request VO")
@Data
public class ErpPurchaseRequestSaveReqVO {
    @Schema(description = "编号(修改时必填)", example = "1")
    private Long id;
    @Schema(description = "申请时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "申请时间不能为空")
    private LocalDateTime requestTime;
    @Schema(description = "申请理由", example = "生产需要")
    private String requestReason;
    @Schema(description = "供应商编号", example = "1")
    private Long supplierId;
    @Schema(description = "优惠率,百分比", example = "10")
    private BigDecimal discountPercent;
    @Schema(description = "附件地址", example = "https://xxx.com/xxx.pdf")
    private String fileUrl;
    @Schema(description = "备注", example = "备注")
    private String remark;
    @Schema(description = "申请明细列表", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "申请明细不能为空")
    @Valid
    private List<Item> items;
    @Schema(description = "申请明细项")
    @Data
    public static class Item {
        @Schema(description = "编号(修改时必填)", example = "1")
        private Long id;
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @NotNull(message = "产品编号不能为空")
        private Long productId;
        @Schema(description = "产品单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @NotNull(message = "产品单位不能为空")
        private Long productUnitId;
        @Schema(description = "产品参考单价", example = "100.00")
        private BigDecimal productPrice;
        @Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
        @NotNull(message = "数量不能为空")
        private BigDecimal count;
        @Schema(description = "税率,百分比", example = "13")
        private BigDecimal taxPercent;
        @Schema(description = "需求日期")
        private LocalDateTime demandTime;
        @Schema(description = "备注", example = "备注")
        private String remark;
        @Schema(description = "产品名称(前端展示用,后端忽略)", example = "产品A")
        private String productName;
        @Schema(description = "产品条码(前端展示用,后端忽略)", example = "12345678")
        private String productBarCode;
        @Schema(description = "产品单位名称(前端展示用,后端忽略)", example = "个")
        private String productUnitName;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
@@ -119,4 +119,9 @@
     */
    private Integer inStatus;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java
@@ -89,4 +89,10 @@
     */
    private BigDecimal returnCount;
    // ========== è´¨æ£€ ==========
    /**
     * æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒ
     */
    private Boolean qcCheckFlag;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,127 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * ERP é‡‡è´­ç”³è¯· DO
 */
@TableName("erp_purchase_request")
@KeySequence("erp_purchase_request_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpPurchaseRequestDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é‡‡è´­ç”³è¯·å•号
     */
    private String no;
    /**
     * çŠ¶æ€
     *
     * 0: æœªæäº¤
     * 10: å®¡æ‰¹ä¸­
     * 20: å®¡æ ¸é€šè¿‡
     * 30: å®¡æ ¸ä¸é€šè¿‡
     * 40: å·²å–消
     */
    private Integer status;
    /**
     * ç”³è¯·äººç¼–号
     */
    private Long requestUserId;
    /**
     * ç”³è¯·éƒ¨é—¨ç¼–号
     */
    private Long requestDeptId;
    /**
     * ç”³è¯·æ—¶é—´
     */
    private LocalDateTime requestTime;
    /**
     * ç”³è¯·ç†ç”±
     */
    private String requestReason;
    /**
     * ä¾›åº”商编号(可选)
     */
    private Long supplierId;
    /**
     * åˆè®¡æ•°é‡
     */
    private BigDecimal totalCount;
    /**
     * åˆè®¡ä»·æ ¼
     */
    private BigDecimal totalPrice;
    /**
     * åˆè®¡äº§å“ä»·æ ¼
     */
    private BigDecimal totalProductPrice;
    /**
     * åˆè®¡ç¨Žé¢
     */
    private BigDecimal totalTaxPrice;
    /**
     * ä¼˜æƒ çŽ‡ï¼Œç™¾åˆ†æ¯”
     */
    private BigDecimal discountPercent;
    /**
     * ä¼˜æƒ é‡‘额
     */
    private BigDecimal discountPrice;
    /**
     * é™„件地址
     */
    private String fileUrl;
    /**
     * å¤‡æ³¨
     */
    private String remark;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
    /**
     * ç”Ÿæˆçš„采购订单编号
     */
    private Long orderId;
    /**
     * ç”Ÿæˆçš„采购订单号
     */
    private String orderNo;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * ERP é‡‡è´­ç”³è¯·æ˜Žç»† DO
 */
@TableName("erp_purchase_request_items")
@KeySequence("erp_purchase_request_items_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ErpPurchaseRequestItemDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * é‡‡è´­ç”³è¯·ç¼–号
     */
    private Long requestId;
    /**
     * äº§å“ç¼–号
     */
    private Long productId;
    /**
     * äº§å“å•位编号
     */
    private Long productUnitId;
    /**
     * äº§å“å‚考单价
     */
    private BigDecimal productPrice;
    /**
     * æ•°é‡
     */
    private BigDecimal count;
    /**
     * æ€»ä»·
     */
    private BigDecimal totalPrice;
    /**
     * ç¨ŽçŽ‡ï¼Œç™¾åˆ†æ¯”
     */
    private BigDecimal taxPercent;
    /**
     * ç¨Žé¢
     */
    private BigDecimal taxPrice;
    /**
     * éœ€æ±‚日期
     */
    private LocalDateTime demandTime;
    /**
     * å¤‡æ³¨
     */
    private String remark;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestItemMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
 * ERP é‡‡è´­ç”³è¯·æ˜Žç»† Mapper
 */
@Mapper
public interface ErpPurchaseRequestItemMapper extends BaseMapperX<ErpPurchaseRequestItemDO> {
    default List<ErpPurchaseRequestItemDO> selectListByRequestId(Long requestId) {
        return selectList(ErpPurchaseRequestItemDO::getRequestId, requestId);
    }
    default List<ErpPurchaseRequestItemDO> selectListByRequestIds(Collection<Long> requestIds) {
        return selectList(ErpPurchaseRequestItemDO::getRequestId, requestIds);
    }
    default void deleteByRequestId(Long requestId) {
        delete(new LambdaQueryWrapperX<ErpPurchaseRequestItemDO>().eq(ErpPurchaseRequestItemDO::getRequestId, requestId));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.erp.dal.mysql.purchase;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
 * ERP é‡‡è´­ç”³è¯· Mapper
 */
@Mapper
public interface ErpPurchaseRequestMapper extends BaseMapperX<ErpPurchaseRequestDO> {
    default ErpPurchaseRequestDO selectByNo(String no) {
        return selectOne(ErpPurchaseRequestDO::getNo, no);
    }
    default List<ErpPurchaseRequestDO> selectRequestListByIds(Collection<Long> ids) {
        return selectList(ErpPurchaseRequestDO::getId, ids);
    }
    default List<ErpPurchaseRequestDO> selectList(Long supplierId, Integer status) {
        return selectList(new LambdaQueryWrapperX<ErpPurchaseRequestDO>()
                .eqIfPresent(ErpPurchaseRequestDO::getSupplierId, supplierId)
                .eqIfPresent(ErpPurchaseRequestDO::getStatus, status));
    }
    default PageResult<ErpPurchaseRequestDO> selectPage(ErpPurchaseRequestPageReqVO pageReqVO) {
        return selectPage(pageReqVO, new LambdaQueryWrapperX<ErpPurchaseRequestDO>()
                .likeIfPresent(ErpPurchaseRequestDO::getNo, pageReqVO.getNo())
                .eqIfPresent(ErpPurchaseRequestDO::getRequestUserId, pageReqVO.getRequestUserId())
                .eqIfPresent(ErpPurchaseRequestDO::getRequestDeptId, pageReqVO.getRequestDeptId())
                .eqIfPresent(ErpPurchaseRequestDO::getSupplierId, pageReqVO.getSupplierId())
                .eqIfPresent(ErpPurchaseRequestDO::getStatus, pageReqVO.getStatus())
                .betweenIfPresent(ErpPurchaseRequestDO::getRequestTime, pageReqVO.getRequestTime())
                .orderByDesc(ErpPurchaseRequestDO::getId));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
@@ -30,6 +30,11 @@
    public static final String PURCHASE_ORDER_NO_PREFIX = "CGDD";
    /**
     * é‡‡è´­ç”³è¯· {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO}
     */
    public static final String PURCHASE_REQUEST_NO_PREFIX = "CGSQ";
    /**
     * ä»˜æ¬¾å• {@link cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO}
     */
    public static final String FINANCE_PAYMENT_NO_PREFIX = "FKD";
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
@@ -17,7 +17,8 @@
@Getter
public enum ErpAuditStatus implements ArrayValuable<Integer> {
    PROCESS(10, "未审核"), // å®¡æ ¸ä¸­
    PROCESS(10, "未审核"), // æœªå®¡æ ¸/草稿
    APPROVING(15, "审批中"), // å®¡æ‰¹ä¸­
    APPROVE(20, "已审核"), // å®¡æ ¸é€šè¿‡
    CANCEL(30, "已作废"); // å·²ä½œåºŸ/已撤销
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.erp.enums;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
 * ERP é‡‡è´­ç”³è¯·çŠ¶æ€æžšä¸¾
 */
@RequiredArgsConstructor
@Getter
public enum ErpPurchaseRequestStatusEnum implements ArrayValuable<Integer> {
    DRAFT(0, "未提交"),
    PROCESS(10, "审批中"),
    APPROVE(20, "审核通过"),
    REJECT(30, "审核不通过"),
    CANCEL(40, "已取消");
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(ErpPurchaseRequestStatusEnum::getStatus).toArray(Integer[]::new);
    /**
     * çŠ¶æ€
     */
    private final Integer status;
    /**
     * çŠ¶æ€å
     */
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -27,6 +27,7 @@
    ErrorCode PURCHASE_ORDER_PROCESS_FAIL_EXISTS_RETURN = new ErrorCode(1_030_101_010, "反审核失败,已存在对应的采购退货单");
    ErrorCode PURCHASE_ORDER_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_101_011, "采购审核分类不存在,请先在BPM模块创建采购审核流程分类");
    ErrorCode PURCHASE_ORDER_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_101_012, "采购审核分类下没有可用的流程模型,请先创建并发布流程");
    ErrorCode PURCHASE_ORDER_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_101_013, "提交审批失败,只有未审核状态的采购订单才能提交");
    // ========== ERP é‡‡è´­å…¥åº“(1-030-102-000) ==========
    ErrorCode PURCHASE_IN_NOT_EXISTS = new ErrorCode(1_030_102_000, "采购入库单不存在");
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -43,6 +43,30 @@
    void updatePurchaseOrderStatus(Long id, Integer status);
    /**
     * æäº¤é‡‡è´­è®¢å•审批
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId);
    /**
     * èŽ·å–é‡‡è´­è®¢å•å®¡æ‰¹æµç¨‹åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     */
    List<Map<String, Object>> getPurchaseOrderApproveProcessDefinitionList();
    /**
     * æ›´æ–°é‡‡è´­è®¢å•审批结果(BPM å›žè°ƒï¼‰
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult);
    /**
     * æ›´æ–°é‡‡è´­è®¢å•的入库数量
     *
     * @param id ç¼–号
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -5,6 +5,13 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
@@ -16,6 +23,8 @@
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -34,7 +43,13 @@
 */
@Service
@Validated
@Slf4j
public class ErpPurchaseOrderServiceImpl implements ErpPurchaseOrderService {
    /**
     * BPM é‡‡è´­è®¢å•审批分类编码
     */
    private static final String PURCHASE_ORDER_APPROVE_CATEGORY_CODE = "purchase_request_approve";
    @Resource
    private ErpPurchaseOrderMapper purchaseOrderMapper;
@@ -48,6 +63,13 @@
    private ErpSupplierService supplierService;
    @Resource
    private ErpAccountService accountService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -66,9 +88,10 @@
            throw exception(PURCHASE_ORDER_NO_EXISTS);
        }
        // 2.1 æ’入订单(采购订单不需要审批,直接为已审核状态)
        // 2.1 æ’入订单(采购订单创建后为未审核状态,需要提交审批)
        ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.APPROVE.getStatus()));
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
                .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()));
        calculateTotalPrice(purchaseOrder, purchaseOrderItems);
        purchaseOrderMapper.insert(purchaseOrder);
        // 2.2 æ’入订单项
@@ -119,8 +142,6 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseOrderStatus(Long id, Integer status) {
        // é‡‡è´­è®¢å•不再支持状态流转(不需要审批)
        // æ­¤æ–¹æ³•保留用于特殊情况(如反审核后重新审核)
        boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
        // 1.1 æ ¡éªŒå­˜åœ¨
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
@@ -147,6 +168,109 @@
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒå­˜åœ¨ + æœªå®¡æ ¸çŠ¶æ€
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
        if (!ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus())) {
            throw exception(PURCHASE_ORDER_SUBMIT_FAIL_STATUS);
        }
        // 2. åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 3. æ›´æ–°é‡‡è´­è®¢å•状态为审批中
        purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
                .setId(id)
                .setStatus(ErpAuditStatus.APPROVING.getStatus())
                .setProcessInstanceId(processInstanceId));
    }
    @Override
    public List<Map<String, Object>> getPurchaseOrderApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validatePurchaseOrderApproveCategoryAndProcessDefinition();
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(PURCHASE_ORDER_APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. éåŽ†èŽ·å–æµç¨‹å®šä¹‰è¯¦æƒ…ï¼Œè¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseOrderAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒå­˜åœ¨
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
        if (!ErpAuditStatus.APPROVING.getStatus().equals(purchaseOrder.getStatus())) {
            log.warn("[updatePurchaseOrderAuditStatus] é‡‡è´­è®¢å•({}) ä¸å¤„于审批中状态", id);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToAuditStatus(bpmResult);
        if (newStatus != null) {
            purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
                    .setId(id).setStatus(newStatus));
        }
    }
    /**
     * æ ¡éªŒé‡‡è´­è®¢å•审批分类和流程定义是否存在
     */
    private void validatePurchaseOrderApproveCategoryAndProcessDefinition() {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(PURCHASE_ORDER_APPROVE_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(PURCHASE_ORDER_BPM_CATEGORY_NOT_EXISTS);
        }
    }
    /**
     * è½¬æ¢ BPM å®¡æ‰¹ç»“果为审核状态
     */
    private Integer convertBpmResultToAuditStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.APPROVE.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.PROCESS.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return ErpAuditStatus.CANCEL.getStatus();
        }
        return null;
    }
    private List<ErpPurchaseOrderItemDO> validatePurchaseOrderItems(List<ErpPurchaseOrderSaveReqVO.Item> list) {
        // è½¬åŒ–为 ErpPurchaseOrderItemDO åˆ—表(产品验证已移除)
        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseOrderItemDO.class, item -> {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,114 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
import jakarta.validation.Valid;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * ERP é‡‡è´­ç”³è¯· Service æŽ¥å£
 */
public interface ErpPurchaseRequestService {
    /**
     * åˆ›å»ºé‡‡è´­ç”³è¯·
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @return ç¼–号
     */
    Long createPurchaseRequest(@Valid ErpPurchaseRequestSaveReqVO createReqVO);
    /**
     * æ›´æ–°é‡‡è´­ç”³è¯·
     *
     * @param updateReqVO æ›´æ–°ä¿¡æ¯
     */
    void updatePurchaseRequest(@Valid ErpPurchaseRequestSaveReqVO updateReqVO);
    /**
     * åˆ é™¤é‡‡è´­ç”³è¯·
     *
     * @param ids ç¼–号数组
     */
    void deletePurchaseRequest(List<Long> ids);
    /**
     * èŽ·å¾—é‡‡è´­ç”³è¯·
     *
     * @param id ç¼–号
     * @return é‡‡è´­ç”³è¯·
     */
    ErpPurchaseRequestDO getPurchaseRequest(Long id);
    /**
     * èŽ·å¾—é‡‡è´­ç”³è¯·åˆ†é¡µ
     *
     * @param pageReqVO åˆ†é¡µæŸ¥è¯¢
     * @return é‡‡è´­ç”³è¯·åˆ†é¡µ
     */
    PageResult<ErpPurchaseRequestDO> getPurchaseRequestPage(ErpPurchaseRequestPageReqVO pageReqVO);
    /**
     * èŽ·å¾—é‡‡è´­ç”³è¯·é¡¹åˆ—è¡¨
     *
     * @param requestId é‡‡è´­ç”³è¯·ç¼–号
     * @return é‡‡è´­ç”³è¯·é¡¹åˆ—表
     */
    List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestId(Long requestId);
    /**
     * èŽ·å¾—é‡‡è´­ç”³è¯·é¡¹ List
     *
     * @param requestIds é‡‡è´­ç”³è¯·ç¼–号数组
     * @return é‡‡è´­ç”³è¯·é¡¹ List
     */
    List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestIds(Collection<Long> requestIds);
    /**
     * æäº¤é‡‡è´­ç”³è¯·å®¡æ‰¹
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     * @param userId ç”¨æˆ·ç¼–号
     */
    void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId);
    /**
     * èŽ·å–é‡‡è´­ç”³è¯·å®¡æ‰¹æµç¨‹åˆ—è¡¨
     *
     * @return æµç¨‹å®šä¹‰åˆ—表
     */
    List<Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList();
    /**
     * æ›´æ–°é‡‡è´­ç”³è¯·å®¡æ‰¹ç»“果(BPM å›žè°ƒï¼‰
     *
     * @param id ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult);
    /**
     * ç”Ÿæˆé‡‡è´­è®¢å•
     *
     * @param id é‡‡è´­ç”³è¯·ç¼–号
     * @param supplierId ä¾›åº”商编号
     * @return é‡‡è´­è®¢å•编号
     */
    Long generatePurchaseOrder(Long id, Long supplierId);
    /**
     * æ ¹æ®ç¼–号列表获取采购申请列表
     *
     * @param ids ç¼–号列表
     * @return é‡‡è´­ç”³è¯·åˆ—表
     */
    List<ErpPurchaseRequestDO> getPurchaseRequestList(List<Long> ids);
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,430 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestPageReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request.ErpPurchaseRequestSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
/**
 * ERP é‡‡è´­ç”³è¯· Service å®žçŽ°ç±»
 */
@Service
@Validated
@Slf4j
public class ErpPurchaseRequestServiceImpl implements ErpPurchaseRequestService {
    /**
     * BPM é‡‡è´­ç”³è¯·å®¡æ‰¹åˆ†ç±»ç¼–码
     */
    private static final String PURCHASE_REQUEST_APPROVE_CATEGORY_CODE = "purchase_request_approve";
    @Resource
    private ErpPurchaseRequestMapper purchaseRequestMapper;
    @Resource
    private ErpPurchaseRequestItemMapper purchaseRequestItemMapper;
    @Resource
    private ErpPurchaseOrderMapper purchaseOrderMapper;
    @Resource
    private ErpPurchaseOrderItemMapper purchaseOrderItemMapper;
    @Resource
    private ErpNoRedisDAO noRedisDAO;
    @Resource
    private ErpSupplierService supplierService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private DeptApi deptApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseRequest(ErpPurchaseRequestSaveReqVO createReqVO) {
        // 1. æ ¡éªŒè®¢å•项的有效性
        List<ErpPurchaseRequestItemDO> requestItems = validatePurchaseRequestItems(createReqVO.getItems());
        // 2. æ ¡éªŒä¾›åº”商(如果指定)
        if (createReqVO.getSupplierId() != null) {
            supplierService.validateSupplier(createReqVO.getSupplierId());
        }
        // 3. ç”Ÿæˆç”³è¯·å•号
        String no = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_REQUEST_NO_PREFIX);
        if (purchaseRequestMapper.selectByNo(no) != null) {
            throw exception(PURCHASE_REQUEST_NO_EXISTS);
        }
        // 4. èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯
        Long userId = Long.parseLong(getCurrentUserId());
        AdminUserRespDTO user = adminUserApi.getUser(userId);
        Long deptId = user != null ? user.getDeptId() : null;
        // 5. æ’入申请单
        ErpPurchaseRequestDO purchaseRequest = BeanUtils.toBean(createReqVO, ErpPurchaseRequestDO.class, req -> req
                .setNo(no)
                .setStatus(ErpPurchaseRequestStatusEnum.DRAFT.getStatus())
                .setRequestUserId(userId)
                .setRequestDeptId(deptId));
        calculateTotalPrice(purchaseRequest, requestItems);
        purchaseRequestMapper.insert(purchaseRequest);
        // 6. æ’入申请明细
        requestItems.forEach(o -> o.setRequestId(purchaseRequest.getId()));
        purchaseRequestItemMapper.insertBatch(requestItems);
        return purchaseRequest.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseRequest(ErpPurchaseRequestSaveReqVO updateReqVO) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExistsAndDraft(updateReqVO.getId());
        // 2. æ ¡éªŒä¾›åº”商(如果指定)
        if (updateReqVO.getSupplierId() != null) {
            supplierService.validateSupplier(updateReqVO.getSupplierId());
        }
        // 3. æ ¡éªŒè®¢å•项的有效性
        List<ErpPurchaseRequestItemDO> requestItems = validatePurchaseRequestItems(updateReqVO.getItems());
        // 4. æ›´æ–°ç”³è¯·å•
        ErpPurchaseRequestDO updateObj = BeanUtils.toBean(updateReqVO, ErpPurchaseRequestDO.class);
        calculateTotalPrice(updateObj, requestItems);
        purchaseRequestMapper.updateById(updateObj);
        // 5. æ›´æ–°ç”³è¯·æ˜Žç»†
        updatePurchaseRequestItemList(updateReqVO.getId(), requestItems);
    }
    private void calculateTotalPrice(ErpPurchaseRequestDO purchaseRequest, List<ErpPurchaseRequestItemDO> requestItems) {
        purchaseRequest.setTotalCount(getSumValue(requestItems, ErpPurchaseRequestItemDO::getCount, BigDecimal::add));
        purchaseRequest.setTotalProductPrice(getSumValue(requestItems, ErpPurchaseRequestItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseRequest.setTotalTaxPrice(getSumValue(requestItems, ErpPurchaseRequestItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseRequest.setTotalPrice(purchaseRequest.getTotalProductPrice().add(purchaseRequest.getTotalTaxPrice()));
        // è®¡ç®—优惠价格
        if (purchaseRequest.getDiscountPercent() == null) {
            purchaseRequest.setDiscountPercent(BigDecimal.ZERO);
        }
        purchaseRequest.setDiscountPrice(MoneyUtils.priceMultiplyPercent(purchaseRequest.getTotalPrice(), purchaseRequest.getDiscountPercent()));
        purchaseRequest.setTotalPrice(purchaseRequest.getTotalPrice().subtract(purchaseRequest.getDiscountPrice()));
    }
    private List<ErpPurchaseRequestItemDO> validatePurchaseRequestItems(List<ErpPurchaseRequestSaveReqVO.Item> list) {
        return convertList(list, o -> BeanUtils.toBean(o, ErpPurchaseRequestItemDO.class, item -> {
            if (item.getProductPrice() != null && item.getCount() != null) {
                item.setTotalPrice(MoneyUtils.priceMultiply(item.getProductPrice(), item.getCount()));
                if (item.getTaxPercent() != null) {
                    item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
                }
            }
        }));
    }
    private void updatePurchaseRequestItemList(Long id, List<ErpPurchaseRequestItemDO> newList) {
        List<ErpPurchaseRequestItemDO> oldList = purchaseRequestItemMapper.selectListByRequestId(id);
        List<List<ErpPurchaseRequestItemDO>> diffList = diffList(oldList, newList,
                (oldVal, newVal) -> oldVal.getId().equals(newVal.getId()));
        if (CollUtil.isNotEmpty(diffList.get(0))) {
            diffList.get(0).forEach(o -> o.setRequestId(id));
            purchaseRequestItemMapper.insertBatch(diffList.get(0));
        }
        if (CollUtil.isNotEmpty(diffList.get(1))) {
            purchaseRequestItemMapper.updateBatch(diffList.get(1));
        }
        if (CollUtil.isNotEmpty(diffList.get(2))) {
            purchaseRequestItemMapper.deleteByIds(convertList(diffList.get(2), ErpPurchaseRequestItemDO::getId));
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deletePurchaseRequest(List<Long> ids) {
        List<ErpPurchaseRequestDO> purchaseRequests = purchaseRequestMapper.selectRequestListByIds(ids);
        if (CollUtil.isEmpty(purchaseRequests)) {
            return;
        }
        purchaseRequests.forEach(purchaseRequest -> {
            // åªæœ‰è‰ç¨¿çŠ¶æ€æ‰èƒ½åˆ é™¤
            if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
                throw exception(PURCHASE_REQUEST_DELETE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
            }
        });
        purchaseRequests.forEach(purchaseRequest -> {
            purchaseRequestMapper.deleteById(purchaseRequest.getId());
            purchaseRequestItemMapper.deleteByRequestId(purchaseRequest.getId());
        });
    }
    @Override
    public ErpPurchaseRequestDO getPurchaseRequest(Long id) {
        return purchaseRequestMapper.selectById(id);
    }
    @Override
    public PageResult<ErpPurchaseRequestDO> getPurchaseRequestPage(ErpPurchaseRequestPageReqVO pageReqVO) {
        return purchaseRequestMapper.selectPage(pageReqVO);
    }
    @Override
    public List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestId(Long requestId) {
        return purchaseRequestItemMapper.selectListByRequestId(requestId);
    }
    @Override
    public List<ErpPurchaseRequestItemDO> getPurchaseRequestItemListByRequestIds(Collection<Long> requestIds) {
        if (CollUtil.isEmpty(requestIds)) {
            return Collections.emptyList();
        }
        return purchaseRequestItemMapper.selectListByRequestIds(requestIds);
    }
    @Override
    public List<ErpPurchaseRequestDO> getPurchaseRequestList(List<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return purchaseRequestMapper.selectRequestListByIds(ids);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExistsAndDraft(id);
        // 2. æ ¡éªŒæ˜¯å¦æœ‰æ˜Žç»†
        List<ErpPurchaseRequestItemDO> items = purchaseRequestItemMapper.selectListByRequestId(id);
        if (CollUtil.isEmpty(items)) {
            throw exception(PURCHASE_REQUEST_NOT_EXISTS); // TODO: æ·»åŠ æ˜Žç»†ä¸ºç©ºçš„é”™è¯¯ç 
        }
        // 3. åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(userId,
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 4. æ›´æ–°é‡‡è´­ç”³è¯·çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­
        purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                .setId(id)
                .setStatus(ErpPurchaseRequestStatusEnum.PROCESS.getStatus())
                .setProcessInstanceId(processInstanceId));
    }
    @Override
    public List<Map<String, Object>> getPurchaseRequestApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
        validatePurchaseRequestApproveCategoryAndProcessDefinition();
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. éåŽ†èŽ·å–æµç¨‹å®šä¹‰è¯¦æƒ…ï¼Œè¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseRequestAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒå­˜åœ¨
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
        if (!ErpPurchaseRequestStatusEnum.PROCESS.getStatus().equals(purchaseRequest.getStatus())) {
            log.warn("[updatePurchaseRequestAuditStatus] é‡‡è´­ç”³è¯·({}) ä¸å¤„于审批中状态", id);
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToStatus(bpmResult);
        if (newStatus != null) {
            purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                    .setId(id).setStatus(newStatus));
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long generatePurchaseOrder(Long id, Long supplierId) {
        // 1. æ ¡éªŒå­˜åœ¨ + å®¡æ ¸é€šè¿‡çŠ¶æ€
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
        if (!ErpPurchaseRequestStatusEnum.APPROVE.getStatus().equals(purchaseRequest.getStatus())) {
            throw exception(PURCHASE_REQUEST_NOT_APPROVE);
        }
        // 2. æ ¡éªŒæ˜¯å¦å·²ç”Ÿæˆè®¢å•
        if (purchaseRequest.getOrderId() != null) {
            throw exception(PURCHASE_REQUEST_ORDER_EXISTS);
        }
        // 3. æ ¡éªŒä¾›åº”商
        supplierService.validateSupplier(supplierId);
        // 4. èŽ·å–ç”³è¯·æ˜Žç»†
        List<ErpPurchaseRequestItemDO> requestItems = purchaseRequestItemMapper.selectListByRequestId(id);
        if (CollUtil.isEmpty(requestItems)) {
            throw exception(PURCHASE_REQUEST_NOT_EXISTS);
        }
        // 5. ç”Ÿæˆé‡‡è´­è®¢å•号
        String orderNo = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
        if (purchaseOrderMapper.selectByNo(orderNo) != null) {
            throw exception(PURCHASE_ORDER_NO_EXISTS);
        }
        // 6. åˆ›å»ºé‡‡è´­è®¢å•
        ErpPurchaseOrderDO purchaseOrder = new ErpPurchaseOrderDO();
        purchaseOrder.setNo(orderNo);
        purchaseOrder.setStatus(ErpAuditStatus.APPROVE.getStatus()); // é‡‡è´­è®¢å•直接为已审核状态
        purchaseOrder.setSupplierId(supplierId);
        purchaseOrder.setOrderTime(purchaseRequest.getRequestTime());
        purchaseOrder.setTotalCount(purchaseRequest.getTotalCount());
        purchaseOrder.setTotalPrice(purchaseRequest.getTotalPrice());
        purchaseOrder.setTotalProductPrice(purchaseRequest.getTotalProductPrice());
        purchaseOrder.setTotalTaxPrice(purchaseRequest.getTotalTaxPrice());
        purchaseOrder.setDiscountPercent(purchaseRequest.getDiscountPercent());
        purchaseOrder.setDiscountPrice(purchaseRequest.getDiscountPrice());
        purchaseOrder.setRemark(purchaseRequest.getRemark());
        purchaseOrder.setInCount(BigDecimal.ZERO);
        purchaseOrder.setInStatus(0);
        purchaseOrderMapper.insert(purchaseOrder);
        // 7. åˆ›å»ºé‡‡è´­è®¢å•明细
        List<ErpPurchaseOrderItemDO> orderItems = convertList(requestItems, item -> {
            ErpPurchaseOrderItemDO orderItem = new ErpPurchaseOrderItemDO();
            orderItem.setOrderId(purchaseOrder.getId());
            orderItem.setProductId(item.getProductId());
            orderItem.setProductUnitId(item.getProductUnitId());
            orderItem.setProductPrice(item.getProductPrice());
            orderItem.setCount(item.getCount());
            orderItem.setTotalPrice(item.getTotalPrice());
            orderItem.setTaxPercent(item.getTaxPercent());
            orderItem.setTaxPrice(item.getTaxPrice());
            orderItem.setRemark(item.getRemark());
            orderItem.setInCount(BigDecimal.ZERO);
            orderItem.setQcCheckFlag(false); // é»˜è®¤ä¸éœ€è¦è´¨æ£€
            return orderItem;
        });
        purchaseOrderItemMapper.insertBatch(orderItems);
        // 8. æ›´æ–°é‡‡è´­ç”³è¯·çš„订单信息
        purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                .setId(id)
                .setOrderId(purchaseOrder.getId())
                .setOrderNo(orderNo));
        return purchaseOrder.getId();
    }
    /**
     * æ ¡éªŒé‡‡è´­ç”³è¯·å®¡æ‰¹åˆ†ç±»å’Œæµç¨‹å®šä¹‰æ˜¯å¦å­˜åœ¨
     */
    private void validatePurchaseRequestApproveCategoryAndProcessDefinition() {
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(PURCHASE_REQUEST_APPROVE_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(PURCHASE_REQUEST_BPM_CATEGORY_NOT_EXISTS);
        }
    }
    /**
     * è½¬æ¢ BPM å®¡æ‰¹ç»“果为状态
     */
    private Integer convertBpmResultToStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return ErpPurchaseRequestStatusEnum.APPROVE.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return ErpPurchaseRequestStatusEnum.REJECT.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return ErpPurchaseRequestStatusEnum.CANCEL.getStatus();
        }
        return null;
    }
    private ErpPurchaseRequestDO validatePurchaseRequestExists(Long id) {
        ErpPurchaseRequestDO purchaseRequest = purchaseRequestMapper.selectById(id);
        if (purchaseRequest == null) {
            throw exception(PURCHASE_REQUEST_NOT_EXISTS);
        }
        return purchaseRequest;
    }
    private ErpPurchaseRequestDO validatePurchaseRequestExistsAndDraft(Long id) {
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
        if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
            throw exception(PURCHASE_REQUEST_UPDATE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
        }
        return purchaseRequest;
    }
    private String getCurrentUserId() {
        return cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId().toString();
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcService.java
@@ -5,6 +5,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.qc.iqc.vo.MesQcIqcSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
import jakarta.validation.Valid;
import java.util.Collection;
@@ -95,4 +96,15 @@
     */
    Long getIqcCountByVendorId(Long vendorId);
    /**
     * æ ¹æ®åˆ°è´§é€šçŸ¥å•行自动创建来料检验单
     *
     * @param noticeId        åˆ°è´§é€šçŸ¥å• ID
     * @param line            åˆ°è´§é€šçŸ¥å•行
     * @param noticeCode      åˆ°è´§é€šçŸ¥å•编号
     * @param vendorId        ä¾›åº”商 ID
     * @return IQC æ£€éªŒå• ID
     */
    Long createIqcFromArrivalNotice(Long noticeId, MesWmArrivalNoticeLineDO line, String noticeCode, Long vendorId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
@@ -10,6 +10,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum;
@@ -308,4 +309,40 @@
        return iqcMapper.selectCountByVendorId(vendorId);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createIqcFromArrivalNotice(Long noticeId, MesWmArrivalNoticeLineDO line, String noticeCode, Long vendorId) {
        // 1. æ ¡éªŒç‰©æ–™æ˜¯å¦é…ç½®äº† IQC æ£€éªŒæ¨¡æ¿
        MesQcTemplateItemDO templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                line.getItemId(), MesQcTypeEnum.IQC.getType());
        Long templateId = templateItem.getTemplateId();
        // 2. ç”Ÿæˆæ£€éªŒå•编码
        String code = "IQC" + System.currentTimeMillis();
        // 3. åˆ›å»º IQC ä¸»è¡¨ï¼ˆå¾…检验状态,初始合格数量为 0)
        MesQcIqcDO iqc = new MesQcIqcDO();
        iqc.setCode(code);
        iqc.setName(noticeCode + "-物料检验");
        iqc.setTemplateId(templateId);
        iqc.setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE);
        iqc.setSourceDocId(noticeId);
        iqc.setSourceLineId(line.getId());
        iqc.setSourceDocCode(noticeCode);
        iqc.setVendorId(vendorId);
        iqc.setItemId(line.getItemId());
        iqc.setReceivedQuantity(line.getArrivalQuantity());
        iqc.setCheckQuantity(BigDecimal.ZERO); // å¾…填写
        iqc.setQualifiedQuantity(BigDecimal.ZERO); // å¾…填写
        iqc.setUnqualifiedQuantity(BigDecimal.ZERO); // å¾…填写
        iqc.setReceiveDate(java.time.LocalDateTime.now());
        iqc.setStatus(MesQcStatusEnum.DRAFT.getStatus());
        iqcMapper.insert(iqc);
        // 4. ä»Žæ¨¡æ¿æŒ‡æ ‡è‡ªåŠ¨ç”Ÿæˆæ£€éªŒè¡Œ
        iqcLineService.createLinesFromTemplate(iqc.getId(), templateId);
        return iqc.getId();
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java
@@ -100,4 +100,12 @@
     */
    void createArrivalNoticeLineFromPurchaseOrderItem(MesWmArrivalNoticeLineDO line);
    /**
     * æ›´æ–°åˆ°è´§é€šçŸ¥å•行的 IQC æ£€éªŒå• ID
     *
     * @param lineId è¡Œç¼–号
     * @param iqcId  IQC æ£€éªŒå•编号
     */
    void updateArrivalNoticeLineIqcId(Long lineId, Long iqcId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
@@ -146,4 +146,10 @@
        arrivalNoticeLineMapper.insert(line);
    }
    @Override
    public void updateArrivalNoticeLineIqcId(Long lineId, Long iqcId) {
        arrivalNoticeLineMapper.updateById(new MesWmArrivalNoticeLineDO()
                .setId(lineId).setIqcId(iqcId));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -15,8 +15,10 @@
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -50,6 +52,10 @@
    @Resource
    private MesMdmItemService mdmItemService;
    @Resource
    @Lazy
    private MesQcIqcService iqcService;
    @Override
    public Long createArrivalNotice(MesWmArrivalNoticeSaveReqVO createReqVO) {
@@ -115,7 +121,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void submitArrivalNotice(Long id) {
        // 1.1 æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        validateArrivalNoticeExistsAndDraft(id);
        MesWmArrivalNoticeDO notice = validateArrivalNoticeExistsAndDraft(id);
        // 1.2 æ£€æŸ¥æ˜¯å¦æœ‰è¡Œé¡¹ç›®
        List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
        if (CollUtil.isEmpty(lines)) {
@@ -126,9 +132,26 @@
        boolean needCheck = CollectionUtils.anyMatch(lines,
                line -> Boolean.TRUE.equals(line.getIqcCheckFlag()));
        if (needCheck) {
            // éœ€è¦æ£€éªŒï¼Œè¿›å…¥å¾…质检
            // 2.1 éœ€è¦æ£€éªŒï¼Œè¿›å…¥å¾…质检
            arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
                    .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()));
            // 2.2 ä¸ºéœ€è¦è´¨æ£€çš„行自动创建 IQC æ£€éªŒå•
            for (MesWmArrivalNoticeLineDO line : lines) {
                if (Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null) {
                    try {
                        Long iqcId = iqcService.createIqcFromArrivalNotice(
                                id, line, notice.getCode(), notice.getVendorId());
                        // æ›´æ–°è¡Œä¸Šçš„ iqcId
                        arrivalNoticeLineService.updateArrivalNoticeLineIqcId(line.getId(), iqcId);
                        log.info("[submitArrivalNotice][到货通知单({}) è¡Œ({}) è‡ªåŠ¨åˆ›å»º IQC æ£€éªŒå•({})]",
                                id, line.getId(), iqcId);
                    } catch (Exception e) {
                        log.warn("[submitArrivalNotice][到货通知单({}) è¡Œ({}) åˆ›å»º IQC å¤±è´¥: {}]",
                                id, line.getId(), e.getMessage());
                        // ç»§ç»­å¤„理其他行,不中断流程
                    }
                }
            }
            return;
        }
        // ä¸éœ€è¦æ£€éªŒï¼Œç›´æŽ¥è¿›å…¥å¾…入库
@@ -295,7 +318,8 @@
            // æ³¨æ„ï¼šè¿™é‡Œéœ€è¦æ ¹æ® ERP äº§å“ ID æ˜ å°„到 MES ç‰©æ–™ ID
            // å®žé™…项目中需要通过映射服务处理,这里暂时留空让用户手动选择
            line.setArrivalQuantity(item.getCount() != null ? item.getCount().subtract(item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO) : BigDecimal.ZERO);
            line.setIqcCheckFlag(false); // é»˜è®¤ä¸éœ€è¦æ£€éªŒï¼Œç”¨æˆ·å¯ä¿®æ”¹
            // ä»Žé‡‡è´­è®¢å•明细带入质检标识
            line.setIqcCheckFlag(Boolean.TRUE.equals(item.getQcCheckFlag()));
            arrivalNoticeLineService.createArrivalNoticeLineFromPurchaseOrderItem(line);
        }