gaoluyang
5 天以前 fbaeeeff5704f210bc6347454c0f16d255001822
refactor(srm): 重构供应商采购域,新增询价比价与保证金管理

- 移除供应商准入/资质/绩效/招投标/统计模块
- 新增询价比价(询价单、供应商报价、询价下单)与投标保证金台账
- 供应商档案支持银行账户,新增供应商对账单(ERP 采购)
- 采购订单/入库/退货适配新的供应商引用
- 补充 SRM 重构迁移脚本及旧表结构备份(2026-11-03)

Co-Authored-By: Claude Code <noreply@anthropic.com>
已添加41个文件
已删除85个文件
已重命名1个文件
已修改33个文件
10399 ■■■■■ 文件已修改
docs/project-business/data/flows.json 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/modules.json 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/objects.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/roles.json 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/index.html 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/01-business-mindmap.mmd 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/03-purchase-flow.mmd 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/mermaid/08-module-relation.mmd 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/finance_menu_init.sql 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/finance_module_init.sql 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/srm_legacy_tables_backup_20261103.sql 351 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/srm_procurement_refactor_20261103.sql 329 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierReferenceApi.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderCreateReqDTO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierReferenceApiImpl.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierStatementController.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/statement/ErpSupplierStatementReqVO.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/statement/ErpSupplierStatementRespVO.java 96 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderSourceTypeEnum.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierStatementService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierStatementServiceImpl.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/SrmSupplierApplyController.java 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyPageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/SrmBidBondController.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/vo/SrmBidBondPageReqVO.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/vo/SrmBidBondRespVO.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/vo/SrmBidBondSaveReqVO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/SrmSupplierEvaluationController.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationPageReqVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationRespVO.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationSaveReqVO.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/SrmInquiryController.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryConfirmReqVO.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryConfirmRespVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryPageReqVO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryQuoteSaveReqVO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryRespVO.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquirySaveReqVO.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/SrmStatisticsController.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/vo/SrmTenderStatisticsRespVO.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierBankRespVO.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierBankSaveReqVO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierPageReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierRespVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierSaveReqVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderConfigController.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardRespVO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardSaveReqVO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationRespVO.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationSaveReqVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidOpenRespVO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteSaveReqVO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidRespVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidSaveReqVO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderConfigRespVO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderConfigSaveReqVO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderConfirmReqVO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialRespVO.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialSaveReqVO.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPageReqVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPartialUpdateReqVO.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidAwardDO.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidBondDO.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidEvaluationDO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidOpenDO.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryDO.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryItemDO.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryOrderDO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryQuoteDO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquirySupplierDO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierBankDO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierCertificateDO.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierEvaluationDO.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierQuoteDO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderBidDO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderMaterialDO.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderProjectDO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidAwardMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidBondMapper.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidEvaluationMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidOpenMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryItemMapper.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryMapper.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryOrderMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryQuoteMapper.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquirySupplierMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierApplyMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierBankMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierEvaluationMapper.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierQuoteMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderConfigMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderMaterialMapper.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderProjectMapper.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/AwardStatusEnum.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BidBondRefundStatusEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BidStatusEnum.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BiddingModeEnum.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/CertificateTypeEnum.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/DeliveryStatusEnum.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/InquiryStatusEnum.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/LogRecordConstants.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/SupplierApplyStatusEnum.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderTypeEnum.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java 330 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/listener/SrmSupplierApplyStatusListener.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/bond/SrmBidBondService.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/bond/SrmBidBondServiceImpl.java 242 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateService.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationService.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationServiceImpl.java 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/inquiry/SrmInquiryService.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/inquiry/SrmInquiryServiceImpl.java 688 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsServiceImpl.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java 231 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java 277 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidService.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderConfigService.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderConfigServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectService.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java 504 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/resources/sql/srm_init.sql 378 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/resources/sql/srm_menu_init.sql 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/resources/sql/srm_test_data.sql 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/project-business/data/flows.json
@@ -10,15 +10,16 @@
       {"stage": "交付", "action": "发货通知审核(占用)→ é”€å”®å‡ºåº“ OQC â†’ å‘运 â†’ å‡ºåº“回写部分/全部出库", "roles": ["仓管", "质检"]},
       {"stage": "回款", "action": "开票 BPM(累计≤合同额) â†’ å›žæ¬¾å¼ºåˆ¶å…ˆå¼€ç¥¨(累计≤合同剩余与开票剩余)", "roles": ["财务", "销售"]}]},
    {"id": "purchase", "name": "采购流程", "mermaid": "mermaid/03-purchase-flow.mmd", "color": "#5fc79e",
     "summary": "SRM å‡†å…¥/定标供源 â†’ é‡‡è´­ç”³è¯· BPM â†’ é‡‡è´­è®¢å• BPM â†’ ç¡®è®¤æ”¶è´§ç”Ÿæˆ MES åˆ°è´§é€šçŸ¥ â†’ IQC(合格/特采入库·退货/报废) â†’ é‡‡è´­å…¥åº“回写订单与申请 â†’ æ¥ç¥¨(AI OCR) â†’ ä»˜æ¬¾å• BPM。",
     "summary": "供应商档案(多联系人/多银行·含银行行号) â†’ è¯¢ä»·æ¯”ä»·(产品×供应商横向报价,按产品选中标) â†’ ç¡®è®¤åŽæŒ‰ä¸­æ ‡ä¾›åº”商拆采购订单草稿并走现有 BPM â†’ ç¡®è®¤æ”¶è´§ç”Ÿæˆ MES åˆ°è´§é€šçŸ¥ â†’ IQC(合格/特采入库·退货/报废) â†’ é‡‡è´­å…¥åº“回写订单与申请 â†’ æ¥ç¥¨(AI OCR) â†’ ä»˜æ¬¾å• BPM â†’ ä¾›åº”商往来对账;投标保证金台账独立记录中标与退回。",
     "steps": [
       {"stage": "寻源", "action": "供应商准入(BPM)建档;招标完整版或简易比价,定标审批通过自动生成采购订单", "roles": ["采购开发"]},
       {"stage": "寻源", "action": "供应商档案维护多个联系人与多个银行账户(含银行行号);新增询价单时一并录入产品×参与供应商报价并选定每个产品的中标供应商,列表直接确认后按中标供应商生成采购订单草稿;投标保证金台账记录是否中标与退回状态/金额/日期", "roles": ["采购员", "供应商管理员"]},
       {"stage": "申请", "action": "采购申请(CGSQ)提交 BPM,驳回可重置回草稿;通过且有供应商自动生成订单", "roles": ["采购员", "审批人"]},
       {"stage": "下单", "action": "采购订单(CGDD)自动提交 BPM(15→20)", "roles": ["采购员"]},
       {"stage": "收货", "action": "确认收货生成到货通知(0→待质检2/待入库3→4);需检物料自动创建 IQC", "roles": ["仓管", "IQC è´¨æ£€å‘˜"]},
       {"stage": "入库", "action": "IQC åˆæ ¼/特采→采购入库(待上架→待执行→完成)库存+,回写订单 inCount/inStatus ä¸Žç”³è¯·æ˜Žç»†", "roles": ["仓管"]},
       {"stage": "退货", "action": "IQC ä¸åˆæ ¼é€€è´§â†’供应商退货出库(选料锁定到订单行);报废走 NCR", "roles": ["仓管"]},
       {"stage": "付款", "action": "采购来票(FPD, AI OCR, â‰¤è®¢å•总价) â†’ ä»˜æ¬¾å•(FKD å…³è”来票+附件, â‰¤æ¥ç¥¨é‡‘额) BPM å®¡æ‰¹", "roles": ["财务", "审批人"]}]},
       {"stage": "付款", "action": "采购来票(FPD, AI OCR, â‰¤è®¢å•总价) â†’ ä»˜æ¬¾å•(FKD å…³è”来票+附件, â‰¤æ¥ç¥¨é‡‘额) BPM å®¡æ‰¹", "roles": ["财务", "审批人"]},
       {"stage": "对账", "action": "供应商往来按供应商汇总:左侧采购订单流水含已开票/未开票金额,右侧来票流水含开票金额/已付/剩余应付并附付款流水,顶部汇总累计采购/开票/付款/未付余额", "roles": ["财务", "采购员"]}]},
    {"id": "production", "name": "生产流程", "mermaid": "mermaid/04-production-flow.mmd", "color": "#e0b34f",
     "summary": "销售订单驱动 MPS â†’ ä¸‹å‘工单 â†’ æŒ‰å·¥è‰ºè·¯çº¿æ‹†å·¥åºä»»åŠ¡ â†’ é¢†æ–™/线边备料 â†’ æŠ¥å·¥(工序顺序硬校验+BPM) â†’ å€’冲消耗/产出入库/IPQC â†’ æœ«é“工序回写进度 â†’ è‡ªåŠ¨å®Œå·¥ â†’ ProductionFinishedEvent å›žå†™é”€å”®è®¢å•。",
     "steps": [
docs/project-business/data/modules.json
@@ -17,20 +17,20 @@
     "upstream": ["主数据(物料)"], "downstream": ["进销存(销售订单)", "售后(客户)", "AI(报价OCR)"],
     "coreTables": ["crm_clue", "crm_customer", "crm_contact", "crm_business", "crm_sale_quotation", "crm_contract", "crm_invoice", "crm_receivable", "crm_receivable_plan", "crm_permission"]},
    {"key": "srm", "name": "供应商管理", "color": "#c9a86a", "tag": "SRM", "flow": "purchase",
     "positioning": "供应商全生命周期:准入-资质-寻源-绩效",
     "problem": "供应商合规准入、阳光寻源(招标/比价)与绩效量化",
     "features": ["供应商档案与分类", "准入申请(BPM é‡‡è´­/质量/财务会签)", "资质证书有效期", "招标:公开/邀请/竞谈完整版+比价简易版", "投标·报价·开标·评标·定标", "定标自动生成采购订单", "四维绩效(质量/交付/ä»·æ ¼/服务)"],
     "objects": ["供应商", "准入申请", "资质证书", "招标项目", "投标", "定标", "绩效评价"],
     "roles": ["采购开发", "供应商管理员", "评审专家"],
     "positioning": "供应商档案 + è¯¢ä»·æ¯”ä»· + æŠ•标保证金",
     "problem": "供应商信息多开户行/多联系人维护,采购寻源按产品横向比价并快速转为采购订单",
     "features": ["供应商档案与分类", "多联系人/多银行账户(含银行行号)", "询价单:新增时一并录入产品行×参与供应商报价", "每个产品单独选定中标供应商", "确认后按中标供应商拆分生成采购订单草稿", "投标保证金台账(是否中标/退回状态金额日期)"],
     "objects": ["供应商", "询价比价", "投标保证金"],
     "roles": ["采购员", "供应商管理员"],
     "upstream": ["主数据(物料)"], "downstream": ["进销存(采购订单)"],
     "coreTables": ["srm_supplier", "srm_supplier_apply", "srm_supplier_certificate", "srm_tender_project", "srm_tender_bid", "srm_bid_award", "srm_supplier_evaluation"]},
     "coreTables": ["srm_supplier", "srm_supplier_contact", "srm_supplier_bank", "srm_supplier_category", "srm_inquiry", "srm_inquiry_item", "srm_inquiry_supplier", "srm_inquiry_quote", "srm_inquiry_order", "srm_bid_bond"]},
    {"key": "erp", "name": "进销存与财务", "color": "#5fc79e", "tag": "ERP", "flow": "purchase",
     "positioning": "商贸与资金单据中枢,向 MES å‘起实物收发",
     "problem": "采购销售单据审批、来票付款控制、订单执行进度回写",
     "features": ["采购申请(BPM)→订单(自动/手动生成)", "确认收货→MES到货通知", "入库/退货数量状态回写", "采购来票(金额≤订单)", "付款单(必须关联来票·BPM)", "销售订单(BPM·审批通过自动建MPS)", "出库状态回写", "收款单(关联CRM客户)", "结算账户", "AI发票OCR"],
     "objects": ["采购申请", "采购订单", "采购来票", "付款单", "销售订单", "收款单", "结算账户"],
     "features": ["采购申请(BPM)→订单(自动/手动生成)", "确认收货→MES到货通知", "入库/退货数量状态回写", "采购来票(金额≤订单)", "付款单(必须关联来票·BPM)", "供应商往来(订单/来票/付款三流水·左右展示开票金额)", "销售订单(BPM·审批通过自动建MPS)", "出库状态回写", "收款单(关联CRM客户)", "结算账户", "AI发票OCR"],
     "objects": ["采购申请", "采购订单", "采购来票", "付款单", "供应商往来", "销售订单", "收款单", "结算账户"],
     "roles": ["采购员", "销售员", "出纳", "会计"],
     "upstream": ["CRM(合同)", "SRM(定标)", "主数据"], "downstream": ["生产(MPS)", "仓储(收发)"],
     "upstream": ["CRM(合同)", "SRM(询价确认)", "主数据"], "downstream": ["生产(MPS)", "仓储(收发)"],
     "coreTables": ["erp_purchase_request", "erp_purchase_order", "erp_purchase_invoice", "erp_finance_payment", "erp_sale_order", "erp_finance_receipt", "erp_account"]},
    {"key": "finance", "name": "财务管理(会计核算)", "color": "#3ec9a7", "tag": "FIN", "flow": "finance",
     "positioning": "独立核算模块:会计核算 + èµ„产核算 + è´¢åŠ¡æŠ¥è¡¨çœ‹æ¿ï¼ˆä¸Ž ERP è¿›é”€å­˜è´¢åŠ¡å•æ®åˆ†ç¦»ï¼Œyudao-module-finance)",
docs/project-business/data/objects.json
@@ -11,13 +11,14 @@
    {"name": "发票", "module": "销售管理", "table": "crm_invoice", "doc": "KP编号(可留空自动生成、系统内唯一),BPM å®¡æ‰¹ï¼Œç´¯è®¡å¼€ç¥¨â‰¤åˆåŒé¢ï¼Œé™„件必传;支持 Excel å¯¼å…¥ï¼ˆä»…新增不更新,合同须审核通过且本次金额≤合同剩余可开票额;同一批数据不可重复导入——同合同下发票号码相同、或发票号码留空时开票金额+发票抬头+开票日期相同即判重复,文件内重复行与库内已有单都拒该行并回显维度与实际值)", "upstream": ["合同"], "downstream": ["回款"], "keywords": "开票 å‘票 invoice å¯¼å…¥ åŽ»é‡"},
    {"name": "回款", "module": "销售管理", "table": "crm_receivable", "doc": "HK编号,创建即审批中(手动审核),强制先开票,金额双校验", "upstream": ["合同", "发票"], "downstream": ["收款单(关联)"], "keywords": "回款 æ”¶æ¬¾è®¡åˆ’ receivable"},
    {"name": "销售目标", "module": "销售管理", "table": "crm_sales_target", "doc": "å¹´/å­£/月×个人/部门/产品维度的合同回款等目标", "upstream": [], "downstream": ["业绩统计"], "keywords": "目标 target ä¸šç»©"},
    {"name": "供应商", "module": "供应商管理", "table": "srm_supplier", "doc": "供应商档案:执照/税号/银行/联系人,ERP ä¸Ž MES å…±ç”¨", "upstream": ["准入申请"], "downstream": ["采购订单", "到货通知", "供应商退货", "绩效评价"], "keywords": "供应商 supplier vendor"},
    {"name": "准入申请", "module": "供应商管理", "table": "srm_supplier_apply", "doc": "AP编号,BPM å®¡æ‰¹(采购/质量/财务),通过自动建供应商档案", "upstream": [], "downstream": ["供应商"], "keywords": "准入 ä¾›åº”商申请 apply"},
    {"name": "招标项目", "module": "供应商管理", "table": "srm_tender_project", "doc": "完整版(发布→投标→开标→评标→定标)与简易比价两种模式", "upstream": [], "downstream": ["定标→采购订单"], "keywords": "招标 æŠ•æ ‡ æ¯”ä»· å®šæ ‡ tender å¯»æº"},
    {"name": "供应商", "module": "供应商管理", "table": "srm_supplier", "doc": "供应商档案:执照/税号;多个联系人(srm_supplier_contact)与多个银行账户(srm_supplier_bank·含银行行号),主表保留默认联系人/默认银行字段,ERP ä¸Ž MES å…±ç”¨", "upstream": ["手工建档/Excel å¯¼å…¥"], "downstream": ["询价比价", "投标保证金", "采购订单", "到货通知", "供应商退货", "供应商往来"], "keywords": "供应商 supplier vendor è”系人 å¼€æˆ·è¡Œ é“¶è¡Œè¡Œå·"},
    {"name": "询价比价", "module": "供应商管理", "table": "srm_inquiry", "doc": "XJ编号,草稿0→已转订单20→已取消30(10询价中为历史状态);新增/编辑时一并录入产品行×参与供应商报价(srm_inquiry_quote)并选定每个产品的中标供应商,无需单独的报价/比价步骤,确认后按中标供应商拆分生成采购订单草稿并记录 srm_inquiry_order", "upstream": ["供应商", "主数据(物料)"], "downstream": ["采购订单(草稿)"], "keywords": "询价 æ¯”ä»· æŠ¥ä»· ä¸­æ ‡ inquiry quote"},
    {"name": "投标保证金", "module": "供应商管理", "table": "srm_bid_bond", "doc": "BZ编号台账,关联供应商/询价单/采购订单,记录保证金金额、缴纳日期、是否中标、退回状态(0未退1部分2已退3无需退)/退回金额/退回日期", "upstream": ["供应商", "询价比价", "采购订单"], "downstream": ["供应商往来(参考)"], "keywords": "投标保证金 ä¸­æ ‡ é€€å›ž bid bond"},
    {"name": "采购申请", "module": "进销存", "table": "erp_purchase_request", "doc": "CGSQ编号,BPM å®¡æ‰¹ï¼Œé€šè¿‡åŽæœ‰ä¾›åº”商自动生成订单", "upstream": ["生产缺口(人工)"], "downstream": ["采购订单"], "keywords": "采购申请 è¯·è´­ request"},
    {"name": "采购订单", "module": "进销存", "table": "erp_purchase_order", "doc": "CGDD编号,BPM å®¡æ‰¹(15审批中/20已审核),入库状态:未0部分1全部2", "upstream": ["采购申请", "定标"], "downstream": ["到货通知", "采购来票", "供应商退货(约束物料)"], "keywords": "采购订单 purchase"},
    {"name": "采购来票", "module": "进销存", "table": "erp_purchase_invoice", "doc": "FPD编号,AI OCR é¢„填,累计≤订单总价,付款单的前置", "upstream": ["采购订单"], "downstream": ["付款单"], "keywords": "来票 å‘票 é‡‡è´­å‘票"},
    {"name": "付款单", "module": "财务", "table": "erp_finance_payment", "doc": "FKD编号,必须关联来票+附件,累计≤来票金额,BPM å®¡æ‰¹", "upstream": ["采购来票"], "downstream": ["结算账户"], "keywords": "付款 åº”付 payment"},
    {"name": "采购订单", "module": "进销存", "table": "erp_purchase_order", "doc": "CGDD编号,BPM å®¡æ‰¹(草稿0→审批中15→已审核20),入库状态:未0部分1全部2;来源可为采购申请或询价比价确认生成的草稿(来源单号回溯);单价为含税单价,价税合计=单价×数量,税额从含税金额中反算(不再额外加税)", "upstream": ["采购申请", "询价比价(确认中标)"], "downstream": ["到货通知", "采购来票", "供应商退货(约束物料)", "供应商往来"], "keywords": "采购订单 purchase"},
    {"name": "采购来票", "module": "进销存", "table": "erp_purchase_invoice", "doc": "FPD编号,AI OCR é¢„填,累计≤订单总价,付款单的前置;供应商往来右侧展示开票金额/已付/剩余应付", "upstream": ["采购订单"], "downstream": ["付款单", "供应商往来"], "keywords": "来票 å‘票 é‡‡è´­å‘票"},
    {"name": "付款单", "module": "财务", "table": "erp_finance_payment", "doc": "FKD编号,必须关联来票+附件,累计≤来票金额,BPM å®¡æ‰¹", "upstream": ["采购来票"], "downstream": ["结算账户", "供应商往来"], "keywords": "付款 åº”付 payment"},
    {"name": "供应商往来", "module": "进销存", "table": "无独立表(聚合视图)", "doc": "按供应商+时间范围聚合采购订单/来票/付款:汇总累计采购额、累计开票额、累计付款额、未付余额;左侧订单行含已开票/未开票金额,右侧来票行含开票金额/已付/剩余应付", "upstream": ["采购订单", "采购来票", "付款单"], "downstream": [], "keywords": "供应商往来 å¯¹è´¦ å¼€ç¥¨é‡‘额 åº”付 statement"},
    {"name": "收款单", "module": "财务", "table": "erp_finance_receipt", "doc": "SKD编号,关联 CRM å®¢æˆ·æ ¡éªŒï¼Œæ—  BPM(手动审核)", "upstream": ["销售(弱关联)"], "downstream": ["结算账户"], "keywords": "收款 åº”æ”¶ receipt"},
    {"name": "会计科目", "module": "会计核算", "table": "fin_account_subject", "doc": "科目树,parent_id è‡ªå…³è”;科目类型1资产2负债3权益4成本5损益,余额方向1借2贷,状态0启用1禁用;账表与分录统一按 subject_code å–æ•°", "upstream": ["会计科目种子约68条一级科目"], "downstream": ["记账凭证分录", "总账", "明细账"], "keywords": "会计科目 ç§‘ç›® subject account èµ„产类 è´Ÿå€ºç±» æŸç›Šç±»"},
    {"name": "记账凭证", "module": "会计核算", "table": "fin_voucher", "doc": "凭证字号可留空由后端自动生成;状态 æœªè¿‡è´¦0/已过账10/已作废20;已过账不可改,未过账与已过账均可作废,未过账与已作废可删除;附件按 recordType=fin_voucher æŒ‚系统附件中心", "upstream": ["会计科目"], "downstream": ["总账", "明细账", "财务报表看板"], "keywords": "记账凭证 å‡­è¯ voucher è¿‡è´¦ ä½œåºŸ å€Ÿè´·å¹³è¡¡"},
docs/project-business/data/roles.json
@@ -10,7 +10,7 @@
    {"role": "销售内勤/销售", "modules": ["CRM", "ERP"], "operations": ["线索客户商机维护", "报价与合同", "销售订单", "跟进记录"], "data": ["crm_*", "erp_sale_order"], "scope": "负责人(owner)+数据权限三级:只读/读写/负责人;公海池可领取;交接人应可通过 expandUserIds çœ‹åˆ°ç¦»èŒäººæ•°æ®(接口已备、待接入)"},
    {"role": "销售经理", "modules": ["CRM"], "operations": ["商机/合同审批参与", "销售目标制定", "业绩排名漏斗分析"], "data": ["团队数据(下属用户)"], "scope": "AdminUserApi ä¸‹çº§ç”¨æˆ·æ‰©å±•"},
    {"role": "采购员", "modules": ["ERP", "SRM"], "operations": ["采购申请/订单", "确认收货", "来票与付款发起"], "data": ["erp_purchase_*", "srm_*"], "scope": "requestUserId/supplierOwner å½’属"},
    {"role": "采购开发/供应商管理员", "modules": ["SRM"], "operations": ["准入审批流转", "招标比价组织", "绩效评价"], "data": ["srm_supplier_apply", "srm_tender_*"], "scope": "BPM å¤šéƒ¨é—¨ä¼šç­¾(采购/质量/财务)"},
    {"role": "采购员/供应商管理员", "modules": ["SRM"], "operations": ["供应商档案维护", "询价比价与确认转订单", "投标保证金台账"], "data": ["srm_supplier*", "srm_inquiry*", "srm_bid_bond"], "scope": "采购管理菜单权限;询价确认生成的采购订单草稿走 ERP BPM"},
    {"role": "计划员", "modules": ["MES"], "operations": ["MPS ä¸Žå·¥å•", "工艺路线维护", "AI é¢„测参考"], "data": ["mes_pro_*"], "scope": "车间/部门数据范围"},
    {"role": "操作工/班组长", "modules": ["MES"], "operations": ["任务接收", "报工提交(受工序顺序约束)", "安灯"], "data": ["mes_pro_feedback", "mes_pro_task"], "scope": "任务指派人可见"},
    {"role": "仓管员", "modules": ["MES-WM"], "operations": ["15 ç±»å‡ºå…¥åº“单据执行", "调拨", "盘点", "条码装箱"], "data": ["mes_wm_*"], "scope": "仓库维度;WMS å¯æŒ‰ä»“库配置出入库/调拨/盘点是否走 BPM"},
docs/project-business/index.html
@@ -350,13 +350,13 @@
     "upstream": ["主数据(物料)"], "downstream": ["进销存(销售订单)", "售后(客户)", "AI(报价OCR)"],
     "coreTables": ["crm_clue", "crm_customer", "crm_contact", "crm_business", "crm_sale_quotation", "crm_contract", "crm_invoice", "crm_receivable", "crm_receivable_plan", "crm_permission"]},
    {"key": "srm", "name": "供应商管理", "color": "#c9a86a", "tag": "SRM", "flow": "purchase",
     "positioning": "供应商全生命周期:准入-资质-寻源-绩效",
     "problem": "供应商合规准入、阳光寻源(招标/比价)与绩效量化",
     "features": ["供应商档案与分类", "准入申请(BPM é‡‡è´­/质量/财务会签)", "资质证书有效期", "招标:公开/邀请/竞谈完整版+比价简易版", "投标·报价·开标·评标·定标", "定标自动生成采购订单", "四维绩效(质量/交付/ä»·æ ¼/服务)"],
     "objects": ["供应商", "准入申请", "资质证书", "招标项目", "投标", "定标", "绩效评价"],
     "roles": ["采购开发", "供应商管理员", "评审专家"],
     "positioning": "供应商档案 + è¯¢ä»·æ¯”ä»· + æŠ•标保证金",
     "problem": "供应商信息多开户行/多联系人维护,采购寻源按产品横向比价并快速转为采购订单",
     "features": ["供应商档案与分类", "多联系人/多银行账户(含银行行号)", "询价单:新增时一并录入产品行×参与供应商报价", "每个产品单独选定中标供应商", "确认后按中标供应商拆分生成采购订单草稿", "投标保证金台账(是否中标/退回状态金额日期)"],
     "objects": ["供应商", "询价比价", "投标保证金"],
     "roles": ["采购员", "供应商管理员"],
     "upstream": ["主数据(物料)"], "downstream": ["进销存(采购订单)"],
     "coreTables": ["srm_supplier", "srm_supplier_apply", "srm_supplier_certificate", "srm_tender_project", "srm_tender_bid", "srm_bid_award", "srm_supplier_evaluation"]},
     "coreTables": ["srm_supplier", "srm_supplier_contact", "srm_supplier_bank", "srm_supplier_category", "srm_inquiry", "srm_inquiry_item", "srm_inquiry_supplier", "srm_inquiry_quote", "srm_inquiry_order", "srm_bid_bond"]},
    {"key": "erp", "name": "进销存与财务", "color": "#5fc79e", "tag": "ERP", "flow": "purchase",
     "positioning": "商贸与资金单据中枢,向 MES å‘起实物收发",
     "problem": "采购销售单据审批、来票付款控制、订单执行进度回写",
@@ -465,13 +465,14 @@
    {"name": "发票", "module": "销售管理", "table": "crm_invoice", "doc": "KP编号(可留空自动生成、系统内唯一),BPM å®¡æ‰¹ï¼Œç´¯è®¡å¼€ç¥¨â‰¤åˆåŒé¢ï¼Œé™„件必传;支持 Excel å¯¼å…¥ï¼ˆä»…新增不更新,合同须审核通过且本次金额≤合同剩余可开票额;同一批数据不可重复导入——同合同下发票号码相同、或发票号码留空时开票金额+发票抬头+开票日期相同即判重复,文件内重复行与库内已有单都拒该行并回显维度与实际值)", "upstream": ["合同"], "downstream": ["回款"], "keywords": "开票 å‘票 invoice å¯¼å…¥ åŽ»é‡"},
    {"name": "回款", "module": "销售管理", "table": "crm_receivable", "doc": "HK编号,创建即审批中(手动审核),强制先开票,金额双校验", "upstream": ["合同", "发票"], "downstream": ["收款单(关联)"], "keywords": "回款 æ”¶æ¬¾è®¡åˆ’ receivable"},
    {"name": "销售目标", "module": "销售管理", "table": "crm_sales_target", "doc": "å¹´/å­£/月×个人/部门/产品维度的合同回款等目标", "upstream": [], "downstream": ["业绩统计"], "keywords": "目标 target ä¸šç»©"},
    {"name": "供应商", "module": "供应商管理", "table": "srm_supplier", "doc": "供应商档案:执照/税号/银行/联系人,ERP ä¸Ž MES å…±ç”¨", "upstream": ["准入申请"], "downstream": ["采购订单", "到货通知", "供应商退货", "绩效评价"], "keywords": "供应商 supplier vendor"},
    {"name": "准入申请", "module": "供应商管理", "table": "srm_supplier_apply", "doc": "AP编号,BPM å®¡æ‰¹(采购/质量/财务),通过自动建供应商档案", "upstream": [], "downstream": ["供应商"], "keywords": "准入 ä¾›åº”商申请 apply"},
    {"name": "招标项目", "module": "供应商管理", "table": "srm_tender_project", "doc": "完整版(发布→投标→开标→评标→定标)与简易比价两种模式", "upstream": [], "downstream": ["定标→采购订单"], "keywords": "招标 æŠ•æ ‡ æ¯”ä»· å®šæ ‡ tender å¯»æº"},
    {"name": "供应商", "module": "供应商管理", "table": "srm_supplier", "doc": "供应商档案:执照/税号;多个联系人(srm_supplier_contact)与多个银行账户(srm_supplier_bank·含银行行号),主表保留默认联系人/默认银行字段,ERP ä¸Ž MES å…±ç”¨", "upstream": ["手工建档/Excel å¯¼å…¥"], "downstream": ["询价比价", "投标保证金", "采购订单", "到货通知", "供应商退货", "供应商往来"], "keywords": "供应商 supplier vendor è”系人 å¼€æˆ·è¡Œ é“¶è¡Œè¡Œå·"},
    {"name": "询价比价", "module": "供应商管理", "table": "srm_inquiry", "doc": "XJ编号,草稿0→已转订单20→已取消30(10询价中为历史状态);新增/编辑时一并录入产品行×参与供应商报价(srm_inquiry_quote)并选定每个产品的中标供应商,无需单独的报价/比价步骤,确认后按中标供应商拆分生成采购订单草稿并记录 srm_inquiry_order", "upstream": ["供应商", "主数据(物料)"], "downstream": ["采购订单(草稿)"], "keywords": "询价 æ¯”ä»· æŠ¥ä»· ä¸­æ ‡ inquiry quote"},
    {"name": "投标保证金", "module": "供应商管理", "table": "srm_bid_bond", "doc": "BZ编号台账,关联供应商/询价单/采购订单,记录保证金金额、缴纳日期、是否中标、退回状态(0未退1部分2已退3无需退)/退回金额/退回日期", "upstream": ["供应商", "询价比价", "采购订单"], "downstream": ["供应商往来(参考)"], "keywords": "投标保证金 ä¸­æ ‡ é€€å›ž bid bond"},
    {"name": "采购申请", "module": "进销存", "table": "erp_purchase_request", "doc": "CGSQ编号,BPM å®¡æ‰¹ï¼Œé€šè¿‡åŽæœ‰ä¾›åº”商自动生成订单", "upstream": ["生产缺口(人工)"], "downstream": ["采购订单"], "keywords": "采购申请 è¯·è´­ request"},
    {"name": "采购订单", "module": "进销存", "table": "erp_purchase_order", "doc": "CGDD编号,BPM å®¡æ‰¹(15审批中/20已审核),入库状态:未0部分1全部2", "upstream": ["采购申请", "定标"], "downstream": ["到货通知", "采购来票", "供应商退货(约束物料)"], "keywords": "采购订单 purchase"},
    {"name": "采购来票", "module": "进销存", "table": "erp_purchase_invoice", "doc": "FPD编号,AI OCR é¢„填,累计≤订单总价,付款单的前置", "upstream": ["采购订单"], "downstream": ["付款单"], "keywords": "来票 å‘票 é‡‡è´­å‘票"},
    {"name": "付款单", "module": "财务", "table": "erp_finance_payment", "doc": "FKD编号,必须关联来票+附件,累计≤来票金额,BPM å®¡æ‰¹", "upstream": ["采购来票"], "downstream": ["结算账户"], "keywords": "付款 åº”付 payment"},
    {"name": "采购订单", "module": "进销存", "table": "erp_purchase_order", "doc": "CGDD编号,BPM å®¡æ‰¹(草稿0→审批中15→已审核20),入库状态:未0部分1全部2;来源可为采购申请或询价比价确认生成的草稿(来源单号回溯);单价为含税单价,价税合计=单价×数量,税额从含税金额中反算(不再额外加税)", "upstream": ["采购申请", "询价比价(确认中标)"], "downstream": ["到货通知", "采购来票", "供应商退货(约束物料)", "供应商往来"], "keywords": "采购订单 purchase"},
    {"name": "采购来票", "module": "进销存", "table": "erp_purchase_invoice", "doc": "FPD编号,AI OCR é¢„填,累计≤订单总价,付款单的前置;供应商往来右侧展示开票金额/已付/剩余应付", "upstream": ["采购订单"], "downstream": ["付款单", "供应商往来"], "keywords": "来票 å‘票 é‡‡è´­å‘票"},
    {"name": "付款单", "module": "财务", "table": "erp_finance_payment", "doc": "FKD编号,必须关联来票+附件,累计≤来票金额,BPM å®¡æ‰¹", "upstream": ["采购来票"], "downstream": ["结算账户", "供应商往来"], "keywords": "付款 åº”付 payment"},
    {"name": "供应商往来", "module": "进销存", "table": "无独立表(聚合视图)", "doc": "按供应商+时间范围聚合采购订单/来票/付款:汇总累计采购额、累计开票额、累计付款额、未付余额;左侧订单行含已开票/未开票金额,右侧来票行含开票金额/已付/剩余应付", "upstream": ["采购订单", "采购来票", "付款单"], "downstream": [], "keywords": "供应商往来 å¯¹è´¦ å¼€ç¥¨é‡‘额 åº”付 statement"},
    {"name": "收款单", "module": "财务", "table": "erp_finance_receipt", "doc": "SKD编号,关联 CRM å®¢æˆ·æ ¡éªŒï¼Œæ—  BPM(手动审核)", "upstream": ["销售(弱关联)"], "downstream": ["结算账户"], "keywords": "收款 åº”æ”¶ receipt"},
    {"name": "会计科目", "module": "会计核算", "table": "fin_account_subject", "doc": "科目树,parent_id è‡ªå…³è”;科目类型1资产2负债3权益4成本5损益,余额方向1借2贷,状态0启用1禁用;账表与分录统一按 subject_code å–æ•°", "upstream": ["会计科目种子约68条一级科目"], "downstream": ["记账凭证分录", "总账", "明细账"], "keywords": "会计科目 ç§‘ç›® subject account èµ„产类 è´Ÿå€ºç±» æŸç›Šç±»"},
    {"name": "记账凭证", "module": "会计核算", "table": "fin_voucher", "doc": "凭证字号可留空由后端自动生成;状态 æœªè¿‡è´¦0/已过账10/已作废20;已过账不可改,未过账与已过账均可作废,未过账与已作废可删除;附件按 recordType=fin_voucher æŒ‚系统附件中心", "upstream": ["会计科目"], "downstream": ["总账", "明细账", "财务报表看板"], "keywords": "记账凭证 å‡­è¯ voucher è¿‡è´¦ ä½œåºŸ å€Ÿè´·å¹³è¡¡"},
@@ -521,15 +522,16 @@
       {"stage": "交付", "action": "发货通知审核(占用)→ é”€å”®å‡ºåº“ OQC â†’ å‘运 â†’ å‡ºåº“回写部分/全部出库", "roles": ["仓管", "质检"]},
       {"stage": "回款", "action": "开票 BPM(累计≤合同额) â†’ å›žæ¬¾å¼ºåˆ¶å…ˆå¼€ç¥¨(累计≤合同剩余与开票剩余)", "roles": ["财务", "销售"]}]},
    {"id": "purchase", "name": "采购流程", "mermaid": "mermaid/03-purchase-flow.mmd", "color": "#5fc79e",
     "summary": "SRM å‡†å…¥/定标供源 â†’ é‡‡è´­ç”³è¯· BPM â†’ é‡‡è´­è®¢å• BPM â†’ ç¡®è®¤æ”¶è´§ç”Ÿæˆ MES åˆ°è´§é€šçŸ¥ â†’ IQC(合格/特采入库·退货/报废) â†’ é‡‡è´­å…¥åº“回写订单与申请 â†’ æ¥ç¥¨(AI OCR) â†’ ä»˜æ¬¾å• BPM。",
     "summary": "供应商档案(多联系人/多银行·含银行行号) â†’ è¯¢ä»·æ¯”ä»·(产品×供应商横向报价,按产品选中标) â†’ ç¡®è®¤åŽæŒ‰ä¸­æ ‡ä¾›åº”商拆采购订单草稿并走现有 BPM â†’ ç¡®è®¤æ”¶è´§ç”Ÿæˆ MES åˆ°è´§é€šçŸ¥ â†’ IQC(合格/特采入库·退货/报废) â†’ é‡‡è´­å…¥åº“回写订单与申请 â†’ æ¥ç¥¨(AI OCR) â†’ ä»˜æ¬¾å• BPM â†’ ä¾›åº”商往来对账;投标保证金台账独立记录中标与退回。",
     "steps": [
       {"stage": "寻源", "action": "供应商准入(BPM)建档;招标完整版或简易比价,定标审批通过自动生成采购订单", "roles": ["采购开发"]},
       {"stage": "寻源", "action": "供应商档案维护多个联系人与多个银行账户(含银行行号);新增询价单时一并录入产品×参与供应商报价并选定每个产品的中标供应商,列表直接确认后按中标供应商生成采购订单草稿;投标保证金台账记录是否中标与退回状态/金额/日期", "roles": ["采购员", "供应商管理员"]},
       {"stage": "申请", "action": "采购申请(CGSQ)提交 BPM,驳回可重置回草稿;通过且有供应商自动生成订单", "roles": ["采购员", "审批人"]},
       {"stage": "下单", "action": "采购订单(CGDD)自动提交 BPM(15→20)", "roles": ["采购员"]},
       {"stage": "收货", "action": "确认收货生成到货通知(0→待质检2/待入库3→4);需检物料自动创建 IQC", "roles": ["仓管", "IQC è´¨æ£€å‘˜"]},
       {"stage": "入库", "action": "IQC åˆæ ¼/特采→采购入库(待上架→待执行→完成)库存+,回写订单 inCount/inStatus ä¸Žç”³è¯·æ˜Žç»†", "roles": ["仓管"]},
       {"stage": "退货", "action": "IQC ä¸åˆæ ¼é€€è´§â†’供应商退货出库(选料锁定到订单行);报废走 NCR", "roles": ["仓管"]},
       {"stage": "付款", "action": "采购来票(FPD, AI OCR, â‰¤è®¢å•总价) â†’ ä»˜æ¬¾å•(FKD å…³è”来票+附件, â‰¤æ¥ç¥¨é‡‘额) BPM å®¡æ‰¹", "roles": ["财务", "审批人"]}]},
       {"stage": "付款", "action": "采购来票(FPD, AI OCR, â‰¤è®¢å•总价) â†’ ä»˜æ¬¾å•(FKD å…³è”来票+附件, â‰¤æ¥ç¥¨é‡‘额) BPM å®¡æ‰¹", "roles": ["财务", "审批人"]},
       {"stage": "对账", "action": "供应商往来按供应商汇总:左侧采购订单流水含已开票/未开票金额,右侧来票流水含开票金额/已付/剩余应付并附付款流水,顶部汇总累计采购/开票/付款/未付余额", "roles": ["财务", "采购员"]}]},
    {"id": "production", "name": "生产流程", "mermaid": "mermaid/04-production-flow.mmd", "color": "#e0b34f",
     "summary": "销售订单驱动 MPS â†’ ä¸‹å‘工单 â†’ æŒ‰å·¥è‰ºè·¯çº¿æ‹†å·¥åºä»»åŠ¡ â†’ é¢†æ–™/线边备料 â†’ æŠ¥å·¥(工序顺序硬校验+BPM) â†’ å€’冲消耗/产出入库/IPQC â†’ æœ«é“工序回写进度 â†’ è‡ªåŠ¨å®Œå·¥ â†’ ProductionFinishedEvent å›žå†™é”€å”®è®¢å•。",
     "steps": [
@@ -596,7 +598,7 @@
    {"role": "销售内勤/销售", "modules": ["CRM", "ERP"], "operations": ["线索客户商机维护", "报价与合同", "销售订单", "跟进记录"], "data": ["crm_*", "erp_sale_order"], "scope": "负责人(owner)+数据权限三级:只读/读写/负责人;公海池可领取;交接人应可通过 expandUserIds çœ‹åˆ°ç¦»èŒäººæ•°æ®(接口已备、待接入)"},
    {"role": "销售经理", "modules": ["CRM"], "operations": ["商机/合同审批参与", "销售目标制定", "业绩排名漏斗分析"], "data": ["团队数据(下属用户)"], "scope": "AdminUserApi ä¸‹çº§ç”¨æˆ·æ‰©å±•"},
    {"role": "采购员", "modules": ["ERP", "SRM"], "operations": ["采购申请/订单", "确认收货", "来票与付款发起"], "data": ["erp_purchase_*", "srm_*"], "scope": "requestUserId/supplierOwner å½’属"},
    {"role": "采购开发/供应商管理员", "modules": ["SRM"], "operations": ["准入审批流转", "招标比价组织", "绩效评价"], "data": ["srm_supplier_apply", "srm_tender_*"], "scope": "BPM å¤šéƒ¨é—¨ä¼šç­¾(采购/质量/财务)"},
    {"role": "采购员/供应商管理员", "modules": ["SRM"], "operations": ["供应商档案维护", "询价比价与确认转订单", "投标保证金台账"], "data": ["srm_supplier*", "srm_inquiry*", "srm_bid_bond"], "scope": "采购管理菜单权限;询价确认生成的采购订单草稿走 ERP BPM"},
    {"role": "计划员", "modules": ["MES"], "operations": ["MPS ä¸Žå·¥å•", "工艺路线维护", "AI é¢„测参考"], "data": ["mes_pro_*"], "scope": "车间/部门数据范围"},
    {"role": "操作工/班组长", "modules": ["MES"], "operations": ["任务接收", "报工提交(受工序顺序约束)", "安灯"], "data": ["mes_pro_feedback", "mes_pro_task"], "scope": "任务指派人可见"},
    {"role": "仓管员", "modules": ["MES-WM"], "operations": ["15 ç±»å‡ºå…¥åº“单据执行", "调拨", "盘点", "条码装箱"], "data": ["mes_wm_*"], "scope": "仓库维度;WMS å¯æŒ‰ä»“库配置出入库/调拨/盘点是否走 BPM"},
@@ -626,11 +628,9 @@
      é”€å”®ç›®æ ‡
      æ•°æ®æƒé™ä¸Žå…¬æµ·
    ä¾›åº”商管理 SRM
      ä¾›åº”商档案
      å‡†å…¥å®¡æ‰¹
      èµ„质证书
      æ‹›æ ‡ä¸Žæ¯”ä»·
      ç»©æ•ˆè¯„ä»·
      ä¾›åº”商档案(多联系人/多银行)
      è¯¢ä»·æ¯”ä»·
      æŠ•标保证金
    è¿›é”€å­˜ä¸Žè´¢åŠ¡ ERP
      é‡‡è´­ç”³è¯·
      é‡‡è´­è®¢å•
@@ -743,10 +743,11 @@
</pre>
<pre style="display:none" class="mmd-src" data-mmd="03-purchase-flow">
flowchart TD
  S0[SRM å‡†å…¥ç”³è¯· AP·待提交0→审批中10→已通过40] --> S1[供应商档案 srm_supplier]
  S1 --> S2[资质证书管理]
  S1 --> S3[绩效评价·质量交付价格服务四维]
  S4[SRM æ‹›æ ‡æ¯”价·发布→投标→开标→评标→定标] -->|定标审批通过生成订单| C3
  S1[供应商档案 srm_supplier·多联系人/多银行含银行行号] --> S2[询价比价 srm_inquiry·草稿0→已转订单20]
  S2 -->|新增时一并录入产品×供应商报价并选定中标·列表直接确认| S3[按中标供应商拆分生成采购订单草稿]
  S3 -->|提交| C3
  S1 -.-> S5[投标保证金 srm_bid_bond·是否中标/退回状态]
  S2 -.-> S5
  C1[采购申请 CGSQ·草稿0→审批中10] -->|提交BPM| C2{审批}
  C2 -->|驳回30→可重置回草稿| C1
  C2 -->|通过20且有供应商·自动生成| C3[采购订单 CGDD·审批中15→已审核20]
@@ -761,6 +762,9 @@
  C7 -->|回写申请明细 inCount| C1
  C3 -->|订单总价控制| C10[采购来票 FPD·累计金额≤订单总价]
  C10 -->|付款单必须关联来票| C11[付款单 FKD·审批中15→已审核20·累计付款≤来票金额]
  C3 --> S6[供应商往来·订单流水含已开票/未开票]
  C10 --> S6
  C11 --> S6
</pre>
<pre style="display:none" class="mmd-src" data-mmd="04-production-flow">
flowchart TD
@@ -938,7 +942,8 @@
  MDM --> ERP[ERP é‡‡è´­Â·é”€å”®Â·è´¢åŠ¡]
  MDM --> CRM
  CRM[CRM çº¿ç´¢å®¢æˆ·å•†æœºæŠ¥ä»·åˆåŒå›žæ¬¾] -->|合同生成销售订单| ERP
  SRM[SRM ä¾›åº”商准入资质证书招标绩效] -->|定标生成采购订单| ERP
  SRM[SRM ä¾›åº”商档案·询价比价·投标保证金] -->|询价确认生成采购订单草稿| ERP
  ERP -.->|供应商往来读取供应商名称| SRM
  ERP -->|确认收货生成到货通知| MES
  ERP -->|销售订单审批生成MPS| MES
  MES -->|入库出库回写单据进度| ERP
@@ -949,7 +954,6 @@
  HRM[HRM å‘˜å·¥å…¥è½¬è°ƒç¦»äº¤æŽ¥è€ƒå‹¤è–ªé…¬] -.->|expandUserIds æ•°æ®å¯è§æ€§æ‰©å±•·接口已就绪待各模块接入| CRM
  BPM[BPM Flowable å®¡æ‰¹å¼•擎] -.->|状态事件回调·13个监听器| ERP
  BPM -.-> CRM
  BPM -.-> SRM
  BPM -.-> MES
  BPM -.-> HRM
  AI[AI å¹³å° é€šä¹‰åƒé—®+Milvus] -.->|发票OCR| ERP
docs/project-business/mermaid/01-business-mindmap.mmd
@@ -15,11 +15,9 @@
      é”€å”®ç›®æ ‡
      æ•°æ®æƒé™ä¸Žå…¬æµ·
    ä¾›åº”商管理 SRM
      ä¾›åº”商档案
      å‡†å…¥å®¡æ‰¹
      èµ„质证书
      æ‹›æ ‡ä¸Žæ¯”ä»·
      ç»©æ•ˆè¯„ä»·
      ä¾›åº”商档案(多联系人/多银行)
      è¯¢ä»·æ¯”ä»·
      æŠ•标保证金
    è¿›é”€å­˜ä¸Žè´¢åŠ¡ ERP
      é‡‡è´­ç”³è¯·
      é‡‡è´­è®¢å•
docs/project-business/mermaid/03-purchase-flow.mmd
@@ -1,8 +1,9 @@
flowchart TD
  S0[SRM å‡†å…¥ç”³è¯· AP·待提交0→审批中10→已通过40] --> S1[供应商档案 srm_supplier]
  S1 --> S2[资质证书管理]
  S1 --> S3[绩效评价·质量交付价格服务四维]
  S4[SRM æ‹›æ ‡æ¯”价·发布→投标→开标→评标→定标] -->|定标审批通过生成订单| C3
  S1[供应商档案 srm_supplier·多联系人/多银行含银行行号] --> S2[询价比价 srm_inquiry·草稿0→已转订单20]
  S2 -->|新增时一并录入产品×供应商报价并选定中标·列表直接确认| S3[按中标供应商拆分生成采购订单草稿]
  S3 -->|提交| C3
  S1 -.-> S5[投标保证金 srm_bid_bond·是否中标/退回状态]
  S2 -.-> S5
  C1[采购申请 CGSQ·草稿0→审批中10] -->|提交BPM| C2{审批}
  C2 -->|驳回30→可重置回草稿| C1
  C2 -->|通过20且有供应商·自动生成| C3[采购订单 CGDD·审批中15→已审核20]
@@ -17,3 +18,6 @@
  C7 -->|回写申请明细 inCount| C1
  C3 -->|订单总价控制| C10[采购来票 FPD·累计金额≤订单总价]
  C10 -->|付款单必须关联来票| C11[付款单 FKD·审批中15→已审核20·累计付款≤来票金额]
  C3 --> S6[供应商往来·订单流水含已开票/未开票]
  C10 --> S6
  C11 --> S6
docs/project-business/mermaid/08-module-relation.mmd
@@ -3,7 +3,8 @@
  MDM --> ERP[ERP é‡‡è´­Â·é”€å”®Â·è´¢åŠ¡]
  MDM --> CRM
  CRM[CRM çº¿ç´¢å®¢æˆ·å•†æœºæŠ¥ä»·åˆåŒå›žæ¬¾] -->|合同生成销售订单| ERP
  SRM[SRM ä¾›åº”商准入资质证书招标绩效] -->|定标生成采购订单| ERP
  SRM[SRM ä¾›åº”商档案·询价比价·投标保证金] -->|询价确认生成采购订单草稿| ERP
  ERP -.->|供应商往来读取供应商名称| SRM
  ERP -->|确认收货生成到货通知| MES
  ERP -->|销售订单审批生成MPS| MES
  MES -->|入库出库回写单据进度| ERP
@@ -14,7 +15,6 @@
  HRM[HRM å‘˜å·¥å…¥è½¬è°ƒç¦»äº¤æŽ¥è€ƒå‹¤è–ªé…¬] -.->|expandUserIds æ•°æ®å¯è§æ€§æ‰©å±•·接口已就绪待各模块接入| CRM
  BPM[BPM Flowable å®¡æ‰¹å¼•擎] -.->|状态事件回调·13个监听器| ERP
  BPM -.-> CRM
  BPM -.-> SRM
  BPM -.-> MES
  BPM -.-> HRM
  AI[AI å¹³å° é€šä¹‰åƒé—®+Milvus] -.->|发票OCR| ERP
sql/mysql/finance_menu_init.sql
@@ -7,66 +7,68 @@
-- åŽç«¯æƒé™ç ï¼šè§å„ Controller çš„ @PreAuthorize
--
-- è¯´æ˜Žï¼š
--   1. æ–°å»ºä¸€çº§èœå•「财务核算」(/finance-core),不触碰原有 ERP ä¸‹çš„「财务管理」(id=2645)
--      åŠå…¶ ç»“算账户/付款单/收款单 ä¸‰ä¸ªèœå•。
--   1. æ–°å»ºä¸€çº§èœå•「财务核算」(/finance-core)。原 ERP ä¸‹çš„「财务管理」(id=2645)
--      å› å…¶çˆ¶èœå•「ERP ç³»ç»Ÿã€(id=2563) å·²åœç”¨è€Œä¸å¯è§ï¼Œæœ¬è„šæœ¬æŠŠå®ƒæŒ‚åˆ°ã€Œè´¢åŠ¡æ ¸ç®—ã€ä¸‹ï¼Œ
--      æ¢å¤ ç»“算账户/付款单/收款单 ä¸‰ä¸ªèœå•(原 role_menu æŽˆæƒä¿ç•™ï¼‰ã€‚
--   2. é¡µé¢èœå•(type=2) å„自携带 :query æƒé™ï¼›å¢žåˆ æ”¹/导出/折旧摊销等操作权限建成按钮(type=3)。
--   3. æ€»è´¦ä¸Žæ˜Žç»†è´¦å…±ç”¨ finance:ledger:query、finance:ledger:export ä¸¤ä¸ªæƒé™ç ï¼Œ
--      å› æ­¤ã€Œå¯¼å‡ºè´¦ç°¿ã€æŒ‰é’®åªæŒ‚在「总账」下 â€”— åŒä¸€æƒé™ç åœ¨è§’色授权树里出现一次即可,
--      æŽˆäºˆåŽã€Œæ˜Žç»†è´¦ã€çš„导出按钮同样生效。
--   4. å¹‚等:先删除本脚本占用的 id åŒºé—´ (1075500~1075549) å†æ’入,可重复执行。
--   4. å¹‚等:先删除本脚本占用的 id åŒºé—´ (1075600~1075649) åŠå¯¹åº”角色授权再插入,可重复执行。
--      æ³¨æ„ï¼šè‹¥ä¹‹åŽå·²åœ¨ã€Œè§’色管理」里给这些菜单授权,重跑本脚本会丢失对应 system_role_menu è®°å½•。
--   5. ä¸å« tenant_id(本项目非多租户)。
-- =============================================
DELETE FROM system_menu WHERE id BETWEEN 1075500 AND 1075549;
DELETE FROM system_role_menu WHERE menu_id BETWEEN 1075600 AND 1075649;
DELETE FROM system_menu WHERE id BETWEEN 1075600 AND 1075649;
-- ---------------------------------------------------------------
-- ä¸€çº§èœå•
-- ---------------------------------------------------------------
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES
(1075500, '财务核算', '', 1, 4, 0, '/finance-core', 'ep:money', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
(1075600, '财务核算', '', 1, 4, 0, '/finance-core', 'ep:money', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- ---------------------------------------------------------------
-- é¡µé¢èœå• (type=2)
-- ---------------------------------------------------------------
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES
(1075501, '记账凭证', 'finance:voucher:query',           2, 1, 1075500, 'voucher',         'ep:document',        'erp/finance/voucher/index',           'FinVoucher',         0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075502, '总账',     'finance:ledger:query',            2, 2, 1075500, 'general-ledger',  'ep:notebook',        'erp/finance/ledger/general/index',    'FinGeneralLedger',   0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075503, '明细账',   'finance:ledger:query',            2, 3, 1075500, 'detail-ledger',   'ep:list',            'erp/finance/ledger/detail/index',     'FinDetailLedger',    0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075504, '会计科目', 'finance:account-subject:query',   2, 4, 1075500, 'subject',         'ep:collection-tag',  'erp/finance/subject/index',           'FinAccountSubject',  0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075505, '固定资产', 'finance:fixed-asset:query',       2, 5, 1075500, 'fixed-asset',     'ep:office-building', 'erp/finance/fixed-asset/index',       'FinFixedAsset',      0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075506, '无形资产', 'finance:intangible-asset:query',  2, 6, 1075500, 'intangible-asset','ep:medal',           'erp/finance/intangible-asset/index',  'FinIntangibleAsset', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075507, '财务报表', 'finance:statistics:query',        2, 7, 1075500, 'statistics',      'ep:data-line',       'erp/finance/statistics/index',        'FinStatistics',      0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0');
(1075601, '记账凭证', 'finance:voucher:query',           2, 1, 1075600, 'voucher',         'ep:document',        'erp/finance/voucher/index',           'FinVoucher',         0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075602, '总账',     'finance:ledger:query',            2, 2, 1075600, 'general-ledger',  'ep:notebook',        'erp/finance/ledger/general/index',    'FinGeneralLedger',   0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075603, '明细账',   'finance:ledger:query',            2, 3, 1075600, 'detail-ledger',   'ep:list',            'erp/finance/ledger/detail/index',     'FinDetailLedger',    0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075604, '会计科目', 'finance:account-subject:query',   2, 4, 1075600, 'subject',         'ep:collection-tag',  'erp/finance/subject/index',           'FinAccountSubject',  0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075605, '固定资产', 'finance:fixed-asset:query',       2, 5, 1075600, 'fixed-asset',     'ep:office-building', 'erp/finance/fixed-asset/index',       'FinFixedAsset',      0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075606, '无形资产', 'finance:intangible-asset:query',  2, 6, 1075600, 'intangible-asset','ep:medal',           'erp/finance/intangible-asset/index',  'FinIntangibleAsset', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075607, '财务报表', 'finance:statistics:query',        2, 7, 1075600, 'statistics',      'ep:data-line',       'erp/finance/statistics/index',        'FinStatistics',      0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0');
-- ---------------------------------------------------------------
-- æŒ‰é’®æƒé™ (type=3)
-- ---------------------------------------------------------------
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES
-- è®°è´¦å‡­è¯
(1075510, '凭证新增',     'finance:voucher:create',        3, 1, 1075501, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075511, '凭证修改',     'finance:voucher:update',        3, 2, 1075501, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075512, '凭证删除',     'finance:voucher:delete',        3, 3, 1075501, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075513, '凭证状态变更', 'finance:voucher:update-status', 3, 4, 1075501, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075514, '凭证导出',     'finance:voucher:export',        3, 5, 1075501, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075610, '凭证新增',     'finance:voucher:create',        3, 1, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075611, '凭证修改',     'finance:voucher:update',        3, 2, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075612, '凭证删除',     'finance:voucher:delete',        3, 3, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075613, '凭证状态变更', 'finance:voucher:update-status', 3, 4, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075614, '凭证导出',     'finance:voucher:export',        3, 5, 1075601, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æ€»è´¦ / æ˜Žç»†è´¦ï¼ˆä¸¤é¡µå…±ç”¨ finance:ledger:query,导出权限只在此处挂一次,见文件头说明 3)
(1075520, '账簿导出',     'finance:ledger:export',         3, 1, 1075502, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075620, '账簿导出',     'finance:ledger:export',         3, 1, 1075602, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- ä¼šè®¡ç§‘ç›®
(1075521, '科目新增',     'finance:account-subject:create',3, 1, 1075504, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075522, '科目修改',     'finance:account-subject:update',3, 2, 1075504, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075523, '科目删除',     'finance:account-subject:delete',3, 3, 1075504, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075524, '科目导出',     'finance:account-subject:export',3, 4, 1075504, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075621, '科目新增',     'finance:account-subject:create',3, 1, 1075604, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075622, '科目修改',     'finance:account-subject:update',3, 2, 1075604, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075623, '科目删除',     'finance:account-subject:delete',3, 3, 1075604, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075624, '科目导出',     'finance:account-subject:export',3, 4, 1075604, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å›ºå®šèµ„产
(1075530, '资产新增',     'finance:fixed-asset:create',    3, 1, 1075505, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075531, '资产修改',     'finance:fixed-asset:update',    3, 2, 1075505, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075532, '资产删除',     'finance:fixed-asset:delete',    3, 3, 1075505, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075533, '计提折旧',     'finance:fixed-asset:depreciate',3, 4, 1075505, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075534, '资产导出',     'finance:fixed-asset:export',    3, 5, 1075505, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075630, '资产新增',     'finance:fixed-asset:create',    3, 1, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075631, '资产修改',     'finance:fixed-asset:update',    3, 2, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075632, '资产删除',     'finance:fixed-asset:delete',    3, 3, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075633, '计提折旧',     'finance:fixed-asset:depreciate',3, 4, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075634, '资产导出',     'finance:fixed-asset:export',    3, 5, 1075605, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æ— å½¢èµ„产
(1075540, '资产新增',     'finance:intangible-asset:create',   3, 1, 1075506, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075541, '资产修改',     'finance:intangible-asset:update',   3, 2, 1075506, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075542, '资产删除',     'finance:intangible-asset:delete',   3, 3, 1075506, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075543, '无形资产摊销', 'finance:intangible-asset:amortize', 3, 4, 1075506, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075544, '资产导出',     'finance:intangible-asset:export',   3, 5, 1075506, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0');
(1075640, '资产新增',     'finance:intangible-asset:create',   3, 1, 1075606, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075641, '资产修改',     'finance:intangible-asset:update',   3, 2, 1075606, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075642, '资产删除',     'finance:intangible-asset:delete',   3, 3, 1075606, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075643, '无形资产摊销', 'finance:intangible-asset:amortize', 3, 4, 1075606, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075644, '资产导出',     'finance:intangible-asset:export',   3, 5, 1075606, '', '', '', '', 0, b'1', b'0', b'1', '1', NOW(), '1', NOW(), b'0');
-- ---------------------------------------------------------------
-- è§’色授权(可选)
@@ -75,11 +77,34 @@
-- ä¹Ÿå¯ç”¨ä¸‹é¢çš„语句直接授权(把 <roleId> æ¢æˆç›®æ ‡è§’色 id,参考:163 è®¾å¤‡ / 164 è´¨é‡ / 165 é”€å”®ï¼‰ï¼š
--
-- INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
-- SELECT 165, id, '1', NOW(), '1', NOW(), b'0' FROM system_menu WHERE id BETWEEN 1075500 AND 1075549;
-- SELECT 165, id, '1', NOW(), '1', NOW(), b'0' FROM system_menu WHERE id BETWEEN 1075600 AND 1075649;
-- ---------------------------------------------------------------
-- ---------------------------------------------------------------
-- æ¢å¤åŽŸæœ‰ ERP「财务管理」(id=2645) å­æ ‘
-- å…¶çˆ¶èœå•「ERP ç³»ç»Ÿã€(id=2563) å·²åœç”¨ï¼Œå¯¼è‡´ ç»“算账户/付款单/收款单 æ— æ³•显示;
-- çŽ°æŒ‚åˆ°ã€Œè´¢åŠ¡æ ¸ç®—ã€ä¸‹ä½œä¸ºäºŒçº§ç›®å½•ï¼ˆæŽ’åº 8,排在财务报表之后)。
-- ---------------------------------------------------------------
UPDATE system_menu SET parent_id = 1075600, sort = 8 WHERE id = 2645;
-- ---------------------------------------------------------------
-- ç»™åŽŸæœ¬æ‹¥æœ‰ã€Œè´¢åŠ¡ç®¡ç†ã€(2645) çš„角色补授新增的财务核算菜单
-- ï¼ˆè¶…管由代码直接返回全部菜单,不受影响)
-- ---------------------------------------------------------------
INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
SELECT DISTINCT rm.role_id, m.id, '1', NOW(), '1', NOW(), b'0'
FROM system_menu m
JOIN system_role_menu rm ON rm.menu_id = 2645 AND rm.deleted = b'0'
WHERE m.id BETWEEN 1075600 AND 1075649
  AND NOT EXISTS (SELECT 1
                  FROM system_role_menu x
                  WHERE x.role_id = rm.role_id
                    AND x.menu_id = m.id
                    AND x.deleted = b'0');
-- ---------------------------------------------------------------
-- å›žæ»šè„šæœ¬
-- DELETE FROM system_menu WHERE id BETWEEN 1075500 AND 1075549;
-- DELETE FROM system_role_menu WHERE menu_id BETWEEN 1075500 AND 1075549;
-- UPDATE system_menu SET parent_id = 2563, sort = 50 WHERE id = 2645;
-- DELETE FROM system_menu WHERE id BETWEEN 1075600 AND 1075649;
-- DELETE FROM system_role_menu WHERE menu_id BETWEEN 1075600 AND 1075649;
-- ---------------------------------------------------------------
sql/mysql/finance_module_init.sql
@@ -110,7 +110,9 @@
    `net_value`                  decimal(24,6) NOT NULL DEFAULT 0 COMMENT '净值 = åŽŸå€¼ - ç´¯è®¡æŠ˜æ—§',
    `location`                   varchar(255)  NULL DEFAULT '' COMMENT '存放地点',
    `department`                 varchar(128)  NULL DEFAULT '' COMMENT '使用部门',
    `department_id`              bigint        NULL DEFAULT NULL COMMENT '使用部门编号',
    `keeper`                     varchar(64)   NULL DEFAULT '' COMMENT '保管人',
    `keeper_id`                  bigint        NULL DEFAULT NULL COMMENT '保管人用户编号',
    `status`                     tinyint       NOT NULL DEFAULT 1 COMMENT '状态:1在用 2闲置 3维修中 4已报废',
    `remark`                     varchar(500)  NULL DEFAULT '' COMMENT '备注',
    `creator`                    varchar(64)   NULL DEFAULT '' COMMENT '创建者',
@@ -155,6 +157,28 @@
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '无形资产';
-- -------------------------------------------------------------------------------------
-- 1.6 å·²å»ºè¡¨çš„补列(兼容早期脚本创建、缺少部门/保管人编号的库)
-- è¯´æ˜Žï¼šMySQL ä¸æ”¯æŒ ALTER TABLE ... ADD COLUMN IF NOT EXISTS,用 information_schema åˆ¤æ–­
-- -------------------------------------------------------------------------------------
SET @col_exists = (SELECT COUNT(*) FROM information_schema.columns
                   WHERE table_schema = DATABASE() AND table_name = 'fin_fixed_asset' AND column_name = 'department_id');
SET @ddl = IF(@col_exists = 0,
              'ALTER TABLE `fin_fixed_asset` ADD COLUMN `department_id` bigint NULL DEFAULT NULL COMMENT ''使用部门编号'' AFTER `department`',
              'SELECT 1');
PREPARE stmt_fin_fixed_asset_department_id FROM @ddl;
EXECUTE stmt_fin_fixed_asset_department_id;
DEALLOCATE PREPARE stmt_fin_fixed_asset_department_id;
SET @col_exists = (SELECT COUNT(*) FROM information_schema.columns
                   WHERE table_schema = DATABASE() AND table_name = 'fin_fixed_asset' AND column_name = 'keeper_id');
SET @ddl = IF(@col_exists = 0,
              'ALTER TABLE `fin_fixed_asset` ADD COLUMN `keeper_id` bigint NULL DEFAULT NULL COMMENT ''保管人用户编号'' AFTER `keeper`',
              'SELECT 1');
PREPARE stmt_fin_fixed_asset_keeper_id FROM @ddl;
EXECUTE stmt_fin_fixed_asset_keeper_id;
DEALLOCATE PREPARE stmt_fin_fixed_asset_keeper_id;
-- -------------------------------------------------------------------------------------
-- 2. å­—å…¸
-- -------------------------------------------------------------------------------------
INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
sql/mysql/srm_legacy_tables_backup_20261103.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,351 @@
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_supplier_apply` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '申请ID',
  `apply_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '申请编号',
  `supplier_id` bigint DEFAULT NULL COMMENT '供应商ID',
  `erp_supplier_id` bigint DEFAULT NULL COMMENT '关联ERP供应商ID',
  `supplier_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '供应商名称',
  `supplier_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '供应商类型',
  `company_nature` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '企业性质',
  `registered_capital` decimal(24,6) DEFAULT NULL COMMENT '注册资金(万元)',
  `business_license_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '营业执照编号',
  `tax_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '税号',
  `address` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '公司地址',
  `bank_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '开户银行',
  `bank_account` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '银行账号',
  `contact_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系人',
  `contact_mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话',
  `contact_email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
  `apply_user_id` bigint DEFAULT NULL COMMENT '申请人ID',
  `apply_user_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '申请人姓名',
  `apply_time` datetime DEFAULT NULL COMMENT '申请时间',
  `apply_status` int NOT NULL DEFAULT '0' COMMENT '审核状态',
  `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'BPM æµç¨‹å®žä¾‹ç¼–号',
  `purchase_reviewer_id` bigint DEFAULT NULL COMMENT '采购审核人ID',
  `purchase_review_time` datetime DEFAULT NULL COMMENT '采购审核时间',
  `purchase_review_opinion` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采购审核意见',
  `quality_reviewer_id` bigint DEFAULT NULL COMMENT '质量审核人ID',
  `quality_review_time` datetime DEFAULT NULL COMMENT '质量审核时间',
  `quality_review_opinion` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '质量审核意见',
  `finance_reviewer_id` bigint DEFAULT NULL COMMENT '财务审核人ID',
  `finance_review_time` datetime DEFAULT NULL COMMENT '财务审核时间',
  `finance_review_opinion` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '财务审核意见',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_apply_no` (`apply_no`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_apply_status` (`apply_status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='供应商准入申请表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_supplier_certificate` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '证书ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `certificate_type` int NOT NULL COMMENT '证书类型',
  `certificate_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '证书名称',
  `certificate_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '证书编号',
  `file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件名称',
  `file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '文件地址',
  `issuing_authority` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '颁发机构',
  `valid_start_date` date DEFAULT NULL COMMENT '有效开始日期',
  `valid_end_date` date DEFAULT NULL COMMENT '有效结束日期',
  `status` int NOT NULL DEFAULT '0' COMMENT '状态',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_certificate_type` (`certificate_type`) USING BTREE,
  KEY `idx_valid_end_date` (`valid_end_date`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='供应商资质证书表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_supplier_evaluation` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '评价ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `evaluation_period` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '评价周期',
  `quality_score` decimal(5,2) DEFAULT NULL COMMENT '质量评分',
  `quality_pass_rate` decimal(5,2) DEFAULT NULL COMMENT '来料合格率',
  `quality_defect_count` int DEFAULT '0' COMMENT '不良次数',
  `delivery_score` decimal(5,2) DEFAULT NULL COMMENT '交付评分',
  `delivery_on_time_rate` decimal(5,2) DEFAULT NULL COMMENT '准时交付率',
  `delivery_delay_count` int DEFAULT '0' COMMENT '延迟次数',
  `price_score` decimal(5,2) DEFAULT NULL COMMENT '价格评分',
  `service_score` decimal(5,2) DEFAULT NULL COMMENT '服务评分',
  `service_response_speed` int DEFAULT NULL COMMENT '响应速度评分',
  `composite_score` decimal(5,2) DEFAULT NULL COMMENT '综合评分',
  `evaluation_level` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '评价等级',
  `evaluator_id` bigint DEFAULT NULL COMMENT '评价人ID',
  `evaluation_time` datetime DEFAULT NULL COMMENT '评价时间',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_evaluation_period` (`evaluation_period`) USING BTREE,
  KEY `idx_composite_score` (`composite_score`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='供应商绩效评价表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_tender_project` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '招标项目ID',
  `tender_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '招标编号',
  `tender_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '招标名称',
  `tender_type` int NOT NULL COMMENT '招标类型',
  `purchase_org` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '采购组织',
  `project_leader_id` bigint DEFAULT NULL COMMENT '项目负责人ID',
  `project_leader_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '项目负责人姓名',
  `tender_status` int NOT NULL DEFAULT '0' COMMENT '招标状态',
  `bidding_mode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'FULL' COMMENT '招投标模式: FULL-完整版, SIMPLE-简易版',
  `budget_amount` decimal(24,6) DEFAULT NULL COMMENT '预算金额',
  `start_time` datetime DEFAULT NULL COMMENT '开始时间',
  `end_time` datetime DEFAULT NULL COMMENT '截止时间',
  `bid_open_time` datetime DEFAULT NULL COMMENT '预计开标时间',
  `tender_desc` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '招标说明',
  `qualification_requirements` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '资质要求',
  `purchase_request_id` bigint DEFAULT NULL COMMENT '关联采购申请ID',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_tender_no` (`tender_no`) USING BTREE,
  KEY `idx_tender_status` (`tender_status`) USING BTREE,
  KEY `idx_purchase_request_id` (`purchase_request_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='招标项目表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_tender_material` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '物料明细ID',
  `tender_project_id` bigint NOT NULL COMMENT '招标项目ID',
  `product_id` bigint DEFAULT NULL COMMENT '物料ID',
  `product_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '物料编码',
  `product_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '物料名称',
  `product_spec` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '规格型号',
  `quantity` decimal(24,6) NOT NULL COMMENT '数量',
  `unit` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '单位',
  `tech_requirement` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '技术要求',
  `estimated_price` decimal(24,6) DEFAULT NULL COMMENT '预估单价',
  `sort` int NOT NULL DEFAULT '0' COMMENT '排序',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_tender_project_id` (`tender_project_id`) USING BTREE,
  KEY `idx_product_code` (`product_code`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='招标物料明细表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_tender_bid` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '投标ID',
  `tender_project_id` bigint NOT NULL COMMENT '招标项目ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `bid_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '投标编号',
  `bid_status` int NOT NULL DEFAULT '0' COMMENT '投标状态',
  `bid_time` datetime DEFAULT NULL COMMENT '投标时间',
  `bid_total_amount` decimal(24,6) DEFAULT NULL COMMENT '投标总金额',
  `bid_file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '投标文件名称',
  `bid_file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '投标文件地址',
  `withdraw_reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '撤标原因',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_bid_no` (`bid_no`) USING BTREE,
  KEY `idx_tender_project_id` (`tender_project_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_bid_status` (`bid_status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='投标记录表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_supplier_quote` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '报价ID',
  `tender_project_id` bigint NOT NULL COMMENT '招标项目ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `bid_id` bigint DEFAULT NULL COMMENT '投标记录ID',
  `tender_material_id` bigint NOT NULL COMMENT '招标物料ID',
  `quote_price` decimal(24,6) NOT NULL COMMENT '报价价格',
  `tax_rate` decimal(5,2) DEFAULT NULL COMMENT '税率(%)',
  `delivery_cycle` int DEFAULT NULL COMMENT '交货周期(天)',
  `payment_terms` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '付款条件',
  `warranty_period` int DEFAULT NULL COMMENT '质保期(月)',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_tender_project_id` (`tender_project_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_bid_id` (`bid_id`) USING BTREE,
  KEY `idx_tender_material_id` (`tender_material_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='供应商报价明细表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_bid_open` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '开标ID',
  `tender_project_id` bigint NOT NULL COMMENT '招标项目ID',
  `open_time` datetime NOT NULL COMMENT '开标时间',
  `open_person_id` bigint DEFAULT NULL COMMENT '开标人员ID',
  `open_person_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '开标人员姓名',
  `open_record` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '开标记录',
  `attachment_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '附件名称',
  `attachment_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '附件地址',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_tender_project_id` (`tender_project_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='开标记录表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_bid_evaluation` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '评标ID',
  `tender_project_id` bigint NOT NULL COMMENT '招标项目ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `bid_id` bigint NOT NULL COMMENT '投标记录ID',
  `price_score` decimal(5,2) DEFAULT NULL COMMENT '价格评分',
  `tech_score` decimal(5,2) DEFAULT NULL COMMENT '技术评分',
  `delivery_score` decimal(5,2) DEFAULT NULL COMMENT '交付评分',
  `service_score` decimal(5,2) DEFAULT NULL COMMENT '服务评分',
  `composite_score` decimal(5,2) DEFAULT NULL COMMENT '综合评分',
  `ranking` int DEFAULT NULL COMMENT '排名',
  `evaluator_id` bigint DEFAULT NULL COMMENT '评标人ID',
  `evaluator_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '评标人姓名',
  `evaluation_opinion` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '评标意见',
  `evaluation_time` datetime DEFAULT NULL COMMENT '评标时间',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_tender_project_id` (`tender_project_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_bid_id` (`bid_id`) USING BTREE,
  KEY `idx_ranking` (`ranking`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='评标记录表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_bid_award` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '定标ID',
  `tender_project_id` bigint NOT NULL COMMENT '招标项目ID',
  `supplier_id` bigint NOT NULL COMMENT '中标供应商ID',
  `bid_id` bigint NOT NULL COMMENT '中标投标ID',
  `award_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '中标通知书编号',
  `award_status` int NOT NULL DEFAULT '0' COMMENT '定标状态',
  `award_amount` decimal(24,6) NOT NULL COMMENT '中标金额',
  `award_time` datetime DEFAULT NULL COMMENT '定标时间',
  `notice_file_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '中标通知书文件名',
  `notice_file_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '中标通知书文件地址',
  `purchase_order_id` bigint DEFAULT NULL COMMENT '关联生成的采购订单ID',
  `purchase_order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '关联生成的采购订单号',
  `approver_id` bigint DEFAULT NULL COMMENT '审批人ID',
  `approval_time` datetime DEFAULT NULL COMMENT '审批时间',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_award_no` (`award_no`) USING BTREE,
  KEY `idx_tender_project_id` (`tender_project_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_purchase_order_id` (`purchase_order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='定标记录表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_tender_config` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '配置ID',
  `default_bidding_mode` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'FULL' COMMENT '默认招投标模式: FULL-完整版, SIMPLE-简易版',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='SRM招投标配置表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_delivery_plan` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '交货计划ID',
  `collaboration_id` bigint NOT NULL COMMENT '采购协同ID',
  `order_id` bigint NOT NULL COMMENT '采购订单ID',
  `order_item_id` bigint DEFAULT NULL COMMENT '采购订单项ID',
  `product_id` bigint DEFAULT NULL COMMENT '物料ID',
  `product_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '物料名称',
  `planned_quantity` decimal(24,6) NOT NULL COMMENT '计划数量',
  `actual_quantity` decimal(24,6) DEFAULT NULL COMMENT '实际数量',
  `planned_delivery_date` date NOT NULL COMMENT '计划交货日期',
  `actual_delivery_date` date DEFAULT NULL COMMENT '实际交货日期',
  `delivery_status` int NOT NULL DEFAULT '0' COMMENT '交货状态',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_collaboration_id` (`collaboration_id`) USING BTREE,
  KEY `idx_order_id` (`order_id`) USING BTREE,
  KEY `idx_delivery_status` (`delivery_status`) USING BTREE,
  KEY `idx_planned_delivery_date` (`planned_delivery_date`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='交货计划表';
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `srm_purchase_collaboration` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '协同ID',
  `order_id` bigint NOT NULL COMMENT '采购订单ID',
  `order_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '采购订单号',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `collaboration_status` int NOT NULL DEFAULT '0' COMMENT '协同状态',
  `confirm_time` datetime DEFAULT NULL COMMENT '确认时间',
  `confirm_user` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '确认人',
  `reject_reason` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '拒绝原因',
  `planned_delivery_time` datetime DEFAULT NULL COMMENT '预计交付时间',
  `actual_delivery_time` datetime DEFAULT NULL COMMENT '实际交付时间',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_order_id` (`order_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_collaboration_status` (`collaboration_status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='采购协同记录表';
/*!40101 SET character_set_client = @saved_cs_client */;
sql/mysql/srm_procurement_refactor_20261103.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,329 @@
-- =============================================================================
-- é‡‡è´­ç®¡ç† / ä¾›åº”商域重构迁移脚本
-- æ—¥æœŸï¼š2026-11-03
-- ç›®æ ‡åº“:mom-ntsh(36.213.150.237:10000)
--
-- å†…容:
--   1. æ–°å»ºä¾›åº”商银行账户表 srm_supplier_bank
--   2. æ–°å»ºè¯¢ä»·æ¯”价相关表 srm_inquiry / srm_inquiry_item / srm_inquiry_supplier
--      / srm_inquiry_quote / srm_inquiry_order
--   3. æ–°å»ºæŠ•标保证金台账 srm_bid_bond
--   4. æ—§ä¾›åº”商扁平联系人/银行字段迁移为默认联系人与默认银行
--   5. åˆ é™¤ä¾›åº”商准入/资质/绩效/招投标/统计菜单,新增询价比价/投标保证金/供应商往来菜单
--   6. åŒæ­¥æ–°èœå•授权给已拥有“供应商档案”的角色
--   7. DROP å·²åºŸå¼ƒçš„ 13 å¼ ç©ºè¡¨ï¼ˆç»“构备份见同目录
--      srm_legacy_tables_backup_20261103.sql)
--
-- è¯´æ˜Žï¼š
--   - è„šæœ¬ä¸ä½¿ç”¨ tenant_id(项目非多租户)。
--   - å»ºè¡¨ä½¿ç”¨ CREATE TABLE IF NOT EXISTS,菜单使用“先删后插”,可重复执行。
--   - DROP çš„ 13 å¼ è¡¨åœ¨æ‰§è¡Œå‰å·²ç¡®è®¤å‡ä¸º 0 è¡Œï¼›æ‰§è¡ŒåŽä¸å¯æ¢å¤ã€‚
-- =============================================================================
-- -----------------------------------------------------------------------------
-- ã€‡ã€è¡¥é½ä¾èµ–字段(供应商往来查询 erp_purchase_invoice éœ€è¦ï¼Œä¸Ž
--     20261027_business_requirements.sql ä¸­çš„定义一致;已存在则跳过)
-- -----------------------------------------------------------------------------
SET @has_arrival_notice_id = (
  SELECT COUNT(*) FROM information_schema.columns
  WHERE table_schema = DATABASE() AND table_name = 'erp_purchase_invoice'
    AND column_name = 'arrival_notice_id'
);
SET @sql = IF(@has_arrival_notice_id = 0,
  'ALTER TABLE `erp_purchase_invoice` ADD COLUMN `arrival_notice_id` BIGINT NULL COMMENT ''关联入库单'' AFTER `purchase_order_id`',
  'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- -----------------------------------------------------------------------------
-- ä¸€ã€æ–°å»ºè¡¨
-- -----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `srm_supplier_bank` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '银行账户ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `bank_name` varchar(128) NOT NULL COMMENT '开户行',
  `bank_account` varchar(64) NOT NULL COMMENT '银行账号',
  `bank_no` varchar(64) DEFAULT NULL COMMENT '银行行号',
  `bank_address` varchar(255) DEFAULT NULL COMMENT '开户行地址',
  `is_default` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否默认银行账户',
  `sort` int NOT NULL DEFAULT '0' COMMENT '排序',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='供应商银行账户表';
CREATE TABLE IF NOT EXISTS `srm_inquiry` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '询价单ID',
  `no` varchar(32) NOT NULL COMMENT '询价单号',
  `title` varchar(128) NOT NULL COMMENT '询价标题',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0草稿 10询价中 20已转订单 30已取消',
  `inquiry_date` date DEFAULT NULL COMMENT '询价日期',
  `deadline` date DEFAULT NULL COMMENT '报价截止日期',
  `purchaser_user_id` bigint DEFAULT NULL COMMENT '采购员用户ID',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_status` (`status`) USING BTREE,
  KEY `idx_inquiry_date` (`inquiry_date`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='SRM询价单';
CREATE TABLE IF NOT EXISTS `srm_inquiry_item` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '询价产品行ID',
  `inquiry_id` bigint NOT NULL COMMENT '询价单ID',
  `product_id` bigint DEFAULT NULL COMMENT '产品ID',
  `product_unit_id` bigint DEFAULT NULL COMMENT '产品单位ID',
  `product_code` varchar(64) DEFAULT NULL COMMENT '产品编码',
  `product_name` varchar(255) DEFAULT NULL COMMENT '产品名称',
  `product_spec` varchar(200) DEFAULT NULL COMMENT '规格型号',
  `unit_name` varchar(50) DEFAULT NULL COMMENT '单位名称',
  `quantity` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '采购数量',
  `estimated_price` decimal(24,6) DEFAULT NULL COMMENT '预估单价',
  `selected_supplier_id` bigint DEFAULT NULL COMMENT '中标供应商ID',
  `selected_quote_id` bigint DEFAULT NULL COMMENT '中标报价ID',
  `selected_price` decimal(24,6) DEFAULT NULL COMMENT '中标单价',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `sort` int NOT NULL DEFAULT '0' COMMENT '排序',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_inquiry_id` (`inquiry_id`) USING BTREE,
  KEY `idx_product_id` (`product_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='SRM询价单产品行';
CREATE TABLE IF NOT EXISTS `srm_inquiry_supplier` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '询价参与供应商ID',
  `inquiry_id` bigint NOT NULL COMMENT '询价单ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `quote_status` tinyint NOT NULL DEFAULT '0' COMMENT '报价状态:0未报价 10已报价',
  `quote_time` datetime DEFAULT NULL COMMENT '最近报价时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_inquiry_id` (`inquiry_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='SRM询价参与供应商';
CREATE TABLE IF NOT EXISTS `srm_inquiry_quote` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '询价报价ID',
  `inquiry_id` bigint NOT NULL COMMENT '询价单ID',
  `inquiry_item_id` bigint NOT NULL COMMENT '询价产品行ID',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `price` decimal(24,6) NOT NULL COMMENT '报价单价',
  `tax_rate` decimal(24,6) DEFAULT NULL COMMENT '税率(%)',
  `delivery_cycle` int DEFAULT NULL COMMENT '交货周期(天)',
  `payment_terms` varchar(128) DEFAULT NULL COMMENT '付款条件',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `is_selected` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否中标报价',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_inquiry_id` (`inquiry_id`) USING BTREE,
  KEY `idx_inquiry_item_id` (`inquiry_item_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='SRM询价报价明细';
CREATE TABLE IF NOT EXISTS `srm_inquiry_order` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '询价转订单记录ID',
  `inquiry_id` bigint NOT NULL COMMENT '询价单ID',
  `supplier_id` bigint NOT NULL COMMENT '中标供应商ID',
  `purchase_order_id` bigint DEFAULT NULL COMMENT '生成的采购订单ID',
  `purchase_order_no` varchar(64) DEFAULT NULL COMMENT '生成的采购订单号',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_inquiry_id` (`inquiry_id`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_purchase_order_id` (`purchase_order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='SRM询价转采购订单记录';
CREATE TABLE IF NOT EXISTS `srm_bid_bond` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '投标保证金ID',
  `no` varchar(32) NOT NULL COMMENT '保证金单号',
  `supplier_id` bigint NOT NULL COMMENT '供应商ID',
  `project_name` varchar(255) DEFAULT NULL COMMENT '项目名称',
  `inquiry_id` bigint DEFAULT NULL COMMENT '关联询价单ID',
  `purchase_order_id` bigint DEFAULT NULL COMMENT '关联采购订单ID',
  `bond_amount` decimal(24,6) NOT NULL DEFAULT '0.000000' COMMENT '保证金金额',
  `pay_date` date DEFAULT NULL COMMENT '缴纳日期',
  `won` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否中标',
  `refund_status` tinyint NOT NULL DEFAULT '0' COMMENT '退回状态:0未退回 1部分退回 2已退回 3无需退回',
  `refund_amount` decimal(24,6) DEFAULT NULL COMMENT '退回金额',
  `refund_date` date DEFAULT NULL COMMENT '退回日期',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  `creator` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updater` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `uk_no` (`no`) USING BTREE,
  KEY `idx_supplier_id` (`supplier_id`) USING BTREE,
  KEY `idx_inquiry_id` (`inquiry_id`) USING BTREE,
  KEY `idx_refund_status` (`refund_status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='SRM投标保证金台账';
-- -----------------------------------------------------------------------------
-- äºŒã€æ—§ä¾›åº”商扁平字段迁移为默认联系人 / é»˜è®¤é“¶è¡Œ
--     ä»…在对应子表还没有数据时补一条,避免重复执行产生重复行。
-- -----------------------------------------------------------------------------
INSERT INTO `srm_supplier_contact`
  (`supplier_id`, `name`, `mobile`, `email`, `is_default`, `sort`, `creator`, `updater`)
SELECT s.`id`, TRIM(s.`contact_name`), s.`contact_mobile`, s.`contact_email`, b'1', 0, 'system', 'system'
FROM `srm_supplier` s
WHERE s.`deleted` = b'0'
  AND s.`contact_name` IS NOT NULL
  AND TRIM(s.`contact_name`) <> ''
  AND NOT EXISTS (
    SELECT 1 FROM `srm_supplier_contact` c
    WHERE c.`supplier_id` = s.`id` AND c.`deleted` = b'0'
  );
INSERT INTO `srm_supplier_bank`
  (`supplier_id`, `bank_name`, `bank_account`, `is_default`, `sort`, `creator`, `updater`)
SELECT s.`id`, TRIM(s.`bank_name`), TRIM(s.`bank_account`), b'1', 0, 'system', 'system'
FROM `srm_supplier` s
WHERE s.`deleted` = b'0'
  AND s.`bank_name` IS NOT NULL
  AND TRIM(s.`bank_name`) <> ''
  AND s.`bank_account` IS NOT NULL
  AND TRIM(s.`bank_account`) <> ''
  AND NOT EXISTS (
    SELECT 1 FROM `srm_supplier_bank` b
    WHERE b.`supplier_id` = s.`id` AND b.`deleted` = b'0'
  );
-- -----------------------------------------------------------------------------
-- ä¸‰ã€èœå•调整
-- -----------------------------------------------------------------------------
-- 3.1 åˆ é™¤æ—§ SRM å­èœå•(准入/资质/绩效/招投标/统计)及其按钮,并移除 SRM ç›®å½•
--     ä¾›åº”商档案/询价比价/投标保证金 ç»Ÿä¸€æŒ‚到采购管理根菜单(2602) ä¸‹
DELETE FROM `system_role_menu`
WHERE `menu_id` IN (
  SELECT `id` FROM (
    SELECT `id` FROM `system_menu`
    WHERE `id` IN (602524, 602530, 602535, 602540, 602545, 602569,
                   602612, 602613, 602614, 1080070, 1080071)
       OR `parent_id` IN (602530, 602535, 602540, 602545, 602569,
                          602612, 602613, 602614, 1080070, 1080071)
  ) t
);
DELETE FROM `system_menu`
WHERE `id` IN (
  SELECT `id` FROM (
    SELECT `id` FROM `system_menu`
    WHERE `id` IN (602524, 602530, 602535, 602540, 602545, 602569,
                   602612, 602613, 602614, 1080070, 1080071)
       OR `parent_id` IN (602530, 602535, 602540, 602545, 602569,
                          602612, 602613, 602614, 1080070, 1080071)
  ) t
);
-- 3.2 æ–°èœå•先删后插,保证可重复执行(602620-602641 ä¸ºç©ºå·æ®µï¼‰
DELETE FROM `system_role_menu` WHERE `menu_id` BETWEEN 602620 AND 602641;
DELETE FROM `system_menu` WHERE `id` BETWEEN 602620 AND 602641;
INSERT INTO `system_menu`
  (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`)
VALUES
  -- è¯¢ä»·æ¯”ä»·
  (602620, '询价比价', '', 2, 2, 2602, 'inquiry', 'ep:search', 'srm/inquiry/index', 'SrmInquiry', 0),
  (602621, '询价查询', 'srm:inquiry:query', 3, 1, 602620, '', '', '', '', 0),
  (602622, '询价创建', 'srm:inquiry:create', 3, 2, 602620, '', '', '', '', 0),
  (602623, '询价更新', 'srm:inquiry:update', 3, 3, 602620, '', '', '', '', 0),
  (602624, '询价删除', 'srm:inquiry:delete', 3, 4, 602620, '', '', '', '', 0),
  (602625, '询价确认转订单', 'srm:inquiry:confirm', 3, 5, 602620, '', '', '', '', 0),
  (602626, '询价录入报价', 'srm:inquiry:quote', 3, 6, 602620, '', '', '', '', 0),
  -- æŠ•标保证金
  (602630, '投标保证金', '', 2, 3, 2602, 'bid-bond', 'ep:money', 'srm/bidBond/index', 'SrmBidBond', 0),
  (602631, '保证金查询', 'srm:bid-bond:query', 3, 1, 602630, '', '', '', '', 0),
  (602632, '保证金创建', 'srm:bid-bond:create', 3, 2, 602630, '', '', '', '', 0),
  (602633, '保证金更新', 'srm:bid-bond:update', 3, 3, 602630, '', '', '', '', 0),
  (602634, '保证金删除', 'srm:bid-bond:delete', 3, 4, 602630, '', '', '', '', 0),
  -- ä¾›åº”商往来(挂在采购管理根下)
  (602640, '供应商往来', '', 2, 8, 2602, 'supplier-statement', 'ep:wallet', 'erp/purchase/statement/index', 'ErpSupplierStatement', 0),
  (602641, '供应商往来查询', 'erp:supplier-statement:query', 3, 1, 602640, '', '', '', '', 0);
-- 3.3 ç»Ÿä¸€é‡‡è´­ç®¡ç†èœå•排序:档案/寻源/保证金 â†’ ç”³è¯· â†’ è®¢å• â†’ æ¥ç¥¨ â†’ ä»˜æ¬¾ â†’ å¾€æ¥
UPDATE `system_menu` SET `sort` = 1 WHERE `id` = 602525;
UPDATE `system_menu` SET `sort` = 2 WHERE `id` = 602620;
UPDATE `system_menu` SET `sort` = 3 WHERE `id` = 602630;
UPDATE `system_menu` SET `sort` = 4 WHERE `id` = 602498;
UPDATE `system_menu` SET `sort` = 5 WHERE `id` = 2666;
UPDATE `system_menu` SET `sort` = 6 WHERE `id` = 1063110;
UPDATE `system_menu` SET `sort` = 7 WHERE `id` = 1063116;
UPDATE `system_menu` SET `sort` = 8 WHERE `id` = 602640;
-- 3.4 æ–°èœå•授权:授予当前已拥有“供应商档案”(602525) çš„角色
INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `updater`)
SELECT r.`role_id`, m.`menu_id`, 'system', 'system'
FROM (
  SELECT DISTINCT `role_id` FROM `system_role_menu`
  WHERE `menu_id` = 602525 AND `deleted` = b'0'
) r
CROSS JOIN (
  SELECT 602620 AS `menu_id`
  UNION ALL SELECT 602621
  UNION ALL SELECT 602622
  UNION ALL SELECT 602623
  UNION ALL SELECT 602624
  UNION ALL SELECT 602625
  UNION ALL SELECT 602626
  UNION ALL SELECT 602630
  UNION ALL SELECT 602631
  UNION ALL SELECT 602632
  UNION ALL SELECT 602633
  UNION ALL SELECT 602634
  UNION ALL SELECT 602640
  UNION ALL SELECT 602641
) m
WHERE NOT EXISTS (
  SELECT 1 FROM `system_role_menu` rm
  WHERE rm.`role_id` = r.`role_id` AND rm.`menu_id` = m.`menu_id` AND rm.`deleted` = b'0'
);
-- -----------------------------------------------------------------------------
-- å››ã€DROP åºŸå¼ƒç©ºè¡¨ï¼ˆæ‰§è¡Œå‰å·²ç¡®è®¤ 0 è¡Œï¼Œç»“构备份见 srm_legacy_tables_backup_20261103.sql)
-- -----------------------------------------------------------------------------
DROP TABLE IF EXISTS `srm_supplier_apply`;
DROP TABLE IF EXISTS `srm_supplier_certificate`;
DROP TABLE IF EXISTS `srm_supplier_evaluation`;
DROP TABLE IF EXISTS `srm_tender_project`;
DROP TABLE IF EXISTS `srm_tender_material`;
DROP TABLE IF EXISTS `srm_tender_bid`;
DROP TABLE IF EXISTS `srm_supplier_quote`;
DROP TABLE IF EXISTS `srm_bid_open`;
DROP TABLE IF EXISTS `srm_bid_evaluation`;
DROP TABLE IF EXISTS `srm_bid_award`;
DROP TABLE IF EXISTS `srm_tender_config`;
DROP TABLE IF EXISTS `srm_delivery_plan`;
DROP TABLE IF EXISTS `srm_purchase_collaboration`;
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/number/MoneyUtils.java
@@ -128,4 +128,24 @@
        return price.multiply(percent).divide(PERCENT_100, PRICE_SCALE, RoundingMode.HALF_UP);
    }
    /**
     * è®¡ç®—含税金额中包含的税额
     *
     * é€‚用场景:单价为含税单价时,价税合计 = å•ä»· * æ•°é‡ï¼Œç¨Žé¢ = ä»·ç¨Žåˆè®¡ * ç¨Žçއ / (100 + ç¨Žçއ)。
     * ä¾‹å¦‚:含税金额 113 å…ƒã€ç¨Žçއ 13%,税额为 13 å…ƒã€‚
     *
     * ä½æ•°ï¼š{@link #PRICE_SCALE}
     *
     * @param totalPrice å«ç¨Žé‡‘额(价税合计)
     * @param percent    ç¨ŽçŽ‡ï¼Œç™¾åˆ†æ¯”
     * @return å«ç¨Žé‡‘额中的税额
     */
    public static BigDecimal taxPriceIncluded(BigDecimal totalPrice, BigDecimal percent) {
        if (totalPrice == null || percent == null || percent.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        return totalPrice.multiply(percent)
                .divide(PERCENT_100.add(percent), PRICE_SCALE, RoundingMode.HALF_UP);
    }
}
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java
@@ -85,6 +85,14 @@
    CommonResult<Long> createPurchaseOrder(ErpPurchaseOrderCreateReqDTO createReqDTO);
    /**
     * åˆ›å»ºé‡‡è´­è®¢å•草稿(不自动提交审批)
     *
     * @param createReqDTO åˆ›å»ºä¿¡æ¯
     * @return è®¢å•编号
     */
    CommonResult<Long> createPurchaseOrderDraft(ErpPurchaseOrderCreateReqDTO createReqDTO);
    /**
     * æŒ‰æ¥æºæŸ¥è¯¢é‡‡è´­è®¢å•明细列表(用于追溯来源工单的采购情况)
     *
     * @param sourceType æ¥æºç±»åž‹
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierReferenceApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.erp.api.purchase;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import java.util.List;
/**
 * ERP ä¾›åº”商引用查询 API
 */
public interface ErpSupplierReferenceApi {
    String PREFIX = "/rpc-api/erp/supplier-reference";
    /**
     * æŸ¥è¯¢ä¾›åº”商被哪些业务表引用
     *
     * @param supplierId ä¾›åº”商编号
     * @return å¼•用来源名称列表(如“采购-采购订单”),无引用时返回空列表
     */
    CommonResult<List<String>> getSupplierReferenceSources(Long supplierId);
}
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpPurchaseOrderCreateReqDTO.java
@@ -26,6 +26,15 @@
    @Schema(description = "定金金额,单位:元", example = "100")
    private BigDecimal depositPrice;
    @Schema(description = "来源类型", example = "2")
    private Integer sourceType;
    @Schema(description = "来源单号", example = "XJ2026110001")
    private String sourceNo;
    @Schema(description = "来源单据编号", example = "1")
    private Long sourceId;
    @Schema(description = "备注", example = "备注信息")
    private String remark;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
@@ -111,11 +111,20 @@
    @Override
    public CommonResult<Long> createPurchaseOrder(ErpPurchaseOrderCreateReqDTO createReqDTO) {
        return success(purchaseOrderService.createPurchaseOrder(buildSaveReqVO(createReqDTO)));
    }
    @Override
    public CommonResult<Long> createPurchaseOrderDraft(ErpPurchaseOrderCreateReqDTO createReqDTO) {
        return success(purchaseOrderService.createPurchaseOrder(buildSaveReqVO(createReqDTO), false));
    }
    private ErpPurchaseOrderSaveReqVO buildSaveReqVO(ErpPurchaseOrderCreateReqDTO createReqDTO) {
        ErpPurchaseOrderSaveReqVO saveReqVO = BeanUtils.toBean(createReqDTO, ErpPurchaseOrderSaveReqVO.class);
        if (createReqDTO.getItems() != null) {
            saveReqVO.setItems(BeanUtils.toBean(createReqDTO.getItems(), ErpPurchaseOrderSaveReqVO.Item.class));
        }
        return success(purchaseOrderService.createPurchaseOrder(saveReqVO));
        return saveReqVO;
    }
    @Override
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierReferenceApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.erp.api.purchase;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpSupplierMapper;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * ERP ä¾›åº”商引用查询 API å®žçް
 */
@RestController
@RequestMapping(ErpSupplierReferenceApi.PREFIX)
@Validated
public class ErpSupplierReferenceApiImpl implements ErpSupplierReferenceApi {
    @Resource
    private ErpSupplierMapper supplierMapper;
    @Override
    public CommonResult<List<String>> getSupplierReferenceSources(Long supplierId) {
        if (supplierId == null) {
            return success(Collections.emptyList());
        }
        List<Map<String, Object>> rows = supplierMapper.selectSupplierReferenceCount(supplierId);
        List<String> sources = rows.stream()
                .map(row -> String.valueOf(row.get("source")))
                .toList();
        return success(sources);
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpSupplierStatementController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement.ErpSupplierStatementReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement.ErpSupplierStatementRespVO;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierStatementService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - ERP ä¾›åº”商往来")
@RestController
@RequestMapping("/erp/supplier-statement")
@Validated
public class ErpSupplierStatementController {
    @Resource
    private ErpSupplierStatementService supplierStatementService;
    @GetMapping("/get")
    @Operation(summary = "获取供应商往来对账数据")
    @PreAuthorize("@ss.hasPermission('erp:supplier-statement:query')")
    public CommonResult<ErpSupplierStatementRespVO> getSupplierStatement(
            @Valid ErpSupplierStatementReqVO reqVO) {
        return success(supplierStatementService.getSupplierStatement(reqVO));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/statement/ErpSupplierStatementReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - ä¾›åº”商往来查询 Request VO")
@Data
public class ErpSupplierStatementReqVO {
    @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "供应商不能为空")
    private Long supplierId;
    @Schema(description = "开始时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime beginTime;
    @Schema(description = "结束时间")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
    private LocalDateTime endTime;
    @Schema(description = "是否包含未审核采购订单")
    private Boolean includeUnapprovedOrder;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/statement/ErpSupplierStatementRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,96 @@
package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement;
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 = "管理后台 - ä¾›åº”商往来 Response VO")
@Data
public class ErpSupplierStatementRespVO {
    @Schema(description = "供应商编号")
    private Long supplierId;
    @Schema(description = "供应商名称")
    private String supplierName;
    @Schema(description = "汇总")
    private Summary summary;
    @Schema(description = "采购订单流水")
    private List<OrderRow> orders;
    @Schema(description = "来票流水")
    private List<InvoiceRow> invoices;
    @Schema(description = "付款流水")
    private List<PaymentRow> payments;
    @Schema(description = "往来汇总")
    @Data
    public static class Summary {
        @Schema(description = "累计采购额")
        private BigDecimal totalPurchaseAmount;
        @Schema(description = "累计开票额")
        private BigDecimal totalInvoiceAmount;
        @Schema(description = "累计付款额")
        private BigDecimal totalPaymentAmount;
        @Schema(description = "未付余额(开票 - ä»˜æ¬¾ï¼‰")
        private BigDecimal unpaidAmount;
    }
    @Schema(description = "采购订单流水")
    @Data
    public static class OrderRow {
        private Long id;
        private String no;
        private LocalDateTime orderTime;
        private BigDecimal orderAmount;
        private BigDecimal invoicedAmount;
        private BigDecimal uninvoicedAmount;
        private Integer status;
        private Integer inStatus;
    }
    @Schema(description = "来票流水")
    @Data
    public static class InvoiceRow {
        private Long id;
        private String no;
        private String invoiceNo;
        private LocalDateTime invoiceTime;
        private BigDecimal invoiceAmount;
        private BigDecimal paidAmount;
        private BigDecimal remainingPayable;
        private Integer paymentStatus;
        private Long purchaseOrderId;
        private String purchaseOrderNo;
    }
    @Schema(description = "付款流水")
    @Data
    public static class PaymentRow {
        private Long id;
        private String no;
        private LocalDateTime paymentTime;
        private BigDecimal paymentAmount;
        private Integer status;
        private Long invoiceId;
        private String invoiceNo;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/finance/ErpFinancePaymentMapper.java
@@ -14,6 +14,7 @@
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -84,4 +85,20 @@
        return convertMap(result, obj -> (Long) obj.get("invoice_id"), obj -> (BigDecimal) obj.get("total_payment_price"));
    }
    /**
     * æŒ‰ä¾›åº”商与付款时间范围查询付款单(供应商往来)
     */
    default List<ErpFinancePaymentDO> selectListBySupplierIdAndPaymentTimeBetween(Long supplierId,
                                                                                 LocalDateTime beginTime,
                                                                                 LocalDateTime endTime,
                                                                                 Collection<Integer> statuses) {
        return selectList(new LambdaQueryWrapperX<ErpFinancePaymentDO>()
                .eq(ErpFinancePaymentDO::getSupplierId, supplierId)
                .geIfPresent(ErpFinancePaymentDO::getPaymentTime, beginTime)
                .leIfPresent(ErpFinancePaymentDO::getPaymentTime, endTime)
                .inIfPresent(ErpFinancePaymentDO::getStatus, statuses)
                .orderByAsc(ErpFinancePaymentDO::getPaymentTime)
                .orderByAsc(ErpFinancePaymentDO::getId));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java
@@ -7,6 +7,7 @@
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
@@ -45,4 +46,18 @@
                .in(ErpPurchaseInvoiceDO::getPurchaseOrderId, orderIds));
    }
    /**
     * æŒ‰ä¾›åº”商与开票时间范围查询来票(供应商往来)
     */
    default List<ErpPurchaseInvoiceDO> selectListBySupplierIdAndInvoiceTimeBetween(Long supplierId,
                                                                                  LocalDateTime beginTime,
                                                                                  LocalDateTime endTime) {
        return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>()
                .eq(ErpPurchaseInvoiceDO::getSupplierId, supplierId)
                .geIfPresent(ErpPurchaseInvoiceDO::getInvoiceTime, beginTime)
                .leIfPresent(ErpPurchaseInvoiceDO::getInvoiceTime, endTime)
                .orderByAsc(ErpPurchaseInvoiceDO::getInvoiceTime)
                .orderByAsc(ErpPurchaseInvoiceDO::getId));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
@@ -11,6 +11,8 @@
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -99,4 +101,20 @@
                .eq(ErpPurchaseOrderDO::getSourceId, sourceId));
    }
    /**
     * æŒ‰ä¾›åº”商与下单时间范围查询采购订单(供应商往来)
     */
    default List<ErpPurchaseOrderDO> selectListBySupplierIdAndOrderTimeBetween(Long supplierId,
                                                                              LocalDateTime beginTime,
                                                                              LocalDateTime endTime,
                                                                              Collection<Integer> statuses) {
        return selectList(new LambdaQueryWrapperX<ErpPurchaseOrderDO>()
                .eq(ErpPurchaseOrderDO::getSupplierId, supplierId)
                .geIfPresent(ErpPurchaseOrderDO::getOrderTime, beginTime)
                .leIfPresent(ErpPurchaseOrderDO::getOrderTime, endTime)
                .inIfPresent(ErpPurchaseOrderDO::getStatus, statuses)
                .orderByAsc(ErpPurchaseOrderDO::getOrderTime)
                .orderByAsc(ErpPurchaseOrderDO::getId));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderSourceTypeEnum.java
@@ -15,7 +15,8 @@
@Getter
public enum ErpPurchaseOrderSourceTypeEnum implements ArrayValuable<Integer> {
    WORK_ORDER(1, "生产工单");
    WORK_ORDER(1, "生产工单"),
    SRM_INQUIRY(2, "SRM询价");
    public static final Integer[] ARRAYS = Arrays.stream(values())
            .map(ErpPurchaseOrderSourceTypeEnum::getType).toArray(Integer[]::new);
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -30,6 +30,15 @@
    Long createPurchaseOrder(@Valid ErpPurchaseOrderSaveReqVO createReqVO);
    /**
     * åˆ›å»ºé‡‡è´­è®¢å•
     *
     * @param createReqVO åˆ›å»ºä¿¡æ¯
     * @param submit      æ˜¯å¦åˆ›å»ºåŽç«‹å³æäº¤å®¡æ‰¹ï¼›false æ—¶ä¿å­˜ä¸ºè‰ç¨¿
     * @return ç¼–号
     */
    Long createPurchaseOrder(@Valid ErpPurchaseOrderSaveReqVO createReqVO, boolean submit);
    /**
     * å¯¼å…¥é‡‡è´­è®¢å•
     *
     * @param importList    å¯¼å…¥æ•°æ®ï¼ˆæ¯ä¸€è¡Œ = ä¸€æ¡æ˜Žç»†ï¼Œç›¸åŒé‡‡è´­å•编号合并为一张订单)
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -117,6 +117,12 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO) {
        return createPurchaseOrder(createReqVO, true);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createPurchaseOrder(ErpPurchaseOrderSaveReqVO createReqVO, boolean submit) {
        // 1.1 æ ¡éªŒè®¢å•项的有效性
        List<ErpPurchaseOrderItemDO> purchaseOrderItems = validatePurchaseOrderItems(createReqVO.getItems());
        // 1.2 æ ¡éªŒä¾›åº”商
@@ -131,9 +137,9 @@
            throw exception(PURCHASE_ORDER_NO_EXISTS);
        }
        // 2.1 æ’入订单(采购订单创建后为未审核状态,需要提交审批)
        // 2.1 æ’入订单(submit=false æ—¶ä¿å­˜ä¸ºè‰ç¨¿ï¼Œç­‰å¾…业务确认后手动提交审批)
        ErpPurchaseOrderDO purchaseOrder = BeanUtils.toBean(createReqVO, ErpPurchaseOrderDO.class, in -> in
                .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
                .setNo(no).setStatus(submit ? ErpAuditStatus.PROCESS.getStatus() : ErpAuditStatus.DRAFT.getStatus())
                .setInCount(BigDecimal.ZERO).setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus()));
        calculateTotalPrice(purchaseOrder, purchaseOrderItems);
        purchaseOrderMapper.insert(purchaseOrder);
@@ -145,7 +151,9 @@
                purchaseOrder.getId(), createReqVO.getBlobIds());
        // 3. åˆ›å»ºåŽè‡ªåŠ¨æäº¤å®¡æ‰¹åˆ°ååŒåŠžå…¬ï¼ˆå®¡æ‰¹åœ¨ååŒåŠžå…¬å®Œæˆï¼Œä¸åœ¨æœ¬é¡µé¢å®¡æ‰¹ï¼‰
        submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
        if (submit) {
            submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
        }
        return purchaseOrder.getId();
    }
@@ -351,7 +359,8 @@
        purchaseOrder.setTotalCount(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getCount, BigDecimal::add));
        purchaseOrder.setTotalProductPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseOrder.setTotalTaxPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTaxPrice, BigDecimal::add, BigDecimal.ZERO));
        purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice().add(purchaseOrder.getTotalTaxPrice()));
        // å•价为含税单价:价税合计 = å•ä»· * æ•°é‡ï¼Œç¨Žé¢å·²åŒ…含在单价中,不再额外加税
        purchaseOrder.setTotalPrice(purchaseOrder.getTotalProductPrice());
        // è®¡ç®—优惠价格
        if (purchaseOrder.getDiscountPercent() == null) {
            purchaseOrder.setDiscountPercent(BigDecimal.ZERO);
@@ -392,9 +401,11 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseOrder(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒå­˜åœ¨ + æœªå®¡æ ¸çŠ¶æ€
        // 1. æ ¡éªŒå­˜åœ¨ + æœªå®¡æ ¸çŠ¶æ€ï¼ˆè‰ç¨¿å’Œæœªå®¡æ ¸å‡å¯æäº¤ï¼‰
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
        if (!ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus())) {
        boolean submitable = ErpAuditStatus.DRAFT.getStatus().equals(purchaseOrder.getStatus())
                || ErpAuditStatus.PROCESS.getStatus().equals(purchaseOrder.getStatus());
        if (!submitable) {
            throw exception(PURCHASE_ORDER_SUBMIT_FAIL_STATUS);
        }
@@ -510,7 +521,8 @@
                return;
            }
            if (item.getTaxPercent() != null) {
                item.setTaxPrice(MoneyUtils.priceMultiplyPercent(item.getTotalPrice(), item.getTaxPercent()));
                // å•价为含税单价:税额从价税合计中反算,而不是在合计上再加税
                item.setTaxPrice(MoneyUtils.taxPriceIncluded(item.getTotalPrice(), item.getTaxPercent()));
            }
        }));
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -360,7 +360,8 @@
        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()));
        // å•价为含税单价:价税合计 = å•ä»· * æ•°é‡ï¼Œç¨Žé¢å·²åŒ…含在单价中,不再额外加税
        purchaseRequest.setTotalPrice(purchaseRequest.getTotalProductPrice());
        // è®¡ç®—优惠价格
        if (purchaseRequest.getDiscountPercent() == null) {
            purchaseRequest.setDiscountPercent(BigDecimal.ZERO);
@@ -374,7 +375,8 @@
            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()));
                    // å•价为含税单价:税额从价税合计中反算,而不是在合计上再加税
                    item.setTaxPrice(MoneyUtils.taxPriceIncluded(item.getTotalPrice(), item.getTaxPercent()));
                }
            }
        }));
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierStatementService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement.ErpSupplierStatementReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement.ErpSupplierStatementRespVO;
import jakarta.validation.Valid;
/**
 * ERP ä¾›åº”商往来 Service æŽ¥å£
 */
public interface ErpSupplierStatementService {
    /**
     * èŽ·å–ä¾›åº”å•†å¾€æ¥å¯¹è´¦æ•°æ®ï¼ˆè®¢å• / æ¥ç¥¨ / ä»˜æ¬¾ + æ±‡æ€»ï¼‰
     */
    ErpSupplierStatementRespVO getSupplierStatement(@Valid ErpSupplierStatementReqVO reqVO);
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierStatementServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,163 @@
package cn.iocoder.yudao.module.erp.service.purchase;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement.ErpSupplierStatementReqVO;
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.statement.ErpSupplierStatementRespVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.finance.ErpFinancePaymentDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.SUPPLIER_NOT_EXISTS;
/**
 * ERP ä¾›åº”商往来 Service å®žçް
 */
@Service
@Validated
public class ErpSupplierStatementServiceImpl implements ErpSupplierStatementService {
    @Resource
    private ErpPurchaseOrderMapper purchaseOrderMapper;
    @Resource
    private ErpPurchaseInvoiceMapper purchaseInvoiceMapper;
    @Resource
    private ErpFinancePaymentMapper financePaymentMapper;
    @Resource
    private ErpPurchaseInvoiceService purchaseInvoiceService;
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @Override
    public ErpSupplierStatementRespVO getSupplierStatement(ErpSupplierStatementReqVO reqVO) {
        // 1. æ ¡éªŒä¾›åº”商
        SrmSupplierRespDTO supplier = srmSupplierApi.getSupplier(reqVO.getSupplierId()).getCheckedData();
        if (supplier == null) {
            throw ServiceExceptionUtil.exception(SUPPLIER_NOT_EXISTS);
        }
        // 2. æŸ¥è¯¢è®¢å• / æ¥ç¥¨ / ä»˜æ¬¾
        List<Integer> orderStatuses = Boolean.TRUE.equals(reqVO.getIncludeUnapprovedOrder())
                ? List.of(ErpAuditStatus.PROCESS.getStatus(), ErpAuditStatus.APPROVING.getStatus(),
                ErpAuditStatus.APPROVE.getStatus())
                : List.of(ErpAuditStatus.APPROVE.getStatus());
        List<ErpPurchaseOrderDO> orders = purchaseOrderMapper.selectListBySupplierIdAndOrderTimeBetween(
                reqVO.getSupplierId(), reqVO.getBeginTime(), reqVO.getEndTime(), orderStatuses);
        List<ErpPurchaseInvoiceDO> invoices = purchaseInvoiceMapper.selectListBySupplierIdAndInvoiceTimeBetween(
                reqVO.getSupplierId(), reqVO.getBeginTime(), reqVO.getEndTime());
        List<Integer> paymentStatuses = List.of(ErpAuditStatus.PROCESS.getStatus(),
                ErpAuditStatus.APPROVING.getStatus(), ErpAuditStatus.APPROVE.getStatus());
        List<ErpFinancePaymentDO> payments = financePaymentMapper.selectListBySupplierIdAndPaymentTimeBetween(
                reqVO.getSupplierId(), reqVO.getBeginTime(), reqVO.getEndTime(), paymentStatuses);
        // 3. è®¢å•已开票金额(不按开票日期过滤,保证未开票金额准确)
        Map<Long, BigDecimal> invoicedMap = CollUtil.isEmpty(orders) ? Collections.emptyMap()
                : purchaseInvoiceService.getPurchaseInvoicePriceSumMapByOrderIds(
                orders.stream().map(ErpPurchaseOrderDO::getId).toList());
        // 4. æ¥ç¥¨å·²ä»˜é‡‘额(按所有有效付款单汇总)
        Map<Long, BigDecimal> paidMap = CollUtil.isEmpty(invoices) ? Collections.emptyMap()
                : financePaymentMapper.selectPaymentPriceMapByInvoiceId(
                invoices.stream().map(ErpPurchaseInvoiceDO::getId).toList());
        // 5. æ¥ç¥¨å…³è”订单号
        Set<Long> invoiceOrderIds = invoices.stream().map(ErpPurchaseInvoiceDO::getPurchaseOrderId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, ErpPurchaseOrderDO> invoiceOrderMap = invoiceOrderIds.isEmpty() ? Collections.emptyMap()
                : purchaseOrderMapper.selectBatchIds(invoiceOrderIds).stream()
                .collect(Collectors.toMap(ErpPurchaseOrderDO::getId, Function.identity()));
        // 6. ä»˜æ¬¾å…³è”来票号
        Set<Long> paymentInvoiceIds = payments.stream().map(ErpFinancePaymentDO::getInvoiceId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, ErpPurchaseInvoiceDO> paymentInvoiceMap = paymentInvoiceIds.isEmpty() ? Collections.emptyMap()
                : purchaseInvoiceMapper.selectBatchIds(paymentInvoiceIds).stream()
                .collect(Collectors.toMap(ErpPurchaseInvoiceDO::getId, Function.identity()));
        // 7. ç»„装
        ErpSupplierStatementRespVO respVO = new ErpSupplierStatementRespVO();
        respVO.setSupplierId(reqVO.getSupplierId());
        respVO.setSupplierName(supplier.getName());
        List<ErpSupplierStatementRespVO.OrderRow> orderRows = new ArrayList<>();
        for (ErpPurchaseOrderDO order : orders) {
            BigDecimal orderAmount = zero(order.getTotalPrice());
            BigDecimal invoicedAmount = zero(invoicedMap.get(order.getId()));
            ErpSupplierStatementRespVO.OrderRow row = new ErpSupplierStatementRespVO.OrderRow();
            row.setId(order.getId());
            row.setNo(order.getNo());
            row.setOrderTime(order.getOrderTime());
            row.setOrderAmount(orderAmount);
            row.setInvoicedAmount(invoicedAmount);
            row.setUninvoicedAmount(orderAmount.subtract(invoicedAmount));
            row.setStatus(order.getStatus());
            row.setInStatus(order.getInStatus());
            orderRows.add(row);
        }
        respVO.setOrders(orderRows);
        List<ErpSupplierStatementRespVO.InvoiceRow> invoiceRows = new ArrayList<>();
        for (ErpPurchaseInvoiceDO invoice : invoices) {
            BigDecimal invoiceAmount = zero(invoice.getPrice());
            BigDecimal paidAmount = zero(paidMap.get(invoice.getId()));
            ErpSupplierStatementRespVO.InvoiceRow row = new ErpSupplierStatementRespVO.InvoiceRow();
            row.setId(invoice.getId());
            row.setNo(invoice.getNo());
            row.setInvoiceNo(invoice.getInvoiceNo());
            row.setInvoiceTime(invoice.getInvoiceTime());
            row.setInvoiceAmount(invoiceAmount);
            row.setPaidAmount(paidAmount);
            row.setRemainingPayable(invoiceAmount.subtract(paidAmount));
            row.setPaymentStatus(paidAmount.compareTo(BigDecimal.ZERO) <= 0 ? 0
                    : paidAmount.compareTo(invoiceAmount) >= 0 ? 2 : 1);
            row.setPurchaseOrderId(invoice.getPurchaseOrderId());
            ErpPurchaseOrderDO order = invoiceOrderMap.get(invoice.getPurchaseOrderId());
            row.setPurchaseOrderNo(order != null ? order.getNo() : null);
            invoiceRows.add(row);
        }
        respVO.setInvoices(invoiceRows);
        List<ErpSupplierStatementRespVO.PaymentRow> paymentRows = new ArrayList<>();
        for (ErpFinancePaymentDO payment : payments) {
            ErpSupplierStatementRespVO.PaymentRow row = new ErpSupplierStatementRespVO.PaymentRow();
            row.setId(payment.getId());
            row.setNo(payment.getNo());
            row.setPaymentTime(payment.getPaymentTime());
            row.setPaymentAmount(zero(payment.getPaymentPrice()));
            row.setStatus(payment.getStatus());
            row.setInvoiceId(payment.getInvoiceId());
            ErpPurchaseInvoiceDO invoice = paymentInvoiceMap.get(payment.getInvoiceId());
            row.setInvoiceNo(invoice != null ? invoice.getNo() : null);
            paymentRows.add(row);
        }
        respVO.setPayments(paymentRows);
        // 8. æ±‡æ€»
        ErpSupplierStatementRespVO.Summary summary = new ErpSupplierStatementRespVO.Summary();
        summary.setTotalPurchaseAmount(orderRows.stream().map(ErpSupplierStatementRespVO.OrderRow::getOrderAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        summary.setTotalInvoiceAmount(invoiceRows.stream().map(ErpSupplierStatementRespVO.InvoiceRow::getInvoiceAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        summary.setTotalPaymentAmount(paymentRows.stream().map(ErpSupplierStatementRespVO.PaymentRow::getPaymentAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add));
        summary.setUnpaidAmount(summary.getTotalInvoiceAmount().subtract(summary.getTotalPaymentAmount()));
        respVO.setSummary(summary);
        return respVO;
    }
    private BigDecimal zero(BigDecimal value) {
        return value != null ? value : BigDecimal.ZERO;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/SrmSupplierApplyController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyPageReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/SrmBidBondController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.srm.controller.admin.bond;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondRespVO;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondSaveReqVO;
import cn.iocoder.yudao.module.srm.service.bond.SrmBidBondService;
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.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM æŠ•标保证金")
@RestController
@RequestMapping("/srm/bid-bond")
@Validated
public class SrmBidBondController {
    @Resource
    private SrmBidBondService bidBondService;
    @PostMapping("/create")
    @Operation(summary = "创建投标保证金记录")
    @PreAuthorize("@ss.hasPermission('srm:bid-bond:create')")
    public CommonResult<Long> createBidBond(@Valid @RequestBody SrmBidBondSaveReqVO createReqVO) {
        return success(bidBondService.createBidBond(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新投标保证金记录")
    @PreAuthorize("@ss.hasPermission('srm:bid-bond:update')")
    public CommonResult<Boolean> updateBidBond(@Valid @RequestBody SrmBidBondSaveReqVO updateReqVO) {
        bidBondService.updateBidBond(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除投标保证金记录")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:bid-bond:delete')")
    public CommonResult<Boolean> deleteBidBond(@RequestParam("id") Long id) {
        bidBondService.deleteBidBond(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获取投标保证金记录")
    @Parameter(name = "id", description = "编号", required = true)
    public CommonResult<SrmBidBondRespVO> getBidBond(@RequestParam("id") Long id) {
        return success(bidBondService.getBidBondDetail(id));
    }
    @GetMapping("/page")
    @Operation(summary = "投标保证金分页查询")
    public CommonResult<PageResult<SrmBidBondRespVO>> getBidBondPage(@Valid SrmBidBondPageReqVO pageReqVO) {
        return success(bidBondService.getBidBondPage(pageReqVO));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/vo/SrmBidBondPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.srm.controller.admin.bond.vo;
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.LocalDate;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - æŠ•标保证金分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SrmBidBondPageReqVO extends PageParam {
    @Schema(description = "保证金单号")
    private String no;
    @Schema(description = "供应商编号")
    private Long supplierId;
    @Schema(description = "项目名称")
    private String projectName;
    @Schema(description = "是否中标")
    private Boolean won;
    @Schema(description = "退回状态:0 æœªé€€å›žã€1 éƒ¨åˆ†é€€å›žã€2 å·²é€€å›žã€3 æ— éœ€é€€å›ž")
    private Integer refundStatus;
    @Schema(description = "缴纳日期开始")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate payDateStart;
    @Schema(description = "缴纳日期结束")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate payDateEnd;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/vo/SrmBidBondRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package cn.iocoder.yudao.module.srm.controller.admin.bond.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - æŠ•标保证金 Response VO")
@Data
public class SrmBidBondRespVO {
    @Schema(description = "保证金记录ID")
    private Long id;
    @Schema(description = "保证金单号")
    private String no;
    @Schema(description = "供应商编号")
    private Long supplierId;
    @Schema(description = "供应商名称")
    private String supplierName;
    @Schema(description = "项目名称")
    private String projectName;
    @Schema(description = "关联询价单编号")
    private Long inquiryId;
    @Schema(description = "关联询价单号")
    private String inquiryNo;
    @Schema(description = "关联采购订单编号")
    private Long purchaseOrderId;
    @Schema(description = "关联采购订单号")
    private String purchaseOrderNo;
    @Schema(description = "保证金金额")
    private BigDecimal bondAmount;
    @Schema(description = "缴纳日期")
    private LocalDate payDate;
    @Schema(description = "是否中标")
    private Boolean won;
    @Schema(description = "退回状态:0 æœªé€€å›žã€1 éƒ¨åˆ†é€€å›žã€2 å·²é€€å›žã€3 æ— éœ€é€€å›ž")
    private Integer refundStatus;
    @Schema(description = "退回金额")
    private BigDecimal refundAmount;
    @Schema(description = "退回日期")
    private LocalDate refundDate;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/bond/vo/SrmBidBondSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.srm.controller.admin.bond.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - æŠ•标保证金创建/更新 Request VO")
@Data
public class SrmBidBondSaveReqVO {
    @Schema(description = "保证金记录ID")
    private Long id;
    @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "供应商不能为空")
    private Long supplierId;
    @Schema(description = "项目名称")
    private String projectName;
    @Schema(description = "关联询价单编号")
    private Long inquiryId;
    @Schema(description = "关联采购订单编号")
    private Long purchaseOrderId;
    @Schema(description = "保证金金额", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "保证金金额不能为空")
    private BigDecimal bondAmount;
    @Schema(description = "缴纳日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate payDate;
    @Schema(description = "是否中标")
    private Boolean won;
    @Schema(description = "退回状态:0 æœªé€€å›žã€1 éƒ¨åˆ†é€€å›žã€2 å·²é€€å›žã€3 æ— éœ€é€€å›ž")
    private Integer refundStatus;
    @Schema(description = "退回金额")
    private BigDecimal refundAmount;
    @Schema(description = "退回日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate refundDate;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/SrmSupplierEvaluationController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationPageReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/evaluation/vo/SrmSupplierEvaluationSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/SrmInquiryController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
package cn.iocoder.yudao.module.srm.controller.admin.inquiry;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryDO;
import cn.iocoder.yudao.module.srm.service.inquiry.SrmInquiryService;
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.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - SRM è¯¢ä»·æ¯”ä»·")
@RestController
@RequestMapping("/srm/inquiry")
@Validated
public class SrmInquiryController {
    @Resource
    private SrmInquiryService inquiryService;
    @PostMapping("/create")
    @Operation(summary = "创建询价单")
    @PreAuthorize("@ss.hasPermission('srm:inquiry:create')")
    public CommonResult<Long> createInquiry(@Valid @RequestBody SrmInquirySaveReqVO createReqVO) {
        return success(inquiryService.createInquiry(createReqVO));
    }
    @PutMapping("/update")
    @Operation(summary = "更新询价单(仅草稿)")
    @PreAuthorize("@ss.hasPermission('srm:inquiry:update')")
    public CommonResult<Boolean> updateInquiry(@Valid @RequestBody SrmInquirySaveReqVO updateReqVO) {
        inquiryService.updateInquiry(updateReqVO);
        return success(true);
    }
    @DeleteMapping("/delete")
    @Operation(summary = "删除询价单(仅草稿)")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:inquiry:delete')")
    public CommonResult<Boolean> deleteInquiry(@RequestParam("id") Long id) {
        inquiryService.deleteInquiry(id);
        return success(true);
    }
    @GetMapping("/get")
    @Operation(summary = "获取询价单详情")
    @Parameter(name = "id", description = "编号", required = true)
    public CommonResult<SrmInquiryRespVO> getInquiry(@RequestParam("id") Long id) {
        return success(inquiryService.getInquiryDetail(id));
    }
    @GetMapping("/page")
    @Operation(summary = "询价单分页查询")
    public CommonResult<PageResult<SrmInquiryRespVO>> getInquiryPage(@Valid SrmInquiryPageReqVO pageReqVO) {
        PageResult<SrmInquiryDO> pageResult = inquiryService.getInquiryPage(pageReqVO);
        return success(BeanUtils.toBean(pageResult, SrmInquiryRespVO.class));
    }
    @GetMapping("/simple-list")
    @Operation(summary = "获取询价单精简列表(用于投标保证金选择)")
    public CommonResult<List<SrmInquiryRespVO>> getInquirySimpleList() {
        List<SrmInquiryDO> list = inquiryService.getInquirySimpleList();
        return success(BeanUtils.toBean(list, SrmInquiryRespVO.class));
    }
    @PutMapping("/publish")
    @Operation(summary = "发布询价单:草稿 -> è¯¢ä»·ä¸­")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:inquiry:update')")
    public CommonResult<Boolean> publishInquiry(@RequestParam("id") Long id) {
        inquiryService.publishInquiry(id);
        return success(true);
    }
    @PutMapping("/cancel")
    @Operation(summary = "取消询价单")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('srm:inquiry:update')")
    public CommonResult<Boolean> cancelInquiry(@RequestParam("id") Long id) {
        inquiryService.cancelInquiry(id);
        return success(true);
    }
    @PostMapping("/save-quotes")
    @Operation(summary = "批量保存供应商报价")
    @PreAuthorize("@ss.hasPermission('srm:inquiry:quote')")
    public CommonResult<Boolean> saveQuotes(@Valid @RequestBody SrmInquiryQuoteSaveReqVO saveReqVO) {
        inquiryService.saveQuotes(saveReqVO);
        return success(true);
    }
    @GetMapping("/compare")
    @Operation(summary = "获取询价比价视图(产品行 x ä¾›åº”商报价)")
    @Parameter(name = "id", description = "编号", required = true)
    public CommonResult<SrmInquiryRespVO> getCompare(@RequestParam("id") Long id) {
        return success(inquiryService.getCompare(id));
    }
    @PostMapping("/confirm")
    @Operation(summary = "确认中标并生成采购订单草稿")
    @PreAuthorize("@ss.hasPermission('srm:inquiry:confirm')")
    public CommonResult<SrmInquiryConfirmRespVO> confirmInquiry(
            @Valid @RequestBody SrmInquiryConfirmReqVO confirmReqVO) {
        return success(inquiryService.confirmInquiry(confirmReqVO));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryConfirmReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - è¯¢ä»·æ¯”价确认转订单 Request VO")
@Data
public class SrmInquiryConfirmReqVO {
    @Schema(description = "询价单ID", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "询价单ID不能为空")
    private Long id;
    @Schema(description = "每个产品选中的中标报价;为空时使用询价单中已保存的中标选择")
    private List<Selection> selections;
    @Schema(description = "产品中标选择")
    @Data
    public static class Selection {
        @Schema(description = "询价产品行ID", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "询价产品行ID不能为空")
        private Long inquiryItemId;
        @Schema(description = "中标报价ID", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "中标报价ID不能为空")
        private Long quoteId;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryConfirmRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - è¯¢ä»·æ¯”价确认转订单 Response VO")
@Data
public class SrmInquiryConfirmRespVO {
    @Schema(description = "询价单ID")
    private Long inquiryId;
    @Schema(description = "生成的采购订单草稿")
    private List<Order> orders;
    @Schema(description = "生成的采购订单")
    @Data
    public static class Order {
        @Schema(description = "供应商编号")
        private Long supplierId;
        @Schema(description = "供应商名称")
        private String supplierName;
        @Schema(description = "采购订单编号")
        private Long purchaseOrderId;
        @Schema(description = "采购订单号")
        private String purchaseOrderNo;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo;
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.LocalDate;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - è¯¢ä»·æ¯”价分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class SrmInquiryPageReqVO extends PageParam {
    @Schema(description = "询价单号")
    private String no;
    @Schema(description = "询价标题")
    private String title;
    @Schema(description = "状态:0 è‰ç¨¿ã€10 è¯¢ä»·ä¸­ã€20 å·²è½¬è®¢å•、30 å·²å–消")
    private Integer status;
    @Schema(description = "询价日期开始")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate inquiryDateStart;
    @Schema(description = "询价日期结束")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate inquiryDateEnd;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryQuoteSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Schema(description = "管理后台 - è¯¢ä»·æŠ¥ä»·ä¿å­˜ Request VO")
@Data
public class SrmInquiryQuoteSaveReqVO {
    @Schema(description = "询价单ID", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "询价单ID不能为空")
    private Long inquiryId;
    @Schema(description = "报价列表", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "报价列表不能为空")
    private List<Quote> quotes;
    @Schema(description = "产品报价")
    @Data
    public static class Quote {
        @Schema(description = "询价产品行ID", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "询价产品行ID不能为空")
        private Long inquiryItemId;
        @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "供应商不能为空")
        private Long supplierId;
        @Schema(description = "报价单价")
        private BigDecimal price;
        @Schema(description = "税率,百分比")
        private BigDecimal taxRate;
        @Schema(description = "交货周期,天")
        private Integer deliveryCycle;
        @Schema(description = "付款条件")
        private String paymentTerms;
        @Schema(description = "备注")
        private String remark;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquiryRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,119 @@
package cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - è¯¢ä»·æ¯”ä»· Response VO")
@Data
public class SrmInquiryRespVO {
    @Schema(description = "询价单ID")
    private Long id;
    @Schema(description = "询价单号")
    private String no;
    @Schema(description = "询价标题")
    private String title;
    @Schema(description = "状态:0 è‰ç¨¿ã€10 è¯¢ä»·ä¸­ã€20 å·²è½¬è®¢å•、30 å·²å–消")
    private Integer status;
    @Schema(description = "询价日期")
    private LocalDate inquiryDate;
    @Schema(description = "报价截止日期")
    private LocalDate deadline;
    @Schema(description = "采购员用户编号")
    private Long purchaserUserId;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "产品行列表")
    private List<Item> items;
    @Schema(description = "参与供应商列表")
    private List<Supplier> suppliers;
    @Schema(description = "全部报价")
    private List<Quote> quotes;
    @Schema(description = "已生成的采购订单")
    private List<Order> orders;
    @Schema(description = "询价产品行")
    @Data
    public static class Item {
        private Long id;
        private Long inquiryId;
        private Long productId;
        private Long productUnitId;
        private String productCode;
        private String productName;
        private String productSpec;
        private String unitName;
        private BigDecimal quantity;
        private BigDecimal estimatedPrice;
        private Long selectedSupplierId;
        private Long selectedQuoteId;
        private BigDecimal selectedPrice;
        private String remark;
        private Integer sort;
    }
    @Schema(description = "参与供应商")
    @Data
    public static class Supplier {
        private Long id;
        private Long supplierId;
        private String supplierName;
        private String supplierCode;
        private Integer quoteStatus;
        private LocalDateTime quoteTime;
        private String remark;
    }
    @Schema(description = "产品报价")
    @Data
    public static class Quote {
        private Long id;
        private Long inquiryId;
        private Long inquiryItemId;
        private Long supplierId;
        private BigDecimal price;
        private BigDecimal taxRate;
        private Integer deliveryCycle;
        private String paymentTerms;
        private String remark;
        private Boolean isSelected;
    }
    @Schema(description = "已生成的采购订单")
    @Data
    public static class Order {
        private Long id;
        private Long supplierId;
        private String supplierName;
        private Long purchaseOrderId;
        private String purchaseOrderNo;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/inquiry/vo/SrmInquirySaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,131 @@
package cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - è¯¢ä»·æ¯”价创建/更新 Request VO")
@Data
public class SrmInquirySaveReqVO {
    @Schema(description = "询价单ID")
    private Long id;
    @Schema(description = "询价标题", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "询价标题不能为空")
    private String title;
    @Schema(description = "询价日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate inquiryDate;
    @Schema(description = "报价截止日期")
    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
    private LocalDate deadline;
    @Schema(description = "采购员用户编号")
    private Long purchaserUserId;
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "产品行列表")
    @NotEmpty(message = "请至少添加一个询价产品")
    private List<Item> items;
    @Schema(description = "参与供应商列表")
    @NotEmpty(message = "请至少添加一个参与供应商")
    private List<Supplier> suppliers;
    @Schema(description = "询价产品行")
    @Data
    public static class Item {
        @Schema(description = "询价产品行ID(编辑已有行时传入)")
        private Long id;
        @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "产品不能为空")
        private Long productId;
        @Schema(description = "产品单位编号")
        private Long productUnitId;
        @Schema(description = "产品编码")
        private String productCode;
        @Schema(description = "产品名称")
        private String productName;
        @Schema(description = "规格型号")
        private String productSpec;
        @Schema(description = "单位名称")
        private String unitName;
        @Schema(description = "采购数量", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "采购数量不能为空")
        private BigDecimal quantity;
        @Schema(description = "目标单价")
        private BigDecimal estimatedPrice;
        @Schema(description = "中标供应商编号")
        private Long selectedSupplierId;
        @Schema(description = "供应商报价列表(创建/编辑时一并提交)")
        private List<Quote> quotes;
        @Schema(description = "备注")
        private String remark;
    }
    @Schema(description = "询价产品供应商报价")
    @Data
    public static class Quote {
        @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "报价供应商不能为空")
        private Long supplierId;
        @Schema(description = "报价单价", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "报价单价不能为空")
        private BigDecimal price;
        @Schema(description = "税率,百分比")
        private BigDecimal taxRate;
        @Schema(description = "交货周期,天")
        private Integer deliveryCycle;
        @Schema(description = "付款条件")
        private String paymentTerms;
        @Schema(description = "备注")
        private String remark;
    }
    @Schema(description = "参与供应商")
    @Data
    public static class Supplier {
        @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED)
        @NotNull(message = "供应商不能为空")
        private Long supplierId;
        @Schema(description = "备注")
        private String remark;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/SrmStatisticsController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/statistics/vo/SrmTenderStatisticsRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java
@@ -65,7 +65,14 @@
    @Parameter(name = "id", description = "编号", required = true)
    public CommonResult<SrmSupplierRespVO> getSupplier(@RequestParam("id") Long id) {
        SrmSupplierDO supplier = supplierService.getSupplier(id);
        return success(BeanUtils.toBean(supplier, SrmSupplierRespVO.class));
        SrmSupplierRespVO respVO = BeanUtils.toBean(supplier, SrmSupplierRespVO.class);
        if (respVO != null) {
            respVO.setContacts(BeanUtils.toBean(supplierService.getSupplierContactList(id),
                    SrmSupplierContactRespVO.class));
            respVO.setBanks(BeanUtils.toBean(supplierService.getSupplierBankList(id),
                    SrmSupplierBankRespVO.class));
        }
        return success(respVO);
    }
    @GetMapping("/page")
@@ -76,14 +83,14 @@
    }
    @GetMapping("/simple-list")
    @Operation(summary = "获取供应商精简列表(仅返回已通过准入审批的供应商)")
    @Operation(summary = "获取供应商精简列表(仅返回启用状态的供应商)")
    public CommonResult<List<SrmSupplierRespVO>> getSupplierSimpleList() {
        List<SrmSupplierDO> list = supplierService.getSupplierSimpleList();
        return success(BeanUtils.toBean(list, SrmSupplierRespVO.class));
    }
    @GetMapping("/list")
    @Operation(summary = "获取供应商档案列表(不过滤准入状态)")
    @Operation(summary = "获取供应商档案列表(排除停用供应商)")
    public CommonResult<List<SrmSupplierRespVO>> getSupplierAllList() {
        List<SrmSupplierDO> list = supplierService.getSupplierAllList();
        return success(BeanUtils.toBean(list, SrmSupplierRespVO.class));
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierBankRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - ä¾›åº”商银行账户 Response VO")
@Data
public class SrmSupplierBankRespVO {
    @Schema(description = "银行账户ID")
    private Long id;
    @Schema(description = "供应商ID")
    private Long supplierId;
    @Schema(description = "开户行")
    private String bankName;
    @Schema(description = "银行账号")
    private String bankAccount;
    @Schema(description = "银行行号")
    private String bankNo;
    @Schema(description = "开户行地址")
    private String bankAddress;
    @Schema(description = "是否默认账户")
    private Boolean isDefault;
    @Schema(description = "排序")
    private Integer sort;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierBankSaveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.srm.controller.admin.supplier.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
@Schema(description = "管理后台 - ä¾›åº”商银行账户保存 Request VO")
@Data
public class SrmSupplierBankSaveReqVO {
    @Schema(description = "银行账户ID")
    private Long id;
    @Schema(description = "供应商ID")
    private Long supplierId;
    @Schema(description = "开户行", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "开户行不能为空")
    private String bankName;
    @Schema(description = "银行账号", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotEmpty(message = "银行账号不能为空")
    private String bankAccount;
    @Schema(description = "银行行号")
    private String bankNo;
    @Schema(description = "开户行地址")
    private String bankAddress;
    @Schema(description = "是否默认账户")
    private Boolean isDefault;
    @Schema(description = "排序")
    private Integer sort;
    @Schema(description = "备注")
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierPageReqVO.java
@@ -30,10 +30,4 @@
    @Schema(description = "联系电话")
    private String contactMobile;
    @Schema(description = "仅显示已通过准入审批的供应商")
    private Boolean approved;
    @Schema(description = "排除已通过准入审批的供应商(用于新增准入时避免重复选择)")
    private Boolean excludeApproved;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierRespVO.java
@@ -5,6 +5,7 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - ä¾›åº”商 Response VO")
@Data
@@ -70,4 +71,10 @@
    @Schema(description = "创建时间")
    private LocalDateTime createTime;
    @Schema(description = "联系人列表")
    private List<SrmSupplierContactRespVO> contacts;
    @Schema(description = "银行账户列表")
    private List<SrmSupplierBankRespVO> banks;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/vo/SrmSupplierSaveReqVO.java
@@ -6,6 +6,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Schema(description = "管理后台 - ä¾›åº”商创建/更新 Request VO")
@Data
@@ -71,4 +72,10 @@
    @Schema(description = "备注")
    private String remark;
    @Schema(description = "联系人列表(传 null è¡¨ç¤ºä¸ä¿®æ”¹ï¼‰")
    private List<SrmSupplierContactSaveReqVO> contacts;
    @Schema(description = "银行账户列表(传 null è¡¨ç¤ºä¸ä¿®æ”¹ï¼‰")
    private List<SrmSupplierBankSaveReqVO> banks;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidAwardController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmBidEvaluationController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderBidController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderConfigController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidAwardSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidEvaluationSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmBidOpenRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmSupplierQuoteSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderBidSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderConfigRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderConfigSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderConfirmReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderMaterialSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPageReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPartialUpdateReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidAwardDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidBondDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
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.LocalDate;
/**
 * SRM æŠ•标保证金台账 DO
 */
@TableName("srm_bid_bond")
@KeySequence("srm_bid_bond_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmBidBondDO extends BaseDO {
    @TableId
    private Long id;
    /**
     * ä¿è¯é‡‘单号
     */
    private String no;
    private Long supplierId;
    /**
     * é¡¹ç›®åç§°
     */
    private String projectName;
    private Long inquiryId;
    private Long purchaseOrderId;
    /**
     * ä¿è¯é‡‘金额
     */
    private BigDecimal bondAmount;
    /**
     * ç¼´çº³æ—¥æœŸ
     */
    private LocalDate payDate;
    /**
     * æ˜¯å¦ä¸­æ ‡
     */
    private Boolean won;
    /**
     * é€€å›žçŠ¶æ€ï¼š0 æœªé€€å›žã€1 éƒ¨åˆ†é€€å›žã€2 å·²é€€å›žã€3 æ— éœ€é€€å›ž
     */
    private Integer refundStatus;
    private BigDecimal refundAmount;
    private LocalDate refundDate;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidEvaluationDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmBidOpenDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
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.time.LocalDate;
/**
 * SRM è¯¢ä»·æ¯”价单 DO
 */
@TableName("srm_inquiry")
@KeySequence("srm_inquiry_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmInquiryDO extends BaseDO {
    @TableId
    private Long id;
    /**
     * è¯¢ä»·å•号
     */
    private String no;
    /**
     * è¯¢ä»·æ ‡é¢˜
     */
    private String title;
    /**
     * çŠ¶æ€ï¼š0 è‰ç¨¿ã€10 è¯¢ä»·ä¸­ã€20 å·²è½¬è®¢å•、30 å·²å–消
     */
    private Integer status;
    /**
     * è¯¢ä»·æ—¥æœŸ
     */
    private LocalDate inquiryDate;
    /**
     * æŠ¥ä»·æˆªæ­¢æ—¥æœŸ
     */
    private LocalDate deadline;
    /**
     * é‡‡è´­å‘˜ç”¨æˆ·ç¼–号
     */
    private Long purchaserUserId;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryItemDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
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;
/**
 * SRM è¯¢ä»·äº§å“è¡Œ DO
 */
@TableName("srm_inquiry_item")
@KeySequence("srm_inquiry_item_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmInquiryItemDO extends BaseDO {
    @TableId
    private Long id;
    private Long inquiryId;
    private Long productId;
    private Long productUnitId;
    private String productCode;
    private String productName;
    private String productSpec;
    private String unitName;
    /**
     * é‡‡è´­æ•°é‡
     */
    private BigDecimal quantity;
    /**
     * ç›®æ ‡å•ä»·
     */
    private BigDecimal estimatedPrice;
    /**
     * é€‰ä¸­çš„中标供应商
     */
    private Long selectedSupplierId;
    /**
     * é€‰ä¸­çš„中标报价
     */
    private Long selectedQuoteId;
    /**
     * é€‰ä¸­çš„中标单价
     */
    private BigDecimal selectedPrice;
    private String remark;
    private Integer sort;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryOrderDO.java
ÎļþÃû´Ó yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderConfigDO.java ÐÞ¸Ä
@@ -6,19 +6,25 @@
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@TableName("srm_tender_config")
@KeySequence("srm_tender_config_seq")
/**
 * SRM è¯¢ä»·è½¬é‡‡è´­è®¢å•记录 DO
 */
@TableName("srm_inquiry_order")
@KeySequence("srm_inquiry_order_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmTenderConfigDO extends BaseDO {
public class SrmInquiryOrderDO extends BaseDO {
    @TableId
    private Long id;
    private String defaultBiddingMode;
    private Long inquiryId;
    private Long supplierId;
    private Long purchaseOrderId;
    private String purchaseOrderNo;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquiryQuoteDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
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;
/**
 * SRM è¯¢ä»·äº§å“æŠ¥ä»· DO
 */
@TableName("srm_inquiry_quote")
@KeySequence("srm_inquiry_quote_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmInquiryQuoteDO extends BaseDO {
    @TableId
    private Long id;
    private Long inquiryId;
    private Long inquiryItemId;
    private Long supplierId;
    /**
     * æŠ¥ä»·å•ä»·
     */
    private BigDecimal price;
    /**
     * ç¨ŽçŽ‡ï¼Œç™¾åˆ†æ¯”
     */
    private BigDecimal taxRate;
    /**
     * äº¤è´§å‘¨æœŸï¼Œå¤©
     */
    private Integer deliveryCycle;
    /**
     * ä»˜æ¬¾æ¡ä»¶
     */
    private String paymentTerms;
    private String remark;
    /**
     * æ˜¯å¦è¯¥äº§å“çš„中标报价
     */
    private Boolean isSelected;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmInquirySupplierDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
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.time.LocalDateTime;
/**
 * SRM è¯¢ä»·å‚与供应商 DO
 */
@TableName("srm_inquiry_supplier")
@KeySequence("srm_inquiry_supplier_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmInquirySupplierDO extends BaseDO {
    @TableId
    private Long id;
    private Long inquiryId;
    private Long supplierId;
    /**
     * æŠ¥ä»·çŠ¶æ€ï¼š0 æœªæŠ¥ä»·ã€10 å·²æŠ¥ä»·
     */
    private Integer quoteStatus;
    private LocalDateTime quoteTime;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierBankDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.srm.dal.dataobject;
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.*;
/**
 * ä¾›åº”商银行账户 DO
 */
@TableName("srm_supplier_bank")
@KeySequence("srm_supplier_bank_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SrmSupplierBankDO extends BaseDO {
    @TableId
    private Long id;
    private Long supplierId;
    /**
     * å¼€æˆ·è¡Œ
     */
    private String bankName;
    /**
     * é“¶è¡Œè´¦å·
     */
    private String bankAccount;
    /**
     * é“¶è¡Œè¡Œå·
     */
    private String bankNo;
    /**
     * å¼€æˆ·è¡Œåœ°å€
     */
    private String bankAddress;
    private Boolean isDefault;
    private Integer sort;
    private String remark;
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierCertificateDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierEvaluationDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierQuoteDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderBidDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderMaterialDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmTenderProjectDO.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidAwardMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidBondMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
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.srm.controller.admin.bond.vo.SrmBidBondPageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidBondDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface SrmBidBondMapper extends BaseMapperX<SrmBidBondDO> {
    default PageResult<SrmBidBondDO> selectPage(SrmBidBondPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SrmBidBondDO>()
                .likeIfPresent(SrmBidBondDO::getNo, reqVO.getNo())
                .eqIfPresent(SrmBidBondDO::getSupplierId, reqVO.getSupplierId())
                .likeIfPresent(SrmBidBondDO::getProjectName, reqVO.getProjectName())
                .eqIfPresent(SrmBidBondDO::getWon, reqVO.getWon())
                .eqIfPresent(SrmBidBondDO::getRefundStatus, reqVO.getRefundStatus())
                .geIfPresent(SrmBidBondDO::getPayDate, reqVO.getPayDateStart())
                .leIfPresent(SrmBidBondDO::getPayDate, reqVO.getPayDateEnd())
                .orderByDesc(SrmBidBondDO::getId));
    }
    default SrmBidBondDO selectByNo(String no) {
        return selectOne(SrmBidBondDO::getNo, no);
    }
    default long selectCountBySupplierId(Long supplierId) {
        return selectCount(SrmBidBondDO::getSupplierId, supplierId);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidEvaluationMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmBidOpenMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryItemMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryItemDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmInquiryItemMapper extends BaseMapperX<SrmInquiryItemDO> {
    default List<SrmInquiryItemDO> selectListByInquiryId(Long inquiryId) {
        return selectList(new LambdaQueryWrapperX<SrmInquiryItemDO>()
                .eq(SrmInquiryItemDO::getInquiryId, inquiryId)
                .orderByAsc(SrmInquiryItemDO::getSort)
                .orderByAsc(SrmInquiryItemDO::getId));
    }
    default void deleteByInquiryId(Long inquiryId) {
        delete(new LambdaQueryWrapperX<SrmInquiryItemDO>()
                .eq(SrmInquiryItemDO::getInquiryId, inquiryId));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
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.srm.controller.admin.inquiry.vo.SrmInquiryPageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmInquiryMapper extends BaseMapperX<SrmInquiryDO> {
    default PageResult<SrmInquiryDO> selectPage(SrmInquiryPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<SrmInquiryDO>()
                .likeIfPresent(SrmInquiryDO::getNo, reqVO.getNo())
                .likeIfPresent(SrmInquiryDO::getTitle, reqVO.getTitle())
                .eqIfPresent(SrmInquiryDO::getStatus, reqVO.getStatus())
                .geIfPresent(SrmInquiryDO::getInquiryDate, reqVO.getInquiryDateStart())
                .leIfPresent(SrmInquiryDO::getInquiryDate, reqVO.getInquiryDateEnd())
                .orderByDesc(SrmInquiryDO::getId));
    }
    default SrmInquiryDO selectByNo(String no) {
        return selectOne(SrmInquiryDO::getNo, no);
    }
    default List<SrmInquiryDO> selectListByStatus(Integer status) {
        return selectList(SrmInquiryDO::getStatus, status);
    }
    /**
     * æŒ‰å½“前状态条件更新,避免并发重复确认
     */
    default int updateByIdAndStatus(Long id, Integer whereStatus, SrmInquiryDO updateObj) {
        return update(updateObj, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<SrmInquiryDO>()
                .eq(SrmInquiryDO::getId, id)
                .eq(SrmInquiryDO::getStatus, whereStatus));
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryOrderMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryOrderDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmInquiryOrderMapper extends BaseMapperX<SrmInquiryOrderDO> {
    default List<SrmInquiryOrderDO> selectListByInquiryId(Long inquiryId) {
        return selectList(new LambdaQueryWrapperX<SrmInquiryOrderDO>()
                .eq(SrmInquiryOrderDO::getInquiryId, inquiryId)
                .orderByAsc(SrmInquiryOrderDO::getId));
    }
    default SrmInquiryOrderDO selectByInquiryIdAndSupplierId(Long inquiryId, Long supplierId) {
        return selectOne(new LambdaQueryWrapperX<SrmInquiryOrderDO>()
                .eq(SrmInquiryOrderDO::getInquiryId, inquiryId)
                .eq(SrmInquiryOrderDO::getSupplierId, supplierId));
    }
    default long selectCountBySupplierId(Long supplierId) {
        return selectCount(SrmInquiryOrderDO::getSupplierId, supplierId);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquiryQuoteMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryQuoteDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmInquiryQuoteMapper extends BaseMapperX<SrmInquiryQuoteDO> {
    default List<SrmInquiryQuoteDO> selectListByInquiryId(Long inquiryId) {
        return selectList(new LambdaQueryWrapperX<SrmInquiryQuoteDO>()
                .eq(SrmInquiryQuoteDO::getInquiryId, inquiryId)
                .orderByAsc(SrmInquiryQuoteDO::getInquiryItemId)
                .orderByAsc(SrmInquiryQuoteDO::getId));
    }
    default SrmInquiryQuoteDO selectByItemIdAndSupplierId(Long inquiryItemId, Long supplierId) {
        return selectOne(new LambdaQueryWrapperX<SrmInquiryQuoteDO>()
                .eq(SrmInquiryQuoteDO::getInquiryItemId, inquiryItemId)
                .eq(SrmInquiryQuoteDO::getSupplierId, supplierId));
    }
    default void deleteByInquiryId(Long inquiryId) {
        delete(new LambdaQueryWrapperX<SrmInquiryQuoteDO>()
                .eq(SrmInquiryQuoteDO::getInquiryId, inquiryId));
    }
    default long selectCountBySupplierId(Long supplierId) {
        return selectCount(SrmInquiryQuoteDO::getSupplierId, supplierId);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmInquirySupplierMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquirySupplierDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmInquirySupplierMapper extends BaseMapperX<SrmInquirySupplierDO> {
    default List<SrmInquirySupplierDO> selectListByInquiryId(Long inquiryId) {
        return selectList(new LambdaQueryWrapperX<SrmInquirySupplierDO>()
                .eq(SrmInquirySupplierDO::getInquiryId, inquiryId)
                .orderByAsc(SrmInquirySupplierDO::getId));
    }
    default SrmInquirySupplierDO selectByInquiryIdAndSupplierId(Long inquiryId, Long supplierId) {
        return selectOne(new LambdaQueryWrapperX<SrmInquirySupplierDO>()
                .eq(SrmInquirySupplierDO::getInquiryId, inquiryId)
                .eq(SrmInquirySupplierDO::getSupplierId, supplierId));
    }
    default void deleteByInquiryId(Long inquiryId) {
        delete(new LambdaQueryWrapperX<SrmInquirySupplierDO>()
                .eq(SrmInquirySupplierDO::getInquiryId, inquiryId));
    }
    default long selectCountBySupplierId(Long supplierId) {
        return selectCount(SrmInquirySupplierDO::getSupplierId, supplierId);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierApplyMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierBankMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.srm.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierBankDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface SrmSupplierBankMapper extends BaseMapperX<SrmSupplierBankDO> {
    default List<SrmSupplierBankDO> selectListBySupplierId(Long supplierId) {
        return selectList(new LambdaQueryWrapperX<SrmSupplierBankDO>()
                .eq(SrmSupplierBankDO::getSupplierId, supplierId)
                .orderByAsc(SrmSupplierBankDO::getSort)
                .orderByAsc(SrmSupplierBankDO::getId));
    }
    default void deleteBySupplierId(Long supplierId) {
        delete(new LambdaQueryWrapperX<SrmSupplierBankDO>()
                .eq(SrmSupplierBankDO::getSupplierId, supplierId));
    }
    default long selectCountBySupplierId(Long supplierId) {
        return selectCount(SrmSupplierBankDO::getSupplierId, supplierId);
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierEvaluationMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierMapper.java
@@ -6,30 +6,21 @@
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Collection;
import java.util.List;
@Mapper
public interface SrmSupplierMapper extends BaseMapperX<SrmSupplierDO> {
    default PageResult<SrmSupplierDO> selectPage(SrmSupplierPageReqVO reqVO) {
        LambdaQueryWrapperX<SrmSupplierDO> wrapper = new LambdaQueryWrapperX<SrmSupplierDO>()
        return selectPage(reqVO, new LambdaQueryWrapperX<SrmSupplierDO>()
                .likeIfPresent(SrmSupplierDO::getCode, reqVO.getCode())
                .likeIfPresent(SrmSupplierDO::getName, reqVO.getName())
                .eqIfPresent(SrmSupplierDO::getType, reqVO.getType())
                .eqIfPresent(SrmSupplierDO::getStatus, reqVO.getStatus())
                .likeIfPresent(SrmSupplierDO::getContactName, reqVO.getContactName())
                .likeIfPresent(SrmSupplierDO::getContactMobile, reqVO.getContactMobile())
                .orderByDesc(SrmSupplierDO::getId);
        if (Boolean.TRUE.equals(reqVO.getApproved())) {
            wrapper.apply("id IN (SELECT supplier_id FROM srm_supplier_apply WHERE apply_status = 40 AND supplier_id IS NOT NULL AND deleted = 0)");
        }
        if (Boolean.TRUE.equals(reqVO.getExcludeApproved())) {
            wrapper.apply("id NOT IN (SELECT supplier_id FROM srm_supplier_apply WHERE apply_status = 40 AND supplier_id IS NOT NULL AND deleted = 0)");
        }
        return selectPage(reqVO, wrapper);
                .orderByDesc(SrmSupplierDO::getId));
    }
    default SrmSupplierDO selectByCode(String code) {
@@ -39,10 +30,5 @@
    default List<SrmSupplierDO> selectListByStatus(Integer status) {
        return selectList(SrmSupplierDO::getStatus, status);
    }
    @Select("SELECT DISTINCT s.* FROM srm_supplier s " +
            "INNER JOIN srm_supplier_apply a ON a.supplier_id = s.id " +
            "WHERE a.apply_status = 40 AND s.status = 0 AND s.deleted = 0")
    List<SrmSupplierDO> selectApprovedList();
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierQuoteMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderBidMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderConfigMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderMaterialMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmTenderProjectMapper.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/AwardStatusEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BidBondRefundStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.srm.enums;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
 * æŠ•标保证金退回状态枚举
 */
@RequiredArgsConstructor
@Getter
public enum BidBondRefundStatusEnum implements ArrayValuable<Integer> {
    NOT_REFUNDED(0, "未退回"),
    PARTIAL_REFUNDED(1, "部分退回"),
    REFUNDED(2, "已退回"),
    NO_NEED(3, "无需退回");
    public static final Integer[] ARRAYS = Arrays.stream(values())
            .map(BidBondRefundStatusEnum::getStatus).toArray(Integer[]::new);
    private final Integer status;
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BidStatusEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/BiddingModeEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/CertificateTypeEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/DeliveryStatusEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
@@ -13,67 +13,36 @@
    ErrorCode SUPPLIER_CODE_EXISTS = new ErrorCode(1_040_100_001, "供应商编码已存在");
    ErrorCode SUPPLIER_EXISTS_REFERENCE = new ErrorCode(1_040_100_002, "供应商被业务引用,无法删除");
    ErrorCode SUPPLIER_CONTACT_NOT_EXISTS = new ErrorCode(1_040_100_003, "供应商联系人不存在");
    ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_040_100_004, "供应商已停用,无法参与投标");
    ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_040_100_004, "供应商已停用,无法参与询价/采购");
    ErrorCode SUPPLIER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_040_100_005, "导入的供应商数据不能为空");
    ErrorCode SUPPLIER_CONTACT_NAME_REQUIRED = new ErrorCode(1_040_100_006, "供应商联系人姓名不能为空");
    ErrorCode SUPPLIER_BANK_INFO_INCOMPLETE = new ErrorCode(1_040_100_007, "供应商银行账户的开户行或银行账号不能为空");
    // ========== ä¾›åº”商准入 1_040_200_000 ==========
    ErrorCode APPLY_NOT_EXISTS = new ErrorCode(1_040_200_000, "准入申请不存在");
    ErrorCode APPLY_STATUS_NOT_DRAFT = new ErrorCode(1_040_200_001, "当前状态不允许提交");
    ErrorCode APPLY_STATUS_NOT_PENDING = new ErrorCode(1_040_200_002, "当前状态不允许审核");
    ErrorCode APPLY_SUPPLIER_EXISTS = new ErrorCode(1_040_200_003, "该供应商已存在准入记录");
    ErrorCode APPLY_SUPPLIER_NAME_REQUIRED = new ErrorCode(1_040_200_004, "未选择SRM供应商时,供应商名称不能为空");
    ErrorCode APPLY_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_040_200_005, "供应商准入审批分类不存在,请先在BPM模块创建供应商准入审批流程分类");
    ErrorCode APPLY_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_040_200_006, "供应商准入审批分类下没有可用的流程模型,请先创建并发布流程");
    // ========== è¯¢ä»·æ¯”ä»· 1_040_600_000 ==========
    ErrorCode INQUIRY_NOT_EXISTS = new ErrorCode(1_040_600_000, "询价单不存在");
    ErrorCode INQUIRY_STATUS_NOT_EDITABLE = new ErrorCode(1_040_600_001, "询价单当前状态不允许编辑");
    ErrorCode INQUIRY_STATUS_NOT_QUOTING = new ErrorCode(1_040_600_002, "询价单当前状态不允许录入报价");
    ErrorCode INQUIRY_STATUS_NOT_CONFIRMABLE = new ErrorCode(1_040_600_003, "询价单当前状态不允许确认转订单");
    ErrorCode INQUIRY_STATUS_NOT_CANCELLABLE = new ErrorCode(1_040_600_004, "询价单当前状态不允许取消");
    ErrorCode INQUIRY_ITEM_EMPTY = new ErrorCode(1_040_600_005, "询价单没有产品行,无法保存");
    ErrorCode INQUIRY_SUPPLIER_EMPTY = new ErrorCode(1_040_600_006, "询价单没有参与供应商,无法保存");
    ErrorCode INQUIRY_SUPPLIER_DUPLICATE = new ErrorCode(1_040_600_007, "询价单存在重复供应商");
    ErrorCode INQUIRY_SUPPLIER_NOT_PARTICIPANT = new ErrorCode(1_040_600_008, "报价供应商未参与该询价单");
    ErrorCode INQUIRY_QUOTE_NOT_EXISTS = new ErrorCode(1_040_600_009, "报价不存在");
    ErrorCode INQUIRY_QUOTE_PRICE_INVALID = new ErrorCode(1_040_600_010, "报价单价必须大于 0");
    ErrorCode INQUIRY_WINNER_REQUIRED = new ErrorCode(1_040_600_011, "产品未选择中标报价");
    ErrorCode INQUIRY_WINNER_DUPLICATE = new ErrorCode(1_040_600_012, "同一产品重复选择中标报价");
    ErrorCode INQUIRY_WINNER_QUOTE_MISMATCH = new ErrorCode(1_040_600_013, "中标报价不属于所选产品");
    ErrorCode INQUIRY_ALREADY_CONFIRMED = new ErrorCode(1_040_600_014, "询价单已被确认转订单,请刷新后查看");
    ErrorCode INQUIRY_ORDER_GENERATE_FAIL = new ErrorCode(1_040_600_015, "询价单生成采购订单失败");
    ErrorCode INQUIRY_NO_GENERATE_FAIL = new ErrorCode(1_040_600_016, "询价单号生成失败");
    // ========== ä¾›åº”商资质 1_040_300_000 ==========
    ErrorCode CERTIFICATE_NOT_EXISTS = new ErrorCode(1_040_300_000, "资质证书不存在");
    ErrorCode CERTIFICATE_EXPIRED = new ErrorCode(1_040_300_001, "资质证书已过期");
    // ========== ä¾›åº”商评价 1_040_400_000 ==========
    ErrorCode EVALUATION_NOT_EXISTS = new ErrorCode(1_040_400_000, "绩效评价不存在");
    ErrorCode EVALUATION_PERIOD_EXISTS = new ErrorCode(1_040_400_001, "该评价周期已存在评价记录");
    // ========== æ‹›æŠ•标管理 1_040_500_000 ==========
    ErrorCode TENDER_NOT_EXISTS = new ErrorCode(1_040_500_000, "招标项目不存在");
    ErrorCode TENDER_STATUS_NOT_DRAFT = new ErrorCode(1_040_500_001, "当前状态不允许编辑");
    ErrorCode TENDER_STATUS_NOT_PUBLISHED = new ErrorCode(1_040_500_002, "当前状态不允许投标");
    ErrorCode TENDER_STATUS_NOT_BIDDING = new ErrorCode(1_040_500_009, "当前状态不允许评标");
    ErrorCode TENDER_STATUS_ALREADY_CLOSED = new ErrorCode(1_040_500_010, "已关闭的项目不允许操作");
    ErrorCode BID_NOT_EXISTS = new ErrorCode(1_040_500_003, "投标记录不存在");
    ErrorCode BID_SUPPLIER_EXISTS = new ErrorCode(1_040_500_004, "供应商已参与投标");
    ErrorCode BID_NO_EXISTS = new ErrorCode(1_040_500_016, "投标编号已存在");
    ErrorCode BID_PAST_END_TIME = new ErrorCode(1_040_500_005, "投标已截止");
    ErrorCode BID_SUPPLIER_NOT_APPROVED = new ErrorCode(1_040_500_011, "供应商未通过准入审批,无法参与投标");
    ErrorCode BID_WITHDRAWN_CANNOT_QUOTE = new ErrorCode(1_040_500_012, "投标已撤标,不允许报价");
    ErrorCode EVALUATION_NOT_EXISTS_TENDER = new ErrorCode(1_040_500_006, "评标记录不存在");
    ErrorCode AWARD_NOT_EXISTS = new ErrorCode(1_040_500_007, "定标记录不存在");
    ErrorCode AWARD_SUPPLIER_EXISTS = new ErrorCode(1_040_500_008, "已存在定标记录");
    ErrorCode AWARD_STATUS_NOT_AWARDED = new ErrorCode(1_040_500_013, "仅已定标状态可生成采购订单");
    ErrorCode AWARD_SUPPLIER_NOT_SYNCED = new ErrorCode(1_040_500_014, "供应商未同步至ERP,无法生成采购订单");
    ErrorCode AWARD_QUOTE_NOT_EXISTS = new ErrorCode(1_040_500_015, "中标供应商报价不存在");
    // çŠ¶æ€æ ¡éªŒç²¾ç»†åŒ–é”™è¯¯ç 
    ErrorCode TENDER_MATERIAL_LOCKED = new ErrorCode(1_040_500_022, "物料清单已锁定,不允许增删改");
    ErrorCode TENDER_BID_FORBIDDEN = new ErrorCode(1_040_500_023, "当前状态不允许新增投标");
    ErrorCode TENDER_QUOTE_FORBIDDEN = new ErrorCode(1_040_500_024, "当前状态不允许报价操作");
    ErrorCode TENDER_END_TIME_PASSED = new ErrorCode(1_040_500_025, "投标截止时间已过,不允许新增投标");
    ErrorCode TENDER_PROJECT_LOCKED = new ErrorCode(1_040_500_026, "项目已锁定,当前状态不允许操作");
    ErrorCode TENDER_HAS_BIDS_CANNOT_DELETE = new ErrorCode(1_040_500_027, "已存在投标数据,不允许删除");
    ErrorCode TENDER_BID_FORBIDDEN_WITHDRAW = new ErrorCode(1_040_500_028, "当前状态不允许撤标");
    ErrorCode TENDER_EVALUATION_FORBIDDEN = new ErrorCode(1_040_500_029, "当前状态不允许评标操作");
    ErrorCode TENDER_AWARD_FORBIDDEN = new ErrorCode(1_040_500_030, "当前状态不允许定标操作");
    ErrorCode AWARD_BID_NOT_IN_PROJECT = new ErrorCode(1_040_500_031, "投标记录不属于当前招标项目");
    ErrorCode AWARD_BID_WITHDRAWN = new ErrorCode(1_040_500_032, "投标已撤标,不允许定标");
    ErrorCode AWARD_SUPPLIER_NOT_MATCH_BID = new ErrorCode(1_040_500_033, "供应商与投标记录不一致");
    // ç®€æ˜“模式
    ErrorCode TENDER_MODE_NOT_SIMPLE = new ErrorCode(1_040_500_034, "当前项目非简易模式,不允许此操作");
    ErrorCode TENDER_STATUS_NOT_PRICE_COMPARING = new ErrorCode(1_040_500_035, "当前状态非比价中,不允许确认");
    ErrorCode TENDER_CONFIRM_BID_REQUIRED = new ErrorCode(1_040_500_036, "简易模式确认时必须指定中标投标");
    ErrorCode TENDER_NO_MATERIALS = new ErrorCode(1_040_500_037, "招标项目没有添加物料,无法开始比价");
    ErrorCode TENDER_NO_BIDS = new ErrorCode(1_040_500_038, "招标项目没有供应商投标,无法开始比价");
    // ========== é€šç”¨ ==========
    ErrorCode QUOTE_MATERIAL_DUPLICATE = new ErrorCode(1_040_900_001, "该投标已存在相同物料的报价");
    // ========== æŠ•标保证金 1_040_700_000 ==========
    ErrorCode BOND_NOT_EXISTS = new ErrorCode(1_040_700_000, "投标保证金记录不存在");
    ErrorCode BOND_NO_EXISTS = new ErrorCode(1_040_700_001, "投标保证金单号已存在");
    ErrorCode BOND_AMOUNT_INVALID = new ErrorCode(1_040_700_002, "保证金金额不能小于 0");
    ErrorCode BOND_REFUND_AMOUNT_INVALID = new ErrorCode(1_040_700_003, "退回金额必须大于 0 ä¸”不能超过保证金金额");
    ErrorCode BOND_REFUND_DATE_REQUIRED = new ErrorCode(1_040_700_004, "退回状态为已退回时,退回日期不能为空");
    ErrorCode BOND_SUPPLIER_MISMATCH = new ErrorCode(1_040_700_005, "关联采购订单的供应商与保证金供应商不一致");
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/InquiryStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.srm.enums;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
/**
 * SRM è¯¢ä»·å•状态枚举
 */
@RequiredArgsConstructor
@Getter
public enum InquiryStatusEnum implements ArrayValuable<Integer> {
    DRAFT(0, "草稿"),
    QUOTING(10, "询价中"),
    CONFIRMED(20, "已转订单"),
    CANCELLED(30, "已取消");
    public static final Integer[] ARRAYS = Arrays.stream(values())
            .map(InquiryStatusEnum::getStatus).toArray(Integer[]::new);
    private final Integer status;
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/LogRecordConstants.java
@@ -5,46 +5,21 @@
 */
public interface LogRecordConstants {
    // ======================= SRM_TENDER æ‹›æ ‡é¡¹ç›® =======================
    String LOG_TENDER_TYPE = "SRM æ‹›æ ‡é¡¹ç›®";
    String LOG_TENDER_UPDATE_SUB_TYPE = "更新招标项目";
    String LOG_TENDER_UPDATE_SUCCESS = "更新了招标项目【{{#tenderName}}】,"
            + "招标说明:{{#oldTenderDesc}} â†’ {{#newTenderDesc}},"
            + "资质要求:{{#oldQualification}} â†’ {{#newQualification}}";
    // ======================= SRM_BID æŠ•æ ‡ =======================
    String LOG_BID_TYPE = "SRM æŠ•æ ‡";
    String LOG_BID_CREATE_SUB_TYPE = "创建投标";
    String LOG_BID_CREATE_SUCCESS = "供应商对招标项目【{{#tenderName}}】提交了投标【{{#bidNo}}】";
    String LOG_BID_WITHDRAW_SUB_TYPE = "撤标";
    String LOG_BID_WITHDRAW_SUCCESS = "撤回了投标【{{#bidNo}}】";
    // ======================= SRM_QUOTE æŠ¥ä»· =======================
    String LOG_QUOTE_TYPE = "SRM æŠ¥ä»·";
    String LOG_QUOTE_CREATE_SUB_TYPE = "创建报价";
    String LOG_QUOTE_CREATE_SUCCESS = "提交了投标的供应商报价";
    String LOG_QUOTE_UPDATE_SUB_TYPE = "更新报价";
    String LOG_QUOTE_UPDATE_SUCCESS = "更新了投标的供应商报价";
    // ======================= SRM_EVALUATION è¯„æ ‡ =======================
    String LOG_EVALUATION_TYPE = "SRM è¯„æ ‡";
    String LOG_EVALUATION_CREATE_SUB_TYPE = "创建评标";
    String LOG_EVALUATION_CREATE_SUCCESS = "对招标项目进行了评标评分";
    String LOG_EVALUATION_UPDATE_SUB_TYPE = "更新评标";
    String LOG_EVALUATION_UPDATE_SUCCESS = "更新了评标记录";
    // ======================= SRM_AWARD å®šæ ‡ =======================
    String LOG_AWARD_TYPE = "SRM å®šæ ‡";
    String LOG_AWARD_CREATE_SUB_TYPE = "创建定标";
    String LOG_AWARD_CREATE_SUCCESS = "创建了定标【{{#createReqVO.awardNo}}】";
    String LOG_AWARD_APPROVE_SUB_TYPE = "审批定标";
    String LOG_AWARD_APPROVE_SUCCESS = "审批通过了定标记录";
    String LOG_AWARD_ORDER_SUB_TYPE = "生成采购订单";
    String LOG_AWARD_ORDER_SUCCESS = "定标记录生成了采购订单";
    // ======================= SRM_SUPPLIER ä¾›åº”商 =======================
    String LOG_SUPPLIER_TYPE = "SRM ä¾›åº”商";
    String LOG_SUPPLIER_IMPORT_SUB_TYPE = "导入供应商";
    String LOG_SUPPLIER_IMPORT_SUCCESS = "导入了供应商:{{#importSummary}}";
    // ======================= SRM_INQUIRY è¯¢ä»·æ¯”ä»· =======================
    String LOG_INQUIRY_TYPE = "SRM è¯¢ä»·æ¯”ä»·";
    String LOG_INQUIRY_CREATE_SUB_TYPE = "创建询价单";
    String LOG_INQUIRY_CREATE_SUCCESS = "创建了询价单【{{#no}}】";
    String LOG_INQUIRY_CONFIRM_SUB_TYPE = "确认询价转订单";
    String LOG_INQUIRY_CONFIRM_SUCCESS = "确认询价单【{{#no}}】并生成采购订单";
    // ======================= SRM_BID_BOND æŠ•标保证金 =======================
    String LOG_BID_BOND_TYPE = "SRM æŠ•标保证金";
    String LOG_BID_BOND_CREATE_SUB_TYPE = "创建保证金记录";
    String LOG_BID_BOND_CREATE_SUCCESS = "创建了保证金记录【{{#no}}】";
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/SupplierApplyStatusEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderTypeEnum.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/listener/SrmSupplierApplyStatusListener.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/bond/SrmBidBondService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.srm.service.bond;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondRespVO;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidBondDO;
import jakarta.validation.Valid;
/**
 * SRM æŠ•标保证金 Service æŽ¥å£
 */
public interface SrmBidBondService {
    Long createBidBond(@Valid SrmBidBondSaveReqVO createReqVO);
    void updateBidBond(@Valid SrmBidBondSaveReqVO updateReqVO);
    void deleteBidBond(Long id);
    SrmBidBondDO getBidBond(Long id);
    SrmBidBondRespVO getBidBondDetail(Long id);
    PageResult<SrmBidBondRespVO> getBidBondPage(SrmBidBondPageReqVO pageReqVO);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/bond/SrmBidBondServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,242 @@
package cn.iocoder.yudao.module.srm.service.bond;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondRespVO;
import cn.iocoder.yudao.module.srm.controller.admin.bond.vo.SrmBidBondSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmBidBondDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidBondMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquiryMapper;
import cn.iocoder.yudao.module.srm.enums.BidBondRefundStatusEnum;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * SRM æŠ•标保证金 Service å®žçް
 */
@Service
@Validated
@Slf4j
public class SrmBidBondServiceImpl implements SrmBidBondService {
    private static final DateTimeFormatter NO_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMM");
    @Resource
    private SrmBidBondMapper bidBondMapper;
    @Resource
    private SrmInquiryMapper inquiryMapper;
    @Resource
    private SrmSupplierService supplierService;
    @Resource
    private ErpPurchaseOrderApi erpPurchaseOrderApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createBidBond(SrmBidBondSaveReqVO createReqVO) {
        validateBidBond(createReqVO);
        SrmBidBondDO bond = BeanUtils.toBean(createReqVO, SrmBidBondDO.class);
        bond.setId(null);
        bond.setNo(generateBondNo());
        bond.setWon(Boolean.TRUE.equals(createReqVO.getWon()));
        bond.setRefundStatus(resolveRefundStatus(createReqVO));
        if (bond.getRefundStatus() == null) {
            bond.setRefundStatus(BidBondRefundStatusEnum.NOT_REFUNDED.getStatus());
        }
        bidBondMapper.insert(bond);
        return bond.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBidBond(SrmBidBondSaveReqVO updateReqVO) {
        validateBidBondExists(updateReqVO.getId());
        validateBidBond(updateReqVO);
        SrmBidBondDO updateObj = BeanUtils.toBean(updateReqVO, SrmBidBondDO.class);
        updateObj.setNo(null);
        updateObj.setWon(Boolean.TRUE.equals(updateReqVO.getWon()));
        updateObj.setRefundStatus(resolveRefundStatus(updateReqVO));
        bidBondMapper.updateById(updateObj);
    }
    @Override
    public void deleteBidBond(Long id) {
        validateBidBondExists(id);
        bidBondMapper.deleteById(id);
    }
    @Override
    public SrmBidBondDO getBidBond(Long id) {
        return bidBondMapper.selectById(id);
    }
    @Override
    public SrmBidBondRespVO getBidBondDetail(Long id) {
        SrmBidBondDO bond = validateBidBondExists(id);
        return buildRespList(List.of(bond)).get(0);
    }
    @Override
    public PageResult<SrmBidBondRespVO> getBidBondPage(SrmBidBondPageReqVO pageReqVO) {
        PageResult<SrmBidBondDO> page = bidBondMapper.selectPage(pageReqVO);
        return new PageResult<>(buildRespList(page.getList()), page.getTotal());
    }
    // ========== æ ¡éªŒ ==========
    private void validateBidBond(SrmBidBondSaveReqVO reqVO) {
        if (reqVO.getBondAmount() == null || reqVO.getBondAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new ServiceException(ErrorCodeConstants.BOND_AMOUNT_INVALID.getCode(),
                    "保证金金额为【" + reqVO.getBondAmount() + "】,不能小于 0,请填写正确的缴纳金额。");
        }
        SrmSupplierDO supplier = supplierService.getSupplier(reqVO.getSupplierId());
        if (supplier == null) {
            throw new ServiceException(ErrorCodeConstants.SUPPLIER_NOT_EXISTS.getCode(),
                    "供应商ID=" + reqVO.getSupplierId() + " ä¸å­˜åœ¨ï¼Œè¯·åˆ·æ–°ä¾›åº”商列表后重新选择。");
        }
        // å…³è”询价单:校验存在,并在未填项目名称时自动带出询价标题
        if (reqVO.getInquiryId() != null) {
            SrmInquiryDO inquiry = inquiryMapper.selectById(reqVO.getInquiryId());
            if (inquiry == null) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_NOT_EXISTS.getCode(),
                        "关联询价单ID=" + reqVO.getInquiryId() + " ä¸å­˜åœ¨ï¼Œè¯·åˆ·æ–°è¯¢ä»·å•列表后重新选择。");
            }
            if (StrUtil.isBlank(reqVO.getProjectName())) {
                reqVO.setProjectName(inquiry.getTitle());
            }
        }
        // å…³è”采购订单:校验存在且供应商一致
        if (reqVO.getPurchaseOrderId() != null) {
            ErpPurchaseOrderRespDTO order = erpPurchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId())
                    .getCheckedData();
            if (order == null) {
                throw new ServiceException(ErrorCodeConstants.BOND_NOT_EXISTS.getCode(),
                        "关联采购订单ID=" + reqVO.getPurchaseOrderId() + " ä¸å­˜åœ¨ï¼Œè¯·åˆ·æ–°é‡‡è´­è®¢å•列表后重新选择。");
            }
            if (!reqVO.getSupplierId().equals(order.getSupplierId())) {
                throw new ServiceException(ErrorCodeConstants.BOND_SUPPLIER_MISMATCH.getCode(),
                        "关联采购订单【" + order.getNo() + "】的供应商ID=" + order.getSupplierId()
                                + ",与保证金供应商ID=" + reqVO.getSupplierId() + " ä¸ä¸€è‡´ï¼Œè¯·é‡æ–°é€‰æ‹©è®¢å•或供应商。");
            }
            if (reqVO.getWon() == null) {
                reqVO.setWon(true);
            }
        }
        // é€€å›žé‡‘额/日期
        if (reqVO.getRefundAmount() != null) {
            if (reqVO.getRefundAmount().compareTo(BigDecimal.ZERO) <= 0
                    || reqVO.getRefundAmount().compareTo(reqVO.getBondAmount()) > 0) {
                throw new ServiceException(ErrorCodeConstants.BOND_REFUND_AMOUNT_INVALID.getCode(),
                        "退回金额为【" + reqVO.getRefundAmount() + "】,保证金金额为【" + reqVO.getBondAmount()
                                + "】,退回金额必须大于 0 ä¸”不能超过保证金金额,请修改后重新保存。");
            }
        }
        if (BidBondRefundStatusEnum.REFUNDED.getStatus().equals(reqVO.getRefundStatus())) {
            if (reqVO.getRefundDate() == null) {
                throw new ServiceException(ErrorCodeConstants.BOND_REFUND_DATE_REQUIRED.getCode(),
                        "退回状态选择【已退回】时,退回日期不能为空,请填写实际退回日期。");
            }
            if (reqVO.getRefundAmount() == null) {
                throw new ServiceException(ErrorCodeConstants.BOND_REFUND_AMOUNT_INVALID.getCode(),
                        "退回状态选择【已退回】时,退回金额不能为空,请填写实际退回金额。");
            }
        }
    }
    /**
     * æœªæ˜¾å¼é€‰æ‹©é€€å›žçŠ¶æ€æ—¶ï¼ŒæŒ‰æ˜¯å¦ä¸­æ ‡æŽ¨å¯¼é»˜è®¤å€¼
     */
    private Integer resolveRefundStatus(SrmBidBondSaveReqVO reqVO) {
        if (reqVO.getRefundStatus() != null) {
            return reqVO.getRefundStatus();
        }
        if (Boolean.TRUE.equals(reqVO.getWon())) {
            return BidBondRefundStatusEnum.NO_NEED.getStatus();
        }
        return BidBondRefundStatusEnum.NOT_REFUNDED.getStatus();
    }
    private SrmBidBondDO validateBidBondExists(Long id) {
        SrmBidBondDO bond = bidBondMapper.selectById(id);
        if (bond == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BOND_NOT_EXISTS);
        }
        return bond;
    }
    private String generateBondNo() {
        String date = LocalDate.now().format(NO_DATE_FORMAT);
        for (int attempt = 0; attempt < 20; attempt++) {
            int seq = (int) (Math.random() * 9000) + 1000;
            String no = "BZ" + date + String.format("%04d", seq);
            if (bidBondMapper.selectByNo(no) == null) {
                return no;
            }
        }
        throw new ServiceException(ErrorCodeConstants.BOND_NO_EXISTS.getCode(),
                "保证金单号在多次尝试后仍重复,请稍后重试。");
    }
    // ========== ç»„装 ==========
    private List<SrmBidBondRespVO> buildRespList(List<SrmBidBondDO> bonds) {
        if (bonds == null || bonds.isEmpty()) {
            return List.of();
        }
        Set<Long> supplierIds = bonds.stream().map(SrmBidBondDO::getSupplierId).collect(Collectors.toSet());
        Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(supplierIds);
        Set<Long> inquiryIds = bonds.stream().map(SrmBidBondDO::getInquiryId)
                .filter(java.util.Objects::nonNull).collect(Collectors.toSet());
        Map<Long, SrmInquiryDO> inquiryMap = inquiryIds.isEmpty() ? Map.of()
                : inquiryMapper.selectBatchIds(inquiryIds).stream()
                .collect(Collectors.toMap(SrmInquiryDO::getId, item -> item));
        return bonds.stream().map(bond -> {
            SrmBidBondRespVO respVO = BeanUtils.toBean(bond, SrmBidBondRespVO.class);
            SrmSupplierDO supplier = supplierMap.get(bond.getSupplierId());
            if (supplier != null) {
                respVO.setSupplierName(supplier.getName());
            }
            SrmInquiryDO inquiry = inquiryMap.get(bond.getInquiryId());
            if (inquiry != null) {
                respVO.setInquiryNo(inquiry.getNo());
                if (StrUtil.isBlank(respVO.getProjectName())) {
                    respVO.setProjectName(inquiry.getTitle());
                }
            }
            if (bond.getPurchaseOrderId() != null) {
                try {
                    ErpPurchaseOrderRespDTO order = erpPurchaseOrderApi.getPurchaseOrder(bond.getPurchaseOrderId())
                            .getCheckedData();
                    if (order != null) {
                        respVO.setPurchaseOrderNo(order.getNo());
                    }
                } catch (Exception ex) {
                    log.warn("[buildRespList] èŽ·å–é‡‡è´­è®¢å•å·å¤±è´¥ï¼ŒpurchaseOrderId={}", bond.getPurchaseOrderId(), ex);
                }
            }
            return respVO;
        }).collect(Collectors.toList());
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/evaluation/SrmSupplierEvaluationServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/inquiry/SrmInquiryService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package cn.iocoder.yudao.module.srm.service.inquiry;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmInquiryDO;
import jakarta.validation.Valid;
import java.util.List;
/**
 * SRM è¯¢ä»·æ¯”ä»· Service æŽ¥å£
 */
public interface SrmInquiryService {
    Long createInquiry(@Valid SrmInquirySaveReqVO createReqVO);
    void updateInquiry(@Valid SrmInquirySaveReqVO updateReqVO);
    void deleteInquiry(Long id);
    SrmInquiryDO getInquiry(Long id);
    /**
     * èŽ·å–è¯¢ä»·å•è¯¦æƒ…ï¼ˆå«äº§å“è¡Œã€å‚ä¸Žä¾›åº”å•†ã€æŠ¥ä»·ã€å·²ç”Ÿæˆè®¢å•ï¼‰
     */
    SrmInquiryRespVO getInquiryDetail(Long id);
    PageResult<SrmInquiryDO> getInquiryPage(SrmInquiryPageReqVO pageReqVO);
    /**
     * èŽ·å–è¯¢ä»·å•ç²¾ç®€åˆ—è¡¨ï¼ˆç”¨äºŽæŠ•æ ‡ä¿è¯é‡‘ç­‰ä¸‹æ‹‰é€‰æ‹©ï¼‰
     */
    List<SrmInquiryDO> getInquirySimpleList();
    /**
     * å‘布询价单:草稿 -> è¯¢ä»·ä¸­
     */
    void publishInquiry(Long id);
    /**
     * å–消询价单:草稿/询价中 -> å·²å–消
     */
    void cancelInquiry(Long id);
    /**
     * æ‰¹é‡ä¿å­˜æŸä¾›åº”商对询价单各产品行的报价
     */
    void saveQuotes(@Valid SrmInquiryQuoteSaveReqVO saveReqVO);
    /**
     * å¯¹æ¯”视图数据(产品行 Ã— ä¾›åº”商报价)
     */
    SrmInquiryRespVO getCompare(Long id);
    /**
     * ç¡®è®¤ä¸­æ ‡å¹¶ç”Ÿæˆé‡‡è´­è®¢å•草稿
     */
    SrmInquiryConfirmRespVO confirmInquiry(@Valid SrmInquiryConfirmReqVO confirmReqVO);
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/inquiry/SrmInquiryServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,688 @@
package cn.iocoder.yudao.module.srm.service.inquiry;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderCreateReqDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mdm.api.unit.MdmUnitMeasureApi;
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.srm.controller.admin.inquiry.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.*;
import cn.iocoder.yudao.module.srm.dal.mysql.*;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.InquiryStatusEnum;
import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
 * SRM è¯¢ä»·æ¯”ä»· Service å®žçް
 */
@Service
@Validated
@Slf4j
public class SrmInquiryServiceImpl implements SrmInquiryService {
    private static final DateTimeFormatter NO_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMM");
    @Resource
    private SrmInquiryMapper inquiryMapper;
    @Resource
    private SrmInquiryItemMapper inquiryItemMapper;
    @Resource
    private SrmInquirySupplierMapper inquirySupplierMapper;
    @Resource
    private SrmInquiryQuoteMapper inquiryQuoteMapper;
    @Resource
    private SrmInquiryOrderMapper inquiryOrderMapper;
    @Resource
    private SrmSupplierService supplierService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private ErpPurchaseOrderApi erpPurchaseOrderApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createInquiry(SrmInquirySaveReqVO createReqVO) {
        validateInquiryInput(createReqVO);
        SrmInquiryDO inquiry = new SrmInquiryDO();
        inquiry.setNo(generateInquiryNo());
        inquiry.setTitle(createReqVO.getTitle());
        inquiry.setStatus(InquiryStatusEnum.DRAFT.getStatus());
        inquiry.setInquiryDate(createReqVO.getInquiryDate() != null ? createReqVO.getInquiryDate() : LocalDate.now());
        inquiry.setDeadline(createReqVO.getDeadline());
        inquiry.setPurchaserUserId(createReqVO.getPurchaserUserId() != null
                ? createReqVO.getPurchaserUserId() : getLoginUserId());
        inquiry.setRemark(createReqVO.getRemark());
        inquiryMapper.insert(inquiry);
        List<SrmInquiryItemDO> items = saveItems(inquiry.getId(), createReqVO.getItems());
        saveSuppliers(inquiry.getId(), createReqVO.getSuppliers());
        saveItemQuotes(inquiry.getId(), items, createReqVO.getItems(),
                createReqVO.getSuppliers().stream()
                        .map(SrmInquirySaveReqVO.Supplier::getSupplierId)
                        .collect(Collectors.toSet()));
        return inquiry.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateInquiry(SrmInquirySaveReqVO updateReqVO) {
        SrmInquiryDO inquiry = validateInquiryExists(updateReqVO.getId());
        if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())) {
            throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_EDITABLE.getCode(),
                    "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus())
                            + "】,仅草稿状态可编辑。如需调整产品行或供应商,请取消后重新创建询价单。");
        }
        validateInquiryInput(updateReqVO);
        SrmInquiryDO updateObj = new SrmInquiryDO();
        updateObj.setId(inquiry.getId());
        updateObj.setTitle(updateReqVO.getTitle());
        updateObj.setInquiryDate(updateReqVO.getInquiryDate());
        updateObj.setDeadline(updateReqVO.getDeadline());
        updateObj.setPurchaserUserId(updateReqVO.getPurchaserUserId());
        updateObj.setRemark(updateReqVO.getRemark());
        inquiryMapper.updateById(updateObj);
        // è‰ç¨¿é˜¶æ®µå…è®¸æ•´ä½“替换产品行、供应商与报价
        inquiryQuoteMapper.deleteByInquiryId(inquiry.getId());
        inquiryItemMapper.deleteByInquiryId(inquiry.getId());
        inquirySupplierMapper.deleteByInquiryId(inquiry.getId());
        List<SrmInquiryItemDO> items = saveItems(inquiry.getId(), updateReqVO.getItems());
        saveSuppliers(inquiry.getId(), updateReqVO.getSuppliers());
        saveItemQuotes(inquiry.getId(), items, updateReqVO.getItems(),
                updateReqVO.getSuppliers().stream()
                        .map(SrmInquirySaveReqVO.Supplier::getSupplierId)
                        .collect(Collectors.toSet()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteInquiry(Long id) {
        SrmInquiryDO inquiry = validateInquiryExists(id);
        if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())) {
            throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_EDITABLE.getCode(),
                    "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus())
                            + "】,仅草稿状态可删除。已发布或已转订单的询价单请改为取消。");
        }
        inquiryItemMapper.deleteByInquiryId(id);
        inquirySupplierMapper.deleteByInquiryId(id);
        inquiryQuoteMapper.deleteByInquiryId(id);
        inquiryMapper.deleteById(id);
    }
    @Override
    public SrmInquiryDO getInquiry(Long id) {
        return inquiryMapper.selectById(id);
    }
    @Override
    public SrmInquiryRespVO getInquiryDetail(Long id) {
        SrmInquiryDO inquiry = validateInquiryExists(id);
        return buildInquiryResp(inquiry);
    }
    @Override
    public PageResult<SrmInquiryDO> getInquiryPage(SrmInquiryPageReqVO pageReqVO) {
        return inquiryMapper.selectPage(pageReqVO);
    }
    @Override
    public List<SrmInquiryDO> getInquirySimpleList() {
        return inquiryMapper.selectList(null).stream()
                .filter(item -> !InquiryStatusEnum.CANCELLED.getStatus().equals(item.getStatus()))
                .sorted(Comparator.comparing(SrmInquiryDO::getId).reversed())
                .collect(Collectors.toList());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void publishInquiry(Long id) {
        SrmInquiryDO inquiry = validateInquiryExists(id);
        if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())) {
            throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_QUOTING.getCode(),
                    "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus())
                            + "】,仅草稿状态可发布。");
        }
        inquiryMapper.updateByIdAndStatus(id, InquiryStatusEnum.DRAFT.getStatus(),
                new SrmInquiryDO().setStatus(InquiryStatusEnum.QUOTING.getStatus()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancelInquiry(Long id) {
        SrmInquiryDO inquiry = validateInquiryExists(id);
        if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())
                && !InquiryStatusEnum.QUOTING.getStatus().equals(inquiry.getStatus())) {
            throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_CANCELLABLE.getCode(),
                    "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus())
                            + "】,仅草稿或询价中状态可取消。");
        }
        inquiryMapper.updateById(new SrmInquiryDO()
                .setId(id).setStatus(InquiryStatusEnum.CANCELLED.getStatus()));
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveQuotes(SrmInquiryQuoteSaveReqVO saveReqVO) {
        SrmInquiryDO inquiry = validateInquiryExists(saveReqVO.getInquiryId());
        if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())
                && !InquiryStatusEnum.QUOTING.getStatus().equals(inquiry.getStatus())) {
            throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_QUOTING.getCode(),
                    "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus())
                            + "】,仅草稿或询价中状态可录入报价。");
        }
        List<SrmInquiryItemDO> items = inquiryItemMapper.selectListByInquiryId(inquiry.getId());
        Map<Long, SrmInquiryItemDO> itemMap = items.stream()
                .collect(Collectors.toMap(SrmInquiryItemDO::getId, Function.identity()));
        List<SrmInquirySupplierDO> participants = inquirySupplierMapper.selectListByInquiryId(inquiry.getId());
        Set<Long> participantIds = participants.stream()
                .map(SrmInquirySupplierDO::getSupplierId).collect(Collectors.toSet());
        Set<Long> touchedSupplierIds = new HashSet<>();
        for (SrmInquiryQuoteSaveReqVO.Quote quoteVO : saveReqVO.getQuotes()) {
            SrmInquiryItemDO item = itemMap.get(quoteVO.getInquiryItemId());
            if (item == null) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_NOT_EXISTS.getCode(),
                        "询价产品行ID=" + quoteVO.getInquiryItemId() + " ä¸å±žäºŽè¯¢ä»·å•【" + inquiry.getNo()
                                + "】,请刷新比价页面后重新录入报价。");
            }
            if (!participantIds.contains(quoteVO.getSupplierId())) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_NOT_PARTICIPANT.getCode(),
                        "供应商ID=" + quoteVO.getSupplierId() + " æœªå‚与询价单【" + inquiry.getNo()
                                + "】,请先在询价单中添加该供应商。");
            }
            SrmInquiryQuoteDO existing = inquiryQuoteMapper.selectByItemIdAndSupplierId(
                    quoteVO.getInquiryItemId(), quoteVO.getSupplierId());
            if (quoteVO.getPrice() == null) {
                // æ¸…空报价
                if (existing != null) {
                    inquiryQuoteMapper.deleteById(existing.getId());
                    touchedSupplierIds.add(quoteVO.getSupplierId());
                }
                continue;
            }
            if (quoteVO.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_PRICE_INVALID.getCode(),
                        "产品【" + item.getProductName() + "】供应商ID=" + quoteVO.getSupplierId()
                                + " çš„æŠ¥ä»·å•价为【" + quoteVO.getPrice() + "】,必须大于 0,请修改后重新保存。");
            }
            SrmInquiryQuoteDO quote = BeanUtils.toBean(quoteVO, SrmInquiryQuoteDO.class);
            quote.setInquiryId(inquiry.getId());
            quote.setIsSelected(existing != null && Boolean.TRUE.equals(existing.getIsSelected()));
            if (existing != null) {
                quote.setId(existing.getId());
                inquiryQuoteMapper.updateById(quote);
            } else {
                inquiryQuoteMapper.insert(quote);
            }
            touchedSupplierIds.add(quoteVO.getSupplierId());
        }
        // åˆ·æ–°å‚与供应商报价状态
        LocalDateTime now = LocalDateTime.now();
        for (Long supplierId : touchedSupplierIds) {
            SrmInquirySupplierDO participant = inquirySupplierMapper
                    .selectByInquiryIdAndSupplierId(inquiry.getId(), supplierId);
            if (participant == null) {
                continue;
            }
            Long quoteCount = inquiryQuoteMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<SrmInquiryQuoteDO>()
                    .eq(SrmInquiryQuoteDO::getInquiryId, inquiry.getId())
                    .eq(SrmInquiryQuoteDO::getSupplierId, supplierId));
            SrmInquirySupplierDO updateObj = new SrmInquirySupplierDO();
            updateObj.setId(participant.getId());
            updateObj.setQuoteStatus(quoteCount != null && quoteCount > 0 ? 10 : 0);
            updateObj.setQuoteTime(now);
            inquirySupplierMapper.updateById(updateObj);
        }
    }
    @Override
    public SrmInquiryRespVO getCompare(Long id) {
        return getInquiryDetail(id);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public SrmInquiryConfirmRespVO confirmInquiry(SrmInquiryConfirmReqVO confirmReqVO) {
        SrmInquiryDO inquiry = validateInquiryExists(confirmReqVO.getId());
        if (!InquiryStatusEnum.DRAFT.getStatus().equals(inquiry.getStatus())
                && !InquiryStatusEnum.QUOTING.getStatus().equals(inquiry.getStatus())) {
            throw new ServiceException(ErrorCodeConstants.INQUIRY_STATUS_NOT_CONFIRMABLE.getCode(),
                    "询价单【" + inquiry.getNo() + "】当前状态为【" + statusName(inquiry.getStatus())
                            + "】,仅草稿或询价中状态可确认转订单。");
        }
        List<SrmInquiryItemDO> items = inquiryItemMapper.selectListByInquiryId(inquiry.getId());
        if (CollUtil.isEmpty(items)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_ITEM_EMPTY);
        }
        Map<Long, SrmInquiryItemDO> itemMap = items.stream()
                .collect(Collectors.toMap(SrmInquiryItemDO::getId, Function.identity()));
        Set<Long> participantIds = inquirySupplierMapper.selectListByInquiryId(inquiry.getId()).stream()
                .map(SrmInquirySupplierDO::getSupplierId).collect(Collectors.toSet());
        List<SrmInquiryQuoteDO> allQuotes = inquiryQuoteMapper.selectListByInquiryId(inquiry.getId());
        Map<Long, SrmInquiryQuoteDO> quoteMap = allQuotes.stream()
                .collect(Collectors.toMap(SrmInquiryQuoteDO::getId, Function.identity()));
        // 1. æ ¡éªŒæ¯ä¸ªäº§å“æ°å¥½é€‰æ‹©ä¸€æ¡æœ‰æ•ˆæŠ¥ä»·
        //    æœªæ˜¾å¼ä¼  selections æ—¶ï¼Œä½¿ç”¨è¯¢ä»·å•中已保存的中标选择(创建/编辑时一并录入)
        List<SrmInquiryConfirmReqVO.Selection> selections = confirmReqVO.getSelections();
        if (CollUtil.isEmpty(selections)) {
            selections = new ArrayList<>();
            for (SrmInquiryItemDO item : items) {
                if (item.getSelectedQuoteId() != null) {
                    SrmInquiryConfirmReqVO.Selection selection = new SrmInquiryConfirmReqVO.Selection();
                    selection.setInquiryItemId(item.getId());
                    selection.setQuoteId(item.getSelectedQuoteId());
                    selections.add(selection);
                }
            }
            if (CollUtil.isEmpty(selections)) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_REQUIRED.getCode(),
                        "询价单【" + inquiry.getNo() + "】尚未保存任何中标选择,请编辑询价单为每个产品选择中标供应商后再确认。");
            }
        }
        Map<Long, SrmInquiryQuoteDO> selectedByItem = new HashMap<>();
        for (SrmInquiryConfirmReqVO.Selection selection : selections) {
            SrmInquiryItemDO item = itemMap.get(selection.getInquiryItemId());
            if (item == null) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_QUOTE_MISMATCH.getCode(),
                        "询价产品行ID=" + selection.getInquiryItemId() + " ä¸å±žäºŽè¯¢ä»·å•【" + inquiry.getNo()
                                + "】,请刷新比价页面后重新选择中标报价。");
            }
            if (selectedByItem.containsKey(item.getId())) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_DUPLICATE.getCode(),
                        "产品【" + item.getProductName() + "】选择了多条中标报价,请为每个产品只保留一条中标报价。");
            }
            SrmInquiryQuoteDO quote = quoteMap.get(selection.getQuoteId());
            if (quote == null) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_NOT_EXISTS.getCode(),
                        "中标报价ID=" + selection.getQuoteId() + " ä¸å­˜åœ¨ï¼Œè¯·åˆ·æ–°æ¯”价页面后重新选择。");
            }
            if (!quote.getInquiryItemId().equals(item.getId())) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_QUOTE_MISMATCH.getCode(),
                        "产品【" + item.getProductName() + "】选择的中标报价属于产品行ID=" + quote.getInquiryItemId()
                                + ",请重新选择该产品自己的报价。");
            }
            if (!participantIds.contains(quote.getSupplierId())) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_NOT_PARTICIPANT.getCode(),
                        "供应商ID=" + quote.getSupplierId() + " æœªå‚与询价单【" + inquiry.getNo()
                                + "】,无法作为中标供应商。");
            }
            if (quote.getPrice() == null || quote.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_PRICE_INVALID.getCode(),
                        "产品【" + item.getProductName() + "】的中标报价单价为空或小于等于 0,请先录入报价后再确认。");
            }
            selectedByItem.put(item.getId(), quote);
        }
        for (SrmInquiryItemDO item : items) {
            if (!selectedByItem.containsKey(item.getId())) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_REQUIRED.getCode(),
                        "产品【" + StrUtil.blankToDefault(item.getProductName(), "ID=" + item.getProductId())
                                + "】尚未选择中标供应商,请为该产品选择一条中标报价后再确认。");
            }
        }
        // 2. è®°å½•中标结果
        for (SrmInquiryQuoteDO quote : allQuotes) {
            if (Boolean.TRUE.equals(quote.getIsSelected())) {
                inquiryQuoteMapper.updateById(new SrmInquiryQuoteDO().setId(quote.getId()).setIsSelected(false));
            }
        }
        for (Map.Entry<Long, SrmInquiryQuoteDO> entry : selectedByItem.entrySet()) {
            SrmInquiryItemDO item = itemMap.get(entry.getKey());
            SrmInquiryQuoteDO quote = entry.getValue();
            inquiryQuoteMapper.updateById(new SrmInquiryQuoteDO().setId(quote.getId()).setIsSelected(true));
            inquiryItemMapper.updateById(new SrmInquiryItemDO()
                    .setId(item.getId())
                    .setSelectedSupplierId(quote.getSupplierId())
                    .setSelectedQuoteId(quote.getId())
                    .setSelectedPrice(quote.getPrice()));
        }
        // 3. æŒ‰ä¸­æ ‡ä¾›åº”商分组生成采购订单草稿
        Map<Long, List<QuoteWithItem>> winnersBySupplier = new LinkedHashMap<>();
        for (Map.Entry<Long, SrmInquiryQuoteDO> entry : selectedByItem.entrySet()) {
            QuoteWithItem pair = new QuoteWithItem(itemMap.get(entry.getKey()), entry.getValue());
            winnersBySupplier.computeIfAbsent(entry.getValue().getSupplierId(), k -> new ArrayList<>()).add(pair);
        }
        Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(winnersBySupplier.keySet());
        List<SrmInquiryConfirmRespVO.Order> orders = new ArrayList<>();
        for (Map.Entry<Long, List<QuoteWithItem>> entry : winnersBySupplier.entrySet()) {
            Long supplierId = entry.getKey();
            SrmSupplierDO supplier = supplierMap.get(supplierId);
            String supplierName = supplier != null ? supplier.getName() : "ID=" + supplierId;
            List<ErpPurchaseOrderCreateReqDTO.Item> orderItems = new ArrayList<>();
            for (QuoteWithItem pair : entry.getValue()) {
                SrmInquiryItemDO item = pair.item;
                SrmInquiryQuoteDO quote = pair.quote;
                Long productUnitId = item.getProductUnitId();
                if (productUnitId == null) {
                    MdmItemRespDTO mdmItem = mdmItemApi.getItem(item.getProductId()).getCheckedData();
                    productUnitId = mdmItem != null ? mdmItem.getUnitMeasureId() : null;
                }
                ErpPurchaseOrderCreateReqDTO.Item orderItem = new ErpPurchaseOrderCreateReqDTO.Item();
                orderItem.setProductId(item.getProductId());
                orderItem.setProductUnitId(productUnitId);
                orderItem.setProductPrice(quote.getPrice());
                orderItem.setCount(item.getQuantity());
                orderItem.setTaxPercent(quote.getTaxRate());
                orderItem.setRemark("SRM询价 " + inquiry.getNo());
                orderItem.setQcCheckFlag(false);
                orderItems.add(orderItem);
            }
            ErpPurchaseOrderCreateReqDTO createReqDTO = new ErpPurchaseOrderCreateReqDTO();
            createReqDTO.setSupplierId(supplierId);
            createReqDTO.setOrderTime(LocalDateTime.now());
            createReqDTO.setDiscountPercent(BigDecimal.ZERO);
            createReqDTO.setDepositPrice(BigDecimal.ZERO);
            createReqDTO.setSourceType(2);
            createReqDTO.setSourceNo(inquiry.getNo());
            createReqDTO.setSourceId(inquiry.getId());
            createReqDTO.setRemark("SRM询价自动生成,询价单号:" + inquiry.getNo());
            createReqDTO.setItems(orderItems);
            Long purchaseOrderId;
            try {
                purchaseOrderId = erpPurchaseOrderApi.createPurchaseOrderDraft(createReqDTO).getCheckedData();
            } catch (Exception ex) {
                log.error("[confirmInquiry] è¯¢ä»·å•({}) ä¾›åº”商({}) ç”Ÿæˆé‡‡è´­è®¢å•失败", inquiry.getNo(), supplierId, ex);
                throw new ServiceException(ErrorCodeConstants.INQUIRY_ORDER_GENERATE_FAIL.getCode(),
                        "询价单【" + inquiry.getNo() + "】为供应商【" + supplierName
                                + "】生成采购订单失败:" + ex.getMessage() + "。请检查该供应商与产品资料后重试。");
            }
            String purchaseOrderNo = null;
            try {
                ErpPurchaseOrderRespDTO order = erpPurchaseOrderApi.getPurchaseOrder(purchaseOrderId).getCheckedData();
                purchaseOrderNo = order != null ? order.getNo() : null;
            } catch (Exception ex) {
                log.warn("[confirmInquiry] èŽ·å–é‡‡è´­è®¢å•å·å¤±è´¥ï¼ŒpurchaseOrderId={}", purchaseOrderId, ex);
            }
            inquiryOrderMapper.insert(SrmInquiryOrderDO.builder()
                    .inquiryId(inquiry.getId())
                    .supplierId(supplierId)
                    .purchaseOrderId(purchaseOrderId)
                    .purchaseOrderNo(purchaseOrderNo)
                    .build());
            SrmInquiryConfirmRespVO.Order orderResult = new SrmInquiryConfirmRespVO.Order();
            orderResult.setSupplierId(supplierId);
            orderResult.setSupplierName(supplierName);
            orderResult.setPurchaseOrderId(purchaseOrderId);
            orderResult.setPurchaseOrderNo(purchaseOrderNo);
            orders.add(orderResult);
        }
        // 4. æ›´æ–°è¯¢ä»·å•为已转订单(状态条件更新,避免并发重复生成)
        int updated = inquiryMapper.updateByIdAndStatus(inquiry.getId(), inquiry.getStatus(),
                new SrmInquiryDO().setStatus(InquiryStatusEnum.CONFIRMED.getStatus()));
        if (updated == 0) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_ALREADY_CONFIRMED);
        }
        SrmInquiryConfirmRespVO result = new SrmInquiryConfirmRespVO();
        result.setInquiryId(inquiry.getId());
        result.setOrders(orders);
        return result;
    }
    // ========== ç»„装 ==========
    private SrmInquiryRespVO buildInquiryResp(SrmInquiryDO inquiry) {
        SrmInquiryRespVO respVO = BeanUtils.toBean(inquiry, SrmInquiryRespVO.class);
        List<SrmInquiryItemDO> items = inquiryItemMapper.selectListByInquiryId(inquiry.getId());
        List<SrmInquirySupplierDO> participants = inquirySupplierMapper.selectListByInquiryId(inquiry.getId());
        List<SrmInquiryQuoteDO> quotes = inquiryQuoteMapper.selectListByInquiryId(inquiry.getId());
        List<SrmInquiryOrderDO> inquiryOrders = inquiryOrderMapper.selectListByInquiryId(inquiry.getId());
        Set<Long> supplierIds = new HashSet<>();
        participants.forEach(p -> supplierIds.add(p.getSupplierId()));
        quotes.forEach(q -> supplierIds.add(q.getSupplierId()));
        inquiryOrders.forEach(o -> supplierIds.add(o.getSupplierId()));
        Map<Long, SrmSupplierDO> supplierMap = supplierService.getSupplierMap(supplierIds);
        respVO.setItems(BeanUtils.toBean(items, SrmInquiryRespVO.Item.class));
        respVO.setQuotes(BeanUtils.toBean(quotes, SrmInquiryRespVO.Quote.class));
        List<SrmInquiryRespVO.Supplier> supplierVOs = BeanUtils.toBean(participants, SrmInquiryRespVO.Supplier.class);
        supplierVOs.forEach(vo -> {
            SrmSupplierDO supplier = supplierMap.get(vo.getSupplierId());
            if (supplier != null) {
                vo.setSupplierName(supplier.getName());
                vo.setSupplierCode(supplier.getCode());
            }
        });
        respVO.setSuppliers(supplierVOs);
        List<SrmInquiryRespVO.Order> orderVOs = BeanUtils.toBean(inquiryOrders, SrmInquiryRespVO.Order.class);
        orderVOs.forEach(vo -> {
            SrmSupplierDO supplier = supplierMap.get(vo.getSupplierId());
            if (supplier != null) {
                vo.setSupplierName(supplier.getName());
            }
        });
        respVO.setOrders(orderVOs);
        return respVO;
    }
    // ========== ä¿å­˜è¾…助 ==========
    private void validateInquiryInput(SrmInquirySaveReqVO reqVO) {
        if (CollUtil.isEmpty(reqVO.getItems())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_ITEM_EMPTY);
        }
        if (CollUtil.isEmpty(reqVO.getSuppliers())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_SUPPLIER_EMPTY);
        }
        Set<Long> productIds = new HashSet<>();
        for (SrmInquirySaveReqVO.Item item : reqVO.getItems()) {
            if (!productIds.add(item.getProductId())) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_ITEM_EMPTY.getCode(),
                        "产品ID=" + item.getProductId() + " åœ¨è¯¢ä»·å•中重复添加,请为每个产品只保留一行。");
            }
            if (item.getQuantity() == null || item.getQuantity().compareTo(BigDecimal.ZERO) <= 0) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_ITEM_EMPTY.getCode(),
                        "产品【" + StrUtil.blankToDefault(item.getProductName(), "ID=" + item.getProductId())
                                + "】的采购数量必须大于 0,请填写数量后重新保存。");
            }
        }
        Set<Long> supplierIds = new HashSet<>();
        for (SrmInquirySaveReqVO.Supplier supplier : reqVO.getSuppliers()) {
            if (!supplierIds.add(supplier.getSupplierId())) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_DUPLICATE.getCode(),
                        "供应商ID=" + supplier.getSupplierId() + " åœ¨è¯¢ä»·å•中重复添加,请为每个供应商只保留一行。");
            }
        }
        validateSuppliers(supplierIds);
    }
    private void validateSuppliers(Collection<Long> supplierIds) {
        List<SrmSupplierDO> suppliers = supplierService.getSupplierList(supplierIds);
        Map<Long, SrmSupplierDO> map = suppliers.stream()
                .collect(Collectors.toMap(SrmSupplierDO::getId, Function.identity()));
        for (Long supplierId : supplierIds) {
            SrmSupplierDO supplier = map.get(supplierId);
            if (supplier == null) {
                throw new ServiceException(ErrorCodeConstants.SUPPLIER_NOT_EXISTS.getCode(),
                        "供应商ID=" + supplierId + " ä¸å­˜åœ¨ï¼Œè¯·åˆ·æ–°ä¾›åº”商列表后重新选择。");
            }
            if (supplier.getStatus() != null && !CommonStatusEnum.ENABLE.getStatus().equals(supplier.getStatus())) {
                throw new ServiceException(ErrorCodeConstants.SUPPLIER_NOT_ENABLE.getCode(),
                        "供应商【" + supplier.getName() + "】已停用,请先在供应商档案中启用后再参与询价。");
            }
        }
    }
    private List<SrmInquiryItemDO> saveItems(Long inquiryId, List<SrmInquirySaveReqVO.Item> itemVOs) {
        List<SrmInquiryItemDO> savedItems = new ArrayList<>();
        Set<Long> productIds = itemVOs.stream().map(SrmInquirySaveReqVO.Item::getProductId)
                .collect(Collectors.toSet());
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemList(productIds).getCheckedData().stream()
                .collect(Collectors.toMap(MdmItemRespDTO::getId, Function.identity()));
        Set<Long> unitIds = itemMap.values().stream().map(MdmItemRespDTO::getUnitMeasureId)
                .filter(Objects::nonNull).collect(Collectors.toSet());
        Map<Long, MdmUnitMeasureRespDTO> unitMap = unitIds.isEmpty() ? Collections.emptyMap()
                : mdmUnitMeasureApi.getUnitMeasureMap(unitIds);
        for (int i = 0; i < itemVOs.size(); i++) {
            SrmInquirySaveReqVO.Item itemVO = itemVOs.get(i);
            MdmItemRespDTO mdmItem = itemMap.get(itemVO.getProductId());
            if (mdmItem == null) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_ITEM_EMPTY.getCode(),
                        "产品ID=" + itemVO.getProductId() + " ä¸å­˜åœ¨ï¼Œè¯·é‡æ–°é€‰æ‹©äº§å“ã€‚");
            }
            SrmInquiryItemDO item = BeanUtils.toBean(itemVO, SrmInquiryItemDO.class);
            item.setId(null);
            item.setInquiryId(inquiryId);
            item.setProductCode(StrUtil.blankToDefault(itemVO.getProductCode(), mdmItem.getCode()));
            item.setProductName(StrUtil.blankToDefault(itemVO.getProductName(), mdmItem.getName()));
            item.setProductSpec(StrUtil.blankToDefault(itemVO.getProductSpec(), mdmItem.getSpecification()));
            Long productUnitId = itemVO.getProductUnitId() != null ? itemVO.getProductUnitId()
                    : mdmItem.getUnitMeasureId();
            item.setProductUnitId(productUnitId);
            if (StrUtil.isBlank(itemVO.getUnitName()) && productUnitId != null) {
                MdmUnitMeasureRespDTO unit = unitMap.get(productUnitId);
                item.setUnitName(unit != null ? unit.getName() : null);
            }
            item.setSort(i);
            inquiryItemMapper.insert(item);
            savedItems.add(item);
        }
        return savedItems;
    }
    /**
     * ä¿å­˜äº§å“è¡ŒæŠ¥ä»·ä¸Žä¸­æ ‡é€‰æ‹©ï¼ˆåˆ›å»º/编辑询价单时一并写入)
     */
    private void saveItemQuotes(Long inquiryId, List<SrmInquiryItemDO> items,
                                List<SrmInquirySaveReqVO.Item> itemVOs, Set<Long> participantIds) {
        for (int i = 0; i < items.size(); i++) {
            SrmInquiryItemDO item = items.get(i);
            SrmInquirySaveReqVO.Item itemVO = itemVOs.get(i);
            String productLabel = StrUtil.blankToDefault(item.getProductName(), "ID=" + item.getProductId());
            List<SrmInquirySaveReqVO.Quote> quoteVOs = itemVO.getQuotes() == null
                    ? Collections.emptyList() : itemVO.getQuotes();
            Set<Long> quoteSupplierIds = new HashSet<>();
            SrmInquiryQuoteDO selectedQuote = null;
            for (SrmInquirySaveReqVO.Quote quoteVO : quoteVOs) {
                if (!quoteSupplierIds.add(quoteVO.getSupplierId())) {
                    throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_DUPLICATE.getCode(),
                            "产品【" + productLabel + "】的供应商ID=" + quoteVO.getSupplierId()
                                    + " æŠ¥ä»·é‡å¤ï¼Œè¯·ä¸ºæ¯ä¸ªä¾›åº”商只保留一条报价。");
                }
                if (!participantIds.contains(quoteVO.getSupplierId())) {
                    throw new ServiceException(ErrorCodeConstants.INQUIRY_SUPPLIER_NOT_PARTICIPANT.getCode(),
                            "供应商ID=" + quoteVO.getSupplierId() + " æœªå‚与询价单,无法录入产品【"
                                    + productLabel + "】的报价,请先在参与供应商中添加该供应商。");
                }
                if (quoteVO.getPrice() == null || quoteVO.getPrice().compareTo(BigDecimal.ZERO) <= 0) {
                    throw new ServiceException(ErrorCodeConstants.INQUIRY_QUOTE_PRICE_INVALID.getCode(),
                            "产品【" + productLabel + "】供应商ID=" + quoteVO.getSupplierId()
                                    + " çš„æŠ¥ä»·å•价为【" + quoteVO.getPrice() + "】,必须大于 0,请修改后重新保存。");
                }
                boolean selected = itemVO.getSelectedSupplierId() != null
                        && itemVO.getSelectedSupplierId().equals(quoteVO.getSupplierId());
                SrmInquiryQuoteDO quote = BeanUtils.toBean(quoteVO, SrmInquiryQuoteDO.class);
                quote.setInquiryId(inquiryId);
                quote.setInquiryItemId(item.getId());
                quote.setIsSelected(selected);
                inquiryQuoteMapper.insert(quote);
                if (selected) {
                    selectedQuote = quote;
                }
            }
            Long selectedSupplierId = itemVO.getSelectedSupplierId();
            if (selectedSupplierId != null && selectedQuote == null) {
                throw new ServiceException(ErrorCodeConstants.INQUIRY_WINNER_QUOTE_MISMATCH.getCode(),
                        "产品【" + productLabel + "】选择的中标供应商ID=" + selectedSupplierId
                                + " æ²¡æœ‰å½•入报价,请先填写该供应商的报价,或改选其他已报价供应商。");
            }
            if (selectedQuote != null) {
                inquiryItemMapper.updateById(new SrmInquiryItemDO()
                        .setId(item.getId())
                        .setSelectedSupplierId(selectedQuote.getSupplierId())
                        .setSelectedQuoteId(selectedQuote.getId())
                        .setSelectedPrice(selectedQuote.getPrice()));
            }
        }
    }
    private void saveSuppliers(Long inquiryId, List<SrmInquirySaveReqVO.Supplier> supplierVOs) {
        for (SrmInquirySaveReqVO.Supplier supplierVO : supplierVOs) {
            inquirySupplierMapper.insert(SrmInquirySupplierDO.builder()
                    .inquiryId(inquiryId)
                    .supplierId(supplierVO.getSupplierId())
                    .quoteStatus(0)
                    .remark(supplierVO.getRemark())
                    .build());
        }
    }
    // ========== æ ¡éªŒ ==========
    private SrmInquiryDO validateInquiryExists(Long id) {
        SrmInquiryDO inquiry = inquiryMapper.selectById(id);
        if (inquiry == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.INQUIRY_NOT_EXISTS);
        }
        return inquiry;
    }
    private String generateInquiryNo() {
        String date = LocalDate.now().format(NO_DATE_FORMAT);
        for (int attempt = 0; attempt < 20; attempt++) {
            int seq = (int) (Math.random() * 9000) + 1000;
            String no = "XJ" + date + String.format("%04d", seq);
            if (inquiryMapper.selectByNo(no) == null) {
                return no;
            }
        }
        throw new ServiceException(ErrorCodeConstants.INQUIRY_NO_GENERATE_FAIL.getCode(),
                "询价单号在多次尝试后仍重复,请稍后重试。");
    }
    private String statusName(Integer status) {
        for (InquiryStatusEnum e : InquiryStatusEnum.values()) {
            if (e.getStatus().equals(status)) {
                return e.getName();
            }
        }
        return "未知";
    }
    private static class QuoteWithItem {
        private final SrmInquiryItemDO item;
        private final SrmInquiryQuoteDO quote;
        private QuoteWithItem(SrmInquiryItemDO item, SrmInquiryQuoteDO quote) {
            this.item = item;
            this.quote = quote;
        }
    }
}
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/statistics/SrmStatisticsServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java
@@ -5,6 +5,8 @@
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierImportRespVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierBankDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierContactDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import jakarta.validation.Valid;
@@ -38,6 +40,16 @@
    SrmSupplierDO getSupplier(Long id);
    /**
     * èŽ·å–ä¾›åº”å•†è”ç³»äººåˆ—è¡¨ï¼ˆæŒ‰æŽ’åºè¿”å›žï¼‰
     */
    List<SrmSupplierContactDO> getSupplierContactList(Long supplierId);
    /**
     * èŽ·å–ä¾›åº”å•†é“¶è¡Œè´¦æˆ·åˆ—è¡¨ï¼ˆæŒ‰æŽ’åºè¿”å›žï¼‰
     */
    List<SrmSupplierBankDO> getSupplierBankList(Long supplierId);
    /**
     * åˆ†é¡µæŸ¥è¯¢ä¾›åº”商
     */
    PageResult<SrmSupplierDO> getSupplierPage(SrmSupplierPageReqVO pageReqVO);
@@ -52,12 +64,12 @@
    SrmSupplierImportRespVO importSupplierList(List<SrmSupplierImportExcelVO> importList, Boolean updateSupport);
    /**
     * èŽ·å–ä¾›åº”å•†åˆ—è¡¨ï¼ˆä»…è¿”å›žå·²é€šè¿‡å‡†å…¥å®¡æ‰¹çš„ä¾›åº”å•†ï¼Œç”¨äºŽä¸‹æ‹‰é€‰æ‹©ï¼‰
     * èŽ·å–å¯ç”¨çŠ¶æ€çš„ä¾›åº”å•†åˆ—è¡¨ï¼Œç”¨äºŽä¸šåŠ¡ä¸‹æ‹‰é€‰æ‹©
     */
    List<SrmSupplierDO> getSupplierSimpleList();
    /**
     * èŽ·å–å…¨éƒ¨ä¾›åº”å•†æ¡£æ¡ˆåˆ—è¡¨ï¼ˆä¸è¿‡æ»¤å‡†å…¥çŠ¶æ€ï¼Œç”¨äºŽæŠ•æ ‡é€‰æ‹©ä¾›åº”å•†ï¼‰
     * èŽ·å–å…¨éƒ¨ä¾›åº”å•†æ¡£æ¡ˆåˆ—è¡¨ï¼ˆæŽ’é™¤åœç”¨ä¾›åº”å•†ï¼‰
     */
    List<SrmSupplierDO> getSupplierAllList();
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
@@ -9,26 +9,26 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.operatelog.core.util.OperateLogUtils;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierImportExcelVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierImportRespVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
import cn.iocoder.yudao.module.srm.dal.dataobject.*;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidAwardMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidEvaluationMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierApplyMapper;
import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.*;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierBankDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierContactDO;
import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierReferenceApi;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidBondMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquiryOrderMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquiryQuoteMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmInquirySupplierMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierBankMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCategoryMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierContactMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCertificateMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierMapper;
import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
import cn.iocoder.yudao.module.srm.enums.SupplierTypeEnum;
import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.ArrayList;
@@ -36,7 +36,7 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.module.srm.enums.LogRecordConstants.*;
@@ -51,34 +51,43 @@
    private SrmSupplierContactMapper supplierContactMapper;
    @Resource
    private SrmSupplierBankMapper supplierBankMapper;
    @Resource
    private SrmSupplierCategoryMapper supplierCategoryMapper;
    @Resource
    private SrmSupplierApplyMapper supplierApplyMapper;
    private SrmInquirySupplierMapper inquirySupplierMapper;
    @Resource
    private SrmSupplierCertificateMapper supplierCertificateMapper;
    private SrmInquiryQuoteMapper inquiryQuoteMapper;
    @Resource
    private SrmTenderBidMapper tenderBidMapper;
    private SrmInquiryOrderMapper inquiryOrderMapper;
    @Resource
    private SrmBidEvaluationMapper bidEvaluationMapper;
    private SrmBidBondMapper bidBondMapper;
    @Resource
    private SrmBidAwardMapper bidAwardMapper;
    private ErpSupplierReferenceApi erpSupplierReferenceApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createSupplier(SrmSupplierSaveReqVO createReqVO) {
        // æ ¡éªŒä¾›åº”商编码唯一性
        validateCodeUnique(createReqVO.getCode(), null);
        // æ’å…¥
        SrmSupplierDO supplier = BeanUtils.toBean(createReqVO, SrmSupplierDO.class);
        fillDefaultContactAndBankFromFlatFields(createReqVO);
        supplierMapper.insert(supplier);
        saveContacts(supplier.getId(), createReqVO.getContacts());
        saveBanks(supplier.getId(), createReqVO.getBanks());
        syncSupplierDefaultFields(supplier.getId(), createReqVO.getContacts(), createReqVO.getBanks());
        return supplier.getId();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSupplier(SrmSupplierSaveReqVO updateReqVO) {
        // æ ¡éªŒå­˜åœ¨æ€§
        validateSupplierExists(updateReqVO.getId());
@@ -87,17 +96,47 @@
        // æ›´æ–°
        SrmSupplierDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierDO.class);
        supplierMapper.updateById(updateObj);
        // åˆ—表传 null è¡¨ç¤ºä¸ä¿®æ”¹ï¼›ä¼ ç©ºåˆ—表表示清空
        if (updateReqVO.getContacts() != null) {
            saveContacts(updateReqVO.getId(), updateReqVO.getContacts());
        }
        if (updateReqVO.getBanks() != null) {
            saveBanks(updateReqVO.getId(), updateReqVO.getBanks());
        }
        syncSupplierDefaultFields(updateReqVO.getId(), updateReqVO.getContacts(), updateReqVO.getBanks());
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteSupplier(Long id) {
        // æ ¡éªŒå­˜åœ¨æ€§
        validateSupplierExists(id);
        // å…³è”数据存在时,不允许删除
        validateNoReference(id);
        SrmSupplierDO supplier = validateSupplierExists(id);
        // æ ¡éªŒä¸šåŠ¡å¼•ç”¨ï¼šè¯¢ä»·ã€ä¿è¯é‡‘ã€ERP é‡‡è´­/付款等单据
        List<String> references = new ArrayList<>();
        if (inquirySupplierMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM询价单");
        }
        if (inquiryQuoteMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM询价报价");
        }
        if (inquiryOrderMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM询价转采购订单记录");
        }
        if (bidBondMapper.selectCountBySupplierId(id) > 0) {
            references.add("SRM投标保证金");
        }
        List<String> erpSources = erpSupplierReferenceApi.getSupplierReferenceSources(id).getCheckedData();
        if (CollUtil.isNotEmpty(erpSources)) {
            references.addAll(erpSources);
        }
        if (!references.isEmpty()) {
            throw new ServiceException(ErrorCodeConstants.SUPPLIER_EXISTS_REFERENCE.getCode(),
                    "供应商【" + supplier.getName() + "】已被业务引用(" + String.join("、", references)
                            + "),无法删除。请先处理相关单据,或将供应商状态改为停用。");
        }
        // å¯ç›´æŽ¥æ¸…理的关联数据
        supplierContactMapper.deleteBySupplierId(id);
        supplierBankMapper.deleteBySupplierId(id);
        supplierCategoryMapper.deleteBySupplierId(id);
        // é€»è¾‘删除
        supplierMapper.deleteById(id);
@@ -106,6 +145,16 @@
    @Override
    public SrmSupplierDO getSupplier(Long id) {
        return supplierMapper.selectById(id);
    }
    @Override
    public List<SrmSupplierContactDO> getSupplierContactList(Long supplierId) {
        return supplierContactMapper.selectListBySupplierId(supplierId);
    }
    @Override
    public List<SrmSupplierBankDO> getSupplierBankList(Long supplierId) {
        return supplierBankMapper.selectListBySupplierId(supplierId);
    }
    @Override
@@ -162,13 +211,14 @@
    @Override
    public List<SrmSupplierDO> getSupplierSimpleList() {
        return supplierMapper.selectApprovedList();
        // å‡†å…¥åŠŸèƒ½å·²ç§»é™¤ï¼Œä¸‹æ‹‰é€‰æ‹©ç»Ÿä¸€è¿”å›žå¯ç”¨çš„ä¾›åº”å•†
        return supplierMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
    }
    @Override
    public List<SrmSupplierDO> getSupplierAllList() {
        return supplierMapper.selectList(new LambdaQueryWrapperX<SrmSupplierDO>()
                // è¿‡æ»¤åœç”¨ä¾›åº”商,保留未设置状态与启用状态的档案供应商(仅用于招投标场景)
                // è¿‡æ»¤åœç”¨ä¾›åº”商,保留未设置状态与启用状态的档案供应商
                .and(w -> w.isNull(SrmSupplierDO::getStatus)
                        .or()
                        .ne(SrmSupplierDO::getStatus, CommonStatusEnum.DISABLE.getStatus()))
@@ -181,6 +231,124 @@
            return Collections.emptyList();
        }
        return supplierMapper.selectBatchIds(ids);
    }
    // ========== è”系人 / é“¶è¡Œè´¦æˆ· ==========
    /**
     * å¯¼å…¥åœºæ™¯æ²¡æœ‰è”系人列表时,用主表扁平字段生成一条默认联系人
     */
    private void fillDefaultContactAndBankFromFlatFields(SrmSupplierSaveReqVO reqVO) {
        if (reqVO.getContacts() == null && StrUtil.isNotBlank(reqVO.getContactName())) {
            SrmSupplierContactSaveReqVO contact = new SrmSupplierContactSaveReqVO();
            contact.setName(reqVO.getContactName());
            contact.setMobile(reqVO.getContactMobile());
            contact.setEmail(reqVO.getContactEmail());
            contact.setIsDefault(Boolean.TRUE);
            reqVO.setContacts(Collections.singletonList(contact));
        }
        if (reqVO.getBanks() == null
                && (StrUtil.isNotBlank(reqVO.getBankName()) || StrUtil.isNotBlank(reqVO.getBankAccount()))) {
            SrmSupplierBankSaveReqVO bank = new SrmSupplierBankSaveReqVO();
            bank.setBankName(reqVO.getBankName());
            bank.setBankAccount(reqVO.getBankAccount());
            bank.setIsDefault(Boolean.TRUE);
            reqVO.setBanks(Collections.singletonList(bank));
        }
    }
    private void saveContacts(Long supplierId, List<SrmSupplierContactSaveReqVO> contacts) {
        supplierContactMapper.deleteBySupplierId(supplierId);
        if (CollUtil.isEmpty(contacts)) {
            return;
        }
        for (int i = 0; i < contacts.size(); i++) {
            SrmSupplierContactSaveReqVO vo = contacts.get(i);
            if (StrUtil.isBlank(vo.getName())) {
                throw new ServiceException(ErrorCodeConstants.SUPPLIER_CONTACT_NAME_REQUIRED.getCode(),
                        "第 " + (i + 1) + " ä¸ªè”系人的姓名不能为空,请填写联系人姓名后重新保存");
            }
        }
        int defaultIndex = resolveDefaultIndex(contacts.size(),
                contacts.stream().map(SrmSupplierContactSaveReqVO::getIsDefault).toList());
        for (int i = 0; i < contacts.size(); i++) {
            SrmSupplierContactDO contact = BeanUtils.toBean(contacts.get(i), SrmSupplierContactDO.class);
            contact.setId(null);
            contact.setSupplierId(supplierId);
            contact.setSort(i);
            contact.setIsDefault(i == defaultIndex);
            supplierContactMapper.insert(contact);
        }
    }
    private void saveBanks(Long supplierId, List<SrmSupplierBankSaveReqVO> banks) {
        supplierBankMapper.deleteBySupplierId(supplierId);
        if (CollUtil.isEmpty(banks)) {
            return;
        }
        for (int i = 0; i < banks.size(); i++) {
            SrmSupplierBankSaveReqVO vo = banks.get(i);
            if (StrUtil.isBlank(vo.getBankName()) || StrUtil.isBlank(vo.getBankAccount())) {
                throw new ServiceException(ErrorCodeConstants.SUPPLIER_BANK_INFO_INCOMPLETE.getCode(),
                        "第 " + (i + 1) + " ä¸ªé“¶è¡Œè´¦æˆ·çš„开户行或银行账号为空,请补全开户行和银行账号后重新保存");
            }
        }
        int defaultIndex = resolveDefaultIndex(banks.size(),
                banks.stream().map(SrmSupplierBankSaveReqVO::getIsDefault).toList());
        for (int i = 0; i < banks.size(); i++) {
            SrmSupplierBankDO bank = BeanUtils.toBean(banks.get(i), SrmSupplierBankDO.class);
            bank.setId(null);
            bank.setSupplierId(supplierId);
            bank.setSort(i);
            bank.setIsDefault(i == defaultIndex);
            supplierBankMapper.insert(bank);
        }
    }
    /**
     * è§£æžé»˜è®¤é¡¹ï¼šä¼˜å…ˆå–标记了默认的第一条,否则取第一条
     */
    private int resolveDefaultIndex(int size, List<Boolean> defaults) {
        for (int i = 0; i < size; i++) {
            if (Boolean.TRUE.equals(defaults.get(i))) {
                return i;
            }
        }
        return 0;
    }
    /**
     * æŠŠé»˜è®¤è”系人/默认银行回写主表扁平字段,兼容列表、搜索与导入导出
     */
    private void syncSupplierDefaultFields(Long supplierId, List<SrmSupplierContactSaveReqVO> contacts,
                                           List<SrmSupplierBankSaveReqVO> banks) {
        SrmSupplierDO updateObj = new SrmSupplierDO();
        updateObj.setId(supplierId);
        boolean changed = false;
        if (contacts != null) {
            SrmSupplierContactSaveReqVO defaultContact = pickDefault(contacts, SrmSupplierContactSaveReqVO::getIsDefault);
            updateObj.setContactName(defaultContact == null ? null : defaultContact.getName());
            updateObj.setContactMobile(defaultContact == null ? null : defaultContact.getMobile());
            updateObj.setContactEmail(defaultContact == null ? null : defaultContact.getEmail());
            changed = true;
        }
        if (banks != null) {
            SrmSupplierBankSaveReqVO defaultBank = pickDefault(banks, SrmSupplierBankSaveReqVO::getIsDefault);
            updateObj.setBankName(defaultBank == null ? null : defaultBank.getBankName());
            updateObj.setBankAccount(defaultBank == null ? null : defaultBank.getBankAccount());
            changed = true;
        }
        if (changed) {
            supplierMapper.updateById(updateObj);
        }
    }
    private <T> T pickDefault(List<T> list, java.util.function.Function<T, Boolean> isDefault) {
        if (CollUtil.isEmpty(list)) {
            return null;
        }
        return list.stream().filter(item -> Boolean.TRUE.equals(isDefault.apply(item)))
                .findFirst().orElse(list.get(0));
    }
    // ========== å¯¼å…¥è¾…助方法 ==========
@@ -243,27 +411,18 @@
    // ========== æ ¡éªŒæ–¹æ³• ==========
    private void validateSupplierExists(Long id) {
        if (supplierMapper.selectById(id) == null) {
    private SrmSupplierDO validateSupplierExists(Long id) {
        SrmSupplierDO supplier = supplierMapper.selectById(id);
        if (supplier == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
        }
        return supplier;
    }
    private void validateCodeUnique(String code, Long id) {
        SrmSupplierDO existing = supplierMapper.selectByCode(code);
        if (existing != null && !existing.getId().equals(id)) {
        if (existing != null && !Objects.equals(existing.getId(), id)) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_CODE_EXISTS);
        }
    }
    private void validateNoReference(Long id) {
        boolean hasReference = supplierApplyMapper.existsApprovedBySupplierId(id)
                || supplierCertificateMapper.selectCount(SrmSupplierCertificateDO::getSupplierId, id) > 0
                || tenderBidMapper.selectCount(SrmTenderBidDO::getSupplierId, id) > 0
                || bidEvaluationMapper.selectCount(SrmBidEvaluationDO::getSupplierId, id) > 0
                || bidAwardMapper.selectCount(SrmBidAwardDO::getSupplierId, id) > 0;
        if (hasReference) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_EXISTS_REFERENCE);
        }
    }
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidEvaluationServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderConfigService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderConfigServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectService.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
ÎļþÒÑɾ³ý
yudao-module-srm/src/main/resources/sql/srm_init.sql
@@ -1,19 +1,11 @@
-- =============================================
-- SRM ä¾›åº”商关系管理模块 - æ•°æ®åº“初始化脚本
-- MySQL 8.4+
--
-- ä¿ç•™èŒƒå›´ï¼šä¾›åº”商档案(含多联系人/多银行账户)、询价比价、投标保证金
-- å·²ç§»é™¤ï¼šä¾›åº”商准入、资质、绩效、招投标、统计分析(对应表已在
--         sql/mysql/srm_procurement_refactor_20261103.sql ä¸­ DROP)
-- =============================================
-- 0. æ‹›æŠ•标配置表
CREATE TABLE IF NOT EXISTS srm_tender_config (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '配置ID',
    default_bidding_mode VARCHAR(20) NOT NULL DEFAULT 'FULL' COMMENT '默认招投标模式: FULL-完整版, SIMPLE-简易版',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SRM招投标配置表';
-- 1. ä¾›åº”商主表
CREATE TABLE IF NOT EXISTS srm_supplier (
@@ -29,11 +21,11 @@
    legal_person VARCHAR(50) DEFAULT NULL COMMENT '法人代表',
    address VARCHAR(500) DEFAULT NULL COMMENT '公司地址',
    website VARCHAR(200) DEFAULT NULL COMMENT '公司网址',
    contact_name VARCHAR(50) DEFAULT NULL COMMENT '联系人',
    contact_mobile VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
    contact_email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
    bank_name VARCHAR(255) DEFAULT NULL COMMENT '开户银行',
    bank_account VARCHAR(100) DEFAULT NULL COMMENT '银行账号',
    contact_name VARCHAR(50) DEFAULT NULL COMMENT '默认联系人(主表冗余)',
    contact_mobile VARCHAR(20) DEFAULT NULL COMMENT '默认联系电话(主表冗余)',
    contact_email VARCHAR(100) DEFAULT NULL COMMENT '默认邮箱(主表冗余)',
    bank_name VARCHAR(255) DEFAULT NULL COMMENT '默认开户银行(主表冗余)',
    bank_account VARCHAR(100) DEFAULT NULL COMMENT '默认银行账号(主表冗余)',
    status INT NOT NULL DEFAULT 0 COMMENT '状态(0启用 1禁用)',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    erp_supplier_id BIGINT DEFAULT NULL COMMENT '关联ERP供应商ID',
@@ -49,7 +41,7 @@
    KEY idx_erp_supplier_id (erp_supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商主表';
-- 2. ä¾›åº”商联系人表
-- 2. ä¾›åº”商联系人表(支持多个联系人,仅一个默认)
CREATE TABLE IF NOT EXISTS srm_supplier_contact (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '联系人ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
@@ -70,7 +62,27 @@
    KEY idx_supplier_id (supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商联系人表';
-- 3. ä¾›åº”商分类关联表
-- 3. ä¾›åº”商银行账户表(支持多个开户行,仅一个默认)
CREATE TABLE IF NOT EXISTS srm_supplier_bank (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '银行账户ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    bank_name VARCHAR(128) NOT NULL COMMENT '开户行',
    bank_account VARCHAR(64) NOT NULL COMMENT '银行账号',
    bank_no VARCHAR(64) DEFAULT NULL COMMENT '银行行号',
    bank_address VARCHAR(255) DEFAULT NULL COMMENT '开户行地址',
    is_default BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否默认银行账户',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商银行账户表';
-- 4. ä¾›åº”商分类关联表
CREATE TABLE IF NOT EXISTS srm_supplier_category (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '关联ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
@@ -78,41 +90,23 @@
    category_name VARCHAR(100) DEFAULT NULL COMMENT '分类名称(冗余)',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted INT NOT NULL DEFAULT 0 COMMENT '是否删除 0=否 1=是',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_category_id (category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商分类关联表';
-- 4. ä¾›åº”商准入申请表
CREATE TABLE IF NOT EXISTS srm_supplier_apply (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '申请ID',
    apply_no VARCHAR(64) NOT NULL COMMENT '申请编号',
    supplier_id BIGINT DEFAULT NULL COMMENT '供应商ID(新建为空,准入后回填)',
    supplier_name VARCHAR(255) NOT NULL COMMENT '供应商名称',
    supplier_type VARCHAR(50) DEFAULT NULL COMMENT '供应商类型',
    company_nature VARCHAR(50) DEFAULT NULL COMMENT '企业性质',
    registered_capital DECIMAL(24,6) DEFAULT NULL COMMENT '注册资金(万元)',
    business_license_no VARCHAR(100) DEFAULT NULL COMMENT '营业执照编号',
    tax_no VARCHAR(100) DEFAULT NULL COMMENT '税号',
    address VARCHAR(500) DEFAULT NULL COMMENT '公司地址',
    bank_name VARCHAR(255) DEFAULT NULL COMMENT '开户银行',
    bank_account VARCHAR(100) DEFAULT NULL COMMENT '银行账号',
    contact_name VARCHAR(50) DEFAULT NULL COMMENT '联系人',
    contact_mobile VARCHAR(20) DEFAULT NULL COMMENT '联系电话',
    contact_email VARCHAR(100) DEFAULT NULL COMMENT '邮箱',
    apply_user_id BIGINT DEFAULT NULL COMMENT '申请人ID',
    apply_user_name VARCHAR(50) DEFAULT NULL COMMENT '申请人姓名',
    apply_time DATETIME DEFAULT NULL COMMENT '申请时间',
    apply_status INT NOT NULL DEFAULT 0 COMMENT '审核状态(0待提交 10待采购审核 20待质量审核 30待财务审核 40已通过 50已拒绝)',
    purchase_reviewer_id BIGINT DEFAULT NULL COMMENT '采购审核人ID',
    purchase_review_time DATETIME DEFAULT NULL COMMENT '采购审核时间',
    purchase_review_opinion VARCHAR(500) DEFAULT NULL COMMENT '采购审核意见',
    quality_reviewer_id BIGINT DEFAULT NULL COMMENT '质量审核人ID',
    quality_review_time DATETIME DEFAULT NULL COMMENT '质量审核时间',
    quality_review_opinion VARCHAR(500) DEFAULT NULL COMMENT '质量审核意见',
    finance_reviewer_id BIGINT DEFAULT NULL COMMENT '财务审核人ID',
    finance_review_time DATETIME DEFAULT NULL COMMENT '财务审核时间',
    finance_review_opinion VARCHAR(500) DEFAULT NULL COMMENT '财务审核意见',
-- 5. è¯¢ä»·å•
CREATE TABLE IF NOT EXISTS srm_inquiry (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '询价单ID',
    no VARCHAR(32) NOT NULL COMMENT '询价单号',
    title VARCHAR(128) NOT NULL COMMENT '询价标题',
    status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿 10询价中 20已转订单 30已取消',
    inquiry_date DATE DEFAULT NULL COMMENT '询价日期',
    deadline DATE DEFAULT NULL COMMENT '报价截止日期',
    purchaser_user_id BIGINT DEFAULT NULL COMMENT '采购员用户ID',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -120,116 +114,27 @@
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_apply_no (apply_no),
    KEY idx_supplier_id (supplier_id),
    KEY idx_apply_status (apply_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商准入申请表';
    UNIQUE KEY uk_no (no),
    KEY idx_status (status),
    KEY idx_inquiry_date (inquiry_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SRM询价单';
-- 5. ä¾›åº”商资质证书表
CREATE TABLE IF NOT EXISTS srm_supplier_certificate (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '证书ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    certificate_type INT NOT NULL COMMENT '证书类型(1营业执照 2认证证书 3检测报告 4其他)',
    certificate_name VARCHAR(255) NOT NULL COMMENT '证书名称',
    certificate_no VARCHAR(100) DEFAULT NULL COMMENT '证书编号',
    file_name VARCHAR(255) DEFAULT NULL COMMENT '文件名称',
    file_url VARCHAR(500) DEFAULT NULL COMMENT '文件地址',
    issuing_authority VARCHAR(255) DEFAULT NULL COMMENT '颁发机构',
    valid_start_date DATE DEFAULT NULL COMMENT '有效开始日期',
    valid_end_date DATE DEFAULT NULL COMMENT '有效结束日期',
    status INT NOT NULL DEFAULT 0 COMMENT '状态(0有效 1过期 2即将过期)',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_certificate_type (certificate_type),
    KEY idx_valid_end_date (valid_end_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商资质证书表';
-- 6. ä¾›åº”商绩效评价表
CREATE TABLE IF NOT EXISTS srm_supplier_evaluation (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '评价ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    evaluation_period VARCHAR(50) NOT NULL COMMENT '评价周期(如:2026-Q1)',
    quality_score DECIMAL(5,2) DEFAULT NULL COMMENT '质量评分(满分100)',
    quality_pass_rate DECIMAL(5,2) DEFAULT NULL COMMENT '来料合格率(%)',
    quality_defect_count INT DEFAULT 0 COMMENT '不良次数',
    delivery_score DECIMAL(5,2) DEFAULT NULL COMMENT '交付评分(满分100)',
    delivery_on_time_rate DECIMAL(5,2) DEFAULT NULL COMMENT '准时交付率(%)',
    delivery_delay_count INT DEFAULT 0 COMMENT '延迟次数',
    price_score DECIMAL(5,2) DEFAULT NULL COMMENT '价格评分(满分100)',
    service_score DECIMAL(5,2) DEFAULT NULL COMMENT '服务评分(满分100)',
    service_response_speed INT DEFAULT NULL COMMENT '响应速度(评分1-10)',
    composite_score DECIMAL(5,2) DEFAULT NULL COMMENT '综合评分(自动计算)',
    evaluation_level VARCHAR(20) DEFAULT NULL COMMENT '评价等级(A/B/C/D)',
    evaluator_id BIGINT DEFAULT NULL COMMENT '评价人ID',
    evaluation_time DATETIME DEFAULT NULL COMMENT '评价时间',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_evaluation_period (evaluation_period),
    KEY idx_composite_score (composite_score)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商绩效评价表';
-- 7. æ‹›æ ‡é¡¹ç›®è¡¨
CREATE TABLE IF NOT EXISTS srm_tender_project (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '招标项目ID',
    tender_no VARCHAR(64) NOT NULL COMMENT '招标编号',
    tender_name VARCHAR(255) NOT NULL COMMENT '招标名称',
    tender_type INT NOT NULL COMMENT '招标类型(1公开招标 2邀请招标 3竞争性谈判)',
    purchase_org VARCHAR(100) DEFAULT NULL COMMENT '采购组织',
    project_leader_id BIGINT DEFAULT NULL COMMENT '项目负责人ID',
    project_leader_name VARCHAR(50) DEFAULT NULL COMMENT '项目负责人姓名',
    tender_status INT NOT NULL DEFAULT 0 COMMENT '招标状态(0草稿 1发布中 2投标中 3评标中 4已定标 5已关闭)',
    budget_amount DECIMAL(24,6) DEFAULT NULL COMMENT '预算金额',
    start_time DATETIME DEFAULT NULL COMMENT '开始时间',
    end_time DATETIME DEFAULT NULL COMMENT '截止时间',
    bid_open_time DATETIME DEFAULT NULL COMMENT '预计开标时间',
    tender_desc TEXT DEFAULT NULL COMMENT '招标说明',
    qualification_requirements TEXT DEFAULT NULL COMMENT '资质要求',
    purchase_request_id BIGINT DEFAULT NULL COMMENT '关联采购申请ID',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_tender_no (tender_no),
    KEY idx_tender_status (tender_status),
    KEY idx_purchase_request_id (purchase_request_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='招标项目表';
-- æ‹›æ ‡é¡¹ç›®è¡¨å¢žåŠ  bidding_mode å­—段(简易模式支持)
SET @exists = (SELECT COUNT(*) FROM information_schema.columns
               WHERE table_schema = DATABASE() AND table_name = 'srm_tender_project' AND column_name = 'bidding_mode');
SET @sql = IF(@exists = 0,
              'ALTER TABLE srm_tender_project ADD COLUMN bidding_mode VARCHAR(20) NOT NULL DEFAULT ''FULL'' COMMENT ''招投标模式: FULL-完整版, SIMPLE-简易版'' AFTER tender_status',
              'SELECT 1');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 8. æ‹›æ ‡ç‰©æ–™æ˜Žç»†è¡¨
CREATE TABLE IF NOT EXISTS srm_tender_material (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '物料明细ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    product_id BIGINT DEFAULT NULL COMMENT '物料ID(关联MDM物料)',
    product_code VARCHAR(64) DEFAULT NULL COMMENT '物料编码',
    product_name VARCHAR(255) NOT NULL COMMENT '物料名称',
-- 6. è¯¢ä»·å•产品行
CREATE TABLE IF NOT EXISTS srm_inquiry_item (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '询价产品行ID',
    inquiry_id BIGINT NOT NULL COMMENT '询价单ID',
    product_id BIGINT DEFAULT NULL COMMENT '产品ID',
    product_unit_id BIGINT DEFAULT NULL COMMENT '产品单位ID',
    product_code VARCHAR(64) DEFAULT NULL COMMENT '产品编码',
    product_name VARCHAR(255) DEFAULT NULL COMMENT '产品名称',
    product_spec VARCHAR(200) DEFAULT NULL COMMENT '规格型号',
    quantity DECIMAL(24,6) NOT NULL COMMENT '数量',
    unit VARCHAR(20) NOT NULL COMMENT '单位',
    tech_requirement TEXT DEFAULT NULL COMMENT '技术要求',
    unit_name VARCHAR(50) DEFAULT NULL COMMENT '单位名称',
    quantity DECIMAL(24,6) NOT NULL DEFAULT 0 COMMENT '采购数量',
    estimated_price DECIMAL(24,6) DEFAULT NULL COMMENT '预估单价',
    selected_supplier_id BIGINT DEFAULT NULL COMMENT '中标供应商ID',
    selected_quote_id BIGINT DEFAULT NULL COMMENT '中标报价ID',
    selected_price DECIMAL(24,6) DEFAULT NULL COMMENT '中标单价',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    sort INT NOT NULL DEFAULT 0 COMMENT '排序',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -237,131 +142,92 @@
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_product_code (product_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='招标物料明细表';
    KEY idx_inquiry_id (inquiry_id),
    KEY idx_product_id (product_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SRM询价单产品行';
-- 9. æŠ•标记录表
CREATE TABLE IF NOT EXISTS srm_tender_bid (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '投标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
-- 7. è¯¢ä»·å‚与供应商
CREATE TABLE IF NOT EXISTS srm_inquiry_supplier (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '询价参与供应商ID',
    inquiry_id BIGINT NOT NULL COMMENT '询价单ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    bid_no VARCHAR(64) NOT NULL COMMENT '投标编号',
    bid_status INT NOT NULL DEFAULT 0 COMMENT '投标状态(0报名 10已投标 20撤标 30中标 40未中标)',
    bid_time DATETIME DEFAULT NULL COMMENT '投标时间',
    bid_total_amount DECIMAL(24,6) DEFAULT NULL COMMENT '投标总金额',
    bid_file_name VARCHAR(255) DEFAULT NULL COMMENT '投标文件名称',
    bid_file_url VARCHAR(500) DEFAULT NULL COMMENT '投标文件地址',
    withdraw_reason VARCHAR(500) DEFAULT NULL COMMENT '撤标原因',
    quote_status TINYINT NOT NULL DEFAULT 0 COMMENT '报价状态:0未报价 10已报价',
    quote_time DATETIME DEFAULT NULL COMMENT '最近报价时间',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_bid_no (bid_no),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_bid_status (bid_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='投标记录表';
    KEY idx_inquiry_id (inquiry_id),
    KEY idx_supplier_id (supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SRM询价参与供应商';
-- 10. ä¾›åº”商报价明细表
CREATE TABLE IF NOT EXISTS srm_supplier_quote (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '报价ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
-- 8. è¯¢ä»·æŠ¥ä»·æ˜Žç»†
CREATE TABLE IF NOT EXISTS srm_inquiry_quote (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '询价报价ID',
    inquiry_id BIGINT NOT NULL COMMENT '询价单ID',
    inquiry_item_id BIGINT NOT NULL COMMENT '询价产品行ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    bid_id BIGINT DEFAULT NULL COMMENT '投标记录ID',
    tender_material_id BIGINT NOT NULL COMMENT '招标物料ID',
    quote_price DECIMAL(24,6) NOT NULL COMMENT '报价价格',
    tax_rate DECIMAL(5,2) DEFAULT NULL COMMENT '税率(%)',
    price DECIMAL(24,6) NOT NULL COMMENT '报价单价',
    tax_rate DECIMAL(24,6) DEFAULT NULL COMMENT '税率(%)',
    delivery_cycle INT DEFAULT NULL COMMENT '交货周期(天)',
    payment_terms VARCHAR(500) DEFAULT NULL COMMENT '付款条件',
    warranty_period INT DEFAULT NULL COMMENT '质保期(月)',
    payment_terms VARCHAR(128) DEFAULT NULL COMMENT '付款条件',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    is_selected BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否中标报价',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_bid_id (bid_id),
    KEY idx_tender_material_id (tender_material_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='供应商报价明细表';
    KEY idx_inquiry_id (inquiry_id),
    KEY idx_inquiry_item_id (inquiry_item_id),
    KEY idx_supplier_id (supplier_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SRM询价报价明细';
-- 11. å¼€æ ‡è®°å½•表
CREATE TABLE IF NOT EXISTS srm_bid_open (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '开标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    open_time DATETIME NOT NULL COMMENT '开标时间',
    open_person_id BIGINT DEFAULT NULL COMMENT '开标人员ID',
    open_person_name VARCHAR(50) DEFAULT NULL COMMENT '开标人员姓名',
    open_record TEXT DEFAULT NULL COMMENT '开标记录',
    attachment_name VARCHAR(255) DEFAULT NULL COMMENT '附件名称',
    attachment_url VARCHAR(500) DEFAULT NULL COMMENT '附件地址',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='开标记录表';
-- 12. è¯„标记录表
CREATE TABLE IF NOT EXISTS srm_bid_evaluation (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '评标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    bid_id BIGINT NOT NULL COMMENT '投标记录ID',
    price_score DECIMAL(5,2) DEFAULT NULL COMMENT '价格评分(满分40)',
    tech_score DECIMAL(5,2) DEFAULT NULL COMMENT '技术评分(满分30)',
    delivery_score DECIMAL(5,2) DEFAULT NULL COMMENT '交付评分(满分20)',
    service_score DECIMAL(5,2) DEFAULT NULL COMMENT '服务评分(满分10)',
    composite_score DECIMAL(5,2) DEFAULT NULL COMMENT '综合评分(自动计算=ä»·æ ¼+技术+交付+服务)',
    ranking INT DEFAULT NULL COMMENT '排名',
    evaluator_id BIGINT DEFAULT NULL COMMENT '评标人ID',
    evaluator_name VARCHAR(50) DEFAULT NULL COMMENT '评标人姓名',
    evaluation_opinion VARCHAR(500) DEFAULT NULL COMMENT '评标意见',
    evaluation_time DATETIME DEFAULT NULL COMMENT '评标时间',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_bid_id (bid_id),
    KEY idx_ranking (ranking)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='评标记录表';
-- 13. å®šæ ‡è®°å½•表
CREATE TABLE IF NOT EXISTS srm_bid_award (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '定标ID',
    tender_project_id BIGINT NOT NULL COMMENT '招标项目ID',
-- 9. è¯¢ä»·è½¬è®¢å•记录
CREATE TABLE IF NOT EXISTS srm_inquiry_order (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '询价转订单记录ID',
    inquiry_id BIGINT NOT NULL COMMENT '询价单ID',
    supplier_id BIGINT NOT NULL COMMENT '中标供应商ID',
    bid_id BIGINT NOT NULL COMMENT '中标投标ID',
    award_no VARCHAR(64) NOT NULL COMMENT '中标通知书编号',
    award_status INT NOT NULL DEFAULT 0 COMMENT '定标状态(0待审批 10已定标 20已生成订单)',
    award_amount DECIMAL(24,6) NOT NULL COMMENT '中标金额',
    award_time DATETIME DEFAULT NULL COMMENT '定标时间',
    notice_file_name VARCHAR(255) DEFAULT NULL COMMENT '中标通知书文件名',
    notice_file_url VARCHAR(500) DEFAULT NULL COMMENT '中标通知书文件地址',
    purchase_order_id BIGINT DEFAULT NULL COMMENT '关联生成的采购订单ID',
    purchase_order_no VARCHAR(64) DEFAULT NULL COMMENT '关联生成的采购订单号',
    approver_id BIGINT DEFAULT NULL COMMENT '审批人ID',
    approval_time DATETIME DEFAULT NULL COMMENT '审批时间',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    purchase_order_id BIGINT DEFAULT NULL COMMENT '生成的采购订单ID',
    purchase_order_no VARCHAR(64) DEFAULT NULL COMMENT '生成的采购订单号',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_award_no (award_no),
    KEY idx_tender_project_id (tender_project_id),
    KEY idx_inquiry_id (inquiry_id),
    KEY idx_supplier_id (supplier_id),
    KEY idx_purchase_order_id (purchase_order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='定标记录表';
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SRM询价转采购订单记录';
-- 10. æŠ•标保证金台账
CREATE TABLE IF NOT EXISTS srm_bid_bond (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '投标保证金ID',
    no VARCHAR(32) NOT NULL COMMENT '保证金单号',
    supplier_id BIGINT NOT NULL COMMENT '供应商ID',
    project_name VARCHAR(255) DEFAULT NULL COMMENT '项目名称',
    inquiry_id BIGINT DEFAULT NULL COMMENT '关联询价单ID',
    purchase_order_id BIGINT DEFAULT NULL COMMENT '关联采购订单ID',
    bond_amount DECIMAL(24,6) NOT NULL DEFAULT 0 COMMENT '保证金金额',
    pay_date DATE DEFAULT NULL COMMENT '缴纳日期',
    won BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否中标',
    refund_status TINYINT NOT NULL DEFAULT 0 COMMENT '退回状态:0未退回 1部分退回 2已退回 3无需退回',
    refund_amount DECIMAL(24,6) DEFAULT NULL COMMENT '退回金额',
    refund_date DATE DEFAULT NULL COMMENT '退回日期',
    remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
    creator VARCHAR(64) DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) DEFAULT '' COMMENT '更新者',
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    deleted BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_no (no),
    KEY idx_supplier_id (supplier_id),
    KEY idx_inquiry_id (inquiry_id),
    KEY idx_refund_status (refund_status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='SRM投标保证金台账';
yudao-module-srm/src/main/resources/sql/srm_menu_init.sql
@@ -1,100 +1,50 @@
-- =====================================================
-- SRM æ¨¡å—菜单数据
-- SRM æ¨¡å—菜单数据(新库初始化用)
-- å·²ç§»é™¤ï¼šä¾›åº”商准入/资质/绩效/招投标/统计分析
-- ä¿ç•™ï¼šä¾›åº”商档案、询价比价、投标保证金、供应商往来
-- è¯´æ˜Žï¼šä½¿ç”¨ INSERT IGNORE,重复执行不会报主键冲突。
-- =====================================================
-- 1. äºŒçº§ç›®å½•:SRM ä¾›åº”商管理(挂在采购管理下)
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602524, 'SRM ä¾›åº”商管理', '', 1, 5, 2602, 'srm', 'ep:avatar', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 1. ä¾›åº”商档案(直接挂在采购管理根菜单下)
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602525, '供应商档案', '', 2, 1, 2602, 'supplier', 'ep:document', 'srm/supplier/index', 'SrmSupplier', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 2. ä¾›åº”商档案
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602525, '供应商档案', '', 2, 10, 602524, 'supplier', 'ep:document', 'srm/supplier/index', 'SrmSupplier', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602526, '供应商查询', 'srm:supplier:query', 3, 1, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602527, '供应商创建', 'srm:supplier:create', 3, 2, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602528, '供应商更新', 'srm:supplier:update', 3, 3, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602529, '供应商删除', 'srm:supplier:delete', 3, 4, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
(602529, '供应商删除', 'srm:supplier:delete', 3, 4, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075301, '供应商导出', 'srm:supplier:export', 3, 5, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(1075302, '供应商导入', 'srm:supplier:import', 3, 6, 602525, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 3. ä¾›åº”商准入
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602530, '供应商准入', '', 2, 20, 602524, 'supplier-apply', 'ep:checked', 'srm/supplierApply/index', 'SrmSupplierApply', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 2. è¯¢ä»·æ¯”ä»·
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602620, '询价比价', '', 2, 2, 2602, 'inquiry', 'ep:search', 'srm/inquiry/index', 'SrmInquiry', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602531, '准入查询', 'srm:supplier-apply:query', 3, 1, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602532, '准入创建', 'srm:supplier-apply:create', 3, 2, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602533, '准入更新', 'srm:supplier-apply:update', 3, 3, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602534, '准入删除', 'srm:supplier-apply:delete', 3, 4, 602530, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
(602621, '询价查询', 'srm:inquiry:query', 3, 1, 602620, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602622, '询价创建', 'srm:inquiry:create', 3, 2, 602620, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602623, '询价更新', 'srm:inquiry:update', 3, 3, 602620, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602624, '询价删除', 'srm:inquiry:delete', 3, 4, 602620, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602625, '询价确认转订单', 'srm:inquiry:confirm', 3, 5, 602620, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602626, '询价录入报价', 'srm:inquiry:quote', 3, 6, 602620, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 4. ä¾›åº”商资质
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602535, '供应商资质', '', 2, 30, 602524, 'supplier-certificate', 'ep:medal', 'srm/supplierCertificate/index', 'SrmSupplierCertificate', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 3. æŠ•标保证金
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602630, '投标保证金', '', 2, 3, 2602, 'bid-bond', 'ep:money', 'srm/bidBond/index', 'SrmBidBond', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602536, '资质查询', 'srm:supplier-certificate:query', 3, 1, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602537, '资质创建', 'srm:supplier-certificate:create', 3, 2, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602538, '资质更新', 'srm:supplier-certificate:update', 3, 3, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602539, '资质删除', 'srm:supplier-certificate:delete', 3, 4, 602535, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
(602631, '保证金查询', 'srm:bid-bond:query', 3, 1, 602630, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602632, '保证金创建', 'srm:bid-bond:create', 3, 2, 602630, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602633, '保证金更新', 'srm:bid-bond:update', 3, 3, 602630, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602634, '保证金删除', 'srm:bid-bond:delete', 3, 4, 602630, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 5. ä¾›åº”商绩效
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602540, '供应商绩效', '', 2, 40, 602524, 'supplier-evaluation', 'ep:data-analysis', 'srm/supplierEvaluation/index', 'SrmSupplierEvaluation', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 4. ä¾›åº”商往来(采购管理根菜单下)
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602640, '供应商往来', '', 2, 8, 2602, 'supplier-statement', 'ep:wallet', 'erp/purchase/statement/index', 'ErpSupplierStatement', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602541, '评价查询', 'srm:supplier-evaluation:query', 3, 1, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602542, '评价创建', 'srm:supplier-evaluation:create', 3, 2, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602543, '评价更新', 'srm:supplier-evaluation:update', 3, 3, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602544, '评价删除', 'srm:supplier-evaluation:delete', 3, 4, 602540, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 6. æ‹›æŠ•标管理
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602545, '招投标管理', '', 2, 50, 602524, 'tender', 'ep:files', 'srm/tender/index', 'SrmTender', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
-- æ‹›æ ‡é¡¹ç›®
(602546, '招标查询', 'srm:tender-project:query', 3, 1, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602547, '招标创建', 'srm:tender-project:create', 3, 2, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602548, '招标更新', 'srm:tender-project:update', 3, 3, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602549, '招标删除', 'srm:tender-project:delete', 3, 4, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æŠ•æ ‡
(602550, '投标查询', 'srm:tender-bid:query', 3, 5, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602551, '投标创建', 'srm:tender-bid:create', 3, 6, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602552, '投标更新', 'srm:tender-bid:update', 3, 7, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- æŠ¥ä»·
(602553, '报价查询', 'srm:supplier-quote:query', 3, 8, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602554, '报价创建', 'srm:supplier-quote:create', 3, 9, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602555, '报价更新', 'srm:supplier-quote:update', 3, 10, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- è¯„æ ‡
(602556, '评标查询', 'srm:bid-evaluation:query', 3, 11, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602557, '评标创建', 'srm:bid-evaluation:create', 3, 12, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602558, '评标更新', 'srm:bid-evaluation:update', 3, 13, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å¼€æ ‡
(602559, '开标查询', 'srm:bid-open:query', 3, 14, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602560, '开标创建', 'srm:bid-open:create', 3, 15, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
-- å®šæ ‡
(602561, '定标查询', 'srm:bid-award:query', 3, 16, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602562, '定标创建', 'srm:bid-award:create', 3, 17, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602563, '定标更新', 'srm:bid-award:update', 3, 18, 602545, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 7. æ‹›æŠ•标配置
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602612, '招投标配置', '', 2, 55, 602524, 'tender-config', 'ep:setting', 'srm/tender/config/index', 'SrmTenderConfig', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602613, '配置查询', 'srm:tender-config:query', 3, 1, 602612, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
(602614, '配置更新', 'srm:tender-config:update', 3, 2, 602612, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
-- 8. ç»Ÿè®¡åˆ†æž
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602569, '统计分析', '', 2, 70, 602524, 'statistics', 'ep:trend-charts', 'srm/statistics/index', 'SrmStatistics', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES
(602570, '统计查询', 'srm:statistics:query', 3, 1, 602569, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
INSERT IGNORE INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
VALUES (602641, '供应商往来查询', 'erp:supplier-statement:query', 3, 1, 602640, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
yudao-module-srm/src/main/resources/sql/srm_test_data.sql
@@ -1,90 +1,39 @@
-- =====================================================
-- SRM æ¨¡å—测试数据
-- è¿è¡Œå‰è¯·å…ˆç¡®ä¿ srm_init.sql å·²æ‰§è¡Œ
-- ä»…包含:供应商、联系人、银行账户、分类关联
-- è¯¢ä»·/保证金数据请通过页面或接口创建,避免与真实单据编号冲突
-- =====================================================
-- 1. ä¾›åº”商测试数据
INSERT INTO srm_supplier (id, code, name, type, status, contact_name, contact_mobile, contact_email, address, bank_name, bank_account, tax_no, remark, create_time, update_time, creator, updater, deleted)
INSERT IGNORE INTO srm_supplier (id, code, name, type, status, contact_name, contact_mobile, contact_email, address, bank_name, bank_account, tax_no, remark, create_time, update_time, creator, updater, deleted)
VALUES
(1001, 'SUP-20240001', '江苏智能制造有限公司', 1, 1, '张三', '13800001111', 'zhangsan@test.com', '江苏省南京市江宁区xxè·¯100号', '中国银行南京分行', '6222024301000000001', '91320115MA1XXXXXX1', '优质供应商', NOW(), NOW(), 'admin', 'admin', 0),
(1002, 'SUP-20240002', '上海贸易进出口公司', 2, 1, '李四', '13800002222', 'lisi@test.com', '上海市浦东新区xxè·¯200号', '工商银行上海分行', '1002024301000000002', '91310115MA2XXXXXX2', '长期合作', NOW(), NOW(), 'admin', 'admin', 0),
(1003, 'SUP-20240003', '深圳科技服务有限公司', 3, 1, '王五', '13800003333', 'wangwu@test.com', '深圳市南山区xxè·¯300号', '招商银行深圳分行', '6225884301000000003', '91440300MA3XXXXXX3', '新供应商', NOW(), NOW(), 'admin', 'admin', 0),
(1004, 'SUP-20240004', '浙江精密机械有限公司', 1, 0, '赵六', '13800004444', 'zhaoliu@test.com', '浙江省杭州市西湖区xxè·¯400号', '建设银行杭州分行', '6227004301000000004', '91330100MA4XXXXXX4', '暂停合作', NOW(), NOW(), 'admin', 'admin', 0);
(1001, 'SUP-20240001', '江苏智能制造有限公司', 1, 0, '张三', '13800001111', 'zhangsan@test.com', '江苏省南京市江宁区xxè·¯100号', '中国银行南京分行', '6222024301000000001', '91320115MA1XXXXXX1', '优质供应商', NOW(), NOW(), 'admin', 'admin', b'0'),
(1002, 'SUP-20240002', '上海贸易进出口公司', 2, 0, '李四', '13800002222', 'lisi@test.com', '上海市浦东新区xxè·¯200号', '工商银行上海分行', '1002024301000000002', '91310115MA2XXXXXX2', '长期合作', NOW(), NOW(), 'admin', 'admin', b'0'),
(1003, 'SUP-20240003', '深圳科技服务有限公司', 3, 0, '王五', '13800003333', 'wangwu@test.com', '深圳市南山区xxè·¯300号', '招商银行深圳分行', '6225884301000000003', '91440300MA3XXXXXX3', '新供应商', NOW(), NOW(), 'admin', 'admin', b'0'),
(1004, 'SUP-20240004', '浙江精密机械有限公司', 1, 1, '赵六', '13800004444', 'zhaoliu@test.com', '浙江省杭州市西湖区xxè·¯400号', '建设银行杭州分行', '6227004301000000004', '91330100MA4XXXXXX4', '暂停合作', NOW(), NOW(), 'admin', 'admin', b'0');
-- 2. ä¾›åº”商联系人
INSERT INTO srm_supplier_contact (id, supplier_id, name, mobile, email, position, is_primary, remark, create_time, update_time, creator, updater, deleted)
-- 2. ä¾›åº”商联系人(每个供应商一条默认联系人)
INSERT IGNORE INTO srm_supplier_contact (id, supplier_id, name, mobile, email, position, is_default, sort, create_time, update_time, creator, updater, deleted)
VALUES
(2001, 1001, '张三', '13800001111', 'zhangsan@test.com', '采购经理', 1, '主要联系人', NOW(), NOW(), 'admin', 'admin', 0),
(2002, 1001, '张三丰', '13800001112', 'zhangsanfeng@test.com', '技术总监', 0, '技术对接', NOW(), NOW(), 'admin', 'admin', 0),
(2003, 1002, '李四', '13800002222', 'lisi@test.com', '总经理', 1, NULL, NOW(), NOW(), 'admin', 'admin', 0);
(2001, 1001, '张三', '13800001111', 'zhangsan@test.com', '采购经理', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0'),
(2002, 1001, '张三丰', '13800001112', 'zhangsanfeng@test.com', '技术总监', b'0', 1, NOW(), NOW(), 'admin', 'admin', b'0'),
(2003, 1002, '李四', '13800002222', 'lisi@test.com', '总经理', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0'),
(2004, 1003, '王五', '13800003333', 'wangwu@test.com', '销售经理', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0'),
(2005, 1004, '赵六', '13800004444', 'zhaoliu@test.com', '厂长', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0');
-- 3. ä¾›åº”商分类关联
INSERT INTO srm_supplier_category (id, supplier_id, category_id, category_name, create_time, update_time, creator, updater, deleted)
-- 3. ä¾›åº”商银行账户(每个供应商一条默认账户)
INSERT IGNORE INTO srm_supplier_bank (id, supplier_id, bank_name, bank_account, bank_no, bank_address, is_default, sort, create_time, update_time, creator, updater, deleted)
VALUES
(2101, 1001, '中国银行南京分行', '6222024301000000001', '104301000013', '南京市中山路1号', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0'),
(2102, 1001, '工商银行南京江宁支行', '6222024301000000099', '102301000088', '南京市江宁区xxè·¯100号', b'0', 1, NOW(), NOW(), 'admin', 'admin', b'0'),
(2103, 1002, '工商银行上海分行', '1002024301000000002', '102290000001', '上海市浦东新区xxè·¯200号', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0'),
(2104, 1003, '招商银行深圳分行', '6225884301000000003', '103584000002', '深圳市南山区xxè·¯300号', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0'),
(2105, 1004, '建设银行杭州分行', '6227004301000000004', '105331000003', '杭州市西湖区xxè·¯400号', b'1', 0, NOW(), NOW(), 'admin', 'admin', b'0');
-- 4. ä¾›åº”商分类关联
INSERT IGNORE INTO srm_supplier_category (id, supplier_id, category_id, category_name, create_time, update_time, creator, updater, deleted)
VALUES
(3001, 1001, 1, '机械加工', NOW(), NOW(), 'admin', 'admin', 0),
(3002, 1001, 2, '精密制造', NOW(), NOW(), 'admin', 'admin', 0),
(3003, 1002, 3, '贸易服务', NOW(), NOW(), 'admin', 'admin', 0);
-- 4. ä¾›åº”商准入申请
INSERT INTO srm_supplier_apply (id, apply_no, supplier_name, contact_name, contact_mobile, contact_email, address, bank_name, bank_account, tax_no, apply_status, audit_comment, remark, create_time, update_time, creator, updater, deleted)
VALUES
(4001, 'AP2024010001', '江苏智能制造有限公司', '张三', '13800001111', 'zhangsan@test.com', '江苏省南京市江宁区xxè·¯100号', '中国银行南京分行', '6222024301000000001', '91320115MA1XXXXXX1', 4, '资质齐全,审批通过', NULL, NOW(), NOW(), 'admin', 'admin', 0),
(4002, 'AP2024020002', '北京新科技有限公司', '刘七', '13800005555', 'liuqi@test.com', '北京市海淀区xxè·¯500号', '农业银行北京分行', '6228484301000000005', '91110108MA5XXXXXX5', 1, NULL, '待审核', NOW(), NOW(), 'admin', 'admin', 0);
-- 5. ä¾›åº”商资质证书
INSERT INTO srm_supplier_certificate (id, supplier_id, certificate_name, certificate_no, certificate_type, issuing_authority, issue_date, expire_date, remark, create_time, update_time, creator, updater, deleted)
VALUES
(5001, 1001, 'ISO9001质量管理体系证书', 'ISO9001-2024-001', 1, 'SGS认证机构', '2024-01-15', '2027-01-14', '三年有效期', NOW(), NOW(), 'admin', 'admin', 0),
(5002, 1001, 'ISO14001环境管理体系证书', 'ISO14001-2024-002', 2, 'TUV认证机构', '2024-03-01', '2027-02-28', NULL, NOW(), NOW(), 'admin', 'admin', 0),
(5003, 1002, '进出口贸易许可证', 'TRADE-2024-001', 4, '商务部', '2024-02-01', '2025-01-31', '即将到期', NOW(), NOW(), 'admin', 'admin', 0);
-- 6. ä¾›åº”商绩效评价
INSERT INTO srm_supplier_evaluation (id, supplier_id, evaluation_period, quality_score, delivery_score, price_score, service_score, composite_score, grade, remark, create_time, update_time, creator, updater, deleted)
VALUES
(6001, 1001, '2024-Q1', 92, 88, 85, 90, 89, 'B', '质量稳定,交付及时', NOW(), NOW(), 'admin', 'admin', 0),
(6002, 1001, '2024-Q2', 95, 92, 88, 93, 92, 'A', '各项指标优秀', NOW(), NOW(), 'admin', 'admin', 0),
(6003, 1002, '2024-Q1', 80, 75, 90, 85, 81, 'B', '价格有优势,交付略慢', NOW(), NOW(), 'admin', 'admin', 0);
-- 7. æ‹›æ ‡é¡¹ç›®
INSERT INTO srm_tender_project (id, tender_no, tender_name, tender_type, purchase_org, project_leader_id, project_leader_name, tender_status, budget_amount, start_time, end_time, bid_open_time, tender_desc, qualification_requirements, purchase_request_id, create_time, update_time, creator, updater, deleted)
VALUES
(7001, 'TEN-202401001', '2024å¹´Q1原材料采购招标', 1, '采购部', 1, '管理员', 4, 5000000.00, '2024-01-01 00:00:00', '2024-01-31 23:59:59', '2024-02-01 10:00:00', '采购不锈钢板材200吨', '1. æ³¨å†Œèµ„金≥500万 2. å…·å¤‡ISO9001认证', NULL, NOW(), NOW(), 'admin', 'admin', 0),
(7002, 'TEN-202402002', '2024å¹´Q2设备采购招标', 2, '设备部', 2, '王经理', 1, 2000000.00, '2024-04-01 00:00:00', '2024-04-30 23:59:59', '2024-05-01 14:00:00', '采购数控机床5台', '1. å…·å¤‡è®¾å¤‡ä»£ç†èµ„è´¨ 2. æä¾›å”®åŽæœåŠ¡æ‰¿è¯º', NULL, NOW(), NOW(), 'admin', 'admin', 0);
-- 8. æ‹›æ ‡ç‰©æ–™
INSERT INTO srm_tender_material (id, tender_project_id, item_code, item_name, spec, unit, quantity, remark, create_time, update_time, creator, updater, deleted)
VALUES
(8001, 7001, 'MAT-001', '不锈钢板304', '2.0*1500*3000mm', '吨', 100, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(8002, 7001, 'MAT-002', '不锈钢板316L', '1.5*1500*3000mm', '吨', 100, '耐腐蚀要求', NOW(), NOW(), 'admin', 'admin', 0),
(8003, 7002, 'MAT-003', '数控机床CNC-500', '加工范围500*400mm', '台', 5, '含安装调试', NOW(), NOW(), 'admin', 'admin', 0);
-- 9. æŠ•标记录
INSERT INTO srm_tender_bid (id, tender_project_id, supplier_id, bid_no, bid_status, bid_time, bid_total_amount, withdraw_reason, create_time, update_time, creator, updater, deleted)
VALUES
(9001, 7001, 1001, 'BID-202401-001', 1, '2024-01-15 10:00:00', 4800000.00, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(9002, 7001, 1002, 'BID-202401-002', 1, '2024-01-20 14:00:00', 4900000.00, NULL, NOW(), NOW(), 'admin', 'admin', 0);
-- 10. ä¾›åº”商报价
INSERT INTO srm_supplier_quote (id, bid_id, material_id, unit_price, total_price, delivery_days, remark, create_time, update_time, creator, updater, deleted)
VALUES
(10001, 9001, 8001, 22000.00, 2200000.00, 30, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(10002, 9001, 8002, 26000.00, 2600000.00, 30, '含运费', NOW(), NOW(), 'admin', 'admin', 0),
(10003, 9002, 8001, 23000.00, 2300000.00, 25, NULL, NOW(), NOW(), 'admin', 'admin', 0),
(10004, 9002, 8002, 26000.00, 2600000.00, 28, NULL, NOW(), NOW(), 'admin', 'admin', 0);
-- 11. å¼€æ ‡è®°å½•
INSERT INTO srm_bid_open (id, tender_project_id, open_no, open_time, bid_count, supplier_count, open_status, remark, create_time, update_time, creator, updater, deleted)
VALUES
(11001, 7001, 'OPEN-20240201', '2024-02-01 10:00:00', 2, 2, 1, '投标截止,共2家供应商参与', NOW(), NOW(), 'admin', 'admin', 0);
-- 12. è¯„标记录
INSERT INTO srm_bid_evaluation (id, tender_project_id, bid_id, technical_score, commercial_score, composite_score, rank, comment, create_time, update_time, creator, updater, deleted)
VALUES
(12001, 7001, 9001, 88, 85, 86, 1, '技术方案优秀,价格合理', NOW(), NOW(), 'admin', 'admin', 0),
(12002, 7001, 9002, 82, 90, 87, 2, '价格有优势,技术方案一般', NOW(), NOW(), 'admin', 'admin', 0);
-- 13. å®šæ ‡è®°å½•
INSERT INTO srm_bid_award (id, tender_project_id, supplier_id, bid_id, award_no, award_status, award_amount, award_time, purchase_order_id, purchase_order_no, remark, create_time, update_time, creator, updater, deleted)
VALUES
(13001, 7001, 1001, 9001, 'AWARD-202402-001', 1, 4800000.00, '2024-02-05 16:00:00', NULL, NULL, '综合评分第一', NOW(), NOW(), 'admin', 'admin', 0);