2026-08-05 3ca1cefd9bed863131ee3f684ff127bd7ac877aa
feat(ai): 优化AI知识文档处理功能

- 集成POI库支持Word文档(.doc/.docx)解析,提升中文文档处理准确性
- 实现storage-blob本地文件读取,避免HTTP认证问题并提升性能
- 添加文档解析异常处理机制,增强系统稳定性
- 优化向量存储批量添加逻辑,修复潜在并发问题

feat(crm): 增强CRM商机管理功能

- 集成MDM物料管理API,支持物料条码和规格型号展示
- 添加商机关联信息填充功能,完善数据展示
- 优化产品信息展示,增加计量单位名称显示

feat(erp): 完善ERP采购订单管理

- 集成计量单位API,支持单位信息展示
- 优化Excel导出功能,添加字典格式转换支持
- 添加库存信息实时查询功能
- 修复采购申请状态校验逻辑,支持更多业务场景

feat(mes): 优化MES生产反馈功能

- 重构审批接口,支持批次属性动态填写
- 添加审批前批次属性检查机制
- 集成仓库管理系统,统一物料仓库管理
- 移除物料分类叶子节点限制,提升系统灵活性

feat(srm): 增强SRM招标定标功能

- 添加投标记录验证机制,确保项目一致性
- 实现撤标状态检查,防止无效操作
- 优化供应商匹配验证,提升数据准确性

chore(config): 更新测试环境配置

- 调整Milvus测试数据库配置,使用独立测试集合
- 更新文件存储域名配置,优化前端访问路径
已修改26个文件
已添加10个文件
1140 ■■■■■ 文件已修改
.playwright-mcp/page-2026-08-04T07-44-03-300Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-04T07-44-19-942Z.yml 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-04T07-44-38-346Z.yml 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ai/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wm/warehouse/MesWmWarehouseApi.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wm/warehouse/dto/MesWmWarehouseRespDTO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/MesWmStockApi.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wm/warehouse/MesWmWarehouseApiImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/MesWmStockApiImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java 15 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackApproveReqVO.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackBatchCheckRespVO.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/src/main/resources/application-test.yaml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/src/main/resources/logback-spring.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-08-04T07-44-03-300Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-08-04T07-44-19-942Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,237 @@
- generic [ref=e403]:
  - banner [ref=e404]:
    - generic [ref=e408]:
      - generic [ref=e409]: ç”Ÿäº§å·¥ä½œå°
      - generic [ref=e410]: æ‹‰ä¸1号工作站
    - navigation [ref=e411]:
      - button "工单详情" [ref=e412] [cursor=pointer]
      - button "报工" [ref=e417] [cursor=pointer]
      - button "报工记录" [active] [ref=e420] [cursor=pointer]
      - button "生产记录参数" [ref=e425] [cursor=pointer]
      - button "领料" [ref=e430] [cursor=pointer]
      - button "退料" [ref=e435] [cursor=pointer]
    - button "返回系统" [ref=e440] [cursor=pointer]
  - generic [ref=e444]:
    - complementary [ref=e445]:
      - generic [ref=e446]:
        - generic [ref=e447]:
          - generic [ref=e448]: å·¥ä½œç«™
          - button "切换" [ref=e454] [cursor=pointer]
        - generic [ref=e455]:
          - generic [ref=e456]: WS_002
          - generic [ref=e457]: æ‹‰ä¸1号工作站
          - generic [ref=e458]: æ‹‰ä¸
        - generic [ref=e459]:
          - generic [ref=e460]:
            - generic [ref=e461]: è½¦é—´
            - generic [ref=e462]: æµ‹è¯•拉丝车间
          - generic [ref=e463]:
            - generic [ref=e464]: åœ°ç‚¹
            - generic [ref=e465]: â€”
        - button "上工" [ref=e467] [cursor=pointer]
      - generic [ref=e470]:
        - generic [ref=e471]:
          - generic [ref=e472]: å·¥å•列表
          - generic [ref=e473]: "6"
        - textbox "订单/产品/工序" [ref=e475]
        - generic [ref=e476]:
          - generic [ref=e479] [cursor=pointer]:
            - generic [ref=e480]:
              - generic [ref=e481]: TK_20260729002
              - generic [ref=e482]: è´¨æ£€
              - generic [ref=e483]: å€’冲
              - generic [ref=e484]: 0%
            - generic [ref=e485]:
              - generic [ref=e486]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e487]: 0 / 234
            - generic [ref=e489]:
              - generic [ref=e490]:
                - generic [ref=e491]: è®¢å•
                - generic [ref=e492]: WO1784883668267 ç”Ÿäº§å·¥å•-MPS20260724002
              - generic [ref=e493]:
                - generic [ref=e494]: å·¥åº
                - generic [ref=e495]: æ‹‰ä¸
              - generic [ref=e496]:
                - generic [ref=e497]: äº§å“
                - generic "铝单线" [ref=e498]: ITEM_20260716005 é“å•线
              - generic [ref=e499]:
                - generic [ref=e500]: å¯æŠ¥å·¥
                - generic [ref=e501]: 224 åƒå…‹
          - generic [ref=e504] [cursor=pointer]:
            - generic [ref=e505]:
              - generic [ref=e506]: TK_20260729001
              - generic [ref=e507]: è´¨æ£€
              - generic [ref=e508]: å€’冲
              - generic [ref=e509]: 0%
            - generic [ref=e510]:
              - generic [ref=e511]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e512]: 0 / 61
            - generic [ref=e514]:
              - generic [ref=e515]:
                - generic [ref=e516]: è®¢å•
                - generic [ref=e517]: WO_20260724003 é…’泉
              - generic [ref=e518]:
                - generic [ref=e519]: å·¥åº
                - generic [ref=e520]: æ‹‰ä¸
              - generic [ref=e521]:
                - generic [ref=e522]: äº§å“
                - generic "铝单线" [ref=e523]: ITEM_20260716005 é“å•线
              - generic [ref=e524]:
                - generic [ref=e525]: å¯æŠ¥å·¥
                - generic [ref=e526]: 61 åƒå…‹
          - generic [ref=e529] [cursor=pointer]:
            - generic [ref=e530]:
              - generic [ref=e531]: TK_20260724001
              - generic [ref=e532]: è´¨æ£€
              - generic [ref=e533]: å€’冲
              - generic [ref=e534]: 0%
            - generic [ref=e535]:
              - generic [ref=e536]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e537]: 0 / 1
            - generic [ref=e539]:
              - generic [ref=e540]:
                - generic [ref=e541]: è®¢å•
                - generic [ref=e542]: WO_20260724003 é…’泉
              - generic [ref=e543]:
                - generic [ref=e544]: å·¥åº
                - generic [ref=e545]: æ‹‰ä¸
              - generic [ref=e546]:
                - generic [ref=e547]: äº§å“
                - generic "铝单线" [ref=e548]: ITEM_20260716005 é“å•线
              - generic [ref=e549]:
                - generic [ref=e550]: å¯æŠ¥å·¥
                - generic [ref=e551]: 0 åƒå…‹
          - generic [ref=e554] [cursor=pointer]:
            - generic [ref=e555]:
              - generic [ref=e556]: TK_20260723001
              - generic [ref=e557]: è´¨æ£€
              - generic [ref=e558]: å€’冲
              - generic [ref=e559]: 27%
            - generic [ref=e560]:
              - generic [ref=e561]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e562]: 27 / 100
            - generic [ref=e565]:
              - generic [ref=e566]:
                - generic [ref=e567]: è®¢å•
                - generic [ref=e568]: WO_20260722009 é…’泉
              - generic [ref=e569]:
                - generic [ref=e570]: å·¥åº
                - generic [ref=e571]: æ‹‰ä¸
              - generic [ref=e572]:
                - generic [ref=e573]: äº§å“
                - generic "铝单线" [ref=e574]: ITEM_20260716005 é“å•线
              - generic [ref=e575]:
                - generic [ref=e576]: å¯æŠ¥å·¥
                - generic [ref=e577]: 73 åƒå…‹
          - generic [ref=e580] [cursor=pointer]:
            - generic [ref=e581]:
              - generic [ref=e582]: TK_20260717001
              - generic [ref=e583]: è´¨æ£€
              - generic [ref=e584]: å€’冲
              - generic [ref=e585]: 0.6%
            - generic [ref=e586]:
              - generic [ref=e587]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e588]: 6 / 1000
            - generic [ref=e591]:
              - generic [ref=e592]:
                - generic [ref=e593]: è®¢å•
                - generic [ref=e594]: WO_20260716050 æµ‹è¯•订单
              - generic [ref=e595]:
                - generic [ref=e596]: å·¥åº
                - generic [ref=e597]: æ‹‰ä¸
              - generic [ref=e598]:
                - generic [ref=e599]: äº§å“
                - generic "铝单线" [ref=e600]: ITEM_20260716005 é“å•线
              - generic [ref=e601]:
                - generic [ref=e602]: å¯æŠ¥å·¥
                - generic [ref=e603]: 994 åƒå…‹
          - generic [ref=e606] [cursor=pointer]:
            - generic [ref=e607]:
              - generic [ref=e608]: TK_20260716001
              - generic [ref=e609]: è´¨æ£€
              - generic [ref=e610]: å€’冲
              - generic [ref=e611]: 0%
            - generic [ref=e612]:
              - generic [ref=e613]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e614]: 0 / 12000
            - generic [ref=e616]:
              - generic [ref=e617]:
                - generic [ref=e618]: è®¢å•
                - generic [ref=e619]: WO_20260716050 æµ‹è¯•订单
              - generic [ref=e620]:
                - generic [ref=e621]: å·¥åº
                - generic [ref=e622]: æ‹‰ä¸
              - generic [ref=e623]:
                - generic [ref=e624]: äº§å“
                - generic "钢芯铝绞线" [ref=e625]: ITEM_20260716006 é’¢èŠ¯é“ç»žçº¿
              - generic [ref=e626]:
                - generic [ref=e627]: å¯æŠ¥å·¥
                - generic [ref=e628]: 10000 åƒå…‹
    - main [ref=e631]:
      - generic [ref=e632]:
        - generic [ref=e633]: æŠ¥å·¥è®°å½•
        - generic [ref=e634]: å·¥å• TK_20260729002
      - generic [ref=e725]:
        - generic [ref=e727]:
          - generic [ref=e728]: æŠ¥å·¥è®°å½•
          - generic [ref=e731]:
            - button "" [ref=e732] [cursor=pointer]
            - button "" [ref=e734] [cursor=pointer]
            - button "" [ref=e736] [cursor=pointer]
        - generic [ref=e743]:
          - generic [ref=e744]:
            - table [ref=e748]:
              - rowgroup [ref=e762]:
                - row [ref=e763]:
                  - columnheader "报订单号" [ref=e764]
                  - columnheader "任务编码" [ref=e769]
                  - columnheader "工序" [ref=e774]
                  - columnheader "产品" [ref=e779]
                  - columnheader "报工数量" [ref=e784]
                  - columnheader "合格品" [ref=e789]
                  - columnheader "不良品" [ref=e794]
                  - columnheader "报工人" [ref=e799]
                  - columnheader "审核人" [ref=e804]
                  - columnheader "状态" [ref=e809]
                  - columnheader "报工时间" [ref=e814]
            - table [ref=e822]:
              - rowgroup [ref=e836]:
                - row [ref=e837]:
                  - cell [ref=e838]:
                    - button "FB_20260804006" [ref=e841] [cursor=pointer]
                  - cell "TK_20260729002" [ref=e843]
                  - cell "拉丝" [ref=e847]
                  - cell "铝单线" [ref=e851]
                  - cell "10" [ref=e855]
                  - cell "0" [ref=e859]
                  - cell "0" [ref=e863]
                  - cell "芋道源码" [ref=e867]
                  - cell "test01" [ref=e871]
                  - cell "审批中" [ref=e875]
                  - cell "2026-08-04 15:42:04" [ref=e879]
          - generic [ref=e883]:
            - table [ref=e886]:
              - rowgroup [ref=e900]:
                - row [ref=e901]:
                  - columnheader "操作" [ref=e902]
            - table [ref=e909]:
              - rowgroup [ref=e912]:
                - row [ref=e913]:
                  - cell [ref=e914]:
                    - generic [ref=e917]:
                      - button "查看" [ref=e919] [cursor=pointer]
                      - button "审批通过" [ref=e922] [cursor=pointer]
                      - button "驳回" [ref=e925] [cursor=pointer]
        - generic [ref=e933]:
          - generic [ref=e934]: å…± 1 æ¡è®°å½•
          - generic [ref=e936] [cursor=pointer]:
            - generic "20条/页" [ref=e937]:
              - textbox "请选择" [ref=e938]: 20条/页
            - generic [ref=e939]: î£­
          - button "" [ref=e942]
          - button "" [ref=e944]
          - button "" [ref=e946]
          - button "1" [ref=e949] [cursor=pointer]
          - button "" [ref=e950]
          - button "" [ref=e952]
          - button "" [ref=e954]
