11 小时以前 f61043f70c126c4b464a4ebc7dd635fcf7b679df
feat(erp): 为ERP模块添加附件功能支持

- 在付款单、收款单、采购订单、采购申请、销售订单中集成附件上传和展示功能
- 添加附件列表查询接口用于获取相关业务单据的附件
- 实现附件绑定、更新和删除的完整生命周期管理
- 重构文件存储路径配置为/data/javaWork/mom/file
- 为采购申请分页查询添加入库状态过滤条件
- 扩展采购申请响应对象增加工作流编号字段
- 优化MES生产工单创建流程,增加工艺路线校验和工序数据同步
- 新增多个业务类型的存储记录枚举值以支持附件分类管理
已修改25个文件
259 ■■■■■ 文件已修改
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinanceReceiptController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptRespVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | 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 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/src/main/resources/application-test.yaml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinancePaymentController.java
@@ -19,6 +19,7 @@
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.erp.service.finance.ErpFinancePaymentService;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
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;
@@ -55,6 +56,8 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @PostMapping("/create")
    @Operation(summary = "创建付款单")
@@ -100,7 +103,8 @@
        }
        List<ErpFinancePaymentItemDO> paymentItemList = financePaymentService.getFinancePaymentItemListByPaymentId(id);
        return success(BeanUtils.toBean(payment, ErpFinancePaymentRespVO.class, financePaymentVO ->
                financePaymentVO.setItems(BeanUtils.toBean(paymentItemList, ErpFinancePaymentRespVO.Item.class))));
                financePaymentVO.setItems(BeanUtils.toBean(paymentItemList, ErpFinancePaymentRespVO.Item.class))
                .setAttachmentList(storageAttachmentApi.listAttachments("erp_finance_payment", id))));
    }
    @GetMapping("/page")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/ErpFinanceReceiptController.java
@@ -19,6 +19,7 @@
import cn.iocoder.yudao.module.erp.service.finance.ErpFinanceReceiptService;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
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;
@@ -55,6 +56,8 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @PostMapping("/create")
    @Operation(summary = "创建收款单")
@@ -100,7 +103,8 @@
        }
        List<ErpFinanceReceiptItemDO> receiptItemList = financeReceiptService.getFinanceReceiptItemListByReceiptId(id);
        return success(BeanUtils.toBean(receipt, ErpFinanceReceiptRespVO.class, financeReceiptVO ->
                financeReceiptVO.setItems(BeanUtils.toBean(receiptItemList, ErpFinanceReceiptRespVO.Item.class))));
                financeReceiptVO.setItems(BeanUtils.toBean(receiptItemList, ErpFinanceReceiptRespVO.Item.class))
                .setAttachmentList(storageAttachmentApi.listAttachments("erp_finance_receipt", id))));
    }
    @GetMapping("/page")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentRespVO.java
@@ -2,6 +2,7 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -54,6 +55,9 @@
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "附件列表")
    private List<StorageBlobRespDTO> attachmentList;
    @Schema(description = "创建人", example = "芋道")
    private String creator;
    @Schema(description = "创建人名称", example = "芋道")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/payment/ErpFinancePaymentSaveReqVO.java
@@ -39,6 +39,9 @@
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "附件 blobId 列表", example = "[1, 2, 3]")
    private List<Long> blobIds;
    @Schema(description = "付款项列表", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "付款项列表不能为空")
    @Valid
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptRespVO.java
@@ -2,6 +2,7 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -54,6 +55,9 @@
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "附件列表")
    private List<StorageBlobRespDTO> attachmentList;
    @Schema(description = "创建人", example = "芋道")
    private String creator;
    @Schema(description = "创建人名称", example = "芋道")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/finance/vo/receipt/ErpFinanceReceiptSaveReqVO.java
@@ -39,6 +39,9 @@
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "附件 blobId 列表", example = "[1, 2, 3]")
    private List<Long> blobIds;
    @Schema(description = "收款项列表", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "收款项列表不能为空")
    @Valid
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -18,6 +18,7 @@
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.storage.StorageAttachmentApi;
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;
@@ -56,6 +57,8 @@
    private MdmItemApi mdmItemApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @PostMapping("/create")
    @Operation(summary = "创建采购订单")