.playwright-mcp/page-2026-08-04T07-44-38-346Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,273 @@
- generic [ref=e403]:
  - banner [ref=e404]:
    - generic [ref=e408]:
      - generic [ref=e409]: ç”Ÿäº§å·¥ä½œå°
      - generic [ref=e410]: æ‹‰ä¸1号工作站
    - navigation [ref=e411]:
      - button "工单详情" [ref=e412] [cursor=pointer]
      - button "报工" [active] [ref=e417] [cursor=pointer]
      - button "报工记录" [ref=e420] [cursor=pointer]
      - button "生产记录参数" [ref=e425] [cursor=pointer]
      - button "领料" [ref=e430] [cursor=pointer]
      - button "退料" [ref=e435] [cursor=pointer]
    - button "返回系统" [ref=e440] [cursor=pointer]
  - generic [ref=e444]:
    - complementary [ref=e445]:
      - generic [ref=e446]:
        - generic [ref=e447]:
          - generic [ref=e448]: å·¥ä½œç«™
          - button "切换" [ref=e454] [cursor=pointer]
        - generic [ref=e455]:
          - generic [ref=e456]: WS_002
          - generic [ref=e457]: æ‹‰ä¸1号工作站
          - generic [ref=e458]: æ‹‰ä¸
        - generic [ref=e459]:
          - generic [ref=e460]:
            - generic [ref=e461]: è½¦é—´
            - generic [ref=e462]: æµ‹è¯•拉丝车间
          - generic [ref=e463]:
            - generic [ref=e464]: åœ°ç‚¹
            - generic [ref=e465]: â€”
        - button "上工" [ref=e467] [cursor=pointer]
      - generic [ref=e470]:
        - generic [ref=e471]:
          - generic [ref=e472]: å·¥å•列表
          - generic [ref=e473]: "6"
        - textbox "订单/产品/工序" [ref=e475]
        - generic [ref=e476]:
          - generic [ref=e479] [cursor=pointer]:
            - generic [ref=e480]:
              - generic [ref=e481]: TK_20260729002
              - generic [ref=e482]: è´¨æ£€
              - generic [ref=e483]: å€’冲
              - generic [ref=e484]: 0%
            - generic [ref=e485]:
              - generic [ref=e486]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e487]: 0 / 234
            - generic [ref=e489]:
              - generic [ref=e490]:
                - generic [ref=e491]: è®¢å•
                - generic [ref=e492]: WO1784883668267 ç”Ÿäº§å·¥å•-MPS20260724002
              - generic [ref=e493]:
                - generic [ref=e494]: å·¥åº
                - generic [ref=e495]: æ‹‰ä¸
              - generic [ref=e496]:
                - generic [ref=e497]: äº§å“
                - generic "铝单线" [ref=e498]: ITEM_20260716005 é“å•线
              - generic [ref=e499]:
                - generic [ref=e500]: å¯æŠ¥å·¥
                - generic [ref=e501]: 224 åƒå…‹
          - generic [ref=e504] [cursor=pointer]:
            - generic [ref=e505]:
              - generic [ref=e506]: TK_20260729001
              - generic [ref=e507]: è´¨æ£€
              - generic [ref=e508]: å€’冲
              - generic [ref=e509]: 0%
            - generic [ref=e510]:
              - generic [ref=e511]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e512]: 0 / 61
            - generic [ref=e514]:
              - generic [ref=e515]:
                - generic [ref=e516]: è®¢å•
                - generic [ref=e517]: WO_20260724003 é…’泉
              - generic [ref=e518]:
                - generic [ref=e519]: å·¥åº
                - generic [ref=e520]: æ‹‰ä¸
              - generic [ref=e521]:
                - generic [ref=e522]: äº§å“
                - generic "铝单线" [ref=e523]: ITEM_20260716005 é“å•线
              - generic [ref=e524]:
                - generic [ref=e525]: å¯æŠ¥å·¥
                - generic [ref=e526]: 61 åƒå…‹
          - generic [ref=e529] [cursor=pointer]:
            - generic [ref=e530]:
              - generic [ref=e531]: TK_20260724001
              - generic [ref=e532]: è´¨æ£€
              - generic [ref=e533]: å€’冲
              - generic [ref=e534]: 0%
            - generic [ref=e535]:
              - generic [ref=e536]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e537]: 0 / 1
            - generic [ref=e539]:
              - generic [ref=e540]:
                - generic [ref=e541]: è®¢å•
                - generic [ref=e542]: WO_20260724003 é…’泉
              - generic [ref=e543]:
                - generic [ref=e544]: å·¥åº
                - generic [ref=e545]: æ‹‰ä¸
              - generic [ref=e546]:
                - generic [ref=e547]: äº§å“
                - generic "铝单线" [ref=e548]: ITEM_20260716005 é“å•线
              - generic [ref=e549]:
                - generic [ref=e550]: å¯æŠ¥å·¥
                - generic [ref=e551]: 0 åƒå…‹
          - generic [ref=e554] [cursor=pointer]:
            - generic [ref=e555]:
              - generic [ref=e556]: TK_20260723001
              - generic [ref=e557]: è´¨æ£€
              - generic [ref=e558]: å€’冲
              - generic [ref=e559]: 27%
            - generic [ref=e560]:
              - generic [ref=e561]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e562]: 27 / 100
            - generic [ref=e565]:
              - generic [ref=e566]:
                - generic [ref=e567]: è®¢å•
                - generic [ref=e568]: WO_20260722009 é…’泉
              - generic [ref=e569]:
                - generic [ref=e570]: å·¥åº
                - generic [ref=e571]: æ‹‰ä¸
              - generic [ref=e572]:
                - generic [ref=e573]: äº§å“
                - generic "铝单线" [ref=e574]: ITEM_20260716005 é“å•线
              - generic [ref=e575]:
                - generic [ref=e576]: å¯æŠ¥å·¥
                - generic [ref=e577]: 73 åƒå…‹
          - generic [ref=e580] [cursor=pointer]:
            - generic [ref=e581]:
              - generic [ref=e582]: TK_20260717001
              - generic [ref=e583]: è´¨æ£€
              - generic [ref=e584]: å€’冲
              - generic [ref=e585]: 0.6%
            - generic [ref=e586]:
              - generic [ref=e587]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e588]: 6 / 1000
            - generic [ref=e591]:
              - generic [ref=e592]:
                - generic [ref=e593]: è®¢å•
                - generic [ref=e594]: WO_20260716050 æµ‹è¯•订单
              - generic [ref=e595]:
                - generic [ref=e596]: å·¥åº
                - generic [ref=e597]: æ‹‰ä¸
              - generic [ref=e598]:
                - generic [ref=e599]: äº§å“
                - generic "铝单线" [ref=e600]: ITEM_20260716005 é“å•线
              - generic [ref=e601]:
                - generic [ref=e602]: å¯æŠ¥å·¥
                - generic [ref=e603]: 994 åƒå…‹
          - generic [ref=e606] [cursor=pointer]:
            - generic [ref=e607]:
              - generic [ref=e608]: TK_20260716001
              - generic [ref=e609]: è´¨æ£€
              - generic [ref=e610]: å€’冲
              - generic [ref=e611]: 0%
            - generic [ref=e612]:
              - generic [ref=e613]: æŠ¥å·¥è¿›åº¦
              - generic [ref=e614]: 0 / 12000
            - generic [ref=e616]:
              - generic [ref=e617]:
                - generic [ref=e618]: è®¢å•
                - generic [ref=e619]: WO_20260716050 æµ‹è¯•订单
              - generic [ref=e620]:
                - generic [ref=e621]: å·¥åº
                - generic [ref=e622]: æ‹‰ä¸
              - generic [ref=e623]:
                - generic [ref=e624]: äº§å“
                - generic "钢芯铝绞线" [ref=e625]: ITEM_20260716006 é’¢èŠ¯é“ç»žçº¿
              - generic [ref=e626]:
                - generic [ref=e627]: å¯æŠ¥å·¥
                - generic [ref=e628]: 10000 åƒå…‹
    - main [ref=e631]:
      - generic [ref=e632]:
        - generic [ref=e633]: æŠ¥å·¥
        - generic [ref=e634]: å·¥å• TK_20260729002
      - generic [ref=e956]:
        - generic [ref=e957]:
          - heading "新建报工" [level=3] [ref=e958]
          - generic [ref=e959]: "工单: WO1784883668267 ç”Ÿäº§å·¥å•-MPS20260724002"
        - generic [ref=e961]:
          - generic [ref=e962]:
            - generic [ref=e963]:
              - generic [ref=e964]: "*"
              - text: æŠ¥è®¢å•号
            - generic [ref=e968]:
              - textbox "* æŠ¥è®¢å•号" [ref=e969]:
                - /placeholder: è¯·è¾“入报订单号
                - text: FB_20260804009
              - button "生 æˆ" [ref=e971] [cursor=pointer]
          - generic [ref=e973]:
            - generic [ref=e974]:
              - generic [ref=e975]: "*"
              - text: æŠ¥å·¥ç±»åž‹
            - generic [ref=e981] [cursor=pointer]:
              - combobox "* æŠ¥å·¥ç±»åž‹" [ref=e983]
              - generic "自行报工" [ref=e984]
          - generic [ref=e985]:
            - generic [ref=e986]:
              - generic [ref=e987]: "*"
              - text: ç”Ÿäº§è®¢å•
            - textbox "请选择订单" [ref=e994]: WO1784883668267
          - generic [ref=e1000]:
            - generic [ref=e1001]:
              - generic [ref=e1002]: "*"
              - text: ç”Ÿäº§ä»»åŠ¡
            - textbox "请选择任务" [ref=e1009]: TK_20260729002
          - generic [ref=e1015]:
            - generic [ref=e1016]:
              - generic [ref=e1017]: "*"
              - text: å·¥ä½œç«™
            - textbox "请选择工作站" [ref=e1024]: æ‹‰ä¸1号工作站
          - generic [ref=e1030]:
            - generic [ref=e1031]: äº§å“ç¼–码
            - textbox "产品编码" [disabled] [ref=e1036]:
              - /placeholder: è¯·è¾“å…¥
              - text: ITEM_20260716005
          - generic [ref=e1037]:
            - generic [ref=e1038]: äº§å“åç§°
            - textbox "产品名称" [disabled] [ref=e1043]:
              - /placeholder: è¯·è¾“å…¥
              - text: é“å•线
          - generic [ref=e1044]:
            - generic [ref=e1045]: å•位
            - textbox "单位" [disabled] [ref=e1050]:
              - /placeholder: è¯·è¾“å…¥
              - text: åƒå…‹
          - generic [ref=e1051]:
            - generic [ref=e1052]: è§„æ ¼
            - textbox "规格" [disabled] [ref=e1057]:
              - /placeholder: è¯·è¾“å…¥
              - text: L-1.72
          - generic [ref=e1058]:
            - generic [ref=e1059]:
              - generic [ref=e1060]: "*"
              - text: æŠ¥å·¥æ•°é‡
            - generic [ref=e1065]:
              - generic [ref=e1066]:
                - button "Increase Value" [ref=e1067] [cursor=pointer]:
                  - img "up" [ref=e1068]
                - button "Decrease Value" [ref=e1071] [cursor=pointer]:
                  - img "down" [ref=e1072]
              - spinbutton "* æŠ¥å·¥æ•°é‡" [ref=e1076]
          - generic [ref=e1077]:
            - generic [ref=e1078]:
              - generic [ref=e1079]: "*"
              - text: æŠ¥å·¥äºº
            - generic [ref=e1085] [cursor=pointer]:
              - combobox "* æŠ¥å·¥äºº" [ref=e1087]
              - generic "芋道源码" [ref=e1088]
          - generic [ref=e1093]:
            - generic [ref=e1094]:
              - generic [ref=e1095]: "*"
              - text: æŠ¥å·¥æ—¶é—´
            - generic [ref=e1101]:
              - textbox "* æŠ¥å·¥æ—¶é—´" [ref=e1102]:
                - /placeholder: è¯·é€‰æ‹©æŠ¥å·¥æ—¶é—´
                - text: Invalid Date
              - generic:
                - img "calendar"
              - button [ref=e1103] [cursor=pointer]:
                - img "close-circle" [ref=e1104]
          - generic [ref=e1107]:
            - generic [ref=e1108]:
              - generic [ref=e1109]: "*"
              - text: å®¡æ ¸äºº
            - generic [ref=e1115] [cursor=pointer]:
              - combobox "* å®¡æ ¸äºº" [ref=e1117]
              - generic: è¯·é€‰æ‹©å®¡æ ¸äºº
          - generic [ref=e1118]:
            - generic [ref=e1119]: å¤‡æ³¨
            - textbox "备注" [ref=e1124]:
              - /placeholder: è¯·è¾“入备注
        - generic [ref=e1125]:
          - button "保 å­˜" [ref=e1126] [cursor=pointer]
          - button "保存并提交" [ref=e1128] [cursor=pointer]