@@ -125,7 +128,7 @@
                    item.setStockCount(BigDecimal.ZERO);
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                }))));
                })).setAttachmentList(storageAttachmentApi.listAttachments("erp_purchase_order", id))));
    }
    @GetMapping("/page")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -20,6 +20,7 @@
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.storage.StorageAttachmentApi;
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;
@@ -59,6 +60,8 @@
    private AdminUserApi adminUserApi;
    @Resource
    private DeptApi deptApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @PostMapping("/create")
    @Operation(summary = "创建采购申请")
@@ -100,7 +103,7 @@
                purchaseRequestVO.setItems(BeanUtils.toBean(purchaseRequestItemList, ErpPurchaseRequestRespVO.Item.class, item -> {
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                }))));
                })).setAttachmentList(storageAttachmentApi.listAttachments("erp_purchase_request", id))));
    }
    @GetMapping("/page")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
@@ -2,6 +2,7 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -67,6 +68,9 @@
    @ExcelProperty("备注")
    private String remark;
    @Schema(description = "附件列表")
    private List<StorageBlobRespDTO> attachmentList;
    @Schema(description = "创建人", example = "芋道")
    private String creator;
    @Schema(description = "创建人名称", example = "芋道")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
@@ -35,6 +35,9 @@
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "附件 blobId 列表", example = "[1, 2, 3]")
    private List<Long> blobIds;
    @Schema(description = "订单清单列表")
    private List<Item> items;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
@@ -39,4 +39,7 @@
    @Schema(description = "产品编号", example = "1")
    private Long productId;
    @Schema(description = "入库状态(0:未入库 1:部分入库 2:全部入库)", example = "0")
    private Integer inStatus;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -1,10 +1,12 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - ERP 采购申请 Response VO")
@Data
@@ -18,6 +20,9 @@
    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
    private Integer status;
    @Schema(description = "工作流编号", example = "1043")
    private String processInstanceId;
    @Schema(description = "申请人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long requestUserId;
@@ -67,6 +72,9 @@
    @Schema(description = "备注", example = "备注")
    private String remark;
    @Schema(description = "附件列表")
    private List<StorageBlobRespDTO> attachmentList;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
@@ -36,6 +36,9 @@
    @Schema(description = "备注", example = "备注")
    private String remark;
    @Schema(description = "附件 blobId 列表", example = "[1, 2, 3]")
    private List<Long> blobIds;
    @Schema(description = "申请明细列表", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "申请明细不能为空")
    @Valid
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -20,6 +20,7 @@
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
import cn.iocoder.yudao.module.mes.api.salesnotice.MesWmSalesNoticeApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
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;
@@ -61,6 +62,8 @@
    private MesWmSalesNoticeApi salesNoticeApi;
    @Resource
    private MesProMpsApi mpsApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @PostMapping("/create")
    @Operation(summary = "创建销售订单")
@@ -131,7 +134,7 @@
                    item.setStockCount(BigDecimal.ZERO);
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                }))));
                })).setAttachmentList(storageAttachmentApi.listAttachments("erp_sale_order", id))));
    }
    @GetMapping("/page")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
@@ -2,6 +2,7 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -76,6 +77,9 @@
    @ExcelProperty("备注")
    private String remark;
    @Schema(description = "附件列表")
    private List<StorageBlobRespDTO> attachmentList;
    @Schema(description = "创建人", example = "芋道")
    private String creator;
    @Schema(description = "创建人名称", example = "芋道")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
@@ -44,6 +44,9 @@
    @Schema(description = "备注", example = "你猜")
    private String remark;
    @Schema(description = "附件 blobId 列表", example = "[1, 2, 3]")
    private List<Long> blobIds;
    @Schema(description = "订单清单列表")
    private List<Item> items;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseRequestMapper.java