yudao-module-ai/pom.xml
@@ -167,6 +167,13 @@
            </exclusions>
        </dependency>
        <!-- Apache POI:负责 .doc/.docx æ–‡ä»¶å†…容提取,比 Tika å¯¹ä¸­æ–‡æ”¯æŒæ›´å¥½ -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.4.0</version>
        </dependency>
        <!-- MCP ç›¸å…³ -->
        <!--
            ç‰¹æ®Šè¯´æ˜Žï¼šä¸èƒ½ä½¿ç”¨ spring-ai-starter-mcp-server-webflux æˆ– spring-ai-starter-mcp-client-webflux ï¼ï¼ï¼
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeDocumentServiceImpl.java
@@ -28,6 +28,10 @@
import org.apache.tika.parser.Parser;
import org.apache.tika.extractor.EmbeddedDocumentExtractor;
import org.apache.tika.sax.BodyContentHandler;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.extractor.WordExtractor;
import org.xml.sax.ContentHandler;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
@@ -57,6 +61,8 @@
    private AiKnowledgeSegmentService segmentService;
    @Resource
    private AiModelService modelService;
    @Resource
    private cn.iocoder.yudao.module.system.service.storage.SystemStorageBlobService storageBlobService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -113,11 +119,28 @@
        }
    }
    /**
     * ä»Ž storage-blob URL è§£æžå‡º fileName å’Œ token
     */
    private static java.util.regex.Pattern STORAGE_URL_PATTERN =
            java.util.regex.Pattern.compile("/storage-blob/(?:preview|download)/([^?]+)\\?.*token=([^&]+)");
    private byte[] downloadFile(String fileUrl) {
        // ä¼˜å…ˆé€šè¿‡ storage-blob æœ¬åœ°è¯»å–,避免 HTTP è®¤è¯é—®é¢˜
        var matcher = STORAGE_URL_PATTERN.matcher(fileUrl);
        if (matcher.find()) {
            String fileName = matcher.group(1);
            String token = matcher.group(2);
            try {
                java.io.File file = storageBlobService.getFileByToken(fileName, token);
                return java.nio.file.Files.readAllBytes(file.toPath());
            } catch (Exception e) {
                log.warn("本地读取存储文件失败,回退 HTTP ä¸‹è½½: {}", e.getMessage());
            }
        }
        // å›žé€€ HTTP ä¸‹è½½
        try {
            // æ‰‹åŠ¨ç¼–ç  URL è·¯å¾„中的非 ASCII å­—符,避免 URI æž„造函数报错
            String encodedUrl = encodeUrlPath(fileUrl);
            URL url = URL.of(new URI(encodedUrl), null);
            URL url = URL.of(new URI(encodeUrlPath(fileUrl)), null);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setConnectTimeout(30000);
            conn.setReadTimeout(60000);
@@ -184,7 +207,36 @@
            if (ArrayUtil.contains(new String[]{"txt", "md", "json", "xml", "csv", "yaml", "yml"}, ext)) {
                return new String(fileBytes, StandardCharsets.UTF_8);
            }
            // ä½¿ç”¨ AutoDetectParser + EmbeddedDocumentExtractor è·³è¿‡åµŒå…¥å›¾ç‰‡ï¼Œé¿å…æå–到二进制乱码
            // .docx ä¼˜å…ˆä½¿ç”¨ POI æå–,中文支持更好
            if ("docx".equals(ext)) {
                try (XWPFDocument doc = new XWPFDocument(new java.io.ByteArrayInputStream(fileBytes));
                     XWPFWordExtractor extractor = new XWPFWordExtractor(doc)) {
                    String text = extractor.getText();
                    if (StrUtil.isNotEmpty(text)) return text.trim();
                } catch (Exception e) {
                    log.warn("POI è§£æž docx å¤±è´¥ï¼Œå›žé€€ Tika: {}", e.getMessage());
                }
            }
            // .doc ä¼˜å…ˆä½¿ç”¨ POI æå–
            if ("doc".equals(ext)) {
                try (HWPFDocument doc = new HWPFDocument(new java.io.ByteArrayInputStream(fileBytes));
                     WordExtractor extractor = new WordExtractor(doc)) {
                    String text = extractor.getText();
                    if (StrUtil.isNotEmpty(text)) return text.trim();
                } catch (Exception e) {
                    log.warn("POI è§£æž doc å¤±è´¥ï¼Œå›žé€€ Tika: {}", e.getMessage());
                }
            }
            // å…¶ä»–格式或 POI å¤±è´¥æ—¶å›žé€€ Tika
            return extractTextWithTika(fileBytes);
        } catch (Exception e) {
            log.warn("文档内容提取失败,尝试 UTF-8 æ–‡æœ¬è¯»å–: {}", e.getMessage());
            return "";
        }
    }
    private String extractTextWithTika(byte[] fileBytes) {
        try {
            Parser parser = new AutoDetectParser();
            ParseContext context = new ParseContext();
            context.set(EmbeddedDocumentExtractor.class, new EmbeddedDocumentExtractor() {
@@ -203,7 +255,7 @@
            }
            return handler.toString().trim();
        } catch (Exception e) {
            log.warn("Tika è§£æžæ–‡æ¡£å¤±è´¥ï¼Œå°è¯• UTF-8 æ–‡æœ¬è¯»å–: {}", e.getMessage());
            log.warn("Tika è§£æžå¤±è´¥: {}", e.getMessage());
            return "";
        }
    }
yudao-module-ai/src/main/java/cn/iocoder/yudao/module/ai/service/knowledge/AiKnowledgeSegmentServiceImpl.java
@@ -217,7 +217,7 @@
        try {
            for (int i = 0; i < documents.size(); i += batchSize) {
                int end = Math.min(i + batchSize, documents.size());
                vectorStore.add(documents.subList(i, end));
                vectorStore.add(new ArrayList<>(documents.subList(i, end)));
            }
            for (AiKnowledgeSegmentDO segment : segments) {
                segment.setVectorId(segment.getId().toString());
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -19,6 +19,8 @@
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
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.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -63,6 +65,8 @@
    private CrmBusinessStatusService businessStatusService;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private AdminUserApi adminUserApi;
@@ -119,10 +123,19 @@
        List<CrmBusinessProductDO> businessProducts = businessService.getBusinessProductListByBusinessId(businessVO.getId());
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(businessProducts, CrmBusinessProductDO::getItemId));
        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(businessProducts, CrmBusinessProductDO::getItemUnitId));
        businessVO.setProducts(BeanUtils.toBean(businessProducts, CrmBusinessRespVO.Product.class, businessProductVO ->
                MapUtils.findAndThen(itemMap, businessProductVO.getItemId(),
                        item -> businessProductVO.setItemName(item.getName())
                                .setItemCode(item.getCode()).setItemUnitId(item.getUnitMeasureId()))));
                                .setItemCode(item.getCode())
                                .setBarCode(item.getBarCode())
                                .setItemSpecification(item.getSpecification()))));
        // å¡«å……单位名称
        businessVO.getProducts().forEach(product -> {
            MapUtils.findAndThen(unitMap, product.getItemUnitId(),
                    unit -> product.setItemUnitName(unit.getName()));
        });
        return businessVO;
    }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
@@ -132,6 +132,14 @@
        private String itemCode;
        @Schema(description = "计量单位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        private Long itemUnitId;
        @Schema(description = "计量单位名称", example = "个")
        private String itemUnitName;
        @Schema(description = "物料条码", example = "12345678")
        private String barCode;
        @Schema(description = "规格型号", example = "M8*20")
        private String itemSpecification;
        @Schema(description = "物料原价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
        private BigDecimal itemPrice;
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java
@@ -13,7 +13,9 @@
import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationSaveReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.quotation.CrmSaleQuotationProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationService;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
@@ -66,6 +68,8 @@
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private CrmCustomerService customerService;
    @Resource
    private CrmBusinessService businessService;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
@@ -178,6 +182,13 @@
                respVO.setCustomerName(customer.getName());
            }
        }
        // 2.1 å¡«å……商机名称
        if (saleQuotation.getBusinessId() != null) {
            CrmBusinessDO business = businessService.getBusiness(saleQuotation.getBusinessId());
            if (business != null) {
                respVO.setBusinessName(business.getName());
            }
        }
        // 3. å¡«å……关联信息
        fillSaleQuotationRelatedFields(respVO);
        // 4. å¡«å……附件列表
@@ -204,6 +215,9 @@
        // 1.5 éƒ¨é—¨ä¿¡æ¯
        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
                convertSet(pageResult.getList(), CrmSaleQuotationDO::getOwnerUserId));
        // 1.6 å•†æœºä¿¡æ¯ï¼ˆè¿‡æ»¤ null,businessId ä¸ºå¯é€‰å­—段)
        Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
                convertSet(pageResult.getList(), CrmSaleQuotationDO::getBusinessId, vo -> vo.getBusinessId() != null));
        // 2. å¼€å§‹æ‹¼æŽ¥
        return BeanUtils.toBean(pageResult, CrmSaleQuotationRespVO.class, vo -> {
@@ -213,6 +227,7 @@
            MapUtils.findAndThen(customerMap, vo.getCustomerId(), customer -> vo.setCustomerName(customer.getName()));
            MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
            MapUtils.findAndThen(deptMap, vo.getOwnerUserId(), dept -> vo.setOwnerUserDeptName(dept.getName()));
            MapUtils.findAndThen(businessMap, vo.getBusinessId(), business -> vo.setBusinessName(business.getName()));
        });
    }
yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -257,6 +257,9 @@
                if (product.getItemUnitId() == null) {
                    product.setItemUnitId(old.getItemUnitId());
                }
            } else {
                // æ–°äº§å“ï¼šæ¸…除前端生成的临时 ID,让数据库 AUTO_INCREMENT æŽ¥ç®¡
                product.setId(null);
            }
            // ä¼˜å…ˆä½¿ç”¨å‰ç«¯ä¼ å…¥çš„ totalPrice,否则通过 businessPrice * count è®¡ç®—
            if (o.getTotalPrice() != null) {
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -19,6 +19,8 @@
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
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.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -56,6 +58,8 @@
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
@@ -123,12 +127,16 @@
        List<ErpPurchaseOrderItemDO> purchaseOrderItemList = purchaseOrderService.getPurchaseOrderItemListByOrderId(id);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseOrderItemList, ErpPurchaseOrderItemDO::getProductId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(purchaseOrderItemList, ErpPurchaseOrderItemDO::getProductUnitId));
        return success(BeanUtils.toBean(purchaseOrder, ErpPurchaseOrderRespVO.class, purchaseOrderVO ->
                purchaseOrderVO.setItems(BeanUtils.toBean(purchaseOrderItemList, ErpPurchaseOrderRespVO.Item.class, item -> {
                    // åº“存信息暂不查询(仓储物流模块)
                    item.setStockCount(BigDecimal.ZERO);
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                    MapUtils.findAndThen(unitMeasureMap, item.getProductUnitId(), unit ->
                            item.setProductUnitName(unit.getName()));
                })).setAttachmentList(storageAttachmentApi.listAttachments("erp_purchase_order", id))
                        .setSourceTypeName(ErpPurchaseOrderSourceTypeEnum.WORK_ORDER.getType()
                                .equals(purchaseOrder.getSourceType()) ? "生产工单" : null)));