@@ -37,6 +37,7 @@
                .eqIfPresent(ErpPurchaseRequestDO::getRequestDeptId, pageReqVO.getRequestDeptId())
                .eqIfPresent(ErpPurchaseRequestDO::getSupplierId, pageReqVO.getSupplierId())
                .eqIfPresent(ErpPurchaseRequestDO::getStatus, pageReqVO.getStatus())
                .eqIfPresent(ErpPurchaseRequestDO::getInStatus, pageReqVO.getInStatus())
                .betweenIfPresent(ErpPurchaseRequestDO::getRequestTime, pageReqVO.getRequestTime())
                .orderByDesc(ErpPurchaseRequestDO::getId));
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
@@ -12,6 +12,7 @@
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -52,6 +53,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createFinancePayment(ErpFinancePaymentSaveReqVO createReqVO) {
@@ -82,6 +86,10 @@
        // 2.2 插入付款单项
        paymentItems.forEach(o -> o.setPaymentId(payment.getId()));
        financePaymentItemMapper.insertBatch(paymentItems);
        // 2.3 绑定附件
        storageAttachmentApi.bindAttachments("file", "erp_finance_payment",
                payment.getId(), createReqVO.getBlobIds());
        // 3. 更新采购入库、退货的付款金额情况
        updatePurchasePrice(paymentItems);
@@ -116,6 +124,12 @@
        financePaymentMapper.updateById(updateObj);
        // 2.2 更新付款单项
        updateFinancePaymentItemList(updateReqVO.getId(), paymentItems);
        // 2.3 更新附件
        if (updateReqVO.getBlobIds() != null) {
            storageAttachmentApi.deleteAttachmentsByRecord("erp_finance_payment", updateReqVO.getId());
            storageAttachmentApi.bindAttachments("file", "erp_finance_payment",
                    updateReqVO.getId(), updateReqVO.getBlobIds());
        }
    }
    private void calculateTotalPrice(ErpFinancePaymentDO payment, List<ErpFinancePaymentItemDO> paymentItems) {
@@ -193,9 +207,11 @@
        // 2. 遍历删除,并记录操作日志
        payments.forEach(payment -> {
            // 2.1 删除付款单
            // 2.1 删除附件
            storageAttachmentApi.deleteAttachmentsByRecord("erp_finance_payment", payment.getId());
            // 2.2 删除付款单
            financePaymentMapper.deleteById(payment.getId());
            // 2.2 删除付款单项
            // 2.3 删除付款单项
            List<ErpFinancePaymentItemDO> paymentItems = financePaymentItemMapper.selectListByPaymentId(payment.getId());
            financePaymentItemMapper.deleteByIds(convertSet(paymentItems, ErpFinancePaymentItemDO::getId));
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinanceReceiptServiceImpl.java
@@ -12,6 +12,7 @@
import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinanceReceiptMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -52,6 +53,9 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createFinanceReceipt(ErpFinanceReceiptSaveReqVO createReqVO) {
@@ -82,6 +86,10 @@
        // 2.2 插入收款单项
        receiptItems.forEach(o -> o.setReceiptId(receipt.getId()));
        financeReceiptItemMapper.insertBatch(receiptItems);
        // 2.3 绑定附件
        storageAttachmentApi.bindAttachments("file", "erp_finance_receipt",
                receipt.getId(), createReqVO.getBlobIds());
        // 3. 更新销售出库、退货的收款金额情况
        updateSalePrice(receiptItems);
@@ -116,6 +124,12 @@
        financeReceiptMapper.updateById(updateObj);
        // 2.2 更新收款单项
        updateFinanceReceiptItemList(updateReqVO.getId(), receiptItems);
        // 2.3 更新附件
        if (updateReqVO.getBlobIds() != null) {
            storageAttachmentApi.deleteAttachmentsByRecord("erp_finance_receipt", updateReqVO.getId());
            storageAttachmentApi.bindAttachments("file", "erp_finance_receipt",
                    updateReqVO.getId(), updateReqVO.getBlobIds());
        }
    }
    private void calculateTotalPrice(ErpFinanceReceiptDO receipt, List<ErpFinanceReceiptItemDO> receiptItems) {
@@ -196,9 +210,11 @@
        // 2. 遍历删除,并记录操作日志
        receipts.forEach(receipt -> {
            // 2.1 删除收款单
            // 2.1 删除附件
            storageAttachmentApi.deleteAttachmentsByRecord("erp_finance_receipt", receipt.getId());
            // 2.2 删除收款单
            financeReceiptMapper.deleteById(receipt.getId());
            // 2.2 删除收款单项
            // 2.3 删除收款单项
            List<ErpFinanceReceiptItemDO> receiptItems = financeReceiptItemMapper.selectListByReceiptId(receipt.getId());
            financeReceiptItemMapper.deleteByIds(convertSet(receiptItems, ErpFinanceReceiptItemDO::getId));
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -27,6 +27,7 @@
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.mes.api.arrivalnotice.MesArrivalNoticeApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
@@ -86,6 +87,9 @@
    @Resource
    private ErpPurchaseRequestMapper purchaseRequestMapper;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) {
@@ -112,6 +116,9 @@
        // 2.2 插入订单项
        purchaseOrderItems.forEach(o -> o.setOrderId(purchaseOrder.getId()));
        purchaseOrderItemMapper.insertBatch(purchaseOrderItems);
        // 2.3 绑定附件
        storageAttachmentApi.bindAttachments("file", "erp_purchase_order",
                purchaseOrder.getId(), createReqVO.getBlobIds());
        return purchaseOrder.getId();
    }
@@ -139,6 +146,12 @@
        purchaseOrderMapper.updateById(updateObj);
        // 2.2 更新订单项
        updatePurchaseOrderItemList(updateReqVO.getId(), purchaseOrderItems);
        // 2.3 更新附件
        if (updateReqVO.getBlobIds() != null) {
            storageAttachmentApi.deleteAttachmentsByRecord("erp_purchase_order", updateReqVO.getId());
            storageAttachmentApi.bindAttachments("file", "erp_purchase_order",
                    updateReqVO.getId(), updateReqVO.getBlobIds());
        }
    }
    private void calculateTotalPrice(ErpPurchaseOrderDO purchaseOrder, List<ErpPurchaseOrderItemDO> purchaseOrderItems) {
@@ -374,7 +387,11 @@
        // 2. 遍历删除
        purchaseOrders.forEach(purchaseOrder -> {
            // 2.1 删除附件
            storageAttachmentApi.deleteAttachmentsByRecord("erp_purchase_order", purchaseOrder.getId());
            // 2.2 删除订单
            purchaseOrderMapper.deleteById(purchaseOrder.getId());
            // 2.3 删除订单项
            purchaseOrderItemMapper.deleteByOrderId(purchaseOrder.getId());
        });
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -25,6 +25,7 @@
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.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
@@ -81,6 +82,8 @@
    private AdminUserApi adminUserApi;
    @Resource
    private DeptApi deptApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -111,7 +114,11 @@
        calculateTotalPrice(purchaseRequest, requestItems);
        purchaseRequestMapper.insert(purchaseRequest);
        // 6. 插入申请明细
        // 6. 绑定附件
        storageAttachmentApi.bindAttachments("file", "erp_purchase_request",
                purchaseRequest.getId(), createReqVO.getBlobIds());
        // 7. 插入申请明细
        requestItems.forEach(o -> o.setRequestId(purchaseRequest.getId()));
        purchaseRequestItemMapper.insertBatch(requestItems);
@@ -137,6 +144,13 @@
        // 5. 更新申请明细
        updatePurchaseRequestItemList(updateReqVO.getId(), requestItems);
        // 6. 更新附件
        if (updateReqVO.getBlobIds() != null) {
            storageAttachmentApi.deleteAttachmentsByRecord("erp_purchase_request", updateReqVO.getId());
            storageAttachmentApi.bindAttachments("file", "erp_purchase_request",
                    updateReqVO.getId(), updateReqVO.getBlobIds());
        }
    }
    private void calculateTotalPrice(ErpPurchaseRequestDO purchaseRequest, List<ErpPurchaseRequestItemDO> requestItems) {
@@ -195,6 +209,7 @@
        });
        purchaseRequests.forEach(purchaseRequest -> {
            storageAttachmentApi.deleteAttachmentsByRecord("erp_purchase_request", purchaseRequest.getId());
            purchaseRequestMapper.deleteById(purchaseRequest.getId());
            purchaseRequestItemMapper.deleteByRequestId(purchaseRequest.getId());
        });
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -21,6 +21,7 @@
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsCreateReqDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -73,6 +74,9 @@
    @Resource
    private MesProMpsApi mpsApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createSaleOrder(ErpSaleOrderSaveReqVO createReqVO) {
@@ -103,6 +107,9 @@
        // 2.2 插入订单项
        saleOrderItems.forEach(o -> o.setOrderId(saleOrder.getId()));
        saleOrderItemMapper.insertBatch(saleOrderItems);
        // 2.3 绑定附件
        storageAttachmentApi.bindAttachments("file", "erp_sale_order",
                saleOrder.getId(), createReqVO.getBlobIds());
        return saleOrder.getId();
    }
@@ -133,6 +140,12 @@
        saleOrderMapper.updateById(updateObj);
        // 2.2 更新订单项
        updateSaleOrderItemList(updateReqVO.getId(), saleOrderItems);
        // 2.3 更新附件
        if (updateReqVO.getBlobIds() != null) {
            storageAttachmentApi.deleteAttachmentsByRecord("erp_sale_order", updateReqVO.getId());
            storageAttachmentApi.bindAttachments("file", "erp_sale_order",
                    updateReqVO.getId(), updateReqVO.getBlobIds());
        }
    }
    private void calculateTotalPrice(ErpSaleOrderDO saleOrder, List<ErpSaleOrderItemDO> saleOrderItems) {
@@ -370,9 +383,11 @@
        // 2. 遍历删除,并记录操作日志
        saleOrders.forEach(saleOrder -> {
            // 2.1 删除订单
            // 2.1 删除附件
            storageAttachmentApi.deleteAttachmentsByRecord("erp_sale_order", saleOrder.getId());
            // 2.2 删除订单
            saleOrderMapper.deleteById(saleOrder.getId());
            // 2.2 删除订单项
            // 2.3 删除订单项
            saleOrderItemMapper.deleteByOrderId(saleOrder.getId());
        });
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -13,13 +13,22 @@
import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMergeItemMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper;
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.pro.route.MesProRouteProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessSaveReqVO;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
@@ -30,9 +39,11 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -66,6 +77,14 @@
    private CrmCustomerApi customerApi;
    @Resource
    private MesProFeedbackMapper feedbackMapper;
    @Resource
    private MesProWorkOrderMapper workOrderMapper;
    @Resource
    private MesProRouteProductService routeProductService;
    @Resource
    private MesProRouteProcessService routeProcessService;
    @Resource
    private MesProWorkOrderProcessService workOrderProcessService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -491,6 +510,20 @@
     * 根据 MPS 创建生产工单
     */
    private Long createWorkOrderFromMps(MesProMpsDO mps, BigDecimal quantity) {
        // 1. 校验产品是否存在工艺路线
        MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(mps.getProductId());
        if (routeProduct == null) {
            throw exception(PRO_ROUTE_NOT_EXISTS);
        }
        Long routeId = routeProduct.getRouteId();
        // 2. 校验工艺路线是否有工序
        List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByRouteId(routeId);
        if (CollUtil.isEmpty(routeProcesses)) {
            throw exception(PRO_ROUTE_ENABLE_NO_PROCESS);
        }
        // 3. 创建生产工单
        MesProWorkOrderSaveReqVO workOrderReq = new MesProWorkOrderSaveReqVO();
        workOrderReq.setCode(generateWorkOrderCode());
        workOrderReq.setName("生产工单-" + mps.getCode());
@@ -505,7 +538,40 @@
        Long workOrderId = workOrderService.createWorkOrder(workOrderReq);
        // 工单直接确认为已确认状态(跳过草稿)
        // 4. 更新工单工艺路线
        workOrderMapper.updateById(new MesProWorkOrderDO().setId(workOrderId).setRouteId(routeId));
        // 5. 保存工单工序(从工艺路线工序带入,含投料BOM)
        Map<Long, List<MesProRouteProductBomDO>> bomMap = routeProcessService.getRouteProcessBomMapByProductId(mps.getProductId());
        List<MesProWorkOrderProcessSaveReqVO> processList = new ArrayList<>();
        for (MesProRouteProcessDO rp : routeProcesses) {
            MesProWorkOrderProcessSaveReqVO process = new MesProWorkOrderProcessSaveReqVO();
            process.setWorkOrderId(workOrderId);
            process.setProcessId(rp.getProcessId());
            process.setSort(rp.getSort());
            process.setKeyFlag(rp.getKeyFlag());
            process.setCheckFlag(rp.getCheckFlag());
            process.setBackflushFlag(rp.getBackflushFlag());
            process.setOutputItemId(rp.getOutputItemId());
            process.setColorCode(rp.getColorCode());
            // 带入工序投料BOM
            List<MesProRouteProductBomDO> routeBomItems = bomMap != null ? bomMap.get(rp.getProcessId()) : null;
            if (CollUtil.isNotEmpty(routeBomItems)) {
                List<MesProWorkOrderProcessSaveReqVO.BomItem> bomItems = new ArrayList<>();
                for (MesProRouteProductBomDO bomDO : routeBomItems) {
                    MesProWorkOrderProcessSaveReqVO.BomItem bomItem = new MesProWorkOrderProcessSaveReqVO.BomItem();
                    bomItem.setItemId(bomDO.getItemId());
                    bomItem.setQuantity(bomDO.getQuantity());
                    bomItem.setRemark(bomDO.getRemark());
                    bomItems.add(bomItem);
                }
                process.setBomItems(bomItems);
            }
            processList.add(process);
        }
        workOrderProcessService.batchSaveWorkOrderProcess(processList);
        // 6. 工单直接确认为已确认状态
        workOrderService.confirmWorkOrder(workOrderId);
        return workOrderId;
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -30,7 +30,31 @@
    NOTICE("notice", "通知公告"),
    // ===== 头像 =====
    AVATAR("avatar", "头像");
    AVATAR("avatar", "头像"),
    // ===== ERP 采购 =====
    ERP_PURCHASE_REQUEST("erp_purchase_request", "ERP 采购申请"),
    ERP_PURCHASE_ORDER("erp_purchase_order", "ERP 采购订单"),
    // ===== ERP 销售 =====
    ERP_SALE_ORDER("erp_sale_order", "ERP 销售订单"),
    // ===== ERP 财务 =====
    ERP_FINANCE_RECEIPT("erp_finance_receipt", "ERP 收款单"),
    ERP_FINANCE_PAYMENT("erp_finance_payment", "ERP 付款单"),
    // ===== CRM =====
    CRM_SALE_QUOTATION("crm_sale_quotation", "CRM 销售报价单"),
    CRM_FOLLOW_UP_RECORD("crm_follow_up_record", "CRM 跟进记录"),
    // ===== HRM =====
    HRM_LEAVE_APPLICATION("hrm_leave_application", "HRM 请假申请"),
    HRM_TRANSFER_APPLICATION("hrm_transfer_application", "HRM 调动申请"),
    HRM_RESIGNATION_APPLICATION("hrm_resignation_application", "HRM 离职申请"),
    HRM_ATTENDANCE_EXCEPTION("hrm_attendance_exception", "HRM 考勤异常"),
    // ===== MES =====
    MES_PD_DOCUMENT("mes_pd_document", "MES 设计资料");
    private final String type;
    private final String description;
yudao-server/src/main/resources/application-test.yaml
@@ -264,7 +264,7 @@
    miniprogram-state: developer # 跳转小程序类型:开发版为 “developer”;体验版为 “trial”为;正式版为 “formal”
  tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
  storage: # 文件上传配置
    path: /data/uploads # 文件存储根目录
    path: /data/javaWork/mom/file # 文件存储根目录
    url-prefix: /admin-api/system/storage-blob # URL路径前缀
    domain: https://test.your-domain.com # 域名前缀(按实际域名配置)
    expired: 120 # token过期时间(分钟)