@@ -180,11 +188,18 @@
        // 1.4 ç®¡ç†å‘˜ä¿¡æ¯
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(pageResult.getList(), purchaseOrder -> Long.parseLong(purchaseOrder.getCreator())));
        // 1.5 å•位信息
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(purchaseOrderItemList, ErpPurchaseOrderItemDO::getProductUnitId));
        // 2. å¼€å§‹æ‹¼æŽ¥
        return BeanUtils.toBean(pageResult, ErpPurchaseOrderRespVO.class, purchaseOrder -> {
            purchaseOrder.setItems(BeanUtils.toBean(purchaseOrderItemMap.get(purchaseOrder.getId()), ErpPurchaseOrderRespVO.Item.class,
                    item -> MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()))));
                    item -> {
                        MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                                .setProductBarCode(mdmItem.getBarCode()));
                        MapUtils.findAndThen(unitMeasureMap, item.getProductUnitId(), unit ->
                                item.setProductUnitName(unit.getName()));
                    }));
            purchaseOrder.setProductNames(CollUtil.join(purchaseOrder.getItems(), ",", ErpPurchaseOrderRespVO.Item::getProductName));
            MapUtils.findAndThen(supplierMap, purchaseOrder.getSupplierId(), supplier -> purchaseOrder.setSupplierName(supplier.getName()));
            MapUtils.findAndThen(userMap, Long.parseLong(purchaseOrder.getCreator()), user -> purchaseOrder.setCreatorName(user.getNickname()));
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseRequestController.java
@@ -18,6 +18,8 @@
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
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.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
@@ -56,6 +58,8 @@
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
@@ -99,10 +103,14 @@
        List<ErpPurchaseRequestItemDO> purchaseRequestItemList = purchaseRequestService.getPurchaseRequestItemListByRequestId(id);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(purchaseRequestItemList, ErpPurchaseRequestItemDO::getProductUnitId));
        return success(BeanUtils.toBean(purchaseRequest, ErpPurchaseRequestRespVO.class, purchaseRequestVO ->
                purchaseRequestVO.setItems(BeanUtils.toBean(purchaseRequestItemList, ErpPurchaseRequestRespVO.Item.class, item -> {
                    MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                            .setProductBarCode(mdmItem.getBarCode()));
                    MapUtils.findAndThen(unitMeasureMap, item.getProductUnitId(), unit ->
                            item.setProductUnitName(unit.getName()));
                })).setAttachmentList(storageAttachmentApi.listAttachments("erp_purchase_request", id))));
    }
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
@@ -2,6 +2,9 @@
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.erp.enums.DictTypeConstants;
import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
@@ -25,7 +28,8 @@
    private String no;
    @Schema(description = "采购状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
    @ExcelProperty("采购状态")
    @ExcelProperty(value = "采购状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.AUDIT_STATUS)
    private Integer status;
    @Schema(description = "供应商编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1724")
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -29,6 +29,9 @@
    @ExcelProperty(value = "状态", converter = cn.iocoder.yudao.module.erp.convert.ErpPurchaseRequestStatusConvert.class)
    private Integer status;
    @Schema(description = "流程实例编号", example = "1024")
    private String processInstanceId;
    @Schema(description = "申请人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @ExcelProperty("申请人编号")
    private Long requestUserId;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -24,6 +24,7 @@
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
import cn.iocoder.yudao.module.mes.api.productsales.MesWmProductSalesLineApi;
import cn.iocoder.yudao.module.mes.api.salesnotice.MesWmSalesNoticeApi;
import cn.iocoder.yudao.module.mes.api.wms.MesWmStockApi;
import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -72,6 +73,8 @@
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
    private MdmUnitMeasureApi mdmUnitMeasureApi;
    @Resource
    private MesWmStockApi mesWmStockApi;
    @PostMapping("/create")
    @Operation(summary = "创建销售订单")
@@ -158,7 +161,7 @@
                convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductUnitId));
        // 3. è½¬æ¢è®¢å•项
        List<ErpSaleOrderRespVO.Item> itemVOList = BeanUtils.toBean(saleOrderItemList, ErpSaleOrderRespVO.Item.class, item -> {
            item.setStockCount(BigDecimal.ZERO); // åº“存信息暂不查询(仓储物流模块)
            item.setStockCount(mesWmStockApi.getStockQuantity(item.getProductId()));
            item.setBatchCode(batchCodeMap.get(item.getId()));
            MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem ->
                    item.setProductName(mdmItem.getName()).setProductBarCode(mdmItem.getBarCode())
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -129,7 +129,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void updatePurchaseRequest(ErpPurchaseRequestSaveReqVO updateReqVO) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExistsAndDraft(updateReqVO.getId());
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestCanModify(updateReqVO.getId());
        // 2. æ ¡éªŒä¾›åº”商(如果指定)
        if (updateReqVO.getSupplierId() != null) {
            supplierService.validateSupplier(updateReqVO.getSupplierId());
@@ -198,8 +198,9 @@
            return;
        }
        purchaseRequests.forEach(purchaseRequest -> {
            // åªæœ‰è‰ç¨¿çŠ¶æ€æ‰èƒ½åˆ é™¤
            if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
            Integer status = purchaseRequest.getStatus();
            if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(status)
                    && !ErpPurchaseRequestStatusEnum.REJECT.getStatus().equals(status)) {
                throw exception(PURCHASE_REQUEST_DELETE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
            }
        });
@@ -246,7 +247,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void submitPurchaseRequest(Long id, String processDefinitionKey, Long userId) {
        // 1. æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExistsAndDraft(id);
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestCanModify(id);
        // 2. æ ¡éªŒæ˜¯å¦æœ‰æ˜Žç»†
        List<ErpPurchaseRequestItemDO> items = purchaseRequestItemMapper.selectListByRequestId(id);
@@ -455,9 +456,11 @@
        return purchaseRequest;
    }
    private ErpPurchaseRequestDO validatePurchaseRequestExistsAndDraft(Long id) {
    private ErpPurchaseRequestDO validatePurchaseRequestCanModify(Long id) {
        ErpPurchaseRequestDO purchaseRequest = validatePurchaseRequestExists(id);
        if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(purchaseRequest.getStatus())) {
        Integer status = purchaseRequest.getStatus();
        if (!ErpPurchaseRequestStatusEnum.DRAFT.getStatus().equals(status)
                && !ErpPurchaseRequestStatusEnum.REJECT.getStatus().equals(status)) {
            throw exception(PURCHASE_REQUEST_UPDATE_FAIL_NOT_DRAFT, purchaseRequest.getNo());
        }
        return purchaseRequest;
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
@@ -11,14 +11,14 @@
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
import cn.iocoder.yudao.module.mdm.dal.dataobject.warehouse.MdmWarehouseDO;
import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemBatchConfigService;
import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
import cn.iocoder.yudao.module.mdm.service.warehouse.MdmWarehouseService;
import cn.iocoder.yudao.module.mes.api.item.MesMdItemApi;
import cn.iocoder.yudao.module.mes.api.wm.warehouse.MesWmWarehouseApi;
import cn.iocoder.yudao.module.mes.api.wm.warehouse.dto.MesWmWarehouseRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -51,7 +51,7 @@
    @Resource
    private MdmUnitMeasureService unitMeasureService;
    @Resource
    private MdmWarehouseService warehouseService;
    private MesWmWarehouseApi mesWmWarehouseApi;
    @Resource
    private MdmBrandService brandService;
    @Resource
@@ -139,14 +139,14 @@
        });
        Map<Long, MdmUnitMeasureDO> auxUnitMap = unitIds.isEmpty() ?
                Collections.emptyMap() : unitMeasureService.getUnitMeasureMap(unitIds);
        // æ‰¹é‡å…³è”查询仓库名称
        // æ‰¹é‡å…³è”查询仓库名称(使用 MES ä»“库)
        Set<Long> warehouseIds = new HashSet<>();
        respList.forEach(resp -> {
            if (resp.getWarehouseId() != null) warehouseIds.add(resp.getWarehouseId());
        });
        Map<Long, MdmWarehouseDO> warehouseMap = warehouseIds.isEmpty() ?
                Collections.emptyMap() : warehouseService.getWarehouseList(warehouseIds).stream()
                        .collect(Collectors.toMap(MdmWarehouseDO::getId, w -> w));
        Map<Long, MesWmWarehouseRespDTO> warehouseMap = warehouseIds.isEmpty() ?
                Collections.emptyMap() : mesWmWarehouseApi.getWarehouseList(warehouseIds).stream()
                        .collect(Collectors.toMap(MesWmWarehouseRespDTO::getId, w -> w));
        // èµ‹å€¼
        respList.forEach(resp -> {
            // åˆ†ç±»åç§°
@@ -166,7 +166,7 @@
            if (auxUnit2 != null) {
                resp.setUnitMeasureName3(auxUnit2.getName());
            }
            MdmWarehouseDO warehouse = warehouseMap.get(resp.getWarehouseId());
            MesWmWarehouseRespDTO warehouse = warehouseMap.get(resp.getWarehouseId());
            if (warehouse != null) {
                resp.setWarehouseName(warehouse.getName());
            }
@@ -234,11 +234,11 @@
                resp.setUnitMeasureName3(unit3.getName());
            }
        }
        // é»˜è®¤ä»“库名称
        // é»˜è®¤ä»“库名称(使用 MES ä»“库)
        if (resp.getWarehouseId() != null) {
            MdmWarehouseDO warehouse = warehouseService.getWarehouse(resp.getWarehouseId());
            if (warehouse != null) {
                resp.setWarehouseName(warehouse.getName());
            List<MesWmWarehouseRespDTO> warehouses = mesWmWarehouseApi.getWarehouseList(List.of(resp.getWarehouseId()));
            if (!warehouses.isEmpty()) {
                resp.setWarehouseName(warehouses.get(0).getName());
            }
        }
    }
yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/warehouse/MdmWarehouseController.java
@@ -89,9 +89,9 @@
    @GetMapping("/list-all-simple")
    @Operation(summary = "获得全部启用的仓库列表")
    public CommonResult<List<MdmWarehouseRespVO>> getSimpleWarehouseList() {
        MdmWarehouseDO query = new MdmWarehouseDO();
        query.setStatus(0);
        List<MdmWarehouseDO> list = warehouseService.getWarehousePage(new MdmWarehousePageReqVO()).getList();
        MdmWarehousePageReqVO reqVO = new MdmWarehousePageReqVO();
        reqVO.setStatus(0);
        List<MdmWarehouseDO> list = warehouseService.getWarehousePage(reqVO).getList();
        return success(BeanUtils.toBean(list, MdmWarehouseRespVO.class));
    }
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wm/warehouse/MesWmWarehouseApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.mes.api.wm.warehouse;
import cn.iocoder.yudao.module.mes.api.wm.warehouse.dto.MesWmWarehouseRespDTO;
import java.util.Collection;
import java.util.List;
/**
 * MES ä»“库 API æŽ¥å£
 */
public interface MesWmWarehouseApi {
    /**
     * èŽ·å–ä»“åº“åˆ—è¡¨
     *
     * @param ids ä»“库ID列表
     * @return ä»“库列表
     */
    List<MesWmWarehouseRespDTO> getWarehouseList(Collection<Long> ids);
}
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wm/warehouse/dto/MesWmWarehouseRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.mes.api.wm.warehouse.dto;
import lombok.Data;
/**
 * MES ä»“库 Response DTO
 */
@Data
public class MesWmWarehouseRespDTO {
    private Long id;
    private String code;
    private String name;
}
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/wms/MesWmStockApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.mes.api.wms;
import java.math.BigDecimal;
/**
 * WMS åº“存查询 API æŽ¥å£
 *
 * ä¾›å…¶ä»–模块(如 ERP)查询物料库存数量
 */
public interface MesWmStockApi {
    /**
     * èŽ·å–ç‰©æ–™åº“å­˜æ€»é‡ï¼ˆæ‰€æœ‰ä»“åº“æ±‡æ€»ï¼‰
     *
     * @param itemId ç‰©æ–™ID
     * @return åº“存数量
     */
    BigDecimal getStockQuantity(Long itemId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wm/warehouse/MesWmWarehouseApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.mes.api.wm.warehouse;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.api.wm.warehouse.dto.MesWmWarehouseRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.warehouse.MesWmWarehouseMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
 * MES ä»“库 API å®žçŽ°ç±»
 */
@Service
@Validated
public class MesWmWarehouseApiImpl implements MesWmWarehouseApi {
    @Resource
    private MesWmWarehouseMapper warehouseMapper;
    @Override
    public List<MesWmWarehouseRespDTO> getWarehouseList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        List<MesWmWarehouseDO> list = warehouseMapper.selectListByIds(ids);
        return BeanUtils.toBean(list, MesWmWarehouseRespDTO.class);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/wms/MesWmStockApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.mes.api.wms;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
/**
 * WMS åº“å­˜ API å®žçŽ°ç±»
 */
@Service
@Validated
public class MesWmStockApiImpl implements MesWmStockApi {
    @Resource
    private MesWmMaterialStockMapper materialStockMapper;
    @Override
    public BigDecimal getStockQuantity(Long itemId) {
        MesWmMaterialStockListReqVO reqVO = new MesWmMaterialStockListReqVO();
        reqVO.setItemId(itemId);
        List<MesWmMaterialStockDO> stockList = materialStockMapper.selectList(reqVO);
        if (CollUtil.isEmpty(stockList)) {
            return BigDecimal.ZERO;
        }
        return stockList.stream()
                .map(MesWmMaterialStockDO::getQuantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/MesProFeedbackController.java
@@ -8,6 +8,8 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackApproveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackBatchCheckRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackBatchCreateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackBatchCreateRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackPageReqVO;
@@ -192,10 +194,17 @@
    @PutMapping("/approve")
    @Operation(summary = "审批报工")
    @Parameter(name = "id", description = "编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pro-feedback:approve')")
    public CommonResult<Boolean> approveFeedback(@RequestParam("id") Long id) {
        return success(feedbackService.approveFeedback(id));
    public CommonResult<Boolean> approveFeedback(@Valid @RequestBody MesProFeedbackApproveReqVO reqVO) {
        return success(feedbackService.approveFeedback(reqVO));
    }
    @GetMapping("/batch-requirements")
    @Operation(summary = "检查报工审批前是否需要填写批次属性")
    @Parameter(name = "id", description = "报工单编号", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pro-feedback:approve')")
    public CommonResult<MesProFeedbackBatchCheckRespVO> getBatchRequirements(@RequestParam("id") Long id) {
        return success(feedbackService.checkBatchRequirements(id));
    }
    @PostMapping("/quick-create")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackApproveReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES ç”Ÿäº§æŠ¥å·¥å®¡æ‰¹é€šè¿‡ Request VO")
@Data
public class MesProFeedbackApproveReqVO {
    @Schema(description = "报工单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
    @NotNull(message = "报工单ID不能为空")
    private Long id;
    @Schema(description = "生产日期")
    private LocalDateTime produceDate;
    @Schema(description = "过期日期")
    private LocalDateTime expireDate;
    @Schema(description = "入库日期")
    private LocalDateTime receiptDate;
    @Schema(description = "供应商ID")
    private Long vendorId;
    @Schema(description = "客户ID")
    private Long clientId;
    @Schema(description = "采购订单编号")
    private String purchaseOrderCode;
    @Schema(description = "销售订单编号")
    private String salesOrderCode;
    @Schema(description = "工具ID")
    private Long toolId;
    @Schema(description = "模具ID")
    private Long moldId;
    @Schema(description = "生产批号")
    private String lotNumber;
    @Schema(description = "质量状态")
    private Integer qualityStatus;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/feedback/vo/MesProFeedbackBatchCheckRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - MES ç”Ÿäº§æŠ¥å·¥æ‰¹æ¬¡éœ€æ±‚检查 Response VO")
@Data
public class MesProFeedbackBatchCheckRespVO {
    @Schema(description = "是否需要批次信息弹窗")
    private Boolean needBatchInput;
    @Schema(description = "是否启用批次管理")
    private Boolean isBatchManaged;
    @Schema(description = "生产日期必填")
    private Boolean produceDateFlag;
    @Schema(description = "有效期必填")
    private Boolean expireDateFlag;
    @Schema(description = "入库日期必填")
    private Boolean receiptDateFlag;
    @Schema(description = "供应商必填")
    private Boolean vendorFlag;
    @Schema(description = "客户必填")
    private Boolean clientFlag;
    @Schema(description = "销售订单编号必填")
    private Boolean salesOrderCodeFlag;
    @Schema(description = "采购订单编号必填")
    private Boolean purchaseOrderCodeFlag;
    @Schema(description = "生产工单必填")
    private Boolean workOrderFlag;
    @Schema(description = "生产任务必填")
    private Boolean taskFlag;
    @Schema(description = "工作站必填")
    private Boolean workstationFlag;
    @Schema(description = "工具必填")
    private Boolean toolFlag;
    @Schema(description = "模具必填")
    private Boolean moldFlag;
    @Schema(description = "生产批号必填")
    private Boolean lotNumberFlag;
    @Schema(description = "质量状态必填")
    private Boolean qualityStatusFlag;
    @Schema(description = "已自动填充的字段名列表")
    private List<String> autoFilledFields;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -18,7 +18,6 @@
    ErrorCode MD_ITEM_TYPE_CODE_DUPLICATE = new ErrorCode(1_040_100_005, "同一父分类下已存在该编码的分类");
    ErrorCode MD_ITEM_TYPE_PARENT_IS_CHILD = new ErrorCode(1_040_100_006, "不能设置自己的子分类为父分类");
    ErrorCode MD_ITEM_TYPE_EXITS_ITEM = new ErrorCode(1_040_100_007, "该分类下存在物料,无法删除");
    ErrorCode MD_ITEM_TYPE_NOT_LEAF = new ErrorCode(1_040_100_008, "只能将物料挂载到叶子分类(该分类下存在子分类)");
    // ========== MES åŸºç¡€æ•°æ®-计量单位(1-040-101-000) ==========
    ErrorCode MD_UNIT_MEASURE_NOT_EXISTS = new ErrorCode(1_040_101_000, "计量单位不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
@@ -213,11 +213,6 @@
        if (itemTypeService.getItemType(itemTypeId) == null) {
            throw exception(MD_ITEM_TYPE_NOT_EXISTS);
        }
        // æ ¡éªŒå¿…须是叶子分类(没有子分类)
        List<MesMdItemTypeDO> children = itemTypeService.getItemTypeChildrenList(itemTypeId);
        if (CollUtil.isNotEmpty(children)) {
            throw exception(MD_ITEM_TYPE_NOT_LEAF);
        }
    }
    private void validateUnitMeasureExists(Long unitMeasureId) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackService.java
@@ -108,10 +108,18 @@
     *   </li>
     * </ol>
     *
     * @param id æŠ¥å·¥å•编号
     * @param reqVO å®¡æ‰¹è¯·æ±‚(含报工单编号及可选批次属性)
     * @return true=已完成, false=待检验(需等质检回调)
     */
    boolean approveFeedback(Long id);
    boolean approveFeedback(@Valid MesProFeedbackApproveReqVO reqVO);
    /**
     * æ£€æŸ¥æŠ¥å·¥å®¡æ‰¹å‰æ˜¯å¦éœ€è¦å¡«å†™æ‰¹æ¬¡å±žæ€§
     *
     * @param feedbackId æŠ¥å·¥å•编号
     * @return æ‰¹æ¬¡éœ€æ±‚检查结果
     */
    MesProFeedbackBatchCheckRespVO checkBatchRequirements(Long feedbackId);
    /**
     * IPQC å®ŒæˆåŽå›žè°ƒï¼šå®ŒæˆæŠ¥å·¥å•并更新任务/工单进度
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -28,6 +28,10 @@
import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceService;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemBatchConfigApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemBatchConfigRespDTO;
import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO;
import cn.iocoder.yudao.module.mes.api.event.ProductionFinishedEvent;
@@ -40,6 +44,7 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -80,6 +85,10 @@
    private MesMdAutoCodeRecordService autoCodeRecordService;
    @Resource
    private ApplicationEventPublisher eventPublisher;
    @Resource
    private MesMdmItemService mdmItemService;
    @Resource
    private MdmItemBatchConfigApi itemBatchConfigApi;
    @Override
    public Long createFeedback(MesProFeedbackSaveReqVO createReqVO) {
@@ -161,9 +170,9 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean approveFeedback(Long id) {
    public boolean approveFeedback(MesProFeedbackApproveReqVO reqVO) {
        // 1.1 æ ¡éªŒå­˜åœ¨ + å®¡æ‰¹ä¸­çŠ¶æ€
        MesProFeedbackDO feedback = validateFeedbackStatusApproving(id);
        MesProFeedbackDO feedback = validateFeedbackStatusApproving(reqVO.getId());
        // 1.2 æ ¡éªŒæŠ¥å·¥æ•°é‡ > 0
        if (feedback.getFeedbackQuantity() == null
                || feedback.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) {
@@ -201,7 +210,7 @@
        }
        // 5. å…ˆæ›´æ–°æŠ¥å·¥çŠ¶æ€ä¸ºå·²å®Œæˆï¼ˆç¡®ä¿ checkAndFinishMps æ£€æŸ¥æ—¶å½“前报工已标记完成)
        feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
        feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId())
                .setStatus(MesProFeedbackStatusEnum.FINISHED.getStatus())
                .setUncheckQuantity(BigDecimal.ZERO));
@@ -209,13 +218,13 @@
        if (checkFlag) {
            // 6.1 éœ€è¦æ£€éªŒï¼šç”Ÿæˆäº§å‡ºå•(质量状态=待检验),更新报工状态为待检验,等质检完成回调后再入库
            // å®Œæˆæ—¶å›žè°ƒè§ï¼šMesQcIpqcServiceImpl#finishIpqc â†’ splitPendingAndFinishProduce + updateProFeedbackWhenIpqcFinish
            productProduceService.generateProductProduce(feedback, true);
            feedbackMapper.updateById(new MesProFeedbackDO().setId(id)
            productProduceService.generateProductProduce(feedback, true, reqVO);
            feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId())
                    .setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus()));
            return false;
        } else {
            // 6.2 æ— éœ€æ£€éªŒï¼šç”Ÿæˆäº§å‡ºå•(按合格/不合格拆行),直接完成入库
            MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false);
            MesWmProductProduceDO produce = productProduceService.generateProductProduce(feedback, false, reqVO);
            productProduceService.finishProductProduce(produce.getId());
            // å…³é”®å·¥åºï¼šæ›´æ–°ä»»åŠ¡ + å·¥å•的已生产数量;非关键工序:只更新任务的已生产数量(工单只看关键工序产出)
            if (keyFlag) {
@@ -480,4 +489,70 @@
        updateTaskAndWorkOrderByFeedback(feedback);
    }
    @Override
    public MesProFeedbackBatchCheckRespVO checkBatchRequirements(Long feedbackId) {
        MesProFeedbackDO feedback = validateFeedbackExists(feedbackId);
        MesProFeedbackBatchCheckRespVO respVO = new MesProFeedbackBatchCheckRespVO();
        // 1. æ£€æŸ¥ç‰©æ–™æ˜¯å¦å¯ç”¨æ‰¹æ¬¡ç®¡ç†
        MesMdmItemDO item = mdmItemService.getItem(feedback.getItemId());
        if (item == null || Boolean.FALSE.equals(item.getIsBatchManaged())) {
            respVO.setNeedBatchInput(false);
            respVO.setIsBatchManaged(false);
            return respVO;
        }
        respVO.setIsBatchManaged(true);
        // 2. èŽ·å–æ‰¹æ¬¡é…ç½®
        MdmItemBatchConfigRespDTO config = itemBatchConfigApi.getItemBatchConfig(feedback.getItemId());
        if (config == null) {
            respVO.setNeedBatchInput(false);
            return respVO;
        }
        // 3. å¡«å……配置标志
        respVO.setProduceDateFlag(config.getProduceDateFlag());
        respVO.setExpireDateFlag(config.getExpireDateFlag());
        respVO.setReceiptDateFlag(config.getReceiptDateFlag());
        respVO.setVendorFlag(config.getVendorFlag());
        respVO.setClientFlag(config.getClientFlag());
        respVO.setSalesOrderCodeFlag(config.getSalesOrderCodeFlag());
        respVO.setPurchaseOrderCodeFlag(config.getPurchaseOrderCodeFlag());
        respVO.setWorkOrderFlag(config.getWorkOrderFlag());
        respVO.setTaskFlag(config.getTaskFlag());
        respVO.setWorkstationFlag(config.getWorkstationFlag());
        respVO.setToolFlag(config.getToolFlag());
        respVO.setMoldFlag(config.getMoldFlag());
        respVO.setLotNumberFlag(config.getLotNumberFlag());
        respVO.setQualityStatusFlag(config.getQualityStatusFlag());
        // 4. åˆ¤æ–­å“ªäº›å­—段已自动填充(报工和生产过程中必然有值的字段)
        List<String> autoFilled = new ArrayList<>();
        // ç”Ÿäº§æ—¥æœŸï¼šå®¡æ‰¹æ—¶è‡ªåŠ¨å–å½“å¤©
        autoFilled.add("produceDate");
        // ç”Ÿäº§å·¥å•:从报工记录获取
        autoFilled.add("workOrderId");
        // ç”Ÿäº§ä»»åŠ¡ï¼šä»ŽæŠ¥å·¥è®°å½•èŽ·å–
        autoFilled.add("taskId");
        // å·¥ä½œç«™ï¼šä»ŽæŠ¥å·¥è®°å½•获取
        autoFilled.add("workstationId");
        respVO.setAutoFilledFields(autoFilled);
        // 5. åˆ¤æ–­æ˜¯å¦éœ€è¦å¼¹çª—:存在必填但无法自动填充的字段
        boolean needBatchInput = false;
        if (Boolean.TRUE.equals(config.getExpireDateFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getReceiptDateFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getVendorFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getClientFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getSalesOrderCodeFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getPurchaseOrderCodeFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getToolFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getMoldFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getLotNumberFlag())) needBatchInput = true;
        if (Boolean.TRUE.equals(config.getQualityStatusFlag())) needBatchInput = true;
        respVO.setNeedBatchInput(needBatchInput);
        return respVO;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceService.java
@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.mes.service.wm.productproduce;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackApproveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
@@ -30,9 +31,11 @@
     *
     * @param feedback  æŠ¥å·¥è®°å½•
     * @param checkFlag æ˜¯å¦éœ€è¦æ£€éªŒï¼ˆtrue=待检验,false=按合格/不合格拆分行)
     * @param approveReqVO å®¡æ‰¹è¯·æ±‚(含可选批次属性,可为 null)
     * @return ç”Ÿæˆçš„产品产出单
     */
    MesWmProductProduceDO generateProductProduce(MesProFeedbackDO feedback, boolean checkFlag);
    MesWmProductProduceDO generateProductProduce(MesProFeedbackDO feedback, boolean checkFlag,
                                                  MesProFeedbackApproveReqVO approveReqVO);
    /**
     * IPQC æ£€éªŒå®Œæˆå›žè°ƒï¼šå°†å¾…检产出拆分为合格/不合格行、生成明细、完成入库
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productproduce/MesWmProductProduceServiceImpl.java
@@ -4,6 +4,7 @@
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackApproveReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
@@ -110,7 +111,8 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesWmProductProduceDO generateProductProduce(MesProFeedbackDO feedback, boolean checkFlag) {
    public MesWmProductProduceDO generateProductProduce(MesProFeedbackDO feedback, boolean checkFlag,
                                                         MesProFeedbackApproveReqVO approveReqVO) {
        // 0. æŸ¥è¯¢å…³è”的工单(用于获取 clientId ç­‰ä¿¡æ¯ï¼‰
        MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(feedback.getWorkOrderId());
@@ -122,16 +124,30 @@
                .build();
        productProduceMapper.insert(produce);
        // 2. èŽ·å–æˆ–ç”Ÿæˆæ‰¹æ¬¡
        // 2. èŽ·å–æˆ–ç”Ÿæˆæ‰¹æ¬¡ï¼ˆå®¡æ‰¹è¯·æ±‚ä¸­çš„æ‰¹æ¬¡å±žæ€§ä¼˜å…ˆï¼‰
        MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO()
                .setItemId(feedback.getItemId())
                .setProduceDate(LocalDate.now().atStartOfDay()) // æˆªæ–­åˆ°å½“天零点,确保同一天的报工生成相同批次号
                .setExpireDate(feedback.getExpireDate())
                .setWorkOrderId(feedback.getWorkOrderId())
                .setTaskId(feedback.getTaskId())
                .setClientId(workOrder != null ? workOrder.getClientId() : null)
                .setSalesOrderCode(workOrder != null ? workOrder.getOrderSourceCode() : null)
                .setWorkstationId(feedback.getWorkstationId())
                .setLotNumber(feedback.getLotNumber());
        // åˆå¹¶å®¡æ‰¹è¯·æ±‚中用户填写的批次属性(用户输入值优先)
        if (approveReqVO != null) {
            if (approveReqVO.getExpireDate() != null) batchReqVO.setExpireDate(approveReqVO.getExpireDate());
            if (approveReqVO.getReceiptDate() != null) batchReqVO.setReceiptDate(approveReqVO.getReceiptDate());
            if (approveReqVO.getVendorId() != null) batchReqVO.setVendorId(approveReqVO.getVendorId());
            if (approveReqVO.getClientId() != null) batchReqVO.setClientId(approveReqVO.getClientId());
            if (approveReqVO.getSalesOrderCode() != null) batchReqVO.setSalesOrderCode(approveReqVO.getSalesOrderCode());
            if (approveReqVO.getPurchaseOrderCode() != null) batchReqVO.setPurchaseOrderCode(approveReqVO.getPurchaseOrderCode());
            if (approveReqVO.getToolId() != null) batchReqVO.setToolId(approveReqVO.getToolId());
            if (approveReqVO.getMoldId() != null) batchReqVO.setMoldId(approveReqVO.getMoldId());
            if (approveReqVO.getLotNumber() != null) batchReqVO.setLotNumber(approveReqVO.getLotNumber());
            if (approveReqVO.getQualityStatus() != null) batchReqVO.setQualityStatus(approveReqVO.getQualityStatus());
        }
        MesWmBatchDO batch = batchService.getOrGenerateBatchCode(batchReqVO);
        Long batchId = batch != null ? batch.getId() : null;
        String batchCode = batch != null ? batch.getCode() : null;
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
@@ -58,6 +58,9 @@
    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 QUOTE_MATERIAL_DUPLICATE = new ErrorCode(1_040_900_001, "该投标已存在相同物料的报价");
yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
@@ -22,6 +22,7 @@
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
@@ -76,17 +77,28 @@
        if (existing != null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_EXISTS);
        }
        // æ ¡éªŒæŠ•标记录:必须属于该项目、未被撤标、且供应商一致
        SrmTenderBidDO bid = tenderBidMapper.selectById(createReqVO.getBidId());
        if (bid == null) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_NOT_EXISTS);
        }
        if (!bid.getTenderProjectId().equals(createReqVO.getTenderProjectId())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_BID_NOT_IN_PROJECT);
        }
        if (BidStatusEnum.WITHDRAWN.getCode().equals(bid.getBidStatus())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_BID_WITHDRAWN);
        }
        if (!bid.getSupplierId().equals(createReqVO.getSupplierId())) {
            throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_NOT_MATCH_BID);
        }
        SrmBidAwardDO award = BeanUtils.toBean(createReqVO, SrmBidAwardDO.class);
        award.setAwardStatus(AwardStatusEnum.PENDING_APPROVAL.getCode());
        award.setAwardTime(LocalDateTime.now());
        bidAwardMapper.insert(award);
        // æ›´æ–°æŠ•标状态为中标
        SrmTenderBidDO bid = tenderBidMapper.selectById(createReqVO.getBidId());
        if (bid != null) {
            bid.setBidStatus(BidStatusEnum.WIN.getCode());
            tenderBidMapper.updateById(bid);
        }
        bid.setBidStatus(BidStatusEnum.WIN.getCode());
        tenderBidMapper.updateById(bid);
        return award.getId();
    }
@@ -175,7 +187,15 @@
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemList(productIds).getCheckedData()
                .stream().collect(Collectors.toMap(MdmItemRespDTO::getId, Function.identity()));
        // 6. æž„建订单项
        // 6. æž„建订单项(报价为总价,需除以数量得到单价)
        BigDecimal totalQuoteAmount = quotes.stream()
                .map(SrmSupplierQuoteDO::getQuotePrice)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal awardAmount = award.getAwardAmount() != null ? award.getAwardAmount() : totalQuoteAmount;
        BigDecimal ratio = totalQuoteAmount.compareTo(BigDecimal.ZERO) > 0
                ? awardAmount.divide(totalQuoteAmount, 8, RoundingMode.HALF_UP)
                : BigDecimal.ONE;
        List<ErpPurchaseOrderCreateReqDTO.Item> orderItems = new ArrayList<>();
        for (SrmTenderMaterialDO material : materials) {
            SrmSupplierQuoteDO quote = quoteMap.get(material.getId());
@@ -186,10 +206,12 @@
            if (mdmItem == null) {
                continue;
            }
            BigDecimal materialTotal = quote.getQuotePrice().multiply(ratio);
            BigDecimal unitPrice = materialTotal.divide(material.getQuantity(), 4, RoundingMode.HALF_UP);
            ErpPurchaseOrderCreateReqDTO.Item item = new ErpPurchaseOrderCreateReqDTO.Item();
            item.setProductId(material.getProductId());
            item.setProductUnitId(mdmItem.getUnitMeasureId());
            item.setProductPrice(quote.getQuotePrice());
            item.setProductPrice(unitPrice);
            item.setCount(material.getQuantity());
            item.setTaxPercent(quote.getTaxRate());
            item.setRemark("招标编号: " + award.getAwardNo());
yudao-server/src/main/resources/application-test.yaml
@@ -227,7 +227,7 @@
      milvus:
        initialize-schema: true
        database-name: test
        collection-name: knowledge_segment
        collection-name: knowledge_segment_test
        host: 36.213.180.217
        port: 19530
@@ -282,7 +282,7 @@
  storage: # æ–‡ä»¶ä¸Šä¼ é…ç½®
    path: /data/javaWork/mom/file # æ–‡ä»¶å­˜å‚¨æ ¹ç›®å½•
    url-prefix: /admin-api/system/storage-blob # URL路径前缀
    domain: https://test.your-domain.com # åŸŸåå‰ç¼€ï¼ˆæŒ‰å®žé™…域名配置)
    domain: http://36.213.180.217:9500 # åŸŸåå‰ç¼€ï¼ˆæŒ‰å®žé™…域名配置),前端配置好像不太对现在是直连后端,所有要后端端口
    expired: 120 # token过期时间(分钟)
    use-limit: 10 # token使用次数上限
    jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWT密钥
yudao-server/src/main/resources/logback-spring.xml
@@ -8,7 +8,7 @@
    <property name="ERROR_LOG_FILE" value="./logs/error/error.log"/>
    <!-- æŽ§åˆ¶å° Appender -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">     
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>