3 天以前 1ba0dbfde5634c8bf2ec20891d6b226b996f6b59
refactor(mes): 优化MES生产扫码入库功能支持多类型扫码

- 移除ERP销售订单中的MES相关API依赖和发货通知单功能
- 新增多种仓库管理错误码,包括合格库验证、库存质检、袋码管理等
- 扩展MES首页统计数据,增加出库流通和质量预警统计项
- 添加种子质检业务类型常量和袋码质检状态字段
- 实现追溯码扫码入库功能,支持袋码/托盘码/批次码三种扫码类型
- 完善托盘和批次整批入库的幂等处理机制
- 移除售后退货中的销售订单关联逻辑
已删除4个文件
已添加41个文件
已修改76个文件
3645 ■■■■ 文件已修改
.playwright-mcp/page-2026-09-02T05-27-28-818Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T05-54-09-946Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T05-56-04-640Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T05-57-05-545Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-03-42-523Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-07-41-739Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-08-22-302Z.yml 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-08-26-480Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-08-42-531Z.yml 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-08-53-561Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-11-00-277Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-14-13-812Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-14-34-873Z.yml 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-16-10-960Z.yml 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T06-16-32-193Z.yml 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/bagcode_home_dashboard_integration.md 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch_trace_refactor_integration.md 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_wm_material_stock_bag_api.md 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sales_notice_to_product_sales.md 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/scan_in_multi_type_inbound.md 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/seed_quality_bag_granularity.md 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/sql/trace_platform_01_schema.sql 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/wm_transaction_query.md 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_pro_bag_code_qc_status.sql 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_pro_scan_event_scan_type.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_qc_seed_quality_record_menu.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_wm_material_stock_bag.sql 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_wm_product_sales_add_columns.sql 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_wm_qualified_warehouse.sql 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_wm_transaction_bag_code.sql 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/wls_sales_outbound_menu_extract.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnServiceImpl.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApi.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/dto/MesWmReturnSalesCreateReqDTO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/salesnotice/MesWmSalesNoticeApi.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApiImpl.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/salesnotice/MesWmSalesNoticeApiImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesBagCodeHomeSummaryRespVO.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/MesProScanEventController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventCreateReqVO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventRespVO.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/MesQcSeedQualityController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualityPageReqVO.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualityRespVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualitySubmitReqVO.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/batch/MesTraceBatchController.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockBagPageReqVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockBagRespVO.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockPageReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/MesWmProductSalesRespVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/MesWmProductSalesSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesPageReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/MesWmSalesNoticeController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/MesWmSalesNoticePageReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/MesWmSalesNoticeRespVO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/MesWmSalesNoticeSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineSaveReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionPageReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehousePageReqVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseSaveReqVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/vo/MesProTracePublicRespVO.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/pallet/MesProPalletDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/scan/MesProScanEventDO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualityDO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/batch/MesWmBatchDO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/materialstockbag/MesWmMaterialStockBagDO.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnsales/MesWmReturnSalesDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/salesnotice/MesWmSalesNoticeDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/salesnotice/MesWmSalesNoticeLineDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/sn/MesWmSnDO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesBagCodeHomeStatisticsMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java 93 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstockbag/MesWmMaterialStockBagMapper.java 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesLineMapper.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/returnsales/MesWmReturnSalesMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/warehouse/MesWmWarehouseMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 13 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProBagCodeQcStatusEnum.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMaterialStockBagStatusEnum.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmWarehouseTypeEnum.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesBagCodeHomeStatisticsServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java 101 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java 84 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstockbag/MesWmMaterialStockBagService.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstockbag/MesWmMaterialStockBagServiceImpl.java 165 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java 37 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/resources/mapper/home/MesBagCodeHomeStatisticsMapper.xml 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.playwright-mcp/page-2026-09-02T05-27-28-818Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active] [ref=e1]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T05-54-09-946Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T05-56-04-640Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T05-57-05-545Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T06-03-42-523Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T06-07-41-739Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T06-08-22-302Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,196 @@
- generic [ref=f10e7]:
  - complementary [ref=f10e9]:
    - generic [ref=f10e10]:
      - link [ref=f10e13] [cursor=pointer]:
        - /url: javascript:void 0
        - img "工业AI智造平台" [ref=f10e16]
      - menu [ref=f10e19]:
        - menuitem [ref=f10e20] [cursor=pointer]
        - listitem [ref=f10e24]:
          - generic [ref=f10e25] [cursor=pointer]
        - listitem [ref=f10e31]:
          - generic [ref=f10e32] [cursor=pointer]
        - listitem [ref=f10e36]:
          - generic [ref=f10e37] [cursor=pointer]
        - listitem [ref=f10e43]:
          - generic [ref=f10e44] [cursor=pointer]
        - listitem [ref=f10e48]:
          - generic [ref=f10e49] [cursor=pointer]
        - listitem [ref=f10e53]:
          - generic [ref=f10e54] [cursor=pointer]
        - listitem [ref=f10e58]:
          - generic [ref=f10e59] [cursor=pointer]
        - listitem [ref=f10e63]:
          - generic [ref=f10e64] [cursor=pointer]
        - listitem [ref=f10e68]:
          - generic [ref=f10e69] [cursor=pointer]
      - generic [ref=f10e74] [cursor=pointer]
  - generic [ref=f10e79]:
    - generic [ref=f10e80]:
      - banner [ref=f10e81]:
        - button [ref=f10e82] [cursor=pointer]
        - button [ref=f10e85] [cursor=pointer]
        - navigation "breadcrumb" [ref=f10e90]:
          - list [ref=f10e91]:
            - listitem [ref=f10e625]:
              - link "出库流通管理" [ref=f10e626] [cursor=pointer]:
                - /url: javascript:void 0
              - listitem [ref=f10e630]
            - listitem [ref=f10e633]:
              - link "销售出库" [disabled] [ref=f10e634]
        - generic [ref=f10e98]:
          - generic [ref=f10e100] [cursor=pointer]:
            - generic [ref=f10e104]: æœç´¢
            - generic [ref=f10e105]: Ctrl K
          - button [ref=f10e107] [cursor=pointer]
          - button "dark" [ref=f10e112] [cursor=pointer]
          - button [ref=f10e121] [cursor=pointer]
          - button [ref=f10e127] [cursor=pointer]
          - button [ref=f10e130] [cursor=pointer]
          - button [ref=f10e136] [cursor=pointer]:
            - button [ref=f10e138]
          - button "工作台" [ref=f10e142] [cursor=pointer]
          - button "理员" [ref=f10e146] [cursor=pointer]
      - generic [ref=f10e153]:
        - generic [ref=f10e158]:
          - generic [ref=f10e159] [cursor=pointer]: é¦–页
          - generic [ref=f10e641] [cursor=pointer]: é”€å”®å‡ºåº“
        - generic [ref=f10e173]:
          - button [ref=f10e174] [cursor=pointer]
          - generic [ref=f10e181] [cursor=pointer]
          - generic [ref=f10e185] [cursor=pointer]
    - main [ref=f10e192]:
      - generic [ref=f10e196]:
        - generic [ref=f10e197]:
          - generic [ref=f10e198]: ä¸šåŠ¡å…¨é“¾è·¯
          - generic [ref=f10e210]:
            - generic [ref=f10e217] [cursor=pointer]:
              - generic [ref=f10e218]: å“ç§
              - generic [ref=f10e219]: "1"
            - generic [ref=f10e227] [cursor=pointer]:
              - generic [ref=f10e228]: äº§çº¿
              - generic [ref=f10e229]: "1"
            - generic [ref=f10e240] [cursor=pointer]:
              - generic [ref=f10e241]: ç”Ÿäº§æ‰¹æ¬¡
              - generic [ref=f10e242]: "103"
            - generic [ref=f10e250] [cursor=pointer]:
              - generic [ref=f10e251]: è¢‹ç 
              - generic [ref=f10e252]: 1,101
            - generic [ref=f10e262] [cursor=pointer]:
              - generic [ref=f10e263]: æ‰˜ç›˜ç 
              - generic [ref=f10e264]: "11"
            - generic [ref=f10e275] [cursor=pointer]:
              - generic [ref=f10e276]: é”€å”®å‡ºåº“
              - generic [ref=f10e277]: "0"
            - generic [ref=f10e285] [cursor=pointer]:
              - generic [ref=f10e286]: æµé€šæ‰«ç 
              - generic [ref=f10e287]: "23"
            - generic [ref=f10e295] [cursor=pointer]:
              - generic [ref=f10e296]: é˜²çªœ/溯源
              - generic [ref=f10e297]: "0"
        - generic [ref=f10e298]:
          - generic [ref=f10e308] [cursor=pointer]:
            - generic [ref=f10e309]: å“ç§æ€»æ•°
            - generic [ref=f10e310]: "1"
            - generic [ref=f10e312]: åŸºç¡€æ•°æ®
          - generic [ref=f10e323] [cursor=pointer]:
            - generic [ref=f10e324]: ç”Ÿäº§æ‰¹æ¬¡
            - generic [ref=f10e325]: "103"
            - generic [ref=f10e327]: ä¸€è¢‹ä¸€ç 
          - generic [ref=f10e335] [cursor=pointer]:
            - generic [ref=f10e336]: è¢‹ç 
            - generic [ref=f10e337]: 1,101
            - generic [ref=f10e339]: èµ‹ç 
          - generic [ref=f10e349] [cursor=pointer]:
            - generic [ref=f10e350]: æ‰˜ç›˜ç 
            - generic [ref=f10e351]: "11"
            - generic [ref=f10e353]: æ‰˜ç›˜
          - generic [ref=f10e364] [cursor=pointer]:
            - generic [ref=f10e365]: é”€å”®å‡ºåº“单
            - generic [ref=f10e366]: "0"
            - generic [ref=f10e368]: å‡ºåº“流通
          - generic [ref=f10e376] [cursor=pointer]:
            - generic [ref=f10e377]: ç´¯è®¡æ‰«ç 
            - generic [ref=f10e378]: "23"
            - generic [ref=f10e380]: æº¯æº
          - generic [ref=f10e387] [cursor=pointer]:
            - generic [ref=f10e388]: åº“存预警
            - generic [ref=f10e389]: "0"
            - generic [ref=f10e391]: ä»“储
          - generic [ref=f10e399] [cursor=pointer]:
            - generic [ref=f10e400]: å¾…评审不合格
            - generic [ref=f10e401]: "0"
            - generic [ref=f10e403]: è´¨é‡
        - generic [ref=f10e404]:
          - generic [ref=f10e408]:
            - generic [ref=f10e409]: è¢‹ç ç”Ÿæˆè¶‹åŠ¿
            - generic [ref=f10e411]:
              - generic [ref=f10e412] [cursor=pointer]:
                - generic [ref=f10e413]:
                  - radio "近 7 å¤©" [checked]
                - generic [ref=f10e414]: è¿‘ 7 å¤©
              - generic [ref=f10e415] [cursor=pointer]:
                - generic [ref=f10e416]:
                  - radio "近 30 å¤©"
                - generic [ref=f10e417]: è¿‘ 30 å¤©
          - generic [ref=f10e422]: æ¶ˆè´¹è€…扫码地区分布
        - generic [ref=f10e431]:
          - generic [ref=f10e432]: æ‰¹æ¬¡çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f10e441]: è¢‹ç çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f10e451]:
            - generic [ref=f10e452]: å¾…办与预警
            - generic [ref=f10e456]:
              - generic [ref=f10e461] [cursor=pointer]:
                - generic [ref=f10e462]: çªœè´§é¢„è­¦
                - generic [ref=f10e463]: å¾…处理的窜货预警
              - generic [ref=f10e467] [cursor=pointer]:
                - generic [ref=f10e468]: åº“存预警
                - generic [ref=f10e469]: æœªå¤„理的库存预警
              - generic [ref=f10e474] [cursor=pointer]:
                - generic [ref=f10e475]: ä¸åˆæ ¼å“
                - generic [ref=f10e476]: å¾…评审的不合格品单
        - generic [ref=f10e477]:
          - generic [ref=f10e478]: å¿«æ·å…¥å£
          - generic [ref=f10e482]:
            - generic [ref=f10e484]:
              - generic [ref=f10e485]: åŸºç¡€æ•°æ®
              - generic [ref=f10e486]:
                - generic [ref=f10e487] [cursor=pointer]: å“ç§ç®¡ç†
                - generic [ref=f10e493] [cursor=pointer]: å“ç§åˆ†ç±»
                - generic [ref=f10e499] [cursor=pointer]: è®¡é‡å•位
            - generic [ref=f10e504]:
              - generic [ref=f10e505]: ä¸€è¢‹ä¸€ç ç”Ÿäº§
              - generic [ref=f10e506]:
                - generic [ref=f10e507] [cursor=pointer]: äº§çº¿ç»´æŠ¤
                - generic [ref=f10e511] [cursor=pointer]: æ‰¹æ¬¡ç”Ÿäº§
                - generic [ref=f10e518] [cursor=pointer]: èµ‹ç ç®¡ç†
                - generic [ref=f10e522] [cursor=pointer]: æ‰˜ç›˜ç 
                - generic [ref=f10e528] [cursor=pointer]: æ‰«ç å…¥åº“
            - generic [ref=f10e533]:
              - generic [ref=f10e534]: å‡ºåº“流通
              - generic [ref=f10e535]:
                - generic [ref=f10e536] [cursor=pointer]: å‘货通知
                - generic [ref=f10e540] [cursor=pointer]: é”€å”®å‡ºåº“
                - generic [ref=f10e547] [cursor=pointer]: é”€å”®é€€è´§
            - generic [ref=f10e554]:
              - generic [ref=f10e555]: é˜²ä¼ªé˜²çªœè´§
              - generic [ref=f10e556]:
                - generic [ref=f10e557] [cursor=pointer]: çªœè´§é¢„è­¦
                - generic [ref=f10e561] [cursor=pointer]: æ‰«ç åœ°ç†åˆ†å¸ƒ
                - generic [ref=f10e567] [cursor=pointer]: å®Œæ•´æ€§æ ¡éªŒ
            - generic [ref=f10e575]:
              - generic [ref=f10e576]: æº¯æºåˆ†æž
              - generic [ref=f10e577]: æ¶ˆè´¹è€…扫码统计
            - generic [ref=f10e583]:
              - generic [ref=f10e584]: ä»“储物流
              - generic [ref=f10e585]:
                - generic [ref=f10e586] [cursor=pointer]: ä»“库设置
                - generic [ref=f10e592] [cursor=pointer]: åº“存现有量
                - generic [ref=f10e598] [cursor=pointer]: åº“存预警
                - generic [ref=f10e601] [cursor=pointer]: è½¬ç§»è°ƒæ‹¨
            - generic [ref=f10e606]:
              - generic [ref=f10e607]: è´¨é‡ç®¡ç†
              - generic [ref=f10e608]:
                - generic [ref=f10e609] [cursor=pointer]: æ‰¹æ¬¡è¿½æº¯
                - generic [ref=f10e615] [cursor=pointer]: ä¸åˆæ ¼å“
                - generic [ref=f10e619] [cursor=pointer]: ç§å­è´¨æ£€
.playwright-mcp/page-2026-09-02T06-08-26-480Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T06-08-42-531Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,196 @@
- generic [ref=f11e7]:
  - complementary [ref=f11e9]:
    - generic [ref=f11e10]:
      - link [ref=f11e13] [cursor=pointer]:
        - /url: javascript:void 0
        - img "工业AI智造平台" [ref=f11e16]
      - menu [ref=f11e19]:
        - menuitem [ref=f11e20] [cursor=pointer]
        - listitem [ref=f11e24]:
          - generic [ref=f11e25] [cursor=pointer]
        - listitem [ref=f11e31]:
          - generic [ref=f11e32] [cursor=pointer]
        - listitem [ref=f11e36]:
          - generic [ref=f11e37] [cursor=pointer]
        - listitem [ref=f11e43]:
          - generic [ref=f11e44] [cursor=pointer]
        - listitem [ref=f11e48]:
          - generic [ref=f11e49] [cursor=pointer]
        - listitem [ref=f11e53]:
          - generic [ref=f11e54] [cursor=pointer]
        - listitem [ref=f11e58]:
          - generic [ref=f11e59] [cursor=pointer]
        - listitem [ref=f11e63]:
          - generic [ref=f11e64] [cursor=pointer]
        - listitem [ref=f11e68]:
          - generic [ref=f11e69] [cursor=pointer]
      - generic [ref=f11e74] [cursor=pointer]
  - generic [ref=f11e79]:
    - generic [ref=f11e80]:
      - banner [ref=f11e81]:
        - button [ref=f11e82] [cursor=pointer]
        - button [ref=f11e85] [cursor=pointer]
        - navigation "breadcrumb" [ref=f11e90]:
          - list [ref=f11e91]:
            - listitem [ref=f11e630]:
              - link "防伪防窜货管理" [ref=f11e631] [cursor=pointer]:
                - /url: javascript:void 0
              - listitem [ref=f11e635]
            - listitem [ref=f11e638]:
              - link "窜货预警" [disabled] [ref=f11e639]
        - generic [ref=f11e98]:
          - generic [ref=f11e100] [cursor=pointer]:
            - generic [ref=f11e104]: æœç´¢
            - generic [ref=f11e105]: Ctrl K
          - button [ref=f11e107] [cursor=pointer]
          - button "dark" [ref=f11e112] [cursor=pointer]
          - button [ref=f11e121] [cursor=pointer]
          - button [ref=f11e127] [cursor=pointer]
          - button [ref=f11e130] [cursor=pointer]
          - button [ref=f11e136] [cursor=pointer]:
            - button [ref=f11e138]
          - button "工作台" [ref=f11e142] [cursor=pointer]
          - button "理员" [ref=f11e146] [cursor=pointer]
      - generic [ref=f11e153]:
        - generic [ref=f11e158]:
          - generic [ref=f11e159] [cursor=pointer]: é¦–页
          - generic [ref=f11e645] [cursor=pointer]: çªœè´§é¢„è­¦
        - generic [ref=f11e173]:
          - button [ref=f11e174] [cursor=pointer]
          - generic [ref=f11e181] [cursor=pointer]
          - generic [ref=f11e185] [cursor=pointer]
    - main [ref=f11e192]:
      - generic [ref=f11e196]:
        - generic [ref=f11e197]:
          - generic [ref=f11e198]: ä¸šåŠ¡å…¨é“¾è·¯
          - generic [ref=f11e210]:
            - generic [ref=f11e217] [cursor=pointer]:
              - generic [ref=f11e218]: å“ç§
              - generic [ref=f11e219]: "1"
            - generic [ref=f11e227] [cursor=pointer]:
              - generic [ref=f11e228]: äº§çº¿
              - generic [ref=f11e229]: "1"
            - generic [ref=f11e240] [cursor=pointer]:
              - generic [ref=f11e241]: ç”Ÿäº§æ‰¹æ¬¡
              - generic [ref=f11e242]: "103"
            - generic [ref=f11e250] [cursor=pointer]:
              - generic [ref=f11e251]: è¢‹ç 
              - generic [ref=f11e252]: 1,101
            - generic [ref=f11e262] [cursor=pointer]:
              - generic [ref=f11e263]: æ‰˜ç›˜ç 
              - generic [ref=f11e264]: "11"
            - generic [ref=f11e275] [cursor=pointer]:
              - generic [ref=f11e276]: é”€å”®å‡ºåº“
              - generic [ref=f11e277]: "0"
            - generic [ref=f11e285] [cursor=pointer]:
              - generic [ref=f11e286]: æµé€šæ‰«ç 
              - generic [ref=f11e287]: "23"
            - generic [ref=f11e295] [cursor=pointer]:
              - generic [ref=f11e296]: é˜²çªœ/溯源
              - generic [ref=f11e297]: "0"
        - generic [ref=f11e298]:
          - generic [ref=f11e308] [cursor=pointer]:
            - generic [ref=f11e309]: å“ç§æ€»æ•°
            - generic [ref=f11e310]: "1"
            - generic [ref=f11e312]: åŸºç¡€æ•°æ®
          - generic [ref=f11e323] [cursor=pointer]:
            - generic [ref=f11e324]: ç”Ÿäº§æ‰¹æ¬¡
            - generic [ref=f11e325]: "103"
            - generic [ref=f11e327]: ä¸€è¢‹ä¸€ç 
          - generic [ref=f11e335] [cursor=pointer]:
            - generic [ref=f11e336]: è¢‹ç 
            - generic [ref=f11e337]: 1,101
            - generic [ref=f11e339]: èµ‹ç 
          - generic [ref=f11e349] [cursor=pointer]:
            - generic [ref=f11e350]: æ‰˜ç›˜ç 
            - generic [ref=f11e351]: "11"
            - generic [ref=f11e353]: æ‰˜ç›˜
          - generic [ref=f11e364] [cursor=pointer]:
            - generic [ref=f11e365]: é”€å”®å‡ºåº“单
            - generic [ref=f11e366]: "0"
            - generic [ref=f11e368]: å‡ºåº“流通
          - generic [ref=f11e376] [cursor=pointer]:
            - generic [ref=f11e377]: ç´¯è®¡æ‰«ç 
            - generic [ref=f11e378]: "23"
            - generic [ref=f11e380]: æº¯æº
          - generic [ref=f11e387] [cursor=pointer]:
            - generic [ref=f11e388]: åº“存预警
            - generic [ref=f11e389]: "0"
            - generic [ref=f11e391]: ä»“储
          - generic [ref=f11e399] [cursor=pointer]:
            - generic [ref=f11e400]: å¾…评审不合格
            - generic [ref=f11e401]: "0"
            - generic [ref=f11e403]: è´¨é‡
        - generic [ref=f11e404]:
          - generic [ref=f11e408]:
            - generic [ref=f11e409]: è¢‹ç ç”Ÿæˆè¶‹åŠ¿
            - generic [ref=f11e411]:
              - generic [ref=f11e412] [cursor=pointer]:
                - generic [ref=f11e413]:
                  - radio "近 7 å¤©" [checked]
                - generic [ref=f11e414]: è¿‘ 7 å¤©
              - generic [ref=f11e415] [cursor=pointer]:
                - generic [ref=f11e416]:
                  - radio "近 30 å¤©"
                - generic [ref=f11e417]: è¿‘ 30 å¤©
          - generic [ref=f11e422]: æ¶ˆè´¹è€…扫码地区分布
        - generic [ref=f11e431]:
          - generic [ref=f11e432]: æ‰¹æ¬¡çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f11e441]: è¢‹ç çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f11e451]:
            - generic [ref=f11e452]: å¾…办与预警
            - generic [ref=f11e456]:
              - generic [ref=f11e461] [cursor=pointer]:
                - generic [ref=f11e462]: çªœè´§é¢„è­¦
                - generic [ref=f11e463]: å¾…处理的窜货预警
              - generic [ref=f11e467] [cursor=pointer]:
                - generic [ref=f11e468]: åº“存预警
                - generic [ref=f11e469]: æœªå¤„理的库存预警
              - generic [ref=f11e474] [cursor=pointer]:
                - generic [ref=f11e475]: ä¸åˆæ ¼å“
                - generic [ref=f11e476]: å¾…评审的不合格品单
        - generic [ref=f11e477]:
          - generic [ref=f11e478]: å¿«æ·å…¥å£
          - generic [ref=f11e482]:
            - generic [ref=f11e484]:
              - generic [ref=f11e485]: åŸºç¡€æ•°æ®
              - generic [ref=f11e486]:
                - generic [ref=f11e487] [cursor=pointer]: å“ç§ç®¡ç†
                - generic [ref=f11e493] [cursor=pointer]: å“ç§åˆ†ç±»
                - generic [ref=f11e499] [cursor=pointer]: è®¡é‡å•位
            - generic [ref=f11e504]:
              - generic [ref=f11e505]: ä¸€è¢‹ä¸€ç ç”Ÿäº§
              - generic [ref=f11e506]:
                - generic [ref=f11e507] [cursor=pointer]: äº§çº¿ç»´æŠ¤
                - generic [ref=f11e511] [cursor=pointer]: æ‰¹æ¬¡ç”Ÿäº§
                - generic [ref=f11e518] [cursor=pointer]: èµ‹ç ç®¡ç†
                - generic [ref=f11e522] [cursor=pointer]: æ‰˜ç›˜ç 
                - generic [ref=f11e528] [cursor=pointer]: æ‰«ç å…¥åº“
            - generic [ref=f11e533]:
              - generic [ref=f11e534]: å‡ºåº“流通
              - generic [ref=f11e535]:
                - generic [ref=f11e536] [cursor=pointer]: å‘货通知
                - generic [ref=f11e540] [cursor=pointer]: é”€å”®å‡ºåº“
                - generic [ref=f11e547] [cursor=pointer]: é”€å”®é€€è´§
            - generic [ref=f11e554]:
              - generic [ref=f11e555]: é˜²ä¼ªé˜²çªœè´§
              - generic [ref=f11e556]:
                - generic [ref=f11e557] [cursor=pointer]: çªœè´§é¢„è­¦
                - generic [ref=f11e561] [cursor=pointer]: æ‰«ç åœ°ç†åˆ†å¸ƒ
                - generic [ref=f11e567] [cursor=pointer]: å®Œæ•´æ€§æ ¡éªŒ
            - generic [ref=f11e575]:
              - generic [ref=f11e576]: æº¯æºåˆ†æž
              - generic [ref=f11e577]: æ¶ˆè´¹è€…扫码统计
            - generic [ref=f11e583]:
              - generic [ref=f11e584]: ä»“储物流
              - generic [ref=f11e585]:
                - generic [ref=f11e586] [cursor=pointer]: ä»“库设置
                - generic [ref=f11e592] [cursor=pointer]: åº“存现有量
                - generic [ref=f11e598] [cursor=pointer]: åº“存预警
                - generic [ref=f11e601] [cursor=pointer]: è½¬ç§»è°ƒæ‹¨
            - generic [ref=f11e606]:
              - generic [ref=f11e607]: è´¨é‡ç®¡ç†
              - generic [ref=f11e608]:
                - generic [ref=f11e609] [cursor=pointer]: æ‰¹æ¬¡è¿½æº¯
                - generic [ref=f11e615] [cursor=pointer]: ä¸åˆæ ¼å“
                - generic [ref=f11e619] [cursor=pointer]: ç§å­è´¨æ£€
.playwright-mcp/page-2026-09-02T06-08-53-561Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T06-11-00-277Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T06-14-13-812Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
.playwright-mcp/page-2026-09-02T06-14-34-873Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,190 @@
- generic [ref=f15e7]:
  - complementary [ref=f15e9]:
    - generic [ref=f15e10]:
      - link [ref=f15e13] [cursor=pointer]:
        - /url: javascript:void 0
        - img "工业AI智造平台" [ref=f15e16]
      - menu [ref=f15e19]:
        - menuitem [ref=f15e20] [cursor=pointer]
        - listitem [ref=f15e24]:
          - generic [ref=f15e25] [cursor=pointer]
        - listitem [ref=f15e31]:
          - generic [ref=f15e32] [cursor=pointer]
        - listitem [ref=f15e36]:
          - generic [ref=f15e37] [cursor=pointer]
        - listitem [ref=f15e43]:
          - generic [ref=f15e44] [cursor=pointer]
        - listitem [ref=f15e48]:
          - generic [ref=f15e49] [cursor=pointer]
        - listitem [ref=f15e53]:
          - generic [ref=f15e54] [cursor=pointer]
        - listitem [ref=f15e58]:
          - generic [ref=f15e59] [cursor=pointer]
        - listitem [ref=f15e63]:
          - generic [ref=f15e64] [cursor=pointer]
        - listitem [ref=f15e68]:
          - generic [ref=f15e69] [cursor=pointer]
      - generic [ref=f15e74] [cursor=pointer]
  - generic [ref=f15e79]:
    - generic [ref=f15e80]:
      - banner [ref=f15e81]:
        - button [ref=f15e82] [cursor=pointer]
        - button [ref=f15e85] [cursor=pointer]
        - navigation "breadcrumb" [ref=f15e90]:
          - list [ref=f15e91]:
            - listitem [ref=f15e92]:
              - link "首页" [disabled] [ref=f15e93]
        - generic [ref=f15e98]:
          - generic [ref=f15e100] [cursor=pointer]:
            - generic [ref=f15e104]: æœç´¢
            - generic [ref=f15e105]: Ctrl K
          - button [ref=f15e107] [cursor=pointer]
          - button "dark" [ref=f15e112] [cursor=pointer]
          - button [ref=f15e121] [cursor=pointer]
          - button [ref=f15e127] [cursor=pointer]
          - button [ref=f15e130] [cursor=pointer]
          - button [ref=f15e136] [cursor=pointer]:
            - button [ref=f15e138]
          - button "工作台" [ref=f15e142] [cursor=pointer]
          - button "理员" [ref=f15e146] [cursor=pointer]
      - generic [ref=f15e153]:
        - generic [ref=f15e154]: é¦–页
        - generic [ref=f15e173]:
          - button [ref=f15e174] [cursor=pointer]
          - generic [ref=f15e181] [cursor=pointer]
          - generic [ref=f15e185] [cursor=pointer]
    - main [ref=f15e192]:
      - generic [ref=f15e196]:
        - generic [ref=f15e197]:
          - generic [ref=f15e198]: ä¸šåŠ¡å…¨é“¾è·¯
          - generic [ref=f15e210]:
            - generic [ref=f15e217] [cursor=pointer]:
              - generic [ref=f15e218]: å“ç§
              - generic [ref=f15e219]: "1"
            - generic [ref=f15e227] [cursor=pointer]:
              - generic [ref=f15e228]: äº§çº¿
              - generic [ref=f15e229]: "1"
            - generic [ref=f15e240] [cursor=pointer]:
              - generic [ref=f15e241]: ç”Ÿäº§æ‰¹æ¬¡
              - generic [ref=f15e242]: "103"
            - generic [ref=f15e250] [cursor=pointer]:
              - generic [ref=f15e251]: è¢‹ç 
              - generic [ref=f15e252]: 1,101
            - generic [ref=f15e262] [cursor=pointer]:
              - generic [ref=f15e263]: æ‰˜ç›˜ç 
              - generic [ref=f15e264]: "11"
            - generic [ref=f15e275] [cursor=pointer]:
              - generic [ref=f15e276]: é”€å”®å‡ºåº“
              - generic [ref=f15e277]: "0"
            - generic [ref=f15e285] [cursor=pointer]:
              - generic [ref=f15e286]: æµé€šæ‰«ç 
              - generic [ref=f15e287]: "23"
            - generic [ref=f15e295] [cursor=pointer]:
              - generic [ref=f15e296]: é˜²çªœ/溯源
              - generic [ref=f15e297]: "0"
        - generic [ref=f15e298]:
          - generic [ref=f15e308] [cursor=pointer]:
            - generic [ref=f15e309]: å“ç§æ€»æ•°
            - generic [ref=f15e310]: "1"
            - generic [ref=f15e311]: åŸºç¡€æ•°æ®
          - generic [ref=f15e322] [cursor=pointer]:
            - generic [ref=f15e323]: ç”Ÿäº§æ‰¹æ¬¡
            - generic [ref=f15e324]: "92"
            - generic [ref=f15e325]: ä¸€è¢‹ä¸€ç 
          - generic [ref=f15e333] [cursor=pointer]:
            - generic [ref=f15e334]: è¢‹ç 
            - generic [ref=f15e335]: "981"
            - generic [ref=f15e336]: èµ‹ç 
          - generic [ref=f15e346] [cursor=pointer]:
            - generic [ref=f15e347]: æ‰˜ç›˜ç 
            - generic [ref=f15e348]: "10"
            - generic [ref=f15e349]: æ‰˜ç›˜
          - generic [ref=f15e360] [cursor=pointer]:
            - generic [ref=f15e361]: é”€å”®å‡ºåº“单
            - generic [ref=f15e362]: "0"
            - generic [ref=f15e363]: å‡ºåº“流通
          - generic [ref=f15e371] [cursor=pointer]:
            - generic [ref=f15e372]: ç´¯è®¡æ‰«ç 
            - generic [ref=f15e373]: "20"
            - generic [ref=f15e374]: æº¯æº
          - generic [ref=f15e381] [cursor=pointer]:
            - generic [ref=f15e382]: åº“存预警
            - generic [ref=f15e383]: "0"
            - generic [ref=f15e384]: ä»“储
          - generic [ref=f15e392] [cursor=pointer]:
            - generic [ref=f15e393]: å¾…评审不合格
            - generic [ref=f15e394]: "0"
            - generic [ref=f15e395]: è´¨é‡
        - generic [ref=f15e396]:
          - generic [ref=f15e400]:
            - generic [ref=f15e401]: è¢‹ç ç”Ÿæˆè¶‹åŠ¿
            - generic [ref=f15e403]:
              - generic [ref=f15e404] [cursor=pointer]:
                - generic [ref=f15e405]:
                  - radio "近 7 å¤©" [checked]
                - generic [ref=f15e406]: è¿‘ 7 å¤©
              - generic [ref=f15e407] [cursor=pointer]:
                - generic [ref=f15e408]:
                  - radio "近 30 å¤©"
                - generic [ref=f15e409]: è¿‘ 30 å¤©
          - generic [ref=f15e414]: æ¶ˆè´¹è€…扫码地区分布
        - generic [ref=f15e423]:
          - generic [ref=f15e424]: æ‰¹æ¬¡çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f15e433]: è¢‹ç çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f15e443]:
            - generic [ref=f15e444]: å¾…办与预警
            - generic [ref=f15e448]:
              - generic [ref=f15e453] [cursor=pointer]:
                - generic [ref=f15e454]: çªœè´§é¢„è­¦
                - generic [ref=f15e455]: å¾…处理的窜货预警
              - generic [ref=f15e459] [cursor=pointer]:
                - generic [ref=f15e460]: åº“存预警
                - generic [ref=f15e461]: æœªå¤„理的库存预警
              - generic [ref=f15e466] [cursor=pointer]:
                - generic [ref=f15e467]: ä¸åˆæ ¼å“
                - generic [ref=f15e468]: å¾…评审的不合格品单
        - generic [ref=f15e469]:
          - generic [ref=f15e470]: å¿«æ·å…¥å£
          - generic [ref=f15e474]:
            - generic [ref=f15e476]:
              - generic [ref=f15e477]: åŸºç¡€æ•°æ®
              - generic [ref=f15e478]:
                - generic [ref=f15e479] [cursor=pointer]: å“ç§ç®¡ç†
                - generic [ref=f15e485] [cursor=pointer]: å“ç§åˆ†ç±»
                - generic [ref=f15e491] [cursor=pointer]: è®¡é‡å•位
            - generic [ref=f15e496]:
              - generic [ref=f15e497]: ä¸€è¢‹ä¸€ç ç”Ÿäº§
              - generic [ref=f15e498]:
                - generic [ref=f15e499] [cursor=pointer]: äº§çº¿ç»´æŠ¤
                - generic [ref=f15e503] [cursor=pointer]: æ‰¹æ¬¡ç”Ÿäº§
                - generic [ref=f15e510] [cursor=pointer]: èµ‹ç ç®¡ç†
                - generic [ref=f15e514] [cursor=pointer]: æ‰˜ç›˜ç 
                - generic [ref=f15e520] [cursor=pointer]: æ‰«ç å…¥åº“
            - generic [ref=f15e525]:
              - generic [ref=f15e526]: å‡ºåº“流通
              - generic [ref=f15e527]:
                - generic [ref=f15e528] [cursor=pointer]: å‘货通知
                - generic [ref=f15e532] [cursor=pointer]: é”€å”®å‡ºåº“
                - generic [ref=f15e539] [cursor=pointer]: é”€å”®é€€è´§
            - generic [ref=f15e546]:
              - generic [ref=f15e547]: é˜²ä¼ªé˜²çªœè´§
              - generic [ref=f15e548]:
                - generic [ref=f15e549] [cursor=pointer]: çªœè´§é¢„è­¦
                - generic [ref=f15e553] [cursor=pointer]: æ‰«ç åœ°ç†åˆ†å¸ƒ
                - generic [ref=f15e559] [cursor=pointer]: å®Œæ•´æ€§æ ¡éªŒ
            - generic [ref=f15e567]:
              - generic [ref=f15e568]: æº¯æºåˆ†æž
              - generic [ref=f15e569]: æ¶ˆè´¹è€…扫码统计
            - generic [ref=f15e575]:
              - generic [ref=f15e576]: ä»“储物流
              - generic [ref=f15e577]:
                - generic [ref=f15e578] [cursor=pointer]: ä»“库设置
                - generic [ref=f15e584] [cursor=pointer]: åº“存现有量
                - generic [ref=f15e590] [cursor=pointer]: åº“存预警
                - generic [ref=f15e593] [cursor=pointer]: è½¬ç§»è°ƒæ‹¨
            - generic [ref=f15e598]:
              - generic [ref=f15e599]: è´¨é‡ç®¡ç†
              - generic [ref=f15e600]:
                - generic [ref=f15e601] [cursor=pointer]: æ‰¹æ¬¡è¿½æº¯
                - generic [ref=f15e607] [cursor=pointer]: ä¸åˆæ ¼å“
                - generic [ref=f15e611] [cursor=pointer]: ç§å­è´¨æ£€
.playwright-mcp/page-2026-09-02T06-16-10-960Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,190 @@
- generic [ref=f15e7]:
  - complementary [ref=f15e9]:
    - generic [ref=f15e10]:
      - link [ref=f15e13] [cursor=pointer]:
        - /url: javascript:void 0
        - img "工业AI智造平台" [ref=f15e16]
      - menu [ref=f15e19]:
        - menuitem [ref=f15e20] [cursor=pointer]
        - listitem [ref=f15e24]:
          - generic [ref=f15e25] [cursor=pointer]
        - listitem [ref=f15e31]:
          - generic [ref=f15e32] [cursor=pointer]
        - listitem [ref=f15e36]:
          - generic [ref=f15e37] [cursor=pointer]
        - listitem [ref=f15e43]:
          - generic [ref=f15e44] [cursor=pointer]
        - listitem [ref=f15e48]:
          - generic [ref=f15e49] [cursor=pointer]
        - listitem [ref=f15e53]:
          - generic [ref=f15e54] [cursor=pointer]
        - listitem [ref=f15e58]:
          - generic [ref=f15e59] [cursor=pointer]
        - listitem [ref=f15e63]:
          - generic [ref=f15e64] [cursor=pointer]
        - listitem [ref=f15e68]:
          - generic [ref=f15e69] [cursor=pointer]
      - generic [ref=f15e74] [cursor=pointer]
  - generic [ref=f15e79]:
    - generic [ref=f15e80]:
      - banner [ref=f15e81]:
        - button [ref=f15e82] [cursor=pointer]
        - button [ref=f15e85] [cursor=pointer]
        - navigation "breadcrumb" [ref=f15e90]:
          - list [ref=f15e91]:
            - listitem [ref=f15e92]:
              - link "首页" [disabled] [ref=f15e93]
        - generic [ref=f15e98]:
          - generic [ref=f15e100] [cursor=pointer]:
            - generic [ref=f15e104]: æœç´¢
            - generic [ref=f15e105]: Ctrl K
          - button [ref=f15e107] [cursor=pointer]
          - button "dark" [ref=f15e112] [cursor=pointer]
          - button [ref=f15e121] [cursor=pointer]
          - button [ref=f15e127] [cursor=pointer]
          - button [ref=f15e130] [cursor=pointer]
          - button [ref=f15e136] [cursor=pointer]:
            - button [ref=f15e138]
          - button "工作台" [ref=f15e142] [cursor=pointer]
          - button "理员" [ref=f15e146] [cursor=pointer]
      - generic [ref=f15e153]:
        - generic [ref=f15e154]: é¦–页
        - generic [ref=f15e173]:
          - button [ref=f15e174] [cursor=pointer]
          - generic [ref=f15e181] [cursor=pointer]
          - generic [ref=f15e185] [cursor=pointer]
    - main [ref=f15e192]:
      - generic [ref=f15e196]:
        - generic [ref=f15e197]:
          - generic [ref=f15e198]: ä¸šåŠ¡å…¨é“¾è·¯
          - generic [ref=f15e210]:
            - generic [ref=f15e217] [cursor=pointer]:
              - generic [ref=f15e218]: å“ç§
              - generic [ref=f15e219]: "1"
            - generic [ref=f15e227] [cursor=pointer]:
              - generic [ref=f15e228]: äº§çº¿
              - generic [ref=f15e229]: "1"
            - generic [ref=f15e240] [cursor=pointer]:
              - generic [ref=f15e241]: ç”Ÿäº§æ‰¹æ¬¡
              - generic [ref=f15e242]: "103"
            - generic [ref=f15e250] [cursor=pointer]:
              - generic [ref=f15e251]: è¢‹ç 
              - generic [ref=f15e252]: 1,101
            - generic [ref=f15e262] [cursor=pointer]:
              - generic [ref=f15e263]: æ‰˜ç›˜ç 
              - generic [ref=f15e264]: "11"
            - generic [ref=f15e275] [cursor=pointer]:
              - generic [ref=f15e276]: é”€å”®å‡ºåº“
              - generic [ref=f15e277]: "0"
            - generic [ref=f15e285] [cursor=pointer]:
              - generic [ref=f15e286]: æµé€šæ‰«ç 
              - generic [ref=f15e287]: "23"
            - generic [ref=f15e295] [cursor=pointer]:
              - generic [ref=f15e296]: é˜²çªœ/溯源
              - generic [ref=f15e297]: "0"
        - generic [ref=f15e298]:
          - generic [ref=f15e308] [cursor=pointer]:
            - generic [ref=f15e309]: å“ç§æ€»æ•°
            - generic [ref=f15e310]: "1"
            - generic [ref=f15e311]: åŸºç¡€æ•°æ®
          - generic [ref=f15e322] [cursor=pointer]:
            - generic [ref=f15e323]: ç”Ÿäº§æ‰¹æ¬¡
            - generic [ref=f15e324]: "103"
            - generic [ref=f15e325]: ä¸€è¢‹ä¸€ç 
          - generic [ref=f15e333] [cursor=pointer]:
            - generic [ref=f15e334]: è¢‹ç 
            - generic [ref=f15e335]: 1,101
            - generic [ref=f15e336]: èµ‹ç 
          - generic [ref=f15e346] [cursor=pointer]:
            - generic [ref=f15e347]: æ‰˜ç›˜ç 
            - generic [ref=f15e348]: "11"
            - generic [ref=f15e349]: æ‰˜ç›˜
          - generic [ref=f15e360] [cursor=pointer]:
            - generic [ref=f15e361]: é”€å”®å‡ºåº“单
            - generic [ref=f15e362]: "0"
            - generic [ref=f15e363]: å‡ºåº“流通
          - generic [ref=f15e371] [cursor=pointer]:
            - generic [ref=f15e372]: ç´¯è®¡æ‰«ç 
            - generic [ref=f15e373]: "23"
            - generic [ref=f15e374]: æº¯æº
          - generic [ref=f15e381] [cursor=pointer]:
            - generic [ref=f15e382]: åº“存预警
            - generic [ref=f15e383]: "0"
            - generic [ref=f15e384]: ä»“储
          - generic [ref=f15e392] [cursor=pointer]:
            - generic [ref=f15e393]: å¾…评审不合格
            - generic [ref=f15e394]: "0"
            - generic [ref=f15e395]: è´¨é‡
        - generic [ref=f15e396]:
          - generic [ref=f15e400]:
            - generic [ref=f15e401]: è¢‹ç ç”Ÿæˆè¶‹åŠ¿
            - generic [ref=f15e403]:
              - generic [ref=f15e404] [cursor=pointer]:
                - generic [ref=f15e405]:
                  - radio "近 7 å¤©" [checked]
                - generic [ref=f15e406]: è¿‘ 7 å¤©
              - generic [ref=f15e407] [cursor=pointer]:
                - generic [ref=f15e408]:
                  - radio "近 30 å¤©"
                - generic [ref=f15e409]: è¿‘ 30 å¤©
          - generic [ref=f15e414]: æ¶ˆè´¹è€…扫码地区分布
        - generic [ref=f15e423]:
          - generic [ref=f15e424]: æ‰¹æ¬¡çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f15e433]: è¢‹ç çŠ¶æ€åˆ†å¸ƒ
          - generic [ref=f15e443]:
            - generic [ref=f15e444]: å¾…办与预警
            - generic [ref=f15e448]:
              - generic [ref=f15e453] [cursor=pointer]:
                - generic [ref=f15e454]: çªœè´§é¢„è­¦
                - generic [ref=f15e455]: å¾…处理的窜货预警
              - generic [ref=f15e459] [cursor=pointer]:
                - generic [ref=f15e460]: åº“存预警
                - generic [ref=f15e461]: æœªå¤„理的库存预警
              - generic [ref=f15e466] [cursor=pointer]:
                - generic [ref=f15e467]: ä¸åˆæ ¼å“
                - generic [ref=f15e468]: å¾…评审的不合格品单
        - generic [ref=f15e469]:
          - generic [ref=f15e470]: å¿«æ·å…¥å£
          - generic [ref=f15e474]:
            - generic [ref=f15e476]:
              - generic [ref=f15e477]: åŸºç¡€æ•°æ®
              - generic [ref=f15e478]:
                - generic [ref=f15e479] [cursor=pointer]: å“ç§ç®¡ç†
                - generic [ref=f15e485] [cursor=pointer]: å“ç§åˆ†ç±»
                - generic [ref=f15e491] [cursor=pointer]: è®¡é‡å•位
            - generic [ref=f15e496]:
              - generic [ref=f15e497]: ä¸€è¢‹ä¸€ç ç”Ÿäº§
              - generic [ref=f15e498]:
                - generic [ref=f15e499] [cursor=pointer]: äº§çº¿ç»´æŠ¤
                - generic [ref=f15e503] [cursor=pointer]: æ‰¹æ¬¡ç”Ÿäº§
                - generic [ref=f15e510] [cursor=pointer]: èµ‹ç ç®¡ç†
                - generic [ref=f15e514] [cursor=pointer]: æ‰˜ç›˜ç 
                - generic [ref=f15e520] [cursor=pointer]: æ‰«ç å…¥åº“
            - generic [ref=f15e525]:
              - generic [ref=f15e526]: å‡ºåº“流通
              - generic [ref=f15e527]:
                - generic [ref=f15e528] [cursor=pointer]: å‘货通知
                - generic [ref=f15e532] [cursor=pointer]: é”€å”®å‡ºåº“
                - generic [ref=f15e539] [cursor=pointer]: é”€å”®é€€è´§
            - generic [ref=f15e546]:
              - generic [ref=f15e547]: é˜²ä¼ªé˜²çªœè´§
              - generic [ref=f15e548]:
                - generic [ref=f15e549] [cursor=pointer]: çªœè´§é¢„è­¦
                - generic [ref=f15e553] [cursor=pointer]: æ‰«ç åœ°ç†åˆ†å¸ƒ
                - generic [ref=f15e559] [cursor=pointer]: å®Œæ•´æ€§æ ¡éªŒ
            - generic [ref=f15e567]:
              - generic [ref=f15e568]: æº¯æºåˆ†æž
              - generic [ref=f15e569]: æ¶ˆè´¹è€…扫码统计
            - generic [ref=f15e575]:
              - generic [ref=f15e576]: ä»“储物流
              - generic [ref=f15e577]:
                - generic [ref=f15e578] [cursor=pointer]: ä»“库设置
                - generic [ref=f15e584] [cursor=pointer]: åº“存现有量
                - generic [ref=f15e590] [cursor=pointer]: åº“存预警
                - generic [ref=f15e593] [cursor=pointer]: è½¬ç§»è°ƒæ‹¨
            - generic [ref=f15e598]:
              - generic [ref=f15e599]: è´¨é‡ç®¡ç†
              - generic [ref=f15e600]:
                - generic [ref=f15e601] [cursor=pointer]: æ‰¹æ¬¡è¿½æº¯
                - generic [ref=f15e607] [cursor=pointer]: ä¸åˆæ ¼å“
                - generic [ref=f15e611] [cursor=pointer]: ç§å­è´¨æ£€
.playwright-mcp/page-2026-09-02T06-16-32-193Z.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
- generic [active]: å·¥ä¸šAI智造平台
docs/bagcode_home_dashboard_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,83 @@
# ä¸€è¢‹ä¸€ç é¦–页改版(全链路驾驶舱) â€” å‰ç«¯è”调方案
## èƒŒæ™¯
将 `/bagcode-home` é¦–页从"基础数据 + ä¸€è¢‹ä¸€ç ç”Ÿäº§"的局部统计页,重构为覆盖**完整价值链**的驾驶舱:品种 â†’ äº§çº¿/批次 â†’ è¢‹ç /托盘 â†’ é”€å”®å‡ºåº“ â†’ æµé€šæ‰«ç  â†’ é˜²çªœæº¯æº,并串联仓储库存、质量预警、快捷入口。整体视觉对齐登录页的**农业翠绿玻璃**风格。
## æ¶‰åŠé¡µé¢
- é¦–页(驾驶舱):`src/views/dashboard/bagCodeHome/index.vue` â€”— å…¨é“¾è·¯é‡æž„
- å…³è”跳转页面:基础数据、一袋一码生产、出库流通、防伪防窜货、溯源分析、仓储物流、质量管理 ä¸‹çš„各业务页
## æŽ¥å£å˜æ›´
### 1. æ‰©å±•汇总接口(返回新增字段)
`GET /mes/bag-code-home-statistics/summary`
新增响应字段(均 `Long`,无默认值时返回 0):
| å­—段 | è¯´æ˜Ž | æ•°æ®æ¥æºè¡¨ |
|------|------|-----------|
| `salesNoticeCount` | å‘货通知单数 | `mes_wm_sales_notice` |
| `productSalesCount` | é”€å”®å‡ºåº“单数 | `mes_wm_product_sales` |
| `returnSalesCount` | é”€å”®é€€è´§å•æ•° | `mes_wm_return_sales` |
| `stockWarningPendingCount` | æœªå¤„理库存预警数(`status=10`) | `mes_wm_stock_warning` |
| `ncrPendingCount` | å¾…评审不合格品单数(`status=0`) | `mes_qc_ncr` |
原有字段(`mdmItemCount/mesItemCount/itemTypeCount/unitMeasureCount/lineCount/batchCount/bagCodeCount/palletCount/batchStatusList/bagCodeStatusList`)保持不变。
### 2. å¤ç”¨å·²æœ‰ç»Ÿè®¡æŽ¥å£(无需改动)
| æŽ¥å£ | ç”¨é€” | å‰ç«¯ API |
|------|------|---------|
| `GET /mes/trace-cross-region/summary` | çªœè´§é¢„警汇总(`total/pending/handled/falseAlarm/crossProvince/crossCity`) | `api/mes/trace-cross-region`(新增) |
| `GET /mes/trace-consumer-scan/summary` | ç´¯è®¡æ‰«ç /成功/失败/涉及批次 | å·²æœ‰ |
| `GET /mes/trace-consumer-scan/distribution?groupBy=province` | æ¶ˆè´¹è€…扫码地区分布 | å·²æœ‰ |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é¦–页结构(index.vue)
| åŒºå— | ç»„ä»¶ | æ•°æ® |
|------|------|------|
| ä¸šåŠ¡å…¨é“¾è·¯ | `modules/business-flow.vue` | summary + æ‰«ç æ€»æ•° + çªœè´§æ€»æ•° |
| å…¨é“¾è·¯ KPI æ€»è§ˆ(8 å¡) | `modules/kpi-cards.vue` | summary + æ‰«ç æ€»æ•° |
| è¢‹ç ç”Ÿæˆè¶‹åŠ¿ | `modules/trend-chart.vue`(复用) | `summary/trend` |
| æ¶ˆè´¹è€…扫码地区分布 | `modules/scan-region-chart.vue`(新增) | `distribution/groupBy=province` |
| æ‰¹æ¬¡çŠ¶æ€åˆ†å¸ƒ / è¢‹ç çŠ¶æ€åˆ†å¸ƒ | `batch-status-chart.vue` / `bag-code-status-chart.vue`(复用) | summary çŠ¶æ€åˆ—è¡¨ |
| å¾…办与预警 | `modules/alert-panel.vue`(新增) | summary + çªœè´§æ±‡æ€» |
| åˆ†ç»„快捷入口 | `modules/shortcuts.vue`(重写为分组) | é™æ€è·¯ç”±é…ç½® |
### 2. æ–°å¢ž/删除文件
- æ–°å¢ž:`api/mes/trace-cross-region/index.ts`
- æ–°å¢ž:首页 `modules/business-flow.vue`、`kpi-cards.vue`、`scan-region-chart.vue`、`alert-panel.vue`
- é‡å†™:首页 `index.vue`、`modules/shortcuts.vue`
- æ›´æ–°:`api/mes/bag-code-home/index.ts`(Summary åŠ  5 å­—段)、`data.ts`、`chart-options.ts`
- åˆ é™¤:`modules/basic-cards.vue`、`modules/bag-code-cards.vue`(不再使用)
### 3. æ ·å¼ä¸»é¢˜
- é¦–页内容区统一"农业翠绿玻璃"主题:浅色翠绿渐变背景(无背景图)、白色磨砂玻璃卡片、薄荷白/墨绿文字。
- å›¾è¡¨ç»Ÿä¸€ç¿ ç»¿ä¸»è‰²(`#10b981`)并适配浅底文字色。
- ä¸»é¢˜æ ·å¼é›†ä¸­åœ¨ `index.vue` çš„非 scoped `<style>` ä¸­(`.agri-dashboard` å®¹å™¨ä¸ºä½œç”¨åŸŸ),各卡片组件使用对应玻璃类。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| å“ç§æ€»æ•° | KPI + ä¸šåŠ¡é“¾è·¯ | ç‚¹æŒ‰è·³è½¬ `MdmItem` |
| ç”Ÿäº§æ‰¹æ¬¡ | KPI + ä¸šåŠ¡é“¾è·¯ | è·³è½¬ `MesProBatch` |
| è¢‹ç  / æ‰˜ç›˜ç  | KPI + ä¸šåŠ¡é“¾è·¯ | è·³è½¬ `MesProBagCode` / `MesProPallet` |
| é”€å”®å‡ºåº“单数 | KPI + ä¸šåŠ¡é“¾è·¯ | è·³è½¬ `MesWmProductSales` |
| ç´¯è®¡æ‰«ç  | KPI + ä¸šåŠ¡é“¾è·¯ | æ¥è‡ª `trace-consumer-scan/summary`,跳转 `MesConsumerScanStatistics` |
| çªœè´§é¢„警总数 | ä¸šåŠ¡é“¾è·¯(防窜/溯源) | æ¥è‡ª `trace-cross-region/summary`,跳转 `MesTraceCrossRegion` |
| åº“存预警(未处理) | KPI + å¾…办预警 | è·³è½¬ `MesWmStockWarning` |
| å¾…评审不合格 | KPI + å¾…办预警 | è·³è½¬ `MesQcNcr` |
## æ³¨æ„äº‹é¡¹
- æ–°å¢žçš„ 5 ä¸ªæ±‡æ€»å­—段需要**后端重新编译并重启**后才会返回;重启前前端已做 `?? 0` å…œåº•,相关卡片/节点显示 0,不影响其它数据。
- è·¯ç”±è·³è½¬ä½¿ç”¨**路由 name**(已与菜单 `component_name` å¯¹é½),未加载的菜单沿用 `message.warning('当前菜单尚未加载...')` å…œåº•。
- æ¶ˆè´¹è€…扫码/窜货无经纬度字段,仅支持省/市文本分布图,不支持地图坐标。
docs/batch_trace_refactor_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,102 @@
# æ‰¹æ¬¡è¿½æº¯ç»Ÿä¸€èšåˆæŽ¥å£ - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- MES â†’ è´¨æ£€ â†’ æ‰¹æ¬¡è¿½æº¯ï¼ˆ`/qc/batch-trace`)
- æ‰¹æ¬¡è¿½æº¯è¯¦æƒ…
- æ‰¹æ¬¡ç ã€æ‰˜ç›˜ç ã€è¢‹ç æ‰«ç è¿½æº¯å…¥å£
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. åœ¨æ‰¹æ¬¡è¿½æº¯åˆ—表输入生产批次码、临时批次码、袋码或托盘码。
2. é¡µé¢è°ƒç”¨ç»Ÿä¸€è¯¦æƒ…接口,后端将入口码解析到生产批次,并聚合生产、质检、仓储、销售和防伪信息。
3. è¯¦æƒ…页以生产批次为根展示全链路数据;袋码和托盘信息来自生产赋码数据,仓储与销售信息来自仓储业务数据。
4. å‰å‘追溯展示当前批次产出的下游批次;后向追溯展示当前批次消耗的上游批次。
5. æ—¶é—´çº¿ã€é˜¶æ®µè§†å›¾å’Œå†³ç­–摘要均使用同一次聚合响应,避免打开详情后重复请求多个旧接口。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|---|---|---|
| GET | `/mes/trace/batch/detail` | æŸ¥è¯¢ç»Ÿä¸€æ‰¹æ¬¡è¿½æº¯è¯¦æƒ… |
### è¯·æ±‚参数
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| `code` | String | æ˜¯ | ç”Ÿäº§æ‰¹æ¬¡ç ã€ä¸´æ—¶æ‰¹æ¬¡ç ã€è¢‹ç æˆ–托盘码 |
示例:`code=BAG-202608280001`
### å“åº”主要字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| `sourceType` | String | å…¥å£ç±»åž‹ï¼š`BAG`、`PALLET`、`BATCH` |
| `requestCode` | String | åŽŸå§‹è¾“å…¥ç  |
| `itemCode` / `itemName` | String | å“ç§ç¼–码、名称 |
| `itemSpecification` | String | å“ç§è§„æ ¼ |
| `batchCode` / `tempCode` | String | æ­£å¼æ‰¹æ¬¡å·ã€ä¸´æ—¶æ‰¹æ¬¡ç  |
| `batchStatusName` | String | æ‰¹æ¬¡çŠ¶æ€åç§° |
| `produceDate` | DateTime | ç”Ÿäº§æ—¥æœŸ |
| `lineCode` / `lineName` | String | äº§çº¿ç¼–码、名称 |
| `ownerUserName` | String | ç”Ÿäº§è´£ä»»äºº |
| `planQuantity` | Decimal | è®¡åˆ’数量 |
| `bagQuantity` / `palletQuantity` | Integer | è¢‹ç æ•°é‡ã€æ‰˜ç›˜æ•°é‡ |
| `workers` | Array | ç”Ÿäº§äººå‘˜ |
| `pallets` | Array | æ‰˜ç›˜æ‘˜è¦ |
| `bagTotal` / `bags` | Long / Array | è¢‹ç æ€»æ•°åŠæœ€å¤š 100 æ¡æ˜Žç»† |
| `inbound` | Object | æœ€æ–°å…¥åº“事件 |
| `quality` | Object | ç§å­è´¨é‡æ£€éªŒä¸ŽæŒ‡æ ‡ |
| `antiFake` | Object | å½“前追溯码扫码次数、首次和最近扫码时间 |
| `purchaseSources` | Array | é‡‡è´­æ¥æºã€ä¾›åº”商和 IQC ä¿¡æ¯ |
| `salesTargets` | Array | é”€å”®å‡ºåº“、客户和 OQC ä¿¡æ¯ |
| `processTraces` | Array | å·¥åºæŠ•入、产出流转信息 |
### å“åº”示例
```json
{
  "code": 0,
  "data": {
    "sourceType": "BAG",
    "requestCode": "BAG-202608280001",
    "batchCode": "B20260828001",
    "itemName": "示例品种",
    "batchStatusName": "已完成",
    "workers": [],
    "pallets": [],
    "bags": [],
    "purchaseSources": [],
    "salesTargets": [],
    "processTraces": []
  }
}
```
## é¡µé¢å±•示规则
| è§†å›¾ | å±•示内容 |
|---|---|
| é˜¶æ®µæ€»è§ˆ | æ¥æºã€ç”Ÿäº§ã€è´¨æ£€ã€èµ‹ç ã€å…¥åº“、销售、消费、防伪八个阶段的状态和数量 |
| å…¨é“¾è·¯æ—¶é—´çº¿ | æŒ‰æ—¶é—´å±•示采购、生产、质检、赋码、入库、出库和扫码事件;异常状态优先高亮 |
| æµåŠ¨å›¾è°± | ä»¥æ‰¹æ¬¡ã€å·¥åºã€ä»“库、托盘、袋码、销售单和客户为节点;默认展示批次级关系 |
| å†³ç­–分析 | å±•示质量结论、库存数量、销售去向、防伪扫码次数和窜货风险 |
| é‡‡è´­æ¥æº | å±•示采购入库、供应商、采购订单、IQC å•号及检验结论 |
| é”€å”®å޻向 | å±•示销售出库、客户、销售订单、OQC å•号及质量状态 |
| å·¥åºæµè½¬ | `IN` æ˜¾ç¤ºä¸ºæŠ•入,`OUT` æ˜¾ç¤ºä¸ºäº§å‡º |
## ä¸šåŠ¡è§„åˆ™
- è¾“入袋码或托盘码时,详情仍以其所属生产批次为根进行聚合。
- å½“前接口返回的袋码明细最多 100 æ¡ï¼Œ`bagTotal` ç”¨äºŽå±•示完整数量;大批次不得按明细数量判断总量。
- ç©ºæ•°ç»„表示该业务阶段暂无记录;`quality`、`inbound`、`antiFake` ä¸ºç©ºè¡¨ç¤ºæš‚无对应信息。
- é˜²ä¼ªæ‰«ç ç»Ÿè®¡ä»…用于展示,不能替代质量检验结论或出库状态。
- å®¢æˆ·ã€ä¾›åº”商、操作人等名称应以接口返回值为准,不在前端重复查询旧批次接口。
## æ³¨æ„äº‹é¡¹
- ç®¡ç†ç«¯æŽ¥å£éœ€è¦æ‰¹æ¬¡è¿½æº¯æŸ¥è¯¢æƒé™ï¼š`mes:trace-batch:query`。
- æ—§çš„ `/mes/wm/batch/*` æŽ¥å£ç»§ç»­ä¾›ä»“储批次页面使用,批次追溯页面切换后不应再并行请求旧的五个详情接口。
- åŽç»­æ‰©å±•时间线和图谱时,建议后端增加分页与节点层级参数,避免一次性加载全部袋码。
- å±•示消费者扫码信息时不得展示 IP åœ°å€å’Œ User-Agent åŽŸæ–‡ã€‚
docs/mes_wm_material_stock_bag_api.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
# åº“存袋码明细联调方案
## ä¸šåŠ¡æµç¨‹
种子质检以袋码提交。质检合格时,暂存库存汇总数量减少 1,合格库存汇总数量增加 1,同时该袋码明细迁移到合格仓;质检不合格时,暂存库存汇总数量减少 1,袋码明细标记为报废。其他袋码不受影响。
库存台账继续按物料、批次、仓库、库区、库位和供应商汇总;袋码明细用于记录每个袋码的具体状态和位置。
## è¢‹ç æ˜Žç»†ç”Ÿå‘½å‘¨æœŸï¼ˆç»Ÿä¸€æœåŠ¡æ‰˜ç®¡ï¼‰
所有带袋码的库存变动统一通过库存事务服务在同一事务内同步维护 **汇总库存 + è¢‹ç æ˜Žç»† + åº“存流水** ä¸‰è€…一致,各业务入口无需各自实现袋码逻辑。生命周期:
| é˜¶æ®µ | åŠ¨ä½œ | è¢‹ç æ˜Žç»†å˜åŒ– |
|------|------|--------------|
| å•袋扫码入库 | ç”Ÿäº§æ‰«ç å…¥åº“(扫码类型 BAG) | å»ºç«‹æ´»åŠ¨æ˜Žç»†ï¼ˆæš‚å­˜åº“ï¼‰ |
| ç§å­è´¨æ£€åˆæ ¼ | æš‚存出库 + åˆæ ¼å…¥åº“ | æ˜Žç»†ç”±æš‚存库迁移到合格库 |
| ç§å­è´¨æ£€ä¸åˆæ ¼ | æš‚存出库 | æ˜Žç»†æŠ¥åºŸ |
| é”€å”®å‡ºåº“完成 | å‡ºåº“扣减 | æ˜Žç»†ç½®ä¸ºå·²å‡ºåº“ |
| æ•´ä½ç§»åº“ | è°ƒæ‹¨ç§»å‡º + ç§»å…¥ | æ˜Žç»†ä½ç½®è¿ç§»åˆ°ç›®æ ‡åº“位 |
说明:
- æ‰˜ç›˜ï¼ˆPALLET)/批次(BATCH)整批入库、采购/杂项/退货等按行按数量入库的出入口,为批量汇总,不逐袋建立明细;这些袋码在首次种子质检时通过按批次定位暂存库存补建明细。
- äº‹åŠ¡æµæ°´ DTO å¢žåŠ  `bagCodeId`/`bagCode` å­—段;出库/移入按袋码自动更新袋码明细。
- æ¯ç¬”带袋码的入库、出库、移库、质检都会生成对应的 `mes_wm_transaction` æµæ°´å¹¶å›žå¡«è¢‹ç ï¼Œä¾¿äºŽåœ¨åº“存流水页面按袋码筛选追溯。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/mes/wm/material-stock/page` | åº“存汇总分页,响应增加袋码统计字段 |
| GET | `/mes/wm/material-stock/bag-details` | æŸ¥è¯¢æŒ‡å®šåº“存汇总记录下的在库袋码明细 |
| GET | `/mes/wm/transaction/page` | åº“存事务流水,支持 `bagCode` ç­›é€‰å¹¶è¿”回袋码 |
### è¢‹ç æ˜Žç»†æŸ¥è¯¢
请求参数:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| materialStockId | Long | æ˜¯ | åº“存汇总记录 ID |
| bagCode | String | å¦ | è¢‹ç æ¨¡ç³ŠæŸ¥è¯¢ |
| pageNo | Integer | æ˜¯ | é¡µç  |
| pageSize | Integer | æ˜¯ | æ¯é¡µæ¡æ•° |
响应字段包括 `bagCode`、`batchCode`、`stockState`、`status`、`quantity`、`warehouseId`、`locationId`、`areaId`、`inboundTime` å’Œ `outboundTime`。
库存汇总分页响应新增:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| bagCount | Long | å½“前在库袋码明细数量 |
| bagDetailSupported | Boolean | æ˜¯å¦å­˜åœ¨è¢‹ç æ˜Žç»†ï¼›åŽ†å²æ— æ³•è¿˜åŽŸè¢‹ç çš„åº“å­˜ä¸º false |
## å‰ç«¯è°ƒç”¨æ—¶åº
1. è°ƒç”¨åº“存台账分页接口获取汇总记录。
2. å¯¹ `bagDetailSupported=true` çš„记录显示“查看袋码”。
3. ç‚¹å‡»åŽè°ƒç”¨ `/mes/wm/material-stock/bag-details`,传入当前行 `id` ä½œä¸º `materialStockId`。
4. è¢‹ç æ˜Žç»†ä¸­çš„ `bagCode` å¯ç»§ç»­è°ƒç”¨æ‰¹æ¬¡/袋码追溯接口查看质检和生产信息。
5. åº“存流水页面使用 `bagCode` ç­›é€‰æ—¶ï¼Œå±•示质检迁移产生的袋码级流水。
## ä¸šåŠ¡è§„åˆ™
- åŒä¸€è¢‹ç åªèƒ½å­˜åœ¨ä¸€æ¡åº“存袋码明细。
- è´¨æ£€æŒ‰å®žé™…袋码定位暂存库存,不按批次随机扣减。
- æŠ¥åºŸè¢‹ç ä¸è®¡å…¥åœ¨åº“明细查询,但保留明细记录用于追溯。
- æ— æ³•从历史汇总库存还原袋码时,不伪造袋码明细。
- SQL è„šæœ¬ä¸åŒ…å« `tenant_id` å­—段。
docs/sales_notice_to_product_sales.md
@@ -101,6 +101,20 @@
**注意:** æ‹£è´§æ˜Žç»†éœ€è¦æ‰‹åŠ¨æ·»åŠ 
## å‘货通知行物料校验
新增或修改发货通知行时,物料必须关联批次,且该物料批次必须存在正式库库存。正式库指库存状态为合格库(`stockState = 20`)的库存记录。
| æ ¡éªŒé¡¹ | è§„则 |
|--------|------|
| ç‰©æ–™ | å¿…须存在且启用 |
| æ‰¹æ¬¡ | `batchId` æˆ– `batchCode` å¿…须能匹配有效批次,且批次属于所选物料 |
| æ­£å¼åº“å­˜ | ä»…统计合格库库存,不统计暂存库库存 |
| æ•°é‡ | åŒä¸€ç‰©æ–™ã€æ‰¹æ¬¡çš„合格库库存合计必须不少于发货数量 |
| åº“存变更 | ä¿å­˜å‘货通知行时不扣减、不预占库存,实际出库时再次校验 |
库存不足时,前端应直接展示后端返回的“库存数量不足”业务提示,并保留用户填写的表单内容。
## å­—段映射表
### ä¸»è¡¨å­—段映射
docs/scan_in_multi_type_inbound.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
# ç”Ÿäº§çº¿æ‰«ç å…¥åº“:支持袋码/托盘码/批次码 + åŽ»æŽ‰è®¾å¤‡ç¼–ç  - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ç”Ÿäº§çº¿æ‰«ç å…¥åº“(`src/views/mes/pro/scan-in/index.vue`)
## ä¸šåŠ¡å˜æ›´
1. **去掉设备编码**:扫码入库不再录入/展示设备编码,接口 `deviceCode` æ”¹ä¸ºéžå¿…填,前端不再传。
2. **产线、库区、库位改为下拉选择**:数据源为现有精简列表接口,不再手填编号。
3. **等待扫码区增加"扫码类型"选择**:袋码 / æ‰˜ç›˜ç  / æ‰¹æ¬¡ç ä¸‰é€‰ä¸€ï¼Œæ‰«ç æ¡†éšç±»åž‹æäº¤å¯¹åº”码值。
## API
### ä¸»æŽ¥å£ï¼šè¿½æº¯ç æ‰«ç å…¥åº“
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /mes/pro/scan-in/inbound | è¿½æº¯ç æ‰«ç å…¥åº“(袋码/托盘码/批次码) |
**请求参数(本次变更):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | å˜æ›´ | è¯´æ˜Ž |
|------|------|------|------|------|
| scanType | String | å¦ | æ–°å¢ž | æ‰«ç ç±»åž‹ï¼š`BAG` è¢‹ç  / `PALLET` æ‰˜ç›˜ç  / `BATCH` æ‰¹æ¬¡ç ï¼Œä¸ä¼ é»˜è®¤ `BAG` |
| deviceCode | String | å¦ | æ”¹ä¸ºéžå¿…å¡« | è®¾å¤‡ç¼–码,前端不再传 |
| bagCode | String | æ˜¯ | è¯­ä¹‰æ‰©å±• | æ‰«ç å†…容:袋码/托盘码/批次码的码值(含临时码) |
| eventId | String | æ˜¯ | - | äº‹ä»¶å·ï¼ˆå»ºè®®ç”¨ `{scanType}-{时间戳}` ä¿è¯å”¯ä¸€ï¼‰ |
| quantity | Number | æ˜¯ | - | è¢‹ç åœºæ™¯ä¼  1;托盘/批次场景后端忽略该值,按已装袋数/批次袋数计算 |
| lineCode | String | å¦ | - | äº§çº¿ç¼–码(前端下拉选择) |
| locationId | Long | æ˜¯ | - | WIP æš‚存库区编号(前端下拉选择) |
| areaId | Long | æ˜¯ | - | WIP æš‚存库位编号(前端下拉选择) |
**响应新增字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| scanType | String | æœ¬æ¬¡æ‰«ç ç±»åž‹ï¼ˆBAG/PALLET/BATCH) |
**入库数量口径:**
| scanType | æ•°é‡ | è¯´æ˜Ž |
|----------|------|------|
| BAG | å‰ç«¯ä¼  1 | å•袋入库 |
| PALLET | æ‰˜ç›˜å·²è£…袋数 `bag_count` | æ•´æ‰˜ä¸€æ¬¡æ€§å…¥æš‚存库 |
| BATCH | æ‰¹æ¬¡è¢‹æ•° `bag_quantity` | æ•´æ‰¹ä¸€æ¬¡æ€§å…¥æš‚存库 |
**入库去重:** è¢‹ç ã€æ‰˜ç›˜ç ã€æ‰¹æ¬¡ç å‡ä»¥å„自 `inbound_event_id` åˆ¤æ–­æ˜¯å¦å·²å…¥åº“,重复扫码返回对应"已入库"错误码,不会重复写入库存事务。
### ä¸‹æ‹‰æ•°æ®æºï¼ˆå¤ç”¨çŽ°æœ‰æŽ¥å£ï¼Œæ— æ–°å¢žï¼‰
| ç”¨é€” | æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|------|
| äº§çº¿ | GET | /mes/pro/line/simple-list | è¿”回启用产线 `{id, code, name}` |
| åº“区 | GET | /mes/wm/warehouse-location/simple-list | è¿”回库区 `{id, code, name}`;选库区后联动加载其下库位 |
| åº“位 | GET | /mes/wm/warehouse-area/simple-list?locationId= | è¿”回库位 `{id, code, name}` |
## æ•°æ®å¸¦å…¥è¯´æ˜Ž
- äº§çº¿/库区/库位:下拉选择,页面 `onMounted` ä¸€æ¬¡æ€§åŠ è½½äº§çº¿ã€åº“åŒºï¼›é€‰æ‹©åº“åŒºåŽæŒ‰ `locationId` è”动加载库位。
- æ‰«ç æäº¤ï¼š`locationId`、`areaId`、`scanType`、`bagCode` ä¸€å¹¶å¸¦å…¥å…¥åº“接口;产线 `lineCode` æœ‰å€¼æ‰ä¼ ã€‚
- äº‹ä»¶åˆ—表/看板:以 `scanType` åŒºåˆ†è¿½æº¯ç ç±»åž‹å±•示(历史无类型的数据默认显示"袋码")。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| æ‰«ç ç±»åž‹ | ç­‰å¾…扫码区下拉 | ä¸‰é€‰ä¸€ï¼šè¢‹ç /托盘码/批次码 |
| ç”Ÿäº§äº§çº¿ | æ‰«ç å¡é…ç½®åŒº + çŠ¶æ€å¡ | ä¸‹æ‹‰é€‰æ‹©ï¼Œå¯æ¸…空 |
| WIP æš‚存库区/库位 | æ‰«ç å¡é…ç½®åŒº + çŠ¶æ€å¡ | ä¸‹æ‹‰é€‰æ‹©ï¼Œåº“位随库区联动禁用/启用 |
| æœ€è¿‘入库追溯码 | ç»Ÿè®¡å¡ | å±•示最新一条扫码内容 |
| è¿½æº¯ç /类型/产线 | æœ€è¿‘扫码事件表 | è¡¨æ ¼æ–°å¢ž"类型"列,原"设备/产线"列改为"产线" |
| æœ€è¿‘扫码类型 | çŠ¶æ€å¡ | å±•示最新事件扫码类型 |
## æ³¨æ„äº‹é¡¹
1. åŽç«¯éœ€**执行 SQL è„šæœ¬** `sql/mysql/mes_pro_scan_event_scan_type.sql`(`mes_pro_scan_event` å¢žåŠ  `scan_type` åˆ—,`mes_pro_pallet` å¢žåŠ  `inbound_event_id`/`inbound_time` åˆ—),再**重新编译并重启**。
2. é‡å¯å‰æ—§æŽ¥å£ä¼šå¿½ç•¥ `scanType`(按袋码处理),托盘/批次码扫码会返回"袋码不存在";前端类型下拉照常可选,功能降级为仅袋码。
3. äº§çº¿/库区/库位下拉若接口 404(后端未重启),下拉为空,需先保障后端可用。
4. æ‰˜ç›˜ã€æ‰¹æ¬¡æ•´æ‰˜/整批入库会同步把其下未入库袋码标记为已入库,避免后续单袋扫码重复入库。
docs/seed_quality_bag_granularity.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
# ç§å­è´¨æ£€æŒ‰è¢‹ç ç²’度流转库存 - å‰ç«¯è”调方案
## èƒŒæ™¯
种子质检原逻辑:质检合格后**整批**将仓库批次库存从暂存库(10)转为合格库(20),一袋合格即整批放行,颗粒度过粗。
本次改造为**按袋码粒度**:质检只针对被扫的那一袋,合格仅将该袋对应库存从暂存库转入合格库,同批次其他袋保持暂存库待检;同时给每袋打上质检标记(`qc_status`),销售出库扫袋码/托盘时校验已质检合格才允许出库。
## æ¶‰åŠé¡µé¢
- è´¨é‡ç®¡ç†-种子质检(提交质检表单)
- é”€å”®å‡ºåº“-扫码拣货(扫袋码/托盘)
- åº“存台账(`mes_wm_material_stock` åˆ†é¡µï¼šåŒæ‰¹æ¬¡å¯èƒ½åŒæ—¶å‡ºçŽ°æš‚å­˜åº“ä¸Žåˆæ ¼åº“ä¸¤æ¡è®°å½•ï¼ŒçŠ¶æ€åˆ—å·²æŒ‰å­—å…¸å±•ç¤ºæ–‡å­—ï¼‰
## API
本次**无新增/修改接口**,均为既有接口的校验语义与数据流转变化。
### 1. æäº¤ç§å­è´¨æ£€
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/mes/qc/seed-quality/submit` | æäº¤ç§å­è´¨æ£€ï¼ˆæŒ‰è¢‹ç ï¼‰ |
**请求参数(不变):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| code | String | æ˜¯ | è¢‹ç ï¼ˆæ‰«ç å†…容),**必须为有效袋码** |
| batchCode | String | å¦ | ç”Ÿäº§æ‰¹æ¬¡å·ï¼ˆå…¼å®¹é¢„留) |
| indicators | Array | æ˜¯ | å››é¡¹æŒ‡æ ‡ï¼ˆpurity/cleanliness/germination_rate/moisture) |
| reason | String | å¦ | å¤‡æ³¨ |
**响应(不变):** `{ "code": 0, "data": è´¨æ£€è®°å½•ID }`
### 2. æ‰«ç æ‹£è´§
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/mes/wm/product-sales/scan` | æ‰«ç æ‹£è´§ï¼ˆè¢‹ç /托盘) |
**请求参数(不变):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| salesId | Long | æ˜¯ | é”€å”®å‡ºåº“单ID |
| scanContent | String | æ˜¯ | è¢‹ç æˆ–托盘码 |
| quantity | BigDecimal | å¦ | å‡ºåº“数量(缺省用行数量) |
**响应(不变):** æ²¿ç”¨ `MesWmProductSalesScanRespVO`,`availableQuantity`/`outboundQuantity` å‡åŸºäºŽ**合格库**库存计算。
## ä¸šåŠ¡è§„åˆ™å˜åŒ–
| åœºæ™¯ | åŽŸè¡Œä¸º | æ–°è¡Œä¸º |
|------|--------|--------|
| ç§å­è´¨æ£€æäº¤éžè¢‹ç  | å…¼å®¹æ‰¹æ¬¡ç æäº¤ | æŠ¥é”™ã€Œæœªè§£æžåˆ°æœ‰æ•ˆè¢‹ç ï¼Œç§å­è´¨æ£€å¿…须以袋码提交」 |
| è´¨æ£€åˆæ ¼ | æ•´æ‰¹æš‚存库→合格库 | ä»…该袋:暂存库 -1、合格库 +1(同批次其他袋仍为暂存库);袋码 qc_status=20 |
| è´¨æ£€ä¸åˆæ ¼ | æ— åº“存变化 | è¯¥è¢‹ä»Žæš‚存库存清除(报废);袋码 qc_status=30 |
| é”€å”®å‡ºåº“扫袋码 | ä¸æ ¡éªŒè´¨æ£€ | qc_status å¿…须为 20,否则报「该袋码未质检合格,禁止出库」 |
| é”€å”®å‡ºåº“扫托盘 | ä¸æ ¡éªŒè´¨æ£€ | æ‰˜ç›˜ä¸‹æ‰€æœ‰è¢‹ç  qc_status å¿…须为 20,否则报「托盘下存在未质检袋码,禁止整托出库」 |
| é”€å”®å‡ºåº“选库存 | å¯èƒ½é€‰ä¸­æš‚存库记录 | ä»…从合格库(20)记录中选数量足够的 |
## åº“存语义说明
同一次(入库仍按批次聚合)质检合格后,同批次 `mes_wm_material_stock` ä¼šåŒæ—¶å­˜åœ¨ä¸¤æ¡è®°å½•:
| è®°å½• | stock_state | å«ä¹‰ | æ˜¯å¦å¯å‡ºåº“ |
|------|-------------|------|-----------|
| æš‚存库 | 10 | è¯¥æ‰¹æ¬¡æœªè´¨æ£€çš„剩余袋数 | å¦ |
| åˆæ ¼åº“ | 20 | å·²è´¨æ£€åˆæ ¼ã€å¯é”€å”®çš„袋数 | æ˜¯ |
示例:批次入库 3 è¢‹ï¼ˆquantity=3、暂存库)。质检合格 1 è¢‹åŽ â†’ æš‚存库 quantity=2 + åˆæ ¼åº“ quantity=1。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| è´¨æ£€çŠ¶æ€ï¼ˆqc_status) | ç§å­è´¨æ£€æäº¤ç»“果反馈 | å‰ç«¯æ— éœ€å±•示,后端在第二次提交同一袋码时报「该袋码已质检,禁止重复提交」 |
| åº“存状态 | åº“存台账列表 | åŒä¸€ç‰©æ–™/批次可能出现两条不同状态记录,均为正常现象 |
| å¯å‡ºåº“数量 | æ‰«ç æ‹£è´§ç»“æžœ | ä»¥ `availableQuantity` ä¸ºå‡†ï¼Œåªåæ˜ åˆæ ¼åº“库存 |
## æ³¨æ„äº‹é¡¹
1. **种子质检提交必须传袋码**:`code` å­—段现在必须是系统中存在的有效袋码;仅传批次号不再被接受。
2. **质检成功后库存总数不变**:暂存库 -1 ä¸Žåˆæ ¼åº“ +1 åŒæ­¥å‘生,物料在库总数守恒,只是从"待检"变为"可售"。
3. **同批次双状态并存**:暂存库与合格库记录同时存在是预期行为,前端在库存台账、出库选库存时应能接受同一物料/批次出现多行。
4. **出库数量提示**:若某批次仅部分袋质检合格,扫码拣货可出库数量只包含合格袋,前端提示应以接口返回的 `availableQuantity` ä¸ºå‡†ã€‚
5. **不合格袋报废**:质检不合格的袋会从暂存库存中扣除,且该袋码 qc_status=30,禁止出库。
docs/sql/trace_platform_01_schema.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,79 @@
-- æ‰¹æ¬¡è¿½æº¯æ•°æ®æ¨¡åž‹è¡¥å¼º
-- è¯´æ˜Žï¼šå­—段先允许为空,历史数据回填完成后再评估是否收紧约束。
-- æ³¨æ„ï¼šé¡¹ç›®ä¸ä½¿ç”¨ tenant_id;执行前请确认字段和索引不存在。
ALTER TABLE mes_wm_batch
    ADD COLUMN pro_batch_id BIGINT NULL COMMENT '对应生产批次ID' AFTER id;
ALTER TABLE mes_wm_batch
    ADD INDEX idx_mes_wm_batch_pro_batch_id (pro_batch_id);
ALTER TABLE mes_wm_sn
    ADD COLUMN pro_batch_id BIGINT NULL COMMENT '生产批次ID' AFTER id;
ALTER TABLE mes_wm_sn
    ADD COLUMN bag_code_id BIGINT NULL COMMENT '袋码ID' AFTER pro_batch_id;
ALTER TABLE mes_wm_sn
    ADD COLUMN pallet_id BIGINT NULL COMMENT '托盘ID' AFTER bag_code_id;
ALTER TABLE mes_wm_sn
    ADD COLUMN sales_id BIGINT NULL COMMENT '销售出库单ID' AFTER pallet_id;
ALTER TABLE mes_wm_sn
    ADD INDEX idx_mes_wm_sn_pro_batch_id (pro_batch_id);
ALTER TABLE mes_wm_sn
    ADD INDEX idx_mes_wm_sn_bag_code_id (bag_code_id);
ALTER TABLE mes_qc_seed_quality
    ADD COLUMN batch_id BIGINT NULL COMMENT '生产批次ID' AFTER item_id;
ALTER TABLE mes_qc_seed_quality
    ADD INDEX idx_mes_qc_seed_quality_batch_id (batch_id);
CREATE TABLE mes_trace_event (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
    event_no VARCHAR(64) NOT NULL COMMENT '事件编号',
    event_type VARCHAR(32) NOT NULL COMMENT '事件类型',
    event_category VARCHAR(32) NULL COMMENT '事件分类',
    event_time DATETIME NOT NULL COMMENT '事件时间',
    pro_batch_id BIGINT NULL COMMENT '生产批次ID',
    wm_batch_id BIGINT NULL COMMENT '仓储批次ID',
    bag_code_id BIGINT NULL COMMENT '袋码ID',
    pallet_id BIGINT NULL COMMENT '托盘ID',
    sn_id BIGINT NULL COMMENT 'SN ID',
    item_id BIGINT NULL COMMENT '物料ID',
    biz_type VARCHAR(32) NULL COMMENT '业务类型',
    biz_id BIGINT NULL COMMENT '业务ID',
    biz_code VARCHAR(100) NULL COMMENT '业务编码',
    biz_line_id BIGINT NULL COMMENT '业务明细ID',
    from_node_type VARCHAR(32) NULL COMMENT '来源节点类型',
    from_node_id BIGINT NULL COMMENT '来源节点ID',
    from_node_code VARCHAR(100) NULL COMMENT '来源节点编码',
    to_node_type VARCHAR(32) NULL COMMENT '目标节点类型',
    to_node_id BIGINT NULL COMMENT '目标节点ID',
    to_node_code VARCHAR(100) NULL COMMENT '目标节点编码',
    quantity DECIMAL(18, 6) NULL COMMENT '数量',
    warehouse_id BIGINT NULL COMMENT '仓库ID',
    location_id BIGINT NULL COMMENT '库区ID',
    area_id BIGINT NULL COMMENT '库位ID',
    line_id BIGINT NULL COMMENT '产线ID',
    work_order_id BIGINT NULL COMMENT '工单ID',
    process_id BIGINT NULL COMMENT '工序ID',
    sales_id BIGINT NULL COMMENT '销售出库单ID',
    quality_id BIGINT NULL COMMENT '质检记录ID',
    operator_id BIGINT NULL COMMENT '操作人ID',
    payload_json JSON NULL COMMENT '事件扩展数据',
    content_hash VARCHAR(64) NULL COMMENT '事件内容SHA-256摘要',
    prev_hash VARCHAR(64) NULL COMMENT '前一事件摘要',
    creator VARCHAR(64) NULL DEFAULT '' COMMENT '创建者',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updater VARCHAR(64) NULL 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_mes_trace_event_no (event_no),
    KEY idx_mes_trace_event_batch_time (pro_batch_id, event_time),
    KEY idx_mes_trace_event_biz (biz_type, biz_id, biz_line_id),
    KEY idx_mes_trace_event_nodes (from_node_id, to_node_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES统一追溯事件';
-- åŽ†å²æ•°æ®å›žå¡«ç¤ºä¾‹ï¼Œç¡®è®¤æ•°æ®è´¨é‡åŽæ‰§è¡Œï¼š
-- UPDATE mes_wm_batch w JOIN mes_pro_batch p ON p.code = w.code AND p.deleted = b'0'
-- SET w.pro_batch_id = p.id WHERE w.pro_batch_id IS NULL AND w.deleted = b'0';
-- UPDATE mes_qc_seed_quality q JOIN mes_pro_batch p ON p.code = q.batch_code AND p.deleted = b'0'
-- SET q.batch_id = p.id WHERE q.batch_id IS NULL AND q.deleted = b'0';
docs/wm_transaction_query.md
@@ -9,6 +9,7 @@
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /mes/wm/transaction/page | åˆ†é¡µæŸ¥è¯¢åº“存事务流水 |
| GET | /mes/pro/batch/get-by-code | ç‚¹å‡»æ‰¹æ¬¡å·æŸ¥è¯¢ç”Ÿäº§æ‰¹æ¬¡ã€æ‰˜ç›˜åŠè¢‹ç ä¿¡æ¯ |
### è¯·æ±‚参数
@@ -23,10 +24,37 @@
| bizType | Integer | å¦ | ä¸šåŠ¡ç±»åž‹ |
| bizCode | String | å¦ | ä¸šåŠ¡å•å·ï¼Œæ¨¡ç³ŠåŒ¹é… |
| materialStockId | Long | å¦ | åº“存记录ID |
| bagCode | String | å¦ | è¢‹ç ï¼Œæ¨¡ç³ŠåŒ¹é…ï¼ˆè´¨æ£€ã€é”€å”®å‡ºåº“等袋码级流水) |
| startTime | String | å¦ | å¼€å§‹æ—¶é—´ï¼ˆæ ¼å¼ï¼šyyyy-MM-dd HH:mm:ss) |
| endTime | String | å¦ | ç»“束时间(格式:yyyy-MM-dd HH:mm:ss) |
| pageNo | Integer | æ˜¯ | é¡µç ï¼Œé»˜è®¤1 |
| pageSize | Integer | æ˜¯ | æ¯é¡µæ¡æ•°ï¼Œé»˜è®¤10 |
### æ‰¹æ¬¡è¿½æº¯æŸ¥è¯¢
点击流水列表中的批次号时,使用当前行的 `batchCode` è°ƒç”¨ä»¥ä¸‹æŽ¥å£ï¼š
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| code | String | æ˜¯ | æ‰¹æ¬¡å·æˆ–批次二维码内容 |
请求示例:`GET /mes/pro/batch/get-by-code?code={batchCode}`
接口返回的重点字段:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| id | Long | ç”Ÿäº§æ‰¹æ¬¡ID |
| code | String | æ­£å¼æ‰¹æ¬¡å· |
| tempCode | String | ä¸´æ—¶æ‰¹æ¬¡ç  |
| itemCode | String | ç‰©æ–™ç¼–码 |
| itemName | String | ç‰©æ–™åç§° |
| workers | Array | æ‰¹æ¬¡äººå‘˜é…ç½® |
| pallets | Array | æ‰¹æ¬¡ä¸‹çš„æ‰˜ç›˜åˆ—表 |
| bagTotal | Integer | è¢‹ç æ€»æ•° |
| bags | Array | æ‰¹æ¬¡ä¸‹çš„袋码列表 |
该接口同时支持正式批次号和临时批次码。若批次号为空,不显示点击入口;若查询不到生产批次,按接口返回的业务错误提示用户。
### å“åº”字段
@@ -44,6 +72,8 @@
| quantity | BigDecimal | å˜åŠ¨æ•°é‡ï¼ˆæ­£æ•°=入库,负数=出库) |
| batchId | Long | æ‰¹æ¬¡ID |
| batchCode | String | æ‰¹æ¬¡å· |
| bagCodeId | Long | è¢‹ç ID(袋码级流水非空) |
| bagCode | String | è¢‹ç ï¼ˆè¢‹ç çº§æµæ°´éžç©ºï¼Œå¦‚质检、销售出库、移库) |
| warehouseId | Long | ä»“库ID |
| warehouseName | String | ä»“库名称 |
| locationId | Long | åº“区ID |
@@ -111,6 +141,24 @@
| 121 | å¤–协发料 | å¤–协发料 |
| 122 | å¤–协入库 | å¤–协入库 |
## è°ƒç”¨æ—¶åº
1. è¿›å…¥åº“存事务流水页面时,调用 `/mes/wm/transaction/page` èŽ·å–æµæ°´åˆ—è¡¨ã€‚
2. åˆ—表中展示 `batchCode`;批次号非空时允许点击。
3. ç‚¹å‡»æ‰¹æ¬¡å·åŽï¼Œå°†å½“前行的 `batchCode` ä½œä¸º `code` è°ƒç”¨ `/mes/pro/batch/get-by-code`。
4. æ ¹æ®è¿”回结果展示生产批次信息、托盘列表和袋码列表;`bagTotal` ç”¨äºŽæ˜¾ç¤ºè¢‹ç æ€»æ•°ï¼Œ`bags` ç”¨äºŽå±•示袋码明细。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| batchCode | åº“存流水列表 | æ‰¹æ¬¡å·éžç©ºæ—¶ä½œä¸ºå¯ç‚¹å‡»å…¥å£ |
| code | æ‰¹æ¬¡è¯¦æƒ… | æ­£å¼æ‰¹æ¬¡å· |
| tempCode | æ‰¹æ¬¡è¯¦æƒ… | ä¸´æ—¶æ‰¹æ¬¡ç ï¼Œæœ‰å€¼æ—¶å±•示 |
| bagTotal | æ‰¹æ¬¡è¯¦æƒ… | å±•示该批次袋码总数 |
| bags[].code | æ‰¹æ¬¡è¯¦æƒ… | å±•示袋码明细 |
| pallets | æ‰¹æ¬¡è¯¦æƒ… | å±•示该批次关联的托盘信息 |
## æœç´¢æ¡ä»¶
| æ¡ä»¶å­—段 | ç»„件类型 | è¯´æ˜Ž |
@@ -140,6 +188,7 @@
| åº“区 | locationName | 100 | - |
| åº“位 | areaName | 100 | - |
| äº‹åŠ¡æ—¶é—´ | transactionTime | 160 | æ ¼å¼ï¼šyyyy-MM-dd HH:mm:ss |
| è¢‹ç  | bagCode | 150 | è¢‹ç çº§æµæ°´çš„袋码;批次/袋码可继续追溯 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
@@ -150,6 +199,7 @@
| æ•°é‡æ˜¾ç¤º | quantity > 0 æ˜¾ç¤ºç»¿è‰²ï¼Œquantity < 0 æ˜¾ç¤ºçº¢è‰² |
| ç±»åž‹æ ‡ç­¾ | å…¥åº“显示绿色 success æ ‡ç­¾ï¼Œå‡ºåº“显示红色 danger æ ‡ç­¾ |
| ä¸šåŠ¡å•å·è·³è½¬ | ç‚¹å‡» bizCode å¯è·³è½¬åˆ°å¯¹åº”业务单据详情页 |
| è¢‹ç ç­›é€‰ | ä¼  bagCode å¯æŸ¥çœ‹æŒ‡å®šè¢‹ç çš„完整流转(质检暂存出库/合格入库/销售出库等) |
## æƒé™é…ç½®
sql/mysql/mes_pro_bag_code_qc_status.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
-- =========================================
-- è¢‹ç è´¨æ£€çŠ¶æ€ï¼šæ”¯æŒç§å­è´¨æ£€æŒ‰è¢‹ç ç²’åº¦æµè½¬åº“å­˜
-- qc_status:10 æœªè´¨æ£€ / 20 è´¨æ£€åˆæ ¼ / 30 è´¨æ£€ä¸åˆæ ¼
-- æ–°å¢žåˆ—用于记录每袋的质检结果,销售出库扫袋码时校验该袋已质检合格。
-- =========================================
ALTER TABLE `mes_pro_bag_code`
    ADD COLUMN `qc_status` TINYINT NOT NULL DEFAULT 10 COMMENT '质检状态(10未质检 20质检合格 30质检不合格)' AFTER `status`;
-- å›žå¡«å­˜é‡æ•°æ®ï¼šæŒ‰å·²æœ‰ç§å­è´¨æ£€è®°å½•同步袋码质检状态
UPDATE `mes_pro_bag_code` b
    JOIN `mes_qc_seed_quality` q ON b.code = q.code AND b.deleted = 0
SET b.qc_status = IF(q.result = 1, 20, 30), b.update_time = NOW();
sql/mysql/mes_pro_scan_event_scan_type.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
-- ç”Ÿäº§çº¿æ‰«ç å…¥åº“:支持袋码/托盘码/批次码三种扫码类型,并去掉设备编码必填
-- è¯´æ˜Žï¼š
-- 1. mes_pro_scan_event å¢žåŠ  scan_type åˆ—,标记扫码类型(BAG è¢‹ç  / PALLET æ‰˜ç›˜ç  / BATCH æ‰¹æ¬¡ç ï¼‰ï¼ŒåŽ†å²è¢‹ç æ•°æ®å›žå¡« BAG
-- 2. mes_pro_pallet å¢žåŠ å…¥åº“æ ‡è®°åˆ—ï¼ˆæ‰˜ç›˜åŽŸå…ˆæ— å…¥åº“æ ‡è®°å­—æ®µï¼Œæ•´æ‰˜å…¥åº“éœ€åŽ»é‡ï¼Œé˜²æ­¢åŒä¸€æ‰˜ç›˜é‡å¤æ‰«ç å…¥åº“ï¼‰
ALTER TABLE mes_pro_scan_event ADD COLUMN scan_type VARCHAR(16) NULL COMMENT '扫码类型(BAG袋码/PALLET托盘码/BATCH批次码)' AFTER bag_code;
-- è®¾å¤‡ç¼–码不再必填(前端去掉设备编码,历史列保持兼容)
ALTER TABLE mes_pro_scan_event MODIFY COLUMN device_code VARCHAR(64) NULL DEFAULT NULL COMMENT '设备编码(不再必填)';
ALTER TABLE mes_pro_pallet ADD COLUMN inbound_event_id BIGINT NULL COMMENT '最近入库扫码事件编号' AFTER bag_count;
ALTER TABLE mes_pro_pallet ADD COLUMN inbound_time DATETIME NULL COMMENT '最近入库时间' AFTER inbound_event_id;
UPDATE mes_pro_scan_event SET scan_type = 'BAG' WHERE scan_type IS NULL;
sql/mysql/mes_qc_seed_quality_record_menu.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
-- =============================================
-- ç§å­è´¨æ£€è®°å½•独立菜单
-- å°†è´¨æ£€è®°å½•从「种子质检」页(提交+记录合一)拆出,独立成菜单页面
-- è¯´æ˜Žï¼š
-- 1. ç§å­è´¨æ£€æäº¤é¡µï¼šseed-quality -> mes/qc/seedQuality/index(仅提交)
-- 2. è´¨æ£€è®°å½•页:seed-quality-record -> mes/qc/seedQuality/record(仅记录查询)
-- æŒ‚在「质量管理」(5500) ç›®å½•下;幂等,可重复执行;无租户字段。
-- =============================================
INSERT INTO system_menu
(id, name, parent_id, type, sort, status, path, icon, component, component_name, permission, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT 1065224, '质检记录', 5500, 2, 95, 0, 'seed-quality-record', 'ep:list', 'mes/qc/seedQuality/record', 'MesQcSeedQualityRecord', '', b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 1065224 AND deleted = b'0');
sql/mysql/mes_wm_material_stock_bag.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,31 @@
CREATE TABLE mes_wm_material_stock_bag (
    id BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号',
    material_stock_id BIGINT NOT NULL COMMENT '库存汇总记录ID',
    bag_code_id BIGINT NOT NULL COMMENT '生产袋码ID',
    bag_code VARCHAR(64) NOT NULL COMMENT '袋码',
    item_id BIGINT NOT NULL COMMENT '物料ID',
    batch_id BIGINT NULL COMMENT '仓库批次ID',
    batch_code VARCHAR(64) NULL COMMENT '批次号',
    vendor_id BIGINT NULL COMMENT '供应商ID',
    warehouse_id BIGINT NOT NULL COMMENT '仓库ID',
    location_id BIGINT NULL COMMENT '库区ID',
    area_id BIGINT NULL COMMENT '库位ID',
    stock_state TINYINT NOT NULL COMMENT '库存状态:10暂存,20合格,30已出库,40报废',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '明细状态:1在库,2已出库,3已报废',
    quantity DECIMAL(18, 6) NOT NULL DEFAULT 1 COMMENT '袋码库存数量',
    frozen BIT NOT NULL DEFAULT 0 COMMENT '是否冻结',
    last_transaction_id BIGINT NULL COMMENT '最近库存事务ID',
    inbound_time DATETIME NULL COMMENT '入库时间',
    outbound_time DATETIME 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 NOT NULL DEFAULT 0 COMMENT '是否删除',
    PRIMARY KEY (id),
    UNIQUE KEY uk_bag_code (bag_code),
    KEY idx_material_stock (material_stock_id),
    KEY idx_batch_state (batch_id, stock_state, status),
    KEY idx_location_state (warehouse_id, location_id, area_id, stock_state, status),
    KEY idx_bag_code_id (bag_code_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='MES库存袋码明细';
sql/mysql/mes_wm_product_sales_add_columns.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
-- MES é”€å”®å‡ºåº“单表结构修改
-- æ ¹æ® MesWmProductSalesDO è¡¥å……缺失字段
-- è¯´æ˜Žï¼šä¿®å¤æŸ¥è¯¢æŠ¥é”™ Unknown column 'client_name',同时补充 outbound_time、outbound_user_id
-- 1. ç»é”€å•†åç§°å¿«ç…§
ALTER TABLE mes_wm_product_sales ADD COLUMN client_name VARCHAR(128) DEFAULT NULL COMMENT '经销商名称快照' AFTER client_id;
-- 2. å®žé™…出库时间
ALTER TABLE mes_wm_product_sales ADD COLUMN outbound_time DATETIME DEFAULT NULL COMMENT '实际出库时间';
-- 3. æ‰§è¡Œå‡ºåº“操作人ID
ALTER TABLE mes_wm_product_sales ADD COLUMN outbound_user_id BIGINT DEFAULT NULL COMMENT '执行出库操作人ID';
sql/mysql/mes_wm_qualified_warehouse.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
-- =====================================================================
-- æ–°å»ºåˆæ ¼ä»“库 + ä»“库类型 + è´¨æ£€å…¥åˆæ ¼åº“改造
--
-- 1) mes_wm_warehouse å¢žåŠ  type å­—段(10 æš‚存库 / 20 åˆæ ¼åº“)
-- 2) çŽ°æœ‰ä»“åº“åˆ†ç±»ï¼šè™šæ‹Ÿçº¿è¾¹ä»“åº“=10(暂存库),产品库/物料库=20(合格库)
-- 3) æ–°å»º"成品合格库"(type=20) + ä¸€ä¸ªåº“区 + ä¸€ä¸ªåº“位
-- 4) è™šæ‹Ÿä»“库中 stock_state=20 çš„合格库存迁移到新合格库
--
-- è¯´æ˜Žï¼šæœ¬è„šæœ¬å¹‚ç­‰ï¼Œå¯é‡å¤æ‰§è¡Œï¼›æ— ç§Ÿæˆ·å­—æ®µã€‚
-- =====================================================================
-- 1) å¢žåŠ ä»“åº“ç±»åž‹å­—æ®µï¼ˆMySQL ä¸æ”¯æŒ ALTER ADD COLUMN IF NOT EXISTS,先查存在性)
SET @type_exists = (
  SELECT COUNT(*)
  FROM information_schema.columns
  WHERE table_schema = DATABASE()
    AND table_name = 'mes_wm_warehouse'
    AND column_name = 'type'
);
SET @type_sql = IF(
  @type_exists = 0,
  'ALTER TABLE mes_wm_warehouse ADD COLUMN type tinyint NOT NULL DEFAULT 10 COMMENT ''仓库类型:10暂存库,20合格库'' AFTER frozen',
  'SELECT 1'
);
PREPARE type_stmt FROM @type_sql;
EXECUTE type_stmt;
DEALLOCATE PREPARE type_stmt;
-- 2) çŽ°æœ‰ä»“åº“åˆ†ç±»
UPDATE mes_wm_warehouse SET type = 10 WHERE code = 'WIP_VIRTUAL_WAREHOUSE' AND deleted = 0;
UPDATE mes_wm_warehouse SET type = 20 WHERE code IN ('1212', 'WHS000001') AND deleted = 0;
-- 3) æ–°å»º"成品合格库"(type=20)
SET @wh_exists = (
  SELECT COUNT(*) FROM mes_wm_warehouse WHERE code = 'FG_QUALIFIED_WH' AND deleted = 0
);
INSERT INTO mes_wm_warehouse (code, name, frozen, type, remark)
SELECT 'FG_QUALIFIED_WH', '成品合格库', 0, 20, '质检合格直接入库的目标仓库(type=20 åˆæ ¼åº“)'
WHERE @wh_exists = 0;
SET @wh_id = (SELECT id FROM mes_wm_warehouse WHERE code = 'FG_QUALIFIED_WH' AND deleted = 0 LIMIT 1);
-- æ–°å»º"成品合格库区"
SET @loc_exists = (
  SELECT COUNT(*) FROM mes_wm_warehouse_location WHERE code = 'FG_QUALIFIED_WH_LOC' AND deleted = 0
);
INSERT INTO mes_wm_warehouse_location (code, name, warehouse_id, frozen, remark)
SELECT 'FG_QUALIFIED_WH_LOC', '成品合格库区', @wh_id, 0, '质检合格入库默认库区'
WHERE @loc_exists = 0;
SET @loc_id = (SELECT id FROM mes_wm_warehouse_location WHERE code = 'FG_QUALIFIED_WH_LOC' AND deleted = 0 LIMIT 1);
-- æ–°å»º"成品合格库位"
SET @area_exists = (
  SELECT COUNT(*) FROM mes_wm_warehouse_area WHERE code = 'FG_QUALIFIED_WH_AREA' AND deleted = 0
);
INSERT INTO mes_wm_warehouse_area (code, name, location_id, frozen, remark)
SELECT 'FG_QUALIFIED_WH_AREA', '成品合格库位', @loc_id, 0, '质检合格入库默认库位'
WHERE @area_exists = 0;
SET @area_id = (SELECT id FROM mes_wm_warehouse_area WHERE code = 'FG_QUALIFIED_WH_AREA' AND deleted = 0 LIMIT 1);
-- 4) è¿ç§»è™šæ‹Ÿä»“库合格库存到新合格库
-- 4.1 è‹¥æ–°åˆæ ¼åº“已存在同 (item_id, batch_id, vendor_id) çš„ state=20 è®°å½•,先数量累加合并
UPDATE mes_wm_material_stock t
JOIN mes_wm_material_stock s
  ON s.warehouse_id = 3 AND s.stock_state = 20 AND s.deleted = 0
 AND t.warehouse_id = @wh_id AND t.stock_state = 20 AND t.deleted = 0
 AND t.item_id = s.item_id
 AND IFNULL(t.batch_id, 0) = IFNULL(s.batch_id, 0)
 AND IFNULL(t.vendor_id, 0) = IFNULL(s.vendor_id, 0)
SET t.quantity = t.quantity + s.quantity,
    t.reserved_quantity = t.reserved_quantity + s.reserved_quantity,
    t.frozen_quantity = t.frozen_quantity + s.frozen_quantity;
-- 4.2 åˆ é™¤å·²åˆå¹¶åˆ°æ–°åˆæ ¼åº“的源记录(虚拟仓库中)
DELETE s FROM mes_wm_material_stock s
JOIN mes_wm_material_stock t
  ON s.warehouse_id = 3 AND s.stock_state = 20 AND s.deleted = 0
 AND t.warehouse_id = @wh_id AND t.stock_state = 20 AND t.deleted = 0
 AND t.item_id = s.item_id
 AND IFNULL(t.batch_id, 0) = IFNULL(s.batch_id, 0)
 AND IFNULL(t.vendor_id, 0) = IFNULL(s.vendor_id, 0);
-- 4.3 å…¶ä½™åˆæ ¼åº“存直接改仓库/库区/库位
UPDATE mes_wm_material_stock
SET warehouse_id = @wh_id, location_id = @loc_id, area_id = @area_id
WHERE warehouse_id = 3 AND stock_state = 20 AND deleted = 0;
sql/mysql/mes_wm_transaction_bag_code.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
SET @exists_bag_code_id = (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'mes_wm_transaction' AND column_name = 'bag_code_id');
SET @sql_bag_code_id = IF(@exists_bag_code_id = 0, 'ALTER TABLE mes_wm_transaction ADD COLUMN bag_code_id BIGINT NULL COMMENT ''生产袋码ID'' AFTER material_stock_id', 'SELECT 1');
PREPARE stmt FROM @sql_bag_code_id;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @exists_bag_code = (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'mes_wm_transaction' AND column_name = 'bag_code');
SET @sql_bag_code = IF(@exists_bag_code = 0, 'ALTER TABLE mes_wm_transaction ADD COLUMN bag_code VARCHAR(64) NULL COMMENT ''袋码'' AFTER bag_code_id', 'SELECT 1');
PREPARE stmt FROM @sql_bag_code;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @exists_bag_code_index = (SELECT COUNT(*) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'mes_wm_transaction' AND index_name = 'idx_bag_code');
SET @sql_bag_code_index = IF(@exists_bag_code_index = 0, 'ALTER TABLE mes_wm_transaction ADD KEY idx_bag_code (bag_code)', 'SELECT 1');
PREPARE stmt FROM @sql_bag_code_index;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
sql/mysql/wls_sales_outbound_menu_extract.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
-- =============================================
-- å‡ºåº“流通管理菜单提取脚本
-- å°†"销售仓库"(602479) æå–为一级菜单"出库流通管理"
-- åŽŸç»“æž„ï¼šä»“å‚¨ç‰©æµ(5780) -> é”€å”®ä»“库(602479) -> å‘货通知/销售出库/销售退货
-- =============================================
-- 1. æå–为一级菜单
-- è¯´æ˜Žï¼šé¡¶çº§"营销管理"(2617) å·²å ç”¨ path=sale,故改用 sales-outbound é¿å…è·¯ç”±å†²çª
UPDATE system_menu SET
  parent_id = 0,
  name = '出库流通管理',
  path = 'sales-outbound',
  sort = 10  -- æ”¾åœ¨ä»“储物流(sort=9)之后
WHERE id = 602479 AND deleted = b'0';
-- =============================================
-- è¯´æ˜Žï¼š
-- 1. å­èœå•(发货通知 5867 / é”€å”®å‡ºåº“ 5970 / é”€å”®é€€è´§ 5847) æ— éœ€æ”¹åŠ¨ï¼Œ
--    å‰ç«¯åŠ¨æ€è·¯ç”±ä¼šè‡ªåŠ¨æ‹¼æŽ¥ä¸º /sales-outbound/sales-notice ç­‰è·¯å¾„
-- 2. é¡¶çº§ç›®å½•菜单由前端自动渲染为 BasicLayout,component å­—段无需填写
-- 3. å¦‚需恢复,执行以下反向脚本:
-- =============================================
-- åå‘脚本(如需恢复):
-- UPDATE system_menu SET parent_id = 5780, name = '销售仓库', path = 'sale', sort = 5 WHERE id = 602479;
yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/returnobj/AfterSaleReturnServiceImpl.java
@@ -130,8 +130,6 @@
        MesWmReturnSalesCreateReqDTO mesReqDTO = new MesWmReturnSalesCreateReqDTO();
        mesReqDTO.setCode(returnDO.getNo());
        mesReqDTO.setName("售后退货-" + returnDO.getNo());
        mesReqDTO.setSaleOrderId(returnDO.getSaleOrderId());
        mesReqDTO.setSalesOrderCode(ticket != null ? ticket.getSaleOrderNo() : null);
        mesReqDTO.setClientId(returnDO.getCustomerId());
        mesReqDTO.setReturnDate(LocalDate.now());
        mesReqDTO.setReturnReason(returnDO.getReturnReason());
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -21,9 +21,6 @@
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.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;
@@ -63,12 +60,6 @@
    private MdmItemApi mdmItemApi;
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private MesWmSalesNoticeApi salesNoticeApi;
    @Resource
    private MesWmProductSalesLineApi productSalesLineApi;
    @Resource
    private MesProMpsApi mpsApi;
    @Resource
    private StorageAttachmentApi storageAttachmentApi;
    @Resource
@@ -155,14 +146,11 @@
        // 2. æ‰¹é‡æŸ¥è¯¢å…³è”数据
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductId));
        Map<Long, String> batchCodeMap = productSalesLineApi.getBatchCodesBySaleOrderItemIds(
                convertSet(saleOrderItemList, ErpSaleOrderItemDO::getId));
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductUnitId));
        // 3. è½¬æ¢è®¢å•项
        List<ErpSaleOrderRespVO.Item> itemVOList = BeanUtils.toBean(saleOrderItemList, ErpSaleOrderRespVO.Item.class, item -> {
            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())
                            .setProductSpecification(mdmItem.getSpecification()));
@@ -213,54 +201,13 @@
        // 1.4 ç®¡ç†å‘˜ä¿¡æ¯
        Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                convertSet(pageResult.getList(), saleOrder -> Long.parseLong(saleOrder.getCreator())));
        // 1.5 å‘货通知单信息
        Map<Long, Boolean> salesNoticeMap = salesNoticeApi.checkSalesNoticeBySaleOrderIds(
                convertSet(pageResult.getList(), ErpSaleOrderDO::getId));
        // 1.6 å‡ºåº“批号
        Map<Long, String> batchCodeMap = productSalesLineApi.getBatchCodesBySaleOrderItemIds(
                convertSet(saleOrderItemList, ErpSaleOrderItemDO::getId));
        // 1.7 è®¡é‡å•位
        // 1.5 è®¡é‡å•位
        Map<Long, MdmUnitMeasureRespDTO> unitMeasureMap = mdmUnitMeasureApi.getUnitMeasureMap(
                convertSet(saleOrderItemList, ErpSaleOrderItemDO::getProductUnitId));
        // 1.8 æ£€æŸ¥æ˜¯å¦å¯ä»¥ç”Ÿæˆå‘货通知单(需要生产的明细对应的 MPS æ˜¯å¦å·²å®Œæˆï¼‰
        // ç­›é€‰å‡ºéœ€è¦ç”Ÿäº§çš„æ˜Žç»†ID
        List<Long> needProductionItemIds = saleOrderItemList.stream()
                .filter(item -> item.getNeedProduction() != null && item.getNeedProduction() == 1)
                .map(ErpSaleOrderItemDO::getId)
                .toList();
        Map<Long, Boolean> canCreateSalesNoticeMap = new java.util.HashMap<>();
        if (!needProductionItemIds.isEmpty()) {
            // æŒ‰è®¢å•分组明细ID
            Map<Long, List<Long>> orderItemIdsMap = new java.util.HashMap<>();
            for (ErpSaleOrderItemDO item : saleOrderItemList) {
                orderItemIdsMap.computeIfAbsent(item.getOrderId(), k -> new java.util.ArrayList<>())
                        .add(item.getId());
            }
            // æ£€æŸ¥æ¯ä¸ªè®¢å•
            for (Map.Entry<Long, List<Long>> entry : orderItemIdsMap.entrySet()) {
                List<Long> itemIds = entry.getValue().stream()
                        .filter(id -> {
                            ErpSaleOrderItemDO item = saleOrderItemList.stream()
                                    .filter(i -> i.getId().equals(id))
                                    .findFirst().orElse(null);
                            return item != null && item.getNeedProduction() != null && item.getNeedProduction() == 1;
                        }
                ).toList();
                if (itemIds.isEmpty()) {
                    canCreateSalesNoticeMap.put(entry.getKey(), true);
                } else {
                    canCreateSalesNoticeMap.put(entry.getKey(), mpsApi.checkCanCreateSalesNotice(itemIds));
                }
            }
        } else {
            // æ²¡æœ‰éœ€è¦ç”Ÿäº§çš„æ˜Žç»†ï¼Œéƒ½å¯ä»¥ç”Ÿæˆå‘货通知单
            pageResult.getList().forEach(order -> canCreateSalesNoticeMap.put(order.getId(), true));
        }
        // 2. å¼€å§‹æ‹¼æŽ¥
        return BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class, saleOrder -> {
            saleOrder.setItems(BeanUtils.toBean(saleOrderItemMap.get(saleOrder.getId()), ErpSaleOrderRespVO.Item.class,
                    item -> {
                        item.setBatchCode(batchCodeMap.get(item.getId()));
                        MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem ->
                                item.setProductName(mdmItem.getName()).setProductBarCode(mdmItem.getBarCode())
                                        .setProductSpecification(mdmItem.getSpecification()));
@@ -270,10 +217,6 @@
            saleOrder.setProductNames(CollUtil.join(saleOrder.getItems(), ",", ErpSaleOrderRespVO.Item::getProductName));
            MapUtils.findAndThen(customerMap, saleOrder.getCustomerId(), supplier -> saleOrder.setCustomerName(supplier.getName()));
            MapUtils.findAndThen(userMap, Long.parseLong(saleOrder.getCreator()), user -> saleOrder.setCreatorName(user.getNickname()));
            // è®¾ç½®æ˜¯å¦å·²ç”Ÿæˆå‘货通知单
            saleOrder.setHasSalesNotice(salesNoticeMap.getOrDefault(saleOrder.getId(), false));
            // è®¾ç½®æ˜¯å¦å¯ä»¥ç”Ÿæˆå‘货通知单:已生成发货通知单时不显示按钮
            saleOrder.setCanCreateSalesNotice(!saleOrder.getHasSalesNotice() && canCreateSalesNoticeMap.getOrDefault(saleOrder.getId(), true));
            // è®¾ç½®æ˜¯å¦æœ‰éœ€è¦ç”Ÿäº§çš„æ˜Žç»†
            boolean hasNeedProduction = saleOrder.getItems().stream()
                    .anyMatch(item -> item.getNeedProduction() != null && item.getNeedProduction() == 1);
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
@@ -104,18 +104,10 @@
    @Schema(description = "出库状态", example = "0")
    private Integer outStatus;
    // ========== å‘货通知 ==========
    @Schema(description = "是否已生成发货通知单", example = "false")
    private Boolean hasSalesNotice;
    @Schema(description = "是否可以生成发货通知单(有需要生产且未完成的明细时为 false)", example = "true")
    private Boolean canCreateSalesNotice;
    // ========== ç”Ÿäº§å…³è” ==========
    @Schema(description = "是否有需要生产的明细(0=否 1=是)", example = "1")
    private Integer needProduction;
    // ========== ç”Ÿäº§å…³è” ==========
    @Schema(description = "生产状态:0-未完成,1-已完成", example = "1")
    private Integer productionStatus;
@@ -163,11 +155,6 @@
        @Schema(description = "关联的 MPS ç¼–号", example = "1024")
        private Long mpsId;
        // ========== å‡ºåº“批号 ==========
        @Schema(description = "出库批号", example = "B2026001、B2026002")
        private String batchCode;
        // ========== å…³è”字段 ==========
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApi.java
ÎļþÒÑɾ³ý
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/returnsales/dto/MesWmReturnSalesCreateReqDTO.java
@@ -20,12 +20,6 @@
    @Schema(description = "退货单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "售后退货")
    private String name;
    @Schema(description = "销售订单 ID", example = "1")
    private Long saleOrderId;
    @Schema(description = "销售订单编号", example = "SO202603010001")
    private String salesOrderCode;
    @Schema(description = "客户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long clientId;
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/salesnotice/MesWmSalesNoticeApi.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/productsales/MesWmProductSalesLineApiImpl.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/salesnotice/MesWmSalesNoticeApiImpl.java
ÎļþÒÑɾ³ý
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/home/vo/MesBagCodeHomeSummaryRespVO.java
@@ -37,6 +37,25 @@
    @Schema(description = "托盘码总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "800")
    private Long palletCount;
    // ========== å‡ºåº“流通 ==========
    @Schema(description = "发货通知单数(mes_wm_sales_notice)", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    private Long salesNoticeCount;
    @Schema(description = "销售出库单数(mes_wm_product_sales)", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
    private Long productSalesCount;
    @Schema(description = "销售退货单数(mes_wm_return_sales)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
    private Long returnSalesCount;
    // ========== ä»“储预警 / è´¨é‡ ==========
    @Schema(description = "未处理库存预警数(mes_wm_stock_warning.status=10)", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
    private Long stockWarningPendingCount;
    @Schema(description = "待评审不合格品单数(mes_qc_ncr.status=0)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
    private Long ncrPendingCount;
    // ========== çŠ¶æ€åˆ†å¸ƒ ==========
    @Schema(description = "批次状态分布")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/MesProScanEventController.java
@@ -23,7 +23,7 @@
@RestController @RequestMapping("/mes/pro/scan-in")
public class MesProScanEventController {
    @Resource private MesProScanEventService service;
    @PostMapping("/inbound") @PreAuthorize("@ss.hasPermission('mes:pro-scan-in:inbound')") @Operation(summary = "袋码扫码入库")
    @PostMapping("/inbound") @PreAuthorize("@ss.hasPermission('mes:pro-scan-in:inbound')") @Operation(summary = "追溯码扫码入库(袋码/托盘码/批次码)")
    public CommonResult<MesProScanEventRespVO> inbound(@Valid @RequestBody MesProScanEventCreateReqVO req) {
        return success(BeanUtils.toBean(service.createInbound(req), MesProScanEventRespVO.class));
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventCreateReqVO.java
@@ -8,9 +8,19 @@
@Data
public class MesProScanEventCreateReqVO {
    @NotBlank private String eventId;
    /**
     * æ‰«ç å†…容(袋码 / æ‰˜ç›˜ç  / æ‰¹æ¬¡ç çš„码值,含临时码)
     */
    @NotBlank private String bagCode;
    /**
     * æ‰«ç ç±»åž‹ï¼šBAG è¢‹ç  / PALLET æ‰˜ç›˜ç  / BATCH æ‰¹æ¬¡ç ï¼Œé»˜è®¤ BAG
     */
    private String scanType;
    @NotNull @DecimalMin("0.000001") private BigDecimal quantity;
    @NotBlank private String deviceCode;
    /**
     * è®¾å¤‡ç¼–码(不再必填,产线扫码以产线+库区库位定位)
     */
    @Size(max = 64) private String deviceCode;
    private String lineCode;
    @NotNull private Long locationId;
    @NotNull private Long areaId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventRespVO.java
@@ -10,6 +10,7 @@
    private Long id;
    private String eventId;
    private String bagCode;
    private String scanType;
    private String batchCode;
    private BigDecimal quantity;
    private String deviceCode;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/MesQcSeedQualityController.java
@@ -1,6 +1,10 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality;
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.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
import cn.iocoder.yudao.module.mes.service.qc.seedquality.MesQcSeedQualityService;
import io.swagger.v3.oas.annotations.Operation;
@@ -16,6 +20,13 @@
@RestController @RequestMapping("/mes/qc/seed-quality") @Validated
public class MesQcSeedQualityController {
    @Resource private MesQcSeedQualityService service;
    @GetMapping("/page")
    @Operation(summary = "获得种子质检记录分页")
    @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:query')")
    public CommonResult<PageResult<MesQcSeedQualityRespVO>> getQualityPage(@Valid MesQcSeedQualityPageReqVO pageReqVO) {
        return success(BeanUtils.toBean(service.getQualityPage(pageReqVO), MesQcSeedQualityRespVO.class));
    }
    @PostMapping("/submit") @Operation(summary = "提交种子质检")
    @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:submit')")
    public CommonResult<Long> submit(@Valid @RequestBody MesQcSeedQualitySubmitReqVO reqVO) { return success(service.submit(reqVO)); }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualityPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - MES ç§å­è´¨æ£€è®°å½•分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class MesQcSeedQualityPageReqVO extends PageParam {
    @Schema(description = "质检袋码")
    private String code;
    @Schema(description = "生产批次")
    private String batchCode;
    @Schema(description = "检验结果:1合格 2不合格")
    private Integer result;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualityRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES ç§å­è´¨æ£€è®°å½• Response VO")
@Data
public class MesQcSeedQualityRespVO {
    private Long id;
    private String code;
    private Long itemId;
    private Long batchId;
    private String batchCode;
    private Integer result;
    private String reason;
    private Long inspectorId;
    private LocalDateTime inspectedAt;
    private LocalDateTime createTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualitySubmitReqVO.java
@@ -9,10 +9,11 @@
@Data
public class MesQcSeedQualitySubmitReqVO {
    @NotBlank private String code;
    @NotNull private Long itemId;
    private String batchCode;
    @NotEmpty @Valid private List<Indicator> indicators;
    private String reason;
    /** ç›®æ ‡åˆæ ¼åº“编号(质检合格后入该仓库),必须是仓库类型为合格库(20)的仓库 */
    @NotNull private Long qualifiedWarehouseId;
    @Data public static class Indicator {
        @NotBlank private String code;
        @NotNull @DecimalMin("0") private BigDecimal value;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/trace/batch/MesTraceBatchController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.mes.controller.admin.trace.batch;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO;
import cn.iocoder.yudao.module.mes.service.trace.MesProTracePublicService;
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.constraints.NotBlank;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - MES æ‰¹æ¬¡è¿½æº¯")
@RestController
@RequestMapping("/mes/trace/batch")
@Validated
public class MesTraceBatchController {
    @Resource
    private MesProTracePublicService traceService;
    @GetMapping("/detail")
    @Operation(summary = "查询统一批次追溯详情")
    @Parameter(name = "code", description = "批次码、临时批次码、袋码或托盘码", required = true,
            example = "BAG-202608280001")
    @PreAuthorize("@ss.hasPermission('mes:trace-batch:query')")
    public CommonResult<MesProTracePublicRespVO> getBatchTraceDetail(
            @RequestParam("code") @NotBlank(message = "追溯码不能为空") String code) {
        return success(traceService.queryTrace(code));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
@@ -14,6 +14,9 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockBagPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockBagRespVO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag.MesWmMaterialStockBagMapper;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmStockSummaryRespVO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
@@ -21,6 +24,7 @@
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
@@ -72,6 +76,8 @@
    private MesWmWarehouseAreaService areaService;
    @Resource
    private SrmSupplierApi srmSupplierApi;
    @Resource
    private MesWmMaterialStockBagMapper materialStockBagMapper;
    @GetMapping("/get")
    @Operation(summary = "获得库存记录")
@@ -92,6 +98,17 @@
            @Valid MesWmMaterialStockPageReqVO pageReqVO) {
        PageResult<MesWmMaterialStockDO> pageResult = materialStockService.getMaterialStockPage(pageReqVO);
        return success(new PageResult<>(buildRespVOList(pageResult.getList()), pageResult.getTotal()));
    }
    @GetMapping("/bag-details")
    @Operation(summary = "查询库存袋码明细")
    @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:query')")
    public CommonResult<PageResult<MesWmMaterialStockBagRespVO>> getMaterialStockBagDetails(
            @Valid MesWmMaterialStockBagPageReqVO pageReqVO) {
        PageResult<MesWmMaterialStockBagRespVO> result = new PageResult<>();
        PageResult<cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO> page =
                materialStockBagMapper.selectPage(pageReqVO);
        return success(new PageResult<>(BeanUtils.toBean(page.getList(), MesWmMaterialStockBagRespVO.class), page.getTotal()));
    }
    @PutMapping("/update-frozen")
@@ -179,6 +196,9 @@
                convertSet(list, MesWmMaterialStockDO::getLocationId));
        Map<Long, MesWmWarehouseAreaDO> areaMap = areaService.getWarehouseAreaMap(
                convertSet(list, MesWmMaterialStockDO::getAreaId));
        Map<Long, Long> bagCountMap = materialStockBagMapper.selectListByMaterialStockIds(
                convertSet(list, MesWmMaterialStockDO::getId)).stream()
                .collect(Collectors.groupingBy(MesWmMaterialStockBagDO::getMaterialStockId, Collectors.counting()));
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmMaterialStockDO::getVendorId)).getCheckedData().stream()
                .collect(Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
@@ -197,6 +217,8 @@
                    area -> vo.setAreaName(area.getName()));
            MapUtils.findAndThen(vendorMap, vo.getVendorId(),
                    vendor -> vo.setVendorName(vendor.getName()));
            vo.setBagCount(bagCountMap.getOrDefault(vo.getId(), 0L));
            vo.setBagDetailSupported(bagCountMap.containsKey(vo.getId()));
            // è®¡ç®—可用量 = åœ¨åº“数量 - å†»ç»“数量 - å ç”¨é‡
            BigDecimal quantity = vo.getQuantity() != null ? vo.getQuantity() : BigDecimal.ZERO;
            BigDecimal frozenQty = vo.getFrozenQuantity() != null ? vo.getFrozenQuantity() : BigDecimal.ZERO;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockBagPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - åº“存袋码明细分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class MesWmMaterialStockBagPageReqVO extends PageParam {
    @Schema(description = "库存汇总记录ID", requiredMode = Schema.RequiredMode.REQUIRED)
    private Long materialStockId;
    @Schema(description = "袋码")
    private String bagCode;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockBagRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - åº“存袋码明细 Response VO")
@Data
public class MesWmMaterialStockBagRespVO {
    private Long id;
    private Long materialStockId;
    private Long bagCodeId;
    private String bagCode;
    private Long itemId;
    private Long batchId;
    private String batchCode;
    private Long warehouseId;
    private Long locationId;
    private Long areaId;
    private Integer stockState;
    private Integer status;
    private BigDecimal quantity;
    private Boolean frozen;
    private LocalDateTime inboundTime;
    private LocalDateTime outboundTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockListReqVO.java
@@ -12,6 +12,9 @@
    @Schema(description = "仓库编号")
    private Long warehouseId;
    @Schema(description = "仓库类型:10暂存库,20合格库(按类型过滤仓库,与 warehouseId äºŒé€‰ä¸€ï¼‰", example = "20")
    private Integer warehouseType;
    @Schema(description = "库区编号")
    private Long locationId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockPageReqVO.java
@@ -39,6 +39,9 @@
    @Schema(description = "仓库编号", example = "1")
    private Long warehouseId;
    @Schema(description = "仓库类型:10暂存库,20合格库(按类型过滤仓库,与 warehouseId äºŒé€‰ä¸€ï¼‰", example = "20")
    private Integer warehouseType;
    @Schema(description = "库区编号", example = "1")
    private Long locationId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmMaterialStockRespVO.java
@@ -46,6 +46,12 @@
    @ExcelProperty("批次号")
    private String batchCode;
    @Schema(description = "当前袋码数量", example = "10")
    private Long bagCount;
    @Schema(description = "是否存在袋码明细", example = "true")
    private Boolean bagDetailSupported;
    @Schema(description = "仓库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long warehouseId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/MesWmProductSalesRespVO.java
@@ -42,10 +42,6 @@
    @ExcelProperty("出库操作人")
    private String outboundUserName;
    @Schema(description = "销售订单号", example = "SO2026030001")
    @ExcelProperty("销售订单号")
    private String salesOrderCode;
    @Schema(description = "出库日期")
    @ExcelProperty("出库日期")
    private LocalDateTime salesDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/productsales/vo/MesWmProductSalesSaveReqVO.java
@@ -26,9 +26,6 @@
    @NotNull(message = "客户不能为空")
    private Long clientId;
    @Schema(description = "销售订单号", example = "SO2026030001")
    private String salesOrderCode;
    @Schema(description = "出库日期", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "出库日期不能为空")
    private LocalDateTime salesDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesPageReqVO.java
@@ -26,12 +26,6 @@
    @Schema(description = "退货单名称", example = "销售退货")
    private String name;
    @Schema(description = "销售订单 ID", example = "1")
    private Long saleOrderId;
    @Schema(description = "销售订单编号", example = "SO202603010001")
    private String salesOrderCode;
    @Schema(description = "客户 ID", example = "1")
    private Long clientId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesRespVO.java
@@ -24,12 +24,6 @@
    @Schema(description = "退货单名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "销售退货")
    private String name;
    @Schema(description = "销售订单 ID", example = "1")
    private Long saleOrderId;
    @Schema(description = "销售订单编号", example = "SO202603010001")
    private String salesOrderCode;
    @Schema(description = "客户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long clientId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnsales/vo/MesWmReturnSalesSaveReqVO.java
@@ -27,12 +27,6 @@
    @NotBlank(message = "退货单名称不能为空")
    private String name;
    @Schema(description = "销售订单 ID", example = "1")
    private Long saleOrderId;
    @Schema(description = "销售订单编号", example = "SO202603010001")
    private String salesOrderCode;
    @Schema(description = "客户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "客户 ID ä¸èƒ½ä¸ºç©º")
    private Long clientId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/MesWmSalesNoticeController.java
@@ -111,14 +111,6 @@
        return success(true);
    }
    @PostMapping("/generate-from-sale-order")
    @Operation(summary = "从销售订单生成发货通知单")
    @PreAuthorize("@ss.hasPermission('mes:wm-sales-notice:create')")
    public CommonResult<Long> generateFromSaleOrder(@RequestBody Map<String, Long> params) {
        Long saleOrderId = params.get("saleOrderId");
        return success(salesNoticeService.generateSalesNoticeFromSaleOrder(saleOrderId));
    }
    @PostMapping("/generate-product-sales")
    @Operation(summary = "从发货通知单生成销售出库单")
    @Parameter(name = "id", description = "发货通知单编号", required = true)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/MesWmSalesNoticePageReqVO.java
@@ -18,9 +18,6 @@
    @Schema(description = "通知单名称", example = "测试发货通知")
    private String name;
    @Schema(description = "销售订单编号", example = "SO202603010001")
    private String saleOrderCode;
    @Schema(description = "客户编号", example = "1")
    private Long clientId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/MesWmSalesNoticeRespVO.java
@@ -31,13 +31,6 @@
    @ExcelProperty("客户名称")
    private String clientName;
    @Schema(description = "销售订单ID", example = "1")
    private Long saleOrderId;
    @Schema(description = "销售订单号", example = "SO202603010001")
    @ExcelProperty("销售订单号")
    private String saleOrderCode;
    @Schema(description = "发货日期", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("发货日期")
    private LocalDateTime salesDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/MesWmSalesNoticeSaveReqVO.java
@@ -26,12 +26,6 @@
    @NotNull(message = "客户编号不能为空")
    private Long clientId;
    @Schema(description = "销售订单ID", example = "1")
    private Long saleOrderId;
    @Schema(description = "销售订单号", example = "SO202603010001")
    private String saleOrderCode;
    @Schema(description = "发货日期", requiredMode = Schema.RequiredMode.REQUIRED)
    @NotNull(message = "发货日期不能为空")
    private LocalDateTime salesDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineRespVO.java
@@ -16,9 +16,6 @@
    @Schema(description = "发货通知单编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long noticeId;
    @Schema(description = "销售订单明细ID", example = "1")
    private Long saleOrderItemId;
    @Schema(description = "物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    private Long itemId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineSaveReqVO.java
@@ -18,9 +18,6 @@
    @NotNull(message = "发货通知单编号不能为空")
    private Long noticeId;
    @Schema(description = "销售订单明细ID", example = "1")
    private Long saleOrderItemId;
    @Schema(description = "物料编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "物料编号不能为空")
    private Long itemId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionPageReqVO.java
@@ -26,6 +26,9 @@
    @Schema(description = "批次号", example = "BATCH001")
    private String batchCode;
    @Schema(description = "袋码", example = "BG202608180001")
    private String bagCode;
    @Schema(description = "仓库ID", example = "1")
    private Long warehouseId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/transaction/vo/MesWmTransactionRespVO.java
@@ -51,6 +51,12 @@
    @Schema(description = "批次号", example = "BATCH001")
    private String batchCode;
    @Schema(description = "袋码ID", example = "1")
    private Long bagCodeId;
    @Schema(description = "袋码", example = "BG202608180001")
    private String bagCode;
    // ========== åº“存位置 ==========
    @Schema(description = "仓库ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehousePageReqVO.java
@@ -21,4 +21,7 @@
    @Schema(description = "是否冻结", example = "false")
    private Boolean frozen;
    @Schema(description = "仓库类型:10暂存库 20合格库", example = "20")
    private Integer type;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseRespVO.java
@@ -40,6 +40,10 @@
    @ExcelProperty("是否冻结")
    private Boolean frozen;
    @Schema(description = "仓库类型:10暂存库 20合格库", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
    @ExcelProperty("仓库类型")
    private Integer type;
    @Schema(description = "备注", example = "备注")
    @ExcelProperty("备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warehouse/vo/MesWmWarehouseSaveReqVO.java
@@ -35,6 +35,10 @@
    @NotNull(message = "是否冻结不能为空")
    private Boolean frozen;
    @Schema(description = "仓库类型:10暂存库 20合格库", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
    @NotNull(message = "仓库类型不能为空")
    private Integer type;
    @Schema(description = "备注", example = "备注")
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/vo/MesProTracePublicRespVO.java
@@ -119,6 +119,44 @@
    @Schema(description = "加工流转(可选)")
    private List<MesWmBatchProcessTraceRespVO> processTraces;
    @Schema(description = "追溯时间线")
    private List<TimelineVO> timeline;
    @Schema(description = "追溯决策摘要")
    private DecisionVO decision;
    @Schema(description = "追溯时间线事件")
    @Data
    public static class TimelineVO {
        @Schema(description = "阶段")
        private String stage;
        @Schema(description = "事件类型")
        private String eventType;
        @Schema(description = "事件名称")
        private String eventName;
        @Schema(description = "事件时间")
        private LocalDateTime eventTime;
        @Schema(description = "数量")
        private BigDecimal quantity;
        @Schema(description = "状态")
        private String status;
    }
    @Schema(description = "追溯决策摘要")
    @Data
    public static class DecisionVO {
        @Schema(description = "质量是否存在风险")
        private Boolean qualityRisk;
        @Schema(description = "库存入库数量")
        private BigDecimal inboundQuantity;
        @Schema(description = "销售去向数量")
        private Integer salesTargetCount;
        @Schema(description = "消费者扫码次数")
        private Long consumerScanCount;
        @Schema(description = "风险等级")
        private String riskLevel;
    }
    @Schema(description = "生产人员")
    @Data
    public static class WorkerVO {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java
@@ -29,6 +29,7 @@
    private Long palletId;
    private String palletCode;
    private Integer status;
    private Integer qcStatus;
    private LocalDateTime printTime;
    private LocalDateTime cancelTime;
    private String cancelReason;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/pallet/MesProPalletDO.java
@@ -62,6 +62,14 @@
     */
    private Integer bagCount;
    /**
     * æœ€è¿‘入库扫码事件编号(整托扫码入库后回写,用于防重复入库)
     */
    private Long inboundEventId;
    /**
     * æœ€è¿‘入库时间
     */
    private java.time.LocalDateTime inboundTime;
    /**
     * æ˜¯å¦è‡ªåŠ¨ç”Ÿæˆï¼ˆæŒ‰æœ‰æ•ˆè¢‹ç ç´¯è®¡é˜ˆå€¼ç”Ÿæˆï¼Œå®¹é‡å›ºå®šä¸ºé˜ˆå€¼ï¼‰
     */
    private Boolean autoGenerated;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/scan/MesProScanEventDO.java
@@ -14,7 +14,14 @@
public class MesProScanEventDO extends BaseDO {
    @TableId private Long id;
    private String eventId;
    /**
     * æ‰«ç å†…容(袋码 / æ‰˜ç›˜ç  / æ‰¹æ¬¡ç çš„码值,含临时码)
     */
    private String bagCode;
    /**
     * æ‰«ç ç±»åž‹ï¼šBAG è¢‹ç  / PALLET æ‰˜ç›˜ç  / BATCH æ‰¹æ¬¡ç 
     */
    private String scanType;
    private Long bagCodeId;
    private Long batchId;
    private String batchCode;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualityDO.java
@@ -12,6 +12,8 @@
    @TableId private Long id;
    private String code;
    private Long itemId;
    /** ç”Ÿäº§æ‰¹æ¬¡ ID */
    private Long batchId;
    private String batchCode;
    private Integer result;
    private String reason;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/batch/MesWmBatchDO.java
@@ -3,6 +3,7 @@
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.tm.tool.MesTmToolDO;
@@ -34,6 +35,12 @@
    @TableId
    private Long id;
    /**
     * å¯¹åº”生产批次 ID
     *
     * å…³è” {@link MesProBatchDO#getId()}
     */
    private Long proBatchId;
    /**
     * æ‰¹æ¬¡ç¼–码
     */
    private String code;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/materialstockbag/MesWmMaterialStockBagDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
 * MES åº“存袋码明细 DO
 */
@TableName("mes_wm_material_stock_bag")
@KeySequence("mes_wm_material_stock_bag_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MesWmMaterialStockBagDO extends BaseDO {
    @TableId
    private Long id;
    private Long materialStockId;
    private Long bagCodeId;
    private String bagCode;
    private Long itemId;
    private Long batchId;
    private String batchCode;
    private Long vendorId;
    private Long warehouseId;
    private Long locationId;
    private Long areaId;
    private Integer stockState;
    private Integer status;
    private BigDecimal quantity;
    private Boolean frozen;
    private Long lastTransactionId;
    private LocalDateTime inboundTime;
    private LocalDateTime outboundTime;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesDO.java
@@ -50,14 +50,6 @@
     */
    private String clientName;
    /**
     * é”€å”®è®¢å•ID
     */
    private Long saleOrderId;
    /**
     * é”€å”®è®¢å•号
     */
    private String salesOrderCode;
    /**
     * å‘货通知单 ID
     *
     * å…³è” {@link MesWmSalesNoticeDO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/productsales/MesWmProductSalesLineDO.java
@@ -42,10 +42,6 @@
     */
    private Long salesId;
    /**
     * é”€å”®è®¢å•明细ID
     */
    private Long saleOrderItemId;
    /**
     * å‘货通知单行ID
     *
     * å…³è” {@link MesWmSalesNoticeLineDO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnsales/MesWmReturnSalesDO.java
@@ -39,14 +39,6 @@
     */
    private String name;
    /**
     * é”€å”®è®¢å• ID
     */
    private Long saleOrderId;
    /**
     * é”€å”®è®¢å•编号
     */
    private String salesOrderCode;
    /**
     * å®¢æˆ· ID
     *
     * å…³è” CRM å®¢æˆ· {@link cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/salesnotice/MesWmSalesNoticeDO.java
@@ -42,14 +42,6 @@
     */
    private Long clientId;
    /**
     * é”€å”®è®¢å•ID
     */
    private Long saleOrderId;
    /**
     * é”€å”®è®¢å•号
     */
    private String saleOrderCode;
    /**
     * å‘货日期
     */
    private LocalDateTime salesDate;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/salesnotice/MesWmSalesNoticeLineDO.java
@@ -35,10 +35,6 @@
     */
    private Long noticeId;
    /**
     * é”€å”®è®¢å•明细ID
     */
    private Long saleOrderItemId;
    /**
     * ç‰©æ–™ç¼–号
     *
     * å…³è” {@link MesMdItemDO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/sn/MesWmSnDO.java
@@ -2,6 +2,9 @@
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -29,6 +32,28 @@
    @TableId
    private Long id;
    /**
     * ç”Ÿäº§æ‰¹æ¬¡ ID
     *
     * å…³è” {@link MesProBatchDO#getId()}
     */
    private Long proBatchId;
    /**
     * è¢‹ç  ID
     *
     * å…³è” {@link MesProBagCodeDO#getId()}
     */
    private Long bagCodeId;
    /**
     * æ‰˜ç›˜ ID
     *
     * å…³è” {@link MesProPalletDO#getId()}
     */
    private Long palletId;
    /**
     * é”€å”®å‡ºåº“单 ID
     */
    private Long salesId;
    /**
     * æ‰¹æ¬¡ UUID(用于标记同一批次生成的 SN ç ï¼‰
     */
    private String uuid;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/transaction/MesWmTransactionDO.java
@@ -107,6 +107,10 @@
     * å…³è” {@link MesWmBatchDO#getCode()}
     */
    private String batchCode;
    /** è¢‹ç ID */
    private Long bagCodeId;
    /** è¢‹ç  */
    private String bagCode;
    // ==================== åº“存位置 ====================
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warehouse/MesWmWarehouseDO.java
@@ -62,6 +62,13 @@
     */
    private Boolean frozen;
    /**
     * ä»“库类型
     *
     * å…³è” {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum}:
     * 10 æš‚存库(如虚拟线边仓库,存放扫码入库待检/暂存库存);20 åˆæ ¼åº“(存放质检合格、可拣货库存)
     */
    private Integer type;
    /**
     * å¤‡æ³¨
     */
    private String remark;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/home/MesBagCodeHomeStatisticsMapper.java
@@ -43,6 +43,23 @@
    /** ç»Ÿè®¡æ‰˜ç›˜ç æ€»æ•°ï¼ˆmes_pro_pallet) */
    Long selectPalletCount();
    // ========== å‡ºåº“流通 / ä»“储 / è´¨é‡ ==========
    /** ç»Ÿè®¡å‘货通知单数(mes_wm_sales_notice) */
    Long selectSalesNoticeCount();
    /** ç»Ÿè®¡é”€å”®å‡ºåº“单数(mes_wm_product_sales) */
    Long selectProductSalesCount();
    /** ç»Ÿè®¡é”€å”®é€€è´§å•数(mes_wm_return_sales) */
    Long selectReturnSalesCount();
    /** ç»Ÿè®¡æœªå¤„理库存预警数(mes_wm_stock_warning.status=10) */
    Long selectStockWarningPendingCount();
    /** ç»Ÿè®¡å¾…评审不合格品单数(mes_qc_ncr.status=0) */
    Long selectNcrPendingCount();
    // ========== çŠ¶æ€åˆ†å¸ƒ ==========
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java
@@ -36,6 +36,30 @@
    int markInbound(@org.apache.ibatis.annotations.Param("id") Long id, @org.apache.ibatis.annotations.Param("eventId") Long eventId,
                    @org.apache.ibatis.annotations.Param("deviceCode") String deviceCode, @org.apache.ibatis.annotations.Param("lineCode") String lineCode);
    /** æ‰¹æ¬¡æ•´æ‰¹å…¥åº“时批量标记其下未入库袋码(幂等),保持袋码与库存状态一致 */
    @Update("UPDATE mes_pro_bag_code SET inbound_time = NOW(), inbound_event_id = #{eventId}, inbound_line_code = #{lineCode}, update_time = NOW() "
            + "WHERE deleted = 0 AND inbound_event_id IS NULL AND batch_id = #{batchId}")
    int markInboundByBatchId(@Param("batchId") Long batchId, @Param("eventId") Long eventId,
                             @Param("lineCode") String lineCode);
    /** æ•´æ‰˜å…¥åº“时批量标记托盘下未入库袋码(幂等) */
    @Update("UPDATE mes_pro_bag_code SET inbound_time = NOW(), inbound_event_id = #{eventId}, inbound_line_code = #{lineCode}, update_time = NOW() "
            + "WHERE deleted = 0 AND inbound_event_id IS NULL AND pallet_id = #{palletId}")
    int markInboundByPalletId(@Param("palletId") Long palletId, @Param("eventId") Long eventId,
                              @Param("lineCode") String lineCode);
    /**
     * æ›´æ–°è¢‹ç è´¨æ£€çŠ¶æ€ï¼ˆç§å­è´¨æ£€åˆæ ¼/不合格时记录该袋结果)
     *
     * @param id       è¢‹ç ç¼–号
     * @param qcStatus è´¨æ£€çŠ¶æ€ï¼ˆè§ MesProBagCodeQcStatusEnum)
     */
    default void updateQcStatus(Long id, Integer qcStatus) {
        update(null, new LambdaUpdateWrapper<MesProBagCodeDO>()
                .eq(MesProBagCodeDO::getId, id)
                .set(MesProBagCodeDO::getQcStatus, qcStatus));
    }
    default List<MesProBagCodeDO> selectListByUuid(String uuid) {
        return selectList(MesProBagCodeDO::getUuid, uuid);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java
@@ -40,6 +40,12 @@
    int markInbound(@org.apache.ibatis.annotations.Param("id") Long id, @org.apache.ibatis.annotations.Param("eventId") Long eventId,
                    @org.apache.ibatis.annotations.Param("deviceCode") String deviceCode, @org.apache.ibatis.annotations.Param("lineCode") String lineCode);
    /** æ‰¹æ¬¡æ•´æ‰¹æ‰«ç å…¥åº“回写:入库袋数直接置为批次袋数,幂等(已有入库标记的不再更新) */
    @org.apache.ibatis.annotations.Update("UPDATE mes_pro_batch SET inbound_time = NOW(), inbound_event_id = #{eventId}, inbound_line_code = #{lineCode}, inbound_bag_quantity = #{bagQuantity}, update_time = NOW() "
            + "WHERE id = #{id} AND deleted = 0 AND inbound_event_id IS NULL")
    int markBatchInbound(@org.apache.ibatis.annotations.Param("id") Long id, @org.apache.ibatis.annotations.Param("eventId") Long eventId,
                         @org.apache.ibatis.annotations.Param("lineCode") String lineCode, @org.apache.ibatis.annotations.Param("bagQuantity") Integer bagQuantity);
    default PageResult<MesProBatchDO> selectPage(MesProBatchPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MesProBatchDO>()
                .likeIfPresent(MesProBatchDO::getCode, reqVO.getCode())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java
@@ -64,6 +64,11 @@
    void markOutbound(@Param("id") Long id, @Param("salesId") Long salesId,
                      @Param("outboundTime") java.time.LocalDateTime outboundTime);
    /** æ•´æ‰˜æ‰«ç å…¥åº“回写:幂等(已有入库标记的不再更新),用于防重复入库 */
    @org.apache.ibatis.annotations.Update("UPDATE mes_pro_pallet SET inbound_event_id = #{eventId}, inbound_time = NOW(), update_time = NOW() "
            + "WHERE id = #{id} AND deleted = 0 AND inbound_event_id IS NULL")
    int markInbound(@Param("id") Long id, @Param("eventId") Long eventId);
    @org.apache.ibatis.annotations.Update("UPDATE mes_pro_pallet SET sales_id = NULL, outbound_time = NULL, update_time = NOW() "
            + "WHERE id = #{id} AND deleted = 0")
    void clearOutbound(@Param("id") Long id);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java
@@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO;
import org.apache.ibatis.annotations.Mapper;
@@ -9,6 +11,15 @@
@Mapper
public interface MesQcSeedQualityMapper extends BaseMapperX<MesQcSeedQualityDO> {
    default PageResult<MesQcSeedQualityDO> selectPage(MesQcSeedQualityPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MesQcSeedQualityDO>()
                .likeIfPresent(MesQcSeedQualityDO::getCode, reqVO.getCode())
                .likeIfPresent(MesQcSeedQualityDO::getBatchCode, reqVO.getBatchCode())
                .eqIfPresent(MesQcSeedQualityDO::getResult, reqVO.getResult())
                .orderByDesc(MesQcSeedQualityDO::getInspectedAt)
                .orderByDesc(MesQcSeedQualityDO::getId));
    }
    default MesQcSeedQualityDO selectByCode(String code) {
        return selectOne(new LambdaQueryWrapperX<MesQcSeedQualityDO>().eq(MesQcSeedQualityDO::getCode, code));
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
@@ -22,13 +23,15 @@
    default PageResult<MesWmMaterialStockDO> selectPage(MesWmMaterialStockPageReqVO reqVO,
                                                         Collection<Long> itemTypeIds,
                                                         Collection<Long> itemIds,
                                                         Long virtualWarehouseId) {
                                                         Long virtualWarehouseId,
                                                         Collection<Long> warehouseIds) {
        LambdaQueryWrapperX<MesWmMaterialStockDO> wrapper = new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                .inIfPresent(MesWmMaterialStockDO::getItemTypeId, itemTypeIds)
                .inIfPresent(MesWmMaterialStockDO::getItemId, itemIds)
                .likeIfPresent(MesWmMaterialStockDO::getBatchCode, reqVO.getBatchCode())
                .eqIfPresent(MesWmMaterialStockDO::getBatchId, reqVO.getBatchId())
                .eqIfPresent(MesWmMaterialStockDO::getWarehouseId, reqVO.getWarehouseId())
                .inIfPresent(MesWmMaterialStockDO::getWarehouseId, warehouseIds)
                .eqIfPresent(MesWmMaterialStockDO::getLocationId, reqVO.getLocationId())
                .eqIfPresent(MesWmMaterialStockDO::getAreaId, reqVO.getAreaId())
                .eqIfPresent(MesWmMaterialStockDO::getVendorId, reqVO.getVendorId())
@@ -149,9 +152,10 @@
    }
    /**
     * æŒ‰ç»„合键查询库存记录(忽略供应商维度)
     * æŒ‰ç»„合键查询库存记录(忽略供应商维度,仅合格库)
     *
     * ç”¨äºŽç§»åº“场景:移库时将库存合并到目标库位已有库存,不区分供应商
     * ç”¨äºŽç§»åº“场景:移库时将合格库存合并到目标库位已有合格库存,不区分供应商
     * åªåŒ¹é…åˆæ ¼åº“(20),避免将移入数量合并到同批次的暂存库记录
     */
    default MesWmMaterialStockDO selectByCompositeKeyIgnoreVendor(Long itemId, Long warehouseId, Long locationId,
                                                                    Long areaId, Long batchId) {
@@ -159,7 +163,8 @@
                .eq(MesWmMaterialStockDO::getItemId, itemId)
                .eq(MesWmMaterialStockDO::getWarehouseId, warehouseId)
                .eq(MesWmMaterialStockDO::getLocationId, locationId)
                .eq(MesWmMaterialStockDO::getAreaId, areaId);
                .eq(MesWmMaterialStockDO::getAreaId, areaId)
                .eq(MesWmMaterialStockDO::getStockState, MesWmStockStateEnum.QUALIFIED.getState());
        // batchId=null æ—¶ç²¾ç¡®åŒ¹é… is null
        if (batchId != null) {
            wrapper.eq(MesWmMaterialStockDO::getBatchId, batchId);
@@ -170,9 +175,41 @@
        return selectOne(wrapper);
    }
    /**
     * æŒ‰ç»„合键查询合格库库存记录(用于出库事务匹配)
     *
     * åŒæ‰¹æ¬¡å¯èƒ½åŒæ—¶å­˜åœ¨æš‚存库(10)与合格库(20)两条记录(种子质检按袋拆数量),
     * å‡ºåº“必须只从合格库匹配,避免扣到未质检的暂存库存。
     */
    default MesWmMaterialStockDO selectQualifiedByCompositeKey(Long itemId, Long warehouseId, Long locationId,
                                                                 Long areaId, Long batchId, Long vendorId) {
        LambdaQueryWrapperX<MesWmMaterialStockDO> wrapper = new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                .eq(MesWmMaterialStockDO::getItemId, itemId)
                .eq(MesWmMaterialStockDO::getWarehouseId, warehouseId)
                .eq(MesWmMaterialStockDO::getLocationId, locationId)
                .eq(MesWmMaterialStockDO::getAreaId, areaId)
                .eq(MesWmMaterialStockDO::getStockState, MesWmStockStateEnum.QUALIFIED.getState());
        // batchId=null æ—¶ç²¾ç¡®åŒ¹é… is null
        if (batchId != null) {
            wrapper.eq(MesWmMaterialStockDO::getBatchId, batchId);
        } else {
            wrapper.isNull(MesWmMaterialStockDO::getBatchId);
        }
        if (vendorId != null) {
            wrapper.eq(MesWmMaterialStockDO::getVendorId, vendorId);
        }
        wrapper.last("ORDER BY quantity DESC LIMIT 1");
        return selectOne(wrapper);
    }
    default List<MesWmMaterialStockDO> selectList(MesWmMaterialStockListReqVO reqVO) {
        return selectList(reqVO, null);
    }
    default List<MesWmMaterialStockDO> selectList(MesWmMaterialStockListReqVO reqVO, Collection<Long> warehouseIds) {
        return selectList(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                .eqIfPresent(MesWmMaterialStockDO::getWarehouseId, reqVO.getWarehouseId())
                .inIfPresent(MesWmMaterialStockDO::getWarehouseId, warehouseIds)
                .eqIfPresent(MesWmMaterialStockDO::getLocationId, reqVO.getLocationId())
                .eqIfPresent(MesWmMaterialStockDO::getAreaId, reqVO.getAreaId())
                .eqIfPresent(MesWmMaterialStockDO::getItemId, reqVO.getItemId())
@@ -197,6 +234,36 @@
                .orderByAsc(MesWmMaterialStockDO::getReceiptTime));
    }
    /**
     * æŒ‰ç‰©æ–™/批次查询合格库库存记录(仅 stock_state=20,且仅限合格仓库)
     *
     * <p>同批次可能同时存在暂存库(10)与合格库(20)两条记录(种子质检按袋拆数量),
     * é”€å”®å‡ºåº“选库存只能从合格库记录中选,杜绝选中未质检的暂存库存;
     * åŒæ—¶é™å®šåˆæ ¼ä»“库(type=20),避免从暂存仓库中误选合格态库存。</p>
     */
    default List<MesWmMaterialStockDO> selectQualifiedListByItemAndBatch(Long itemId, Long batchId,
                                                                          Collection<Long> qualifiedWarehouseIds) {
        return selectList(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                .eq(MesWmMaterialStockDO::getItemId, itemId)
                .eqIfPresent(MesWmMaterialStockDO::getBatchId, batchId)
                .eq(MesWmMaterialStockDO::getStockState, MesWmStockStateEnum.QUALIFIED.getState())
                .in(MesWmMaterialStockDO::getWarehouseId, qualifiedWarehouseIds)
                .gt(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)
                .orderByAsc(MesWmMaterialStockDO::getReceiptTime));
    }
    /**
     * æŒ‰ä»“库批次查询全部库存记录(含暂存库与合格库,quantity>0)
     *
     * <p>用于种子质检按袋码流转:定位该批次暂存库记录扣减 1 è¢‹ï¼Œä»¥åŠå®šä½/新建合格库记录累加 1 è¢‹ã€‚</p>
     */
    default List<MesWmMaterialStockDO> selectStockListByBatchId(Long batchId) {
        return selectList(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                .eq(MesWmMaterialStockDO::getBatchId, batchId)
                .gt(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)
                .orderByAsc(MesWmMaterialStockDO::getReceiptTime));
    }
    default List<MesWmMaterialStockDO> selectListByItemIds(Collection<Long> itemIds) {
        if (itemIds == null || itemIds.isEmpty()) {
            return java.util.Collections.emptyList();
@@ -207,18 +274,16 @@
    }
    /**
     * æŒ‰æ‰¹æ¬¡å·æ›´æ–°åº“存状态(用于种子质检合格后,将暂存库整批转为合格库)
     * ç»Ÿè®¡æ‰¹æ¬¡åœ¨æš‚存库中的库存记录数(用于种子质检前校验袋码/批次是否处于待质检状态)
     *
     * @param batchId   ä»“库批次ID
     * @param toState   ç›®æ ‡åº“存状态
     * @param fromState åŽŸåº“å­˜çŠ¶æ€ï¼ˆé™å®šåªæ›´æ–°è¯¥çŠ¶æ€çš„è®°å½•ï¼‰
     * @return å½±å“è¡Œæ•°
     * @param batchId ä»“库批次ID
     * @return æš‚存库库存记录数
     */
    default int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState) {
        return update(new MesWmMaterialStockDO().setStockState(toState),
                new LambdaUpdateWrapper<MesWmMaterialStockDO>()
                        .eq(MesWmMaterialStockDO::getBatchId, batchId)
                        .eq(MesWmMaterialStockDO::getStockState, fromState));
    default Long selectTempStockCountByBatchId(Long batchId) {
        return selectCount(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
                .eq(MesWmMaterialStockDO::getBatchId, batchId)
                .eq(MesWmMaterialStockDO::getStockState, MesWmStockStateEnum.TEMP.getState())
                .gt(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstockbag/MesWmMaterialStockBagMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,82 @@
package cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockBagPageReqVO;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
@Mapper
public interface MesWmMaterialStockBagMapper extends BaseMapperX<MesWmMaterialStockBagDO> {
    default PageResult<MesWmMaterialStockBagDO> selectPage(MesWmMaterialStockBagPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getMaterialStockId, reqVO.getMaterialStockId())
                .likeIfPresent(MesWmMaterialStockBagDO::getBagCode, reqVO.getBagCode())
                .eq(MesWmMaterialStockBagDO::getStatus, 1)
                .orderByAsc(MesWmMaterialStockBagDO::getInboundTime));
    }
    default MesWmMaterialStockBagDO selectByBagCode(String bagCode) {
        return selectOne(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getBagCode, bagCode)
                .orderByDesc(MesWmMaterialStockBagDO::getId)
                .last("LIMIT 1"));
    }
    default MesWmMaterialStockBagDO selectByBagCodeId(Long bagCodeId) {
        return selectOne(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getBagCodeId, bagCodeId)
                .orderByDesc(MesWmMaterialStockBagDO::getId)
                .last("LIMIT 1"));
    }
    default MesWmMaterialStockBagDO selectOneActiveByMaterialStockId(Long materialStockId) {
        return selectOne(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getMaterialStockId, materialStockId)
                .eq(MesWmMaterialStockBagDO::getStatus, 1)
                .last("LIMIT 1"));
    }
    default MesWmMaterialStockBagDO selectActiveByBagCode(String bagCode) {
        return selectOne(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getBagCode, bagCode)
                .eq(MesWmMaterialStockBagDO::getStatus, 1)
                .last("LIMIT 1"));
    }
    default MesWmMaterialStockBagDO selectActiveByBagCodeId(Long bagCodeId) {
        return selectOne(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getBagCodeId, bagCodeId)
                .eq(MesWmMaterialStockBagDO::getStatus, 1)
                .last("LIMIT 1"));
    }
    default List<MesWmMaterialStockBagDO> selectActiveListByItemAndBatch(Long itemId, Long batchId, Integer stockState) {
        return selectList(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getItemId, itemId)
                .eqIfPresent(MesWmMaterialStockBagDO::getBatchId, batchId)
                .eqIfPresent(MesWmMaterialStockBagDO::getStockState, stockState)
                .eq(MesWmMaterialStockBagDO::getStatus, 1)
                .eq(MesWmMaterialStockBagDO::getFrozen, false)
                .orderByAsc(MesWmMaterialStockBagDO::getInboundTime));
    }
    default List<MesWmMaterialStockBagDO> selectListByMaterialStockIds(Collection<Long> materialStockIds) {
        return selectList(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .in(MesWmMaterialStockBagDO::getMaterialStockId, materialStockIds)
                .eq(MesWmMaterialStockBagDO::getStatus, 1)
                .orderByAsc(MesWmMaterialStockBagDO::getInboundTime));
    }
    default List<MesWmMaterialStockBagDO> selectListByMaterialStockId(Long materialStockId) {
        return selectList(new LambdaQueryWrapperX<MesWmMaterialStockBagDO>()
                .eq(MesWmMaterialStockBagDO::getMaterialStockId, materialStockId)
                .eq(MesWmMaterialStockBagDO::getStatus, 1)
                .orderByAsc(MesWmMaterialStockBagDO::getInboundTime));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesLineMapper.java
@@ -7,7 +7,6 @@
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
/**
 * MES é”€å”®å‡ºåº“单行 Mapper
@@ -31,26 +30,6 @@
                .eqIfPresent(MesWmProductSalesLineDO::getSalesId, reqVO.getSalesId())
                .inIfPresent(MesWmProductSalesLineDO::getSalesId, reqVO.getSalesIds())
                .orderByDesc(MesWmProductSalesLineDO::getId));
    }
    /**
     * æ ¹æ®é”€å”®è®¢å•明细 ID åˆ—表查询出库行,按 saleOrderItemId åˆ†ç»„返回批号
     */
    default Map<Long, String> selectBatchCodesBySaleOrderItemIds(java.util.Collection<Long> saleOrderItemIds) {
        if (saleOrderItemIds == null || saleOrderItemIds.isEmpty()) {
            return java.util.Collections.emptyMap();
        }
        List<MesWmProductSalesLineDO> lines = selectList(new LambdaQueryWrapperX<MesWmProductSalesLineDO>()
                .in(MesWmProductSalesLineDO::getSaleOrderItemId, saleOrderItemIds));
        return lines.stream()
                .filter(l -> l.getBatchCode() != null && !l.getBatchCode().isEmpty())
                .collect(java.util.stream.Collectors.groupingBy(
                        MesWmProductSalesLineDO::getSaleOrderItemId,
                        java.util.LinkedHashMap::new,
                        java.util.stream.Collectors.collectingAndThen(
                                java.util.stream.Collectors.mapping(MesWmProductSalesLineDO::getBatchCode,
                                        java.util.stream.Collectors.toSet()),
                                codes -> String.join("、", codes))));
    }
    default void updateQualityStatusByIds(List<Long> ids, Integer qualityStatus) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/returnsales/MesWmReturnSalesMapper.java
@@ -22,7 +22,6 @@
        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmReturnSalesDO>()
                .likeIfPresent(MesWmReturnSalesDO::getCode, reqVO.getCode())
                .likeIfPresent(MesWmReturnSalesDO::getName, reqVO.getName())
                .likeIfPresent(MesWmReturnSalesDO::getSalesOrderCode, reqVO.getSalesOrderCode())
                .eqIfPresent(MesWmReturnSalesDO::getClientId, reqVO.getClientId())
                .eqIfPresent(MesWmReturnSalesDO::getStatus, reqVO.getStatus())
                .betweenIfPresent(MesWmReturnSalesDO::getReturnDate, reqVO.getReturnDate())
@@ -36,7 +35,6 @@
        LambdaQueryWrapperX<MesWmReturnSalesDO> wrapper = new LambdaQueryWrapperX<MesWmReturnSalesDO>()
                .likeIfPresent(MesWmReturnSalesDO::getCode, reqVO.getCode())
                .likeIfPresent(MesWmReturnSalesDO::getName, reqVO.getName())
                .likeIfPresent(MesWmReturnSalesDO::getSalesOrderCode, reqVO.getSalesOrderCode())
                .eqIfPresent(MesWmReturnSalesDO::getClientId, reqVO.getClientId())
                .eqIfPresent(MesWmReturnSalesDO::getStatus, reqVO.getStatus())
                .betweenIfPresent(MesWmReturnSalesDO::getReturnDate, reqVO.getReturnDate())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java
@@ -8,7 +8,6 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.Collection;
import java.util.List;
/**
@@ -21,7 +20,6 @@
        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmSalesNoticeDO>()
                .likeIfPresent(MesWmSalesNoticeDO::getCode, reqVO.getCode())
                .likeIfPresent(MesWmSalesNoticeDO::getName, reqVO.getName())
                .likeIfPresent(MesWmSalesNoticeDO::getSaleOrderCode, reqVO.getSaleOrderCode())
                .eqIfPresent(MesWmSalesNoticeDO::getClientId, reqVO.getClientId())
                .eqIfPresent(MesWmSalesNoticeDO::getStatus, reqVO.getStatus())
                .orderByDesc(MesWmSalesNoticeDO::getId));
@@ -38,7 +36,6 @@
        LambdaQueryWrapperX<MesWmSalesNoticeDO> wrapper = new LambdaQueryWrapperX<MesWmSalesNoticeDO>()
                .likeIfPresent(MesWmSalesNoticeDO::getCode, reqVO.getCode())
                .likeIfPresent(MesWmSalesNoticeDO::getName, reqVO.getName())
                .likeIfPresent(MesWmSalesNoticeDO::getSaleOrderCode, reqVO.getSaleOrderCode())
                .eqIfPresent(MesWmSalesNoticeDO::getClientId, reqVO.getClientId())
                .eqIfPresent(MesWmSalesNoticeDO::getStatus, reqVO.getStatus())
                .orderByDesc(MesWmSalesNoticeDO::getId);
@@ -55,19 +52,6 @@
    default MesWmSalesNoticeDO selectByCode(String code) {
        return selectOne(MesWmSalesNoticeDO::getCode, code);
    }
    default MesWmSalesNoticeDO selectBySaleOrderId(Long saleOrderId) {
        return selectOne(MesWmSalesNoticeDO::getSaleOrderId, saleOrderId);
    }
    /**
     * æŸ¥è¯¢å·²å­˜åœ¨å‘货通知单的销售订单 ID åˆ—表
     */
    default List<Long> selectExistingSaleOrderIds(Collection<Long> saleOrderIds) {
        return selectObjs(new LambdaQueryWrapperX<MesWmSalesNoticeDO>()
                .select(MesWmSalesNoticeDO::getSaleOrderId)
                .in(MesWmSalesNoticeDO::getSaleOrderId, saleOrderIds));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/transaction/MesWmTransactionMapper.java
@@ -17,6 +17,7 @@
        return selectPage(reqVO, new LambdaQueryWrapperX<MesWmTransactionDO>()
                .eqIfPresent(MesWmTransactionDO::getItemId, reqVO.getItemId())
                .likeIfPresent(MesWmTransactionDO::getBatchCode, reqVO.getBatchCode())
                .likeIfPresent(MesWmTransactionDO::getBagCode, reqVO.getBagCode())
                .eqIfPresent(MesWmTransactionDO::getWarehouseId, reqVO.getWarehouseId())
                .eqIfPresent(MesWmTransactionDO::getLocationId, reqVO.getLocationId())
                .eqIfPresent(MesWmTransactionDO::getAreaId, reqVO.getAreaId())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/warehouse/MesWmWarehouseMapper.java
@@ -21,9 +21,16 @@
                .likeIfPresent(MesWmWarehouseDO::getCode, reqVO.getCode())
                .likeIfPresent(MesWmWarehouseDO::getName, reqVO.getName())
                .eqIfPresent(MesWmWarehouseDO::getFrozen, reqVO.getFrozen())
                .eqIfPresent(MesWmWarehouseDO::getType, reqVO.getType())
                .orderByDesc(MesWmWarehouseDO::getId));
    }
    default List<MesWmWarehouseDO> selectListByType(Integer type) {
        return selectList(new LambdaQueryWrapperX<MesWmWarehouseDO>()
                .eq(MesWmWarehouseDO::getType, type)
                .orderByAsc(MesWmWarehouseDO::getId));
    }
    default MesWmWarehouseDO selectByCode(String code) {
        return selectOne(MesWmWarehouseDO::getCode, code);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -455,6 +455,8 @@
    ErrorCode WM_WAREHOUSE_HAS_WORKSTATION = new ErrorCode(1_040_700_004, "仓库已被工作站引用,无法删除");
    ErrorCode WM_WAREHOUSE_HAS_MATERIAL_STOCK = new ErrorCode(1_040_700_005, "仓库下有库存记录,无法删除");
    ErrorCode WM_WAREHOUSE_IS_VIRTUAL = new ErrorCode(1_040_700_006, "虚拟仓库不允许操作");
    ErrorCode WM_WAREHOUSE_TYPE_NOT_QUALIFIED = new ErrorCode(1_040_700_007, "所选仓库不是合格库");
    ErrorCode WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED = new ErrorCode(1_040_700_008, "未配置合格库,请先在仓库管理中创建并设置仓库类型为合格库");
    // ========== MES ä»“库管理-库区(1-040-701-000) ==========
    ErrorCode WM_WAREHOUSE_LOCATION_NOT_EXISTS = new ErrorCode(1_040_701_000, "库区不存在");
@@ -496,6 +498,12 @@
    ErrorCode WM_MATERIAL_STOCK_SELECTION_MISMATCH = new ErrorCode(1_040_703_014, "库存记录与提交的物料、批次或库位信息不一致");
    ErrorCode WM_MATERIAL_STOCK_QUANTITY_INVALID = new ErrorCode(1_040_703_015, "调整数量不能为空或零");
    ErrorCode WM_MATERIAL_STOCK_FROZEN = new ErrorCode(1_040_703_016, "库存记录已冻结,无法移库");
    ErrorCode WM_MATERIAL_STOCK_NOT_QUALIFIED = new ErrorCode(1_040_703_017, "非合格库存不可出库");
    ErrorCode WM_MATERIAL_STOCK_BAG_NOT_EXISTS = new ErrorCode(1_040_703_018, "库存袋码明细不存在");
    ErrorCode WM_MATERIAL_STOCK_BAG_DUPLICATE = new ErrorCode(1_040_703_019, "袋码({})已在库,禁止重复入库");
    ErrorCode WM_MATERIAL_STOCK_BAG_NOT_ACTIVE = new ErrorCode(1_040_703_020, "袋码({})不在库或已出库/报废,无法操作");
    ErrorCode WM_MATERIAL_STOCK_BAG_REQUIRED = new ErrorCode(1_040_703_021, "该物料启用袋码管理,必须携带袋码");
    ErrorCode WM_MATERIAL_STOCK_BAG_INSUFFICIENT = new ErrorCode(1_040_703_022, "在库袋码明细数量不足,无法出库");
    // ========== MES ä»“库管理-到货通知单(1-040-704-000) ==========
    ErrorCode WM_ARRIVAL_NOTICE_NOT_EXISTS = new ErrorCode(1_040_704_000, "到货通知单不存在");
@@ -727,6 +735,8 @@
    ErrorCode WM_PRODUCT_SALES_SCAN_STOCK_NOT_FOUND = new ErrorCode(1_040_714_105, "扫描批次在合格库中未找到对应库存记录");
    ErrorCode WM_PRODUCT_SALES_SCAN_NO_MATCH_LINE = new ErrorCode(1_040_714_106, "出库单中没有与该物料/批次匹配的行");
    ErrorCode WM_PRODUCT_SALES_SCAN_BAG_ALREADY_SCANNED = new ErrorCode(1_040_714_107, "该袋码已扫描出库,请勿重复扫描");
    ErrorCode WM_PRODUCT_SALES_SCAN_BAG_NOT_QUALIFIED = new ErrorCode(1_040_714_108, "该袋码未质检合格,禁止出库");
    ErrorCode WM_PRODUCT_SALES_SCAN_PALLET_NOT_ALL_QUALIFIED = new ErrorCode(1_040_714_109, "托盘下存在未质检袋码,禁止整托出库");
    // ========== MES ä»“库管理-杂项出库单(1-040-715-000) ==========
    ErrorCode WM_MISC_ISSUE_NOT_EXISTS = new ErrorCode(1_040_715_000, "杂项出库单不存在");
@@ -760,9 +770,6 @@
    ErrorCode WM_SALES_NOTICE_LINE_NOT_EXISTS = new ErrorCode(1_040_720_010, "发货通知单行不存在");
    ErrorCode WM_SALES_NOTICE_LINE_EMPTY = new ErrorCode(1_040_720_011, "发货通知单行为空,不能提交");
    ErrorCode WM_SALES_NOTICE_LINE_NOT_MATCH = new ErrorCode(1_040_720_012, "发货通知单行不属于指定的发货通知单");
    ErrorCode WM_SALES_NOTICE_SALE_ORDER_NOT_EXISTS = new ErrorCode(1_040_720_013, "销售订单不存在");
    ErrorCode WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY = new ErrorCode(1_040_720_014, "销售订单明细为空");
    ErrorCode WM_SALES_NOTICE_ALREADY_EXISTS = new ErrorCode(1_040_720_015, "该销售订单已生成发货通知单,不能重复生成");
    // ========== MES ä»“库管理-条码配置(1-040-730-000) ==========
    ErrorCode WM_BARCODE_CONFIG_NOT_EXISTS = new ErrorCode(1_040_730_000, "条码配置不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesBizTypeConstants.java
@@ -18,6 +18,7 @@
    public static final int QC_IPQC = 2; // è¿‡ç¨‹æ£€éªŒï¼šMesQcIpqcDO
    public static final int QC_OQC = 3; // å‡ºè´§æ£€éªŒï¼šMesQcOqcDO
    public static final int QC_RQC = 4; // é€€è´§æ£€éªŒï¼šMesQcRqcDO
    public static final int QC_SEED_QUALITY = 5; // ç§å­è´¨æ£€ï¼šMesQcSeedQualityDO(含暂存出库/合格入库/报废)
    // ========== WM ä»“库模块 [100, 200) ==========
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProBagCodeQcStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.mes.enums.pro;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
 * MES è¢‹ç ï¼ˆä¸€è¢‹ä¸€ç ï¼‰è´¨æ£€çŠ¶æ€æžšä¸¾
 *
 * <p>记录每袋的种子质检结果,销售出库扫袋码时校验该袋已质检合格。</p>
 *
 * @author è¶…级管理员
 */
@Getter
@AllArgsConstructor
public enum MesProBagCodeQcStatusEnum implements ArrayValuable<Integer> {
    UNCHECKED(10, "未质检"),
    QUALIFIED(20, "质检合格"),
    UNQUALIFIED(30, "质检不合格");
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesProBagCodeQcStatusEnum::getStatus).toArray(Integer[]::new);
    /**
     * çŠ¶æ€
     */
    private final Integer status;
    /**
     * çŠ¶æ€åç§°
     */
    private final String name;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmMaterialStockBagStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,15 @@
package cn.iocoder.yudao.module.mes.enums.wm;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MesWmMaterialStockBagStatusEnum {
    ACTIVE(1, "在库"),
    OUT(2, "已出库"),
    SCRAPPED(3, "已报废");
    private final Integer status;
    private final String name;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmWarehouseTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.mes.enums.wm;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * MES ä»“库类型
 *
 * ä¸Ž {@link MesWmStockStateEnum} è¯­ä¹‰å¯¹é½ï¼šæš‚存库存放待检/暂存库存,合格库存放质检合格、可拣货库存。
 */
@Getter
@AllArgsConstructor
public enum MesWmWarehouseTypeEnum {
    TEMP(10, "暂存库"),
    QUALIFIED(20, "合格库");
    private final Integer type;
    private final String name;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/home/MesBagCodeHomeStatisticsServiceImpl.java
@@ -42,7 +42,16 @@
        summary.setBagCodeCount(getCount(homeStatisticsMapper.selectBagCodeCount()));
        summary.setPalletCount(getCount(homeStatisticsMapper.selectPalletCount()));
        // 3. çŠ¶æ€åˆ†å¸ƒ
        // 3. å‡ºåº“流通
        summary.setSalesNoticeCount(getCount(homeStatisticsMapper.selectSalesNoticeCount()));
        summary.setProductSalesCount(getCount(homeStatisticsMapper.selectProductSalesCount()));
        summary.setReturnSalesCount(getCount(homeStatisticsMapper.selectReturnSalesCount()));
        // 4. ä»“储预警 / è´¨é‡
        summary.setStockWarningPendingCount(getCount(homeStatisticsMapper.selectStockWarningPendingCount()));
        summary.setNcrPendingCount(getCount(homeStatisticsMapper.selectNcrPendingCount()));
        // 5. çŠ¶æ€åˆ†å¸ƒ
        summary.setBatchStatusList(toStatusList(homeStatisticsMapper.selectBatchCountGroupByStatus()));
        summary.setBagCodeStatusList(toStatusList(homeStatisticsMapper.selectBagCodeCountGroupByStatus()));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
@@ -5,11 +5,13 @@
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.scan.MesProScanEventMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.MesProScanEventStatusEnum;
@@ -20,47 +22,102 @@
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
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.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@Service
@Validated
public class MesProScanEventServiceImpl implements MesProScanEventService {
    @Resource private MesProScanEventMapper eventMapper;
    @Resource private MesProBagCodeMapper bagCodeMapper;
    @Resource private MesProBatchMapper batchMapper;
    @Resource private MesProPalletMapper palletMapper;
    @Resource private MesWmTransactionService transactionService;
    @Resource private MesWmWarehouseService warehouseService;
    @Resource private MesWmBatchService mesWmBatchService;
    /** æ‰«ç ç±»åž‹ï¼šè¢‹ç ï¼ˆé»˜è®¤ï¼‰ */
    private static final String SCAN_TYPE_BAG = "BAG";
    /** æ‰«ç ç±»åž‹ï¼šæ‰˜ç›˜ç ï¼ˆæŒ‰æ‰˜ç›˜å·²è£…袋数整托入库) */
    private static final String SCAN_TYPE_PALLET = "PALLET";
    /** æ‰«ç ç±»åž‹ï¼šæ‰¹æ¬¡ç ï¼ˆæŒ‰æ‰¹æ¬¡è¢‹æ•°æ•´æ‰¹å…¥åº“) */
    private static final String SCAN_TYPE_BATCH = "BATCH";
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProScanEventDO createInbound(MesProScanEventCreateReqVO req) {
        MesProScanEventDO existing = eventMapper.selectByEventIdForUpdate(req.getEventId());
        if (existing != null) {
            if (!req.getBagCode().equals(existing.getBagCode()) || !req.getDeviceCode().equals(existing.getDeviceCode())) {
            boolean contentMatch = req.getBagCode().equals(existing.getBagCode())
                    && (req.getDeviceCode() == null || req.getDeviceCode().equals(existing.getDeviceCode()));
            if (!contentMatch) {
                return failed(existing, "MES_SCAN_EVENT_ID_CONFLICT", "事件号已存在但内容不一致");
            }
            return existing;
        }
        String scanType = (req.getScanType() == null || req.getScanType().isBlank()) ? SCAN_TYPE_BAG : req.getScanType();
        MesProScanEventDO event = new MesProScanEventDO();
        event.setEventId(req.getEventId()).setBagCode(req.getBagCode()).setQuantity(req.getQuantity())
                .setDeviceCode(req.getDeviceCode()).setLineCode(req.getLineCode()).setRemark(req.getRemark())
                .setStatus(MesProScanEventStatusEnum.FAILED).setReceivedTime(LocalDateTime.now());
        event.setEventId(req.getEventId()).setBagCode(req.getBagCode()).setScanType(scanType)
                .setQuantity(req.getQuantity()).setDeviceCode(req.getDeviceCode()).setLineCode(req.getLineCode())
                .setRemark(req.getRemark()).setStatus(MesProScanEventStatusEnum.FAILED).setReceivedTime(LocalDateTime.now());
        eventMapper.insert(event);
        MesProBagCodeDO bag = bagCodeMapper.selectByCodeForUpdate(req.getBagCode());
        if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "袋码不存在");
        event.setBagCodeId(bag.getId());
        if (bag.getInboundEventId() != null) return failed(event, "MES_SCAN_BAG_ALREADY_INBOUND", "袋码已入库");
        MesProBatchDO batch = batchMapper.selectByIdForUpdate(bag.getBatchId());
        if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在");
        event.setBatchId(batch.getId()).setBatchCode(batch.getCode()).setItemId(batch.getItemId());
        // 1. æŒ‰æ‰«ç ç±»åž‹è§£æžæ‰«ç ç›®æ ‡ä¸Žå…¥åº“数量(托盘按已装袋数、批次按批次袋数整批/整托入库)
        Long targetId;
        Long batchId;
        BigDecimal quantity;
        MesProBatchDO batch;
        // å•袋扫码入库时传递袋码,供库存袋码明细在入库第一时间建立;托盘/批次整批入库为批量,不逐袋建明细
        Long bagCodeId = null;
        String bagCode = null;
        if (SCAN_TYPE_PALLET.equals(scanType)) {
            MesProPalletDO pallet = palletMapper.selectByCode(req.getBagCode());
            if (pallet == null) {
                pallet = palletMapper.selectByTempCode(req.getBagCode());
            }
            if (pallet == null) return failed(event, "MES_SCAN_PALLET_NOT_EXISTS", "托盘码不存在");
            if (pallet.getInboundEventId() != null) return failed(event, "MES_SCAN_PALLET_ALREADY_INBOUND", "托盘已入库");
            batch = batchMapper.selectByIdForUpdate(pallet.getBatchId());
            if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在");
            batchId = batch.getId();
            targetId = pallet.getId();
            quantity = pallet.getBagCount() != null && pallet.getBagCount() > 0
                    ? BigDecimal.valueOf(pallet.getBagCount())
                    : BigDecimal.valueOf(bagCodeMapper.selectCountByPalletId(pallet.getId()));
        } else if (SCAN_TYPE_BATCH.equals(scanType)) {
            batch = batchMapper.selectByCode(req.getBagCode());
            if (batch == null) {
                batch = batchMapper.selectByTempCode(req.getBagCode());
            }
            if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在");
            if (batch.getInboundEventId() != null) return failed(event, "MES_SCAN_BATCH_ALREADY_INBOUND", "批次已整批入库");
            batchId = batch.getId();
            targetId = batch.getId();
            quantity = batch.getBagQuantity() != null && batch.getBagQuantity() > 0
                    ? BigDecimal.valueOf(batch.getBagQuantity())
                    : BigDecimal.valueOf(bagCodeMapper.selectCountByBatchId(batch.getId()));
        } else {
            MesProBagCodeDO bag = bagCodeMapper.selectByCodeForUpdate(req.getBagCode());
            if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "袋码不存在");
            event.setBagCodeId(bag.getId());
            bagCodeId = bag.getId();
            bagCode = req.getBagCode();
            if (bag.getInboundEventId() != null) return failed(event, "MES_SCAN_BAG_ALREADY_INBOUND", "袋码已入库");
            batch = batchMapper.selectByIdForUpdate(bag.getBatchId());
            if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在");
            batchId = batch.getId();
            targetId = bag.getId();
            quantity = req.getQuantity();
        }
        event.setBatchId(batchId).setBatchCode(batch.getCode()).setItemId(batch.getItemId());
        MesWmWarehouseDO warehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);
        if (warehouse == null) return failed(event, "MES_SCAN_WIP_WAREHOUSE_NOT_EXISTS", "WIP虚拟仓不存在");
        event.setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId());
@@ -72,19 +129,31 @@
            }
            Long txId = transactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                    .setType(MesWmTransactionTypeEnum.IN.getType()).setBizType(MesBizTypeConstants.WM_PRODUCT_PRODUCE)
                    .setBizId(batch.getId()).setBizCode(batch.getCode()).setBizLineId(bag.getId())
                    .setItemId(batch.getItemId()).setQuantity(req.getQuantity())
                    .setBizId(batchId).setBizCode(batch.getCode()).setBizLineId(targetId)
                    .setItemId(batch.getItemId()).setQuantity(quantity)
                    .setBatchId(wmBatch.getId()).setBatchCode(batch.getCode())
                    .setBagCodeId(bagCodeId).setBagCode(bagCode)
                    .setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId())
                    .setReceiptTime(LocalDateTime.now()).setCheckFlag(true)
                    .setStockState(MesWmStockStateEnum.TEMP.getState()));
            event.setTransactionId(txId).setStatus(MesProScanEventStatusEnum.SUCCESS).setEventTime(LocalDateTime.now());
            eventMapper.updateById(event);
            bagCodeMapper.markInbound(bag.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
            batchMapper.markInbound(batch.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
            // 2. å›žå†™å…¥åº“标记(托盘/批次整托/整批入库时同步标记其下未入库袋码,保持状态一致)
            if (SCAN_TYPE_PALLET.equals(scanType)) {
                palletMapper.markInbound(targetId, event.getId());
                bagCodeMapper.markInboundByPalletId(targetId, event.getId(), req.getLineCode());
            } else if (SCAN_TYPE_BATCH.equals(scanType)) {
                batchMapper.markBatchInbound(targetId, event.getId(), req.getLineCode(), batch.getBagQuantity());
                bagCodeMapper.markInboundByBatchId(targetId, event.getId(), req.getLineCode());
            } else {
                bagCodeMapper.markInbound(targetId, event.getId(), req.getDeviceCode(), req.getLineCode());
                batchMapper.markInbound(batchId, event.getId(), req.getDeviceCode(), req.getLineCode());
            }
            return event;
        } catch (Exception ex) {
            return failed(event, "MES_SCAN_TRANSACTION_FAILED", "库存事务处理失败");
            log.error("[createInbound] åº“存事务处理失败,eventId={}, bagCode={}, batchId={}, itemId={}",
                    req.getEventId(), req.getBagCode(), batchId, batch.getItemId(), ex);
            throw ex;
        }
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java
@@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.mes.service.qc.seedquality;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO;
@@ -10,6 +12,8 @@
    Long submit(MesQcSeedQualitySubmitReqVO reqVO);
    PageResult<MesQcSeedQualityDO> getQualityPage(MesQcSeedQualityPageReqVO reqVO);
    /**
     * æ ¹æ®æ‰¹æ¬¡å·æŸ¥è¯¢è´¨æ£€è®°å½•(按检验时间倒序,取最新),用于公开溯源。
     * ç§å­è´¨æ£€è¡¨æœªåˆå§‹åŒ–时返回空列表(容错,不影响追溯)。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
@@ -1,41 +1,117 @@
package cn.iocoder.yudao.module.mes.service.qc.seedquality;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.*;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality.*;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag.MesWmMaterialStockBagMapper;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeQcStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum;
import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import cn.iocoder.yudao.module.mes.service.wm.materialstockbag.MesWmMaterialStockBagService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_NOT_EXISTS;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_TYPE_NOT_QUALIFIED;
@Service @Validated @Slf4j
public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService {
    private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture");
    @Resource private MesQcSeedQualityMapper qualityMapper;
    @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper;
    @Resource private MesProBagCodeMapper bagCodeMapper;
    @Resource private MesProBatchService proBatchService;
    @Resource private MesWmBatchService mesWmBatchService;
    @Resource private MesWmMaterialStockService materialStockService;
    @Resource private MesWmBatchMapper mesWmBatchMapper;
    @Resource private MesWmMaterialStockMapper materialStockMapper;
    @Resource private MesWmMaterialStockBagMapper materialStockBagMapper;
    @Resource private MesWmMaterialStockBagService materialStockBagService;
    @Resource private MesWmTransactionService wmTransactionService;
    @Resource private MesWmWarehouseService warehouseService;
    @Resource private MesWmWarehouseLocationService warehouseLocationService;
    @Resource private MesWmWarehouseAreaService warehouseAreaService;
    @Override
    public PageResult<MesQcSeedQualityDO> getQualityPage(MesQcSeedQualityPageReqVO reqVO) {
        return qualityMapper.selectPage(reqVO);
    }
    @Override @Transactional(rollbackFor = Exception.class)
    public Long submit(MesQcSeedQualitySubmitReqVO reqVO) {
        if (reqVO.getIndicators().size() != 4) throw new IllegalArgumentException("必须提交四项指标");
        if (qualityMapper.selectByCode(reqVO.getCode()) != null) throw new IllegalArgumentException("该袋码已质检,禁止重复提交");
        // ç§å­è´¨æ£€å¿…须以袋码(单袋)提交,物料信息由袋码自动解析,前端不再单独选择物料
        MesProBagCodeDO bag = bagCodeMapper.selectByCode(reqVO.getCode());
        if (bag == null) {
            throw new IllegalArgumentException("未解析到有效袋码,种子质检必须以袋码提交");
        }
        MesProBatchDO batch = resolveBatch(reqVO.getCode(), reqVO.getBatchCode());
        if (batch == null || batch.getItemId() == null) {
            throw new IllegalArgumentException("未根据袋码解析到物料信息,请确认扫码内容或批次号正确");
        }
        // è¢‹ç å¯¹åº”批次的库存必须处于暂存库(待质检)中,才能进行种子质检
        MesWmBatchDO wmBatch = mesWmBatchMapper.selectByCode(batch.getCode());
        if (wmBatch == null || materialStockMapper.selectTempStockCountByBatchId(wmBatch.getId()) == 0) {
            throw new IllegalArgumentException("该袋码/批次对应的库存不在暂存库中,无法进行质检");
        }
        // æ ¡éªŒç›®æ ‡åˆæ ¼åº“:必须存在且仓库类型为合格库(20)
        if (warehouseService.getFirstQualifiedWarehouse() == null) {
            throw exception(WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED);
        }
        MesWmWarehouseDO qualifiedWarehouse = warehouseService.getWarehouse(reqVO.getQualifiedWarehouseId());
        if (qualifiedWarehouse == null) {
            throw exception(WM_WAREHOUSE_NOT_EXISTS);
        }
        if (!MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType())) {
            throw exception(WM_WAREHOUSE_TYPE_NOT_QUALIFIED);
        }
        // è§£æžåˆæ ¼åº“默认库区/库位(取该仓库下第一个库区、其下第一个库位,未配置则为 null)
        Long qualifiedLocationId = null;
        Long qualifiedAreaId = null;
        List<MesWmWarehouseLocationDO> locations = warehouseLocationService.getWarehouseLocationList(qualifiedWarehouse.getId());
        if (CollUtil.isNotEmpty(locations)) {
            qualifiedLocationId = locations.get(0).getId();
            List<MesWmWarehouseAreaDO> areas = warehouseAreaService.getWarehouseAreaList(qualifiedLocationId);
            if (CollUtil.isNotEmpty(areas)) {
                qualifiedAreaId = areas.get(0).getId();
            }
        }
        Set<String> codes = new HashSet<>();
        boolean passed = true;
        MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(reqVO.getCode()).itemId(reqVO.getItemId())
                .batchCode(reqVO.getBatchCode()).reason(reqVO.getReason()).result(0)
        MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(reqVO.getCode()).itemId(batch.getItemId())
                .batchCode(batch.getCode()).reason(reqVO.getReason()).result(0)
                .inspectedAt(LocalDateTime.now()).build();
        qualityMapper.insert(quality);
        for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) {
@@ -47,20 +123,151 @@
        if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("四项指标不完整");
        quality.setResult(passed ? 1 : 2);
        qualityMapper.updateById(quality);
        // è´¨æ£€åˆæ ¼ â†’ åº“存整批由暂存库(待质检)转为合格库(正式库存)
        // æŒ‰è¢‹ç ç²’度流转库存:锁定该袋在暂存库中的活动明细,禁止按批次任意扣减
        MesWmMaterialStockBagDO stockBag = materialStockBagMapper.selectActiveByBagCode(bag.getCode());
        MesWmMaterialStockDO tempStock = resolveTempStock(bag, wmBatch, stockBag);
        // A. æš‚存出库(-1):该袋离开暂存库,写入质检暂存出库事务(跳过袋码自动维护,由质检统一控制袋码最终落点)
        MesWmTransactionSaveReqDTO tempOutReq = new MesWmTransactionSaveReqDTO()
                .setType(MesWmTransactionTypeEnum.OUT.getType())
                .setBizType(MesBizTypeConstants.QC_SEED_QUALITY)
                .setBizId(quality.getId())
                .setBizCode(reqVO.getCode())
                .setBizLineId(quality.getId())
                .setItemId(batch.getItemId())
                .setQuantity(BigDecimal.ONE.negate())
                .setBatchId(tempStock.getBatchId())
                .setBatchCode(tempStock.getBatchCode())
                .setBagCodeId(bag.getId())
                .setBagCode(bag.getCode())
                .setWarehouseId(tempStock.getWarehouseId())
                .setLocationId(tempStock.getLocationId())
                .setAreaId(tempStock.getAreaId())
                .setVendorId(tempStock.getVendorId())
                .setMaterialStockId(tempStock.getId())
                .setCheckFlag(true)
                .setSkipBagManage(true);
        Long tempOutTxId = wmTransactionService.createTransaction(tempOutReq);
        if (passed) {
            MesProBatchDO proBatch = proBatchService.getBatchByCode(reqVO.getBatchCode());
            if (proBatch != null) {
                MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(proBatch);
                if (wmBatch != null) {
                    int updated = materialStockService.updateStockStateByBatchId(wmBatch.getId(),
                            MesWmStockStateEnum.QUALIFIED.getState(), MesWmStockStateEnum.TEMP.getState());
                    log.info("[submit][质检合格,批次 {} åº“存由暂存库转合格库,影响 {} æ¡]", reqVO.getBatchCode(), updated);
            // B1. è´¨æ£€åˆæ ¼ â†’ è¯¥è¢‹è½¬å…¥æ“ä½œå‘˜é€‰æ‹©çš„合格库,写入合格入库事务(跳过袋码自动维护)
            MesWmMaterialStockDO qualifiedStock = getOrCreateQualifiedStock(tempStock, qualifiedWarehouse.getId(), qualifiedLocationId, qualifiedAreaId);
            MesWmTransactionSaveReqDTO qualInReq = new MesWmTransactionSaveReqDTO()
                    .setType(MesWmTransactionTypeEnum.IN.getType())
                    .setBizType(MesBizTypeConstants.QC_SEED_QUALITY)
                    .setBizId(quality.getId())
                    .setBizCode(reqVO.getCode())
                    .setBizLineId(quality.getId())
                    .setItemId(batch.getItemId())
                    .setQuantity(BigDecimal.ONE)
                    .setBatchId(qualifiedStock.getBatchId())
                    .setBatchCode(qualifiedStock.getBatchCode())
                    .setBagCodeId(bag.getId())
                    .setBagCode(bag.getCode())
                    .setWarehouseId(qualifiedStock.getWarehouseId())
                    .setLocationId(qualifiedStock.getLocationId())
                    .setAreaId(qualifiedStock.getAreaId())
                    .setVendorId(qualifiedStock.getVendorId())
                    .setMaterialStockId(qualifiedStock.getId())
                    .setCheckFlag(false)
                    .setStockState(MesWmStockStateEnum.QUALIFIED.getState())
                    .setSkipBagManage(true);
            Long qualInTxId = wmTransactionService.createTransaction(qualInReq);
            // B2. åŒæ­¥è¿ç§»è¢‹ç æ˜Žç»†åˆ°åˆæ ¼åº“存落点
            materialStockBagService.moveIn(qualInReq, qualifiedStock, qualInTxId);
            bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.QUALIFIED.getStatus());
            log.info("[submit][质检合格,袋码 {} ç”±æš‚存库转合格库 {}]", bag.getCode(), qualifiedWarehouse.getName());
        } else {
            // C. è´¨æ£€ä¸åˆæ ¼ â†’ è¯¥è¢‹æŠ¥åºŸï¼›æš‚存出库事务已扣减暂存库存,标记袋码报废
            materialStockBagService.scrap(tempOutReq, tempOutTxId);
            bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.UNQUALIFIED.getStatus());
            log.info("[submit][质检不合格,袋码 {} æŠ¥åºŸ]", bag.getCode());
        }
        return quality.getId();
    }
    /**
     * å®šä½è¯¥è¢‹çš„æš‚存库存汇总记录(只定位,不扣减;扣减由质检暂存出库事务完成)。
     * <p>
     * ä¼˜å…ˆä¸¥æ ¼æŒ‰è¢‹ç é”å®šå…¶æ´»åŠ¨æ˜Žç»†å¯¹åº”çš„æš‚å­˜è®°å½•ï¼›åŽ†å²æ— æ˜Žç»†æ—¶å›žé€€æŒ‰æ‰¹æ¬¡å®šä½å¹¶è¡¥å»ºæ˜Žç»†ã€‚
     */
    private MesWmMaterialStockDO resolveTempStock(MesProBagCodeDO bag, MesWmBatchDO wmBatch, MesWmMaterialStockBagDO stockBag) {
        if (stockBag != null && stockBag.getMaterialStockId() != null) {
            MesWmMaterialStockDO stock = materialStockMapper.selectById(stockBag.getMaterialStockId());
            if (stock != null && MesWmStockStateEnum.TEMP.getState().equals(stock.getStockState())) {
                return stock;
            }
        }
        MesWmMaterialStockDO tempStock = materialStockMapper.selectStockListByBatchId(wmBatch.getId()).stream()
                .filter(s -> MesWmStockStateEnum.TEMP.getState().equals(s.getStockState()))
                .max(Comparator.comparing(MesWmMaterialStockDO::getQuantity))
                .orElse(null);
        if (tempStock == null) {
            throw new IllegalArgumentException("该批次暂存库存不足,无法质检");
        }
        getOrCreateStockBag(bag, tempStock);
        return tempStock;
    }
    /**
     * è´¨æ£€åˆæ ¼ï¼šå°†è´¨æ£€é€šè¿‡çš„那袋转入操作员选择的合格库(同组合键合格库记录 +1,不存在则新建)。
     *
     * @param tempStock            ä»Žæš‚存库扣减后返回的暂存记录(携带 item/batch/vendor ç»´åº¦ï¼‰
     * @param qualifiedWarehouseId ç›®æ ‡åˆæ ¼åº“仓库编号
     * @param qualifiedLocationId  ç›®æ ‡åˆæ ¼åº“库区编号(未配置则为 null)
     * @param qualifiedAreaId      ç›®æ ‡åˆæ ¼åº“库位编号(未配置则为 null)
     */
    private MesWmMaterialStockBagDO getOrCreateStockBag(MesProBagCodeDO bag, MesWmMaterialStockDO tempStock) {
        MesWmMaterialStockBagDO stockBag = materialStockBagMapper.selectByBagCode(bag.getCode());
        if (stockBag != null) {
            return stockBag;
        }
        stockBag = MesWmMaterialStockBagDO.builder().materialStockId(tempStock.getId())
                .bagCodeId(bag.getId()).bagCode(bag.getCode()).itemId(tempStock.getItemId())
                .batchId(tempStock.getBatchId()).batchCode(tempStock.getBatchCode()).vendorId(tempStock.getVendorId())
                .warehouseId(tempStock.getWarehouseId()).locationId(tempStock.getLocationId()).areaId(tempStock.getAreaId())
                .stockState(MesWmStockStateEnum.TEMP.getState()).status(1).quantity(BigDecimal.ONE)
                .frozen(false).inboundTime(tempStock.getReceiptTime()).build();
        materialStockBagMapper.insert(stockBag);
        return stockBag;
    }
    private MesWmMaterialStockDO getOrCreateQualifiedStock(MesWmMaterialStockDO tempStock, Long qualifiedWarehouseId,
                                         Long qualifiedLocationId, Long qualifiedAreaId) {
        MesWmMaterialStockDO qualified = materialStockMapper.selectQualifiedByCompositeKey(
                tempStock.getItemId(), qualifiedWarehouseId, qualifiedLocationId,
                qualifiedAreaId, tempStock.getBatchId(), tempStock.getVendorId());
        if (qualified != null) {
            return qualified;
        }
        // ä¸å­˜åœ¨åˆ™æ–°å»ºåˆæ ¼åº“记录(仓库/库区/库位采用所选合格库的维度,数量由质检入库事务 +1)
        MesWmMaterialStockDO newStock = MesWmMaterialStockDO.builder()
                .itemId(tempStock.getItemId()).itemTypeId(tempStock.getItemTypeId())
                .warehouseId(qualifiedWarehouseId).locationId(qualifiedLocationId).areaId(qualifiedAreaId)
                .batchId(tempStock.getBatchId()).batchCode(tempStock.getBatchCode()).vendorId(tempStock.getVendorId())
                .quantity(BigDecimal.ZERO).receiptTime(LocalDateTime.now())
                .stockState(MesWmStockStateEnum.QUALIFIED.getState())
                .frozen(false)
                .build();
        materialStockMapper.insert(newStock);
        return newStock;
    }
    /**
     * ç”±è¢‹ç æˆ–生产批次解析所属生产批次(袋码优先,批次码兼容临时码)
     */
    private MesProBatchDO resolveBatch(String code, String batchCode) {
        if (StrUtil.isNotBlank(code)) {
            MesProBagCodeDO bag = bagCodeMapper.selectByCode(code);
            if (bag != null && bag.getBatchId() != null) {
                MesProBatchDO batch = proBatchService.getBatch(bag.getBatchId());
                if (batch != null) {
                    return batch;
                }
            }
        }
        // åº“位由后台配置服务接管,当前版本保留审计闭环,禁止前端传入库位。
        return quality.getId();
        if (StrUtil.isNotBlank(batchCode)) {
            return proBatchService.getBatchByCode(batchCode);
        }
        return null;
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java
@@ -38,6 +38,9 @@
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -212,6 +215,7 @@
        fillAntiFake(resp, traceCode);
        // å¯é€‰æ‰©å±•区块(有数据才返回)
        fillOptionalModules(resp, batch);
        fillTimelineAndDecision(resp);
        return resp;
    }
@@ -338,6 +342,47 @@
        }
    }
    private void fillTimelineAndDecision(MesProTracePublicRespVO resp) {
        List<MesProTracePublicRespVO.TimelineVO> timeline = new ArrayList<>();
        if (resp.getProduceDate() != null) {
            timeline.add(timeline("生产", "BATCH_CREATED", "生产批次建立", resp.getProduceDate(), null,
                    resp.getBatchStatusName()));
        }
        if (resp.getProcessTraces() != null) {
            resp.getProcessTraces().forEach(item -> timeline.add(timeline("加工", item.getEventType(),
                    item.getEventType() == null ? "工序流转" : ("IN".equals(item.getEventType()) ? "工序投入" : "工序产出"),
                    item.getEventTime(), item.getQuantity(), item.getQualityStatus() == null ? null : String.valueOf(item.getQualityStatus()))));
        }
        if (resp.getInbound() != null) {
            timeline.add(timeline("仓储", "INBOUND", "仓储入库", resp.getInbound().getEventTime(),
                    resp.getInbound().getQuantity(), resp.getInbound().getStatus()));
        }
        if (resp.getSalesTargets() != null) {
            resp.getSalesTargets().forEach(item -> timeline.add(timeline("销售", "OUTBOUND", "销售出库",
                    item.getSalesDate(), item.getQuantity(), item.getQualityStatus() == null ? null : String.valueOf(item.getQualityStatus()))));
        }
        timeline.sort(Comparator.comparing(MesProTracePublicRespVO.TimelineVO::getEventTime,
                Comparator.nullsLast(Comparator.naturalOrder())));
        resp.setTimeline(timeline);
        MesProTracePublicRespVO.DecisionVO decision = new MesProTracePublicRespVO.DecisionVO();
        decision.setQualityRisk(resp.getQuality() != null && Integer.valueOf(2).equals(resp.getQuality().getResult()));
        decision.setInboundQuantity(resp.getInbound() == null ? null : resp.getInbound().getQuantity());
        decision.setSalesTargetCount(resp.getSalesTargets() == null ? 0 : resp.getSalesTargets().size());
        decision.setConsumerScanCount(resp.getAntiFake() == null ? 0L : resp.getAntiFake().getScanCount());
        decision.setRiskLevel(Boolean.TRUE.equals(decision.getQualityRisk()) ? "HIGH" : "NORMAL");
        resp.setDecision(decision);
    }
    private MesProTracePublicRespVO.TimelineVO timeline(String stage, String eventType, String eventName,
                                                         java.time.LocalDateTime eventTime, BigDecimal quantity,
                                                         String status) {
        MesProTracePublicRespVO.TimelineVO vo = new MesProTracePublicRespVO.TimelineVO();
        vo.setStage(stage).setEventType(eventType).setEventName(eventName).setEventTime(eventTime)
                .setQuantity(quantity).setStatus(status);
        return vo;
    }
    // ==================== åç§°æ˜ å°„ ====================
    private String userName(Long userId) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
@@ -423,13 +423,21 @@
        if (proBatch == null || StrUtil.isBlank(proBatch.getCode())) {
            return null;
        }
        // 1. å¤ç”¨å·²æœ‰ä»“库批次(同一生产批次号)
        MesWmBatchDO existing = batchMapper.selectByCode(proBatch.getCode());
        // 1. ä¼˜å…ˆæŒ‰ç”Ÿäº§æ‰¹æ¬¡ ID å¤ç”¨ï¼Œå…¼å®¹åŽ†å²æ•°æ®å†æŒ‰æ‰¹æ¬¡å·åŒ¹é…
        MesWmBatchDO existing = batchMapper.selectOne(MesWmBatchDO::getProBatchId, proBatch.getId());
        if (existing == null) {
            existing = batchMapper.selectByCode(proBatch.getCode());
        }
        if (existing != null) {
            if (existing.getProBatchId() == null && proBatch.getId() != null) {
                existing.setProBatchId(proBatch.getId());
                batchMapper.updateById(existing);
            }
            return existing;
        }
        // 2. æ–°å»ºä»“库批次:以生产批次号作为仓库批次号,质量状态置为待检验
        MesWmBatchDO batch = MesWmBatchDO.builder()
                .proBatchId(proBatch.getId())
                .code(proBatch.getCode())
                .itemId(proBatch.getItemId())
                .produceDate(proBatch.getProduceDate())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.java
@@ -132,6 +132,18 @@
    List<MesWmMaterialStockDO> getMaterialStockListByItemAndBatch(Long itemId, Long batchId);
    /**
     * æŸ¥è¯¢ç‰©æ–™/批次的合格库库存记录(仅 stock_state=20)
     *
     * <p>同批次可能同时存在暂存库与合格库两条记录(种子质检按袋拆数量),
     * é”€å”®å‡ºåº“选库存必须从合格库记录中选,避免选中未质检的暂存库存。</p>
     *
     * @param itemId  ç‰©æ–™ç¼–号
     * @param batchId æ‰¹æ¬¡ç¼–号
     * @return åˆæ ¼åº“库存记录列表
     */
    List<MesWmMaterialStockDO> getQualifiedMaterialStockListByItemAndBatch(Long itemId, Long batchId);
    /**
     * æ›´æ–°åº“存数量
     *
     * @param id åº“存记录编号
@@ -165,16 +177,6 @@
     * @return ä»“库汇总列表
     */
    List<MesWmStockSummaryRespVO> getStockSummaryByWarehouse(MesWmMaterialStockListReqVO reqVO);
    /**
     * æŒ‰ä»“库批次更新库存状态(用于种子质检合格后,将暂存库整批转为合格库)
     *
     * @param batchId   ä»“库批次ID
     * @param toState   ç›®æ ‡åº“存状态
     * @param fromState åŽŸåº“å­˜çŠ¶æ€ï¼ˆé™å®šåªæ›´æ–°è¯¥çŠ¶æ€çš„è®°å½•ï¼‰
     * @return å½±å“è¡Œæ•°
     */
    int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState);
    /**
     * åº“位混放规则校验
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockServiceImpl.java
@@ -29,6 +29,9 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.mes.service.wm.materialstockbag.MesWmMaterialStockBagService;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag.MesWmMaterialStockBagMapper;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import jakarta.annotation.Resource;
@@ -44,6 +47,7 @@
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -74,6 +78,11 @@
    @Resource
    @Lazy
    private MesWmBatchService batchService;
    @Resource
    private MesWmMaterialStockBagMapper materialStockBagMapper;
    @Resource
    private MesWmMaterialStockBagService materialStockBagService;
    @Override
    public MesWmMaterialStockDO getMaterialStock(Long id) {
@@ -121,9 +130,27 @@
            Assert.notNull(virtualWarehouse, "虚拟仓库(WIP_VIRTUAL_WAREHOUSE)不存在");
            virtualWarehouseId = virtualWarehouse.getId();
        }
        // 1.4 è§£æž warehouseType:转换为该类型仓库 warehouseIds(与 warehouseId / virtualFilter å¹¶å­˜ï¼Œä¿ç•™å…¼å®¹ï¼‰
        List<Long> warehouseIds = resolveWarehouseIdsByType(pageReqVO.getWarehouseType());
        if (warehouseIds != null && warehouseIds.isEmpty()) {
            return PageResult.empty();
        }
        // 2. åˆ†é¡µæŸ¥è¯¢
        return materialStockMapper.selectPage(pageReqVO, itemTypeIds, itemIds, virtualWarehouseId);
        return materialStockMapper.selectPage(pageReqVO, itemTypeIds, itemIds, virtualWarehouseId, warehouseIds);
    }
    /**
     * å°†ä»“库类型解析为仓库 id é›†åˆï¼ˆwarehouseType ä¸º null æ—¶ä¸åŠ è¿‡æ»¤ï¼Œè¿”å›ž null)
     *
     * @param warehouseType ä»“库类型,见 {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum}
     * @return è¯¥ç±»åž‹ä»“库 id é›†åˆï¼›warehouseType ä¸º null è¿”回 null;该类型无仓库返回空集合
     */
    private List<Long> resolveWarehouseIdsByType(Integer warehouseType) {
        if (warehouseType == null) {
            return null;
        }
        return convertList(warehouseService.getWarehouseListByType(warehouseType), MesWmWarehouseDO::getId);
    }
    @Override
@@ -169,7 +196,11 @@
    @Override
    public List<MesWmMaterialStockDO> getMaterialStockList(MesWmMaterialStockListReqVO reqVO) {
        return materialStockMapper.selectList(reqVO);
        List<Long> warehouseIds = resolveWarehouseIdsByType(reqVO.getWarehouseType());
        if (warehouseIds != null && warehouseIds.isEmpty()) {
            return Collections.emptyList();
        }
        return materialStockMapper.selectList(reqVO, warehouseIds);
    }
    @Override
@@ -282,7 +313,17 @@
    @Override
    public MesWmMaterialStockDO getMaterialStockByCompositeKey(Long itemId, Long warehouseId, Long locationId, Long areaId,
                                                                Long batchId, Long vendorId) {
        return materialStockMapper.selectByCompositeKey(itemId, warehouseId, locationId, areaId, batchId, vendorId);
        // å‡ºåº“/占用只从合格库匹配,避免扣到同批次的暂存库(种子质检按袋拆数量后同批次双状态并存)
        return materialStockMapper.selectQualifiedByCompositeKey(itemId, warehouseId, locationId, areaId, batchId, vendorId);
    }
    @Override
    public List<MesWmMaterialStockDO> getQualifiedMaterialStockListByItemAndBatch(Long itemId, Long batchId) {
        List<Long> qualifiedWarehouseIds = warehouseService.getQualifiedWarehouseIds();
        if (CollUtil.isEmpty(qualifiedWarehouseIds)) {
            return Collections.emptyList();
        }
        return materialStockMapper.selectQualifiedListByItemAndBatch(itemId, batchId, qualifiedWarehouseIds);
    }
    @Override
@@ -302,11 +343,6 @@
    @Override
    public void updateMaterialStockState(Long id, Integer stockState) {
        materialStockMapper.updateById(new MesWmMaterialStockDO().setId(id).setStockState(stockState));
    }
    @Override
    public int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState) {
        return materialStockMapper.updateStockStateByBatchId(batchId, toState, fromState);
    }
    @Override
@@ -356,7 +392,11 @@
                || ObjUtil.notEqual(stock.getAreaId(), areaId)) {
            throw exception(WM_MATERIAL_STOCK_SELECTION_MISMATCH);
        }
        // 3. æ ¡éªŒåº“存数量充足(如果前端传了 quantity,则必须保证库存数量 >= quantity)
        // 3. æ ¡éªŒåº“存记录必须是合格库(暂存库存未质检,不可用于出库/占用)
        if (!MesWmStockStateEnum.QUALIFIED.getState().equals(stock.getStockState())) {
            throw exception(WM_MATERIAL_STOCK_NOT_QUALIFIED);
        }
        // 4. æ ¡éªŒåº“存数量充足(如果前端传了 quantity,则必须保证库存数量 >= quantity)
        if (quantity != null && stock.getQuantity() != null && stock.getQuantity().compareTo(quantity) < 0) {
            throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
        }
@@ -592,6 +632,32 @@
                .setRelatedTransactionId(outTransactionId)
                .setCheckFlag(false);
        wmTransactionService.createTransaction(inTransaction);
        // 9. æ•´ä½ç½®ç§»åº“时,逐袋迁移袋码明细位置(数量等于源库位活动袋数且为整数才视为整位移库)
        MesWmMaterialStockDO finalTargetStock = materialStockMapper.selectByCompositeKeyIgnoreVendor(
                sourceStock.getItemId(), reqVO.getToWarehouseId(), reqVO.getToLocationId(),
                reqVO.getToAreaId(), sourceStock.getBatchId());
        relocateBagsForMove(sourceStock, reqVO.getQuantity(), inTransaction, finalTargetStock);
    }
    /**
     * æ•´ä½ç§»åº“:将源库位的活动袋码明细迁移到目标库位。
     * <p>
     * ä»…当移库数量等于源库位活动袋数且为整数时执行(整位置搬移),将每袋明细的库存汇总、仓库、库区、库位
     * æ›´æ–°ä¸ºç›®æ ‡ä½ç½®ï¼›éƒ¨åˆ†ç§»åº“/无袋码明细的旧库存不做袋码迁移,保持汇总库存变动即可。
     */
    private void relocateBagsForMove(MesWmMaterialStockDO sourceStock, BigDecimal quantity,
                                     MesWmTransactionSaveReqDTO moveInReq, MesWmMaterialStockDO targetStock) {
        List<MesWmMaterialStockBagDO> bags = materialStockBagMapper.selectActiveListByItemAndBatch(
                sourceStock.getItemId(), sourceStock.getBatchId(), sourceStock.getStockState());
        if (CollUtil.isEmpty(bags) || targetStock == null || quantity == null
                || quantity.compareTo(BigDecimal.valueOf(bags.size())) != 0) {
            return;
        }
        for (MesWmMaterialStockBagDO bag : bags) {
            moveInReq.setBagCodeId(bag.getBagCodeId()).setBagCode(bag.getBagCode());
            materialStockBagService.moveIn(moveInReq, targetStock, null);
        }
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstockbag/MesWmMaterialStockBagService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.mes.service.wm.materialstockbag;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
/**
 * MES åº“存袋码明细 Service æŽ¥å£
 *
 * ç»Ÿä¸€å°è£…袋码库存明细的建立、迁移、出库、报废逻辑,供所有带袋码的库存变动入口复用。
 * æ¯ä¸ªè¢‹ç åœ¨åŒä¸€æ—¶é—´åªæœ‰ä¸€æ¡åœ¨åº“明细(唯一约束 bag_code),流转通过更新明细位置/状态实现。
 */
public interface MesWmMaterialStockBagService {
    /**
     * å…¥åº“:建立或激活活动袋码明细。
     * <p>
     * è¢‹ç é¦–次入库时新建明细;已出库/已报废的袋码再次入库时激活为在库并更新位置;已在库则视为重复入库抛出异常。
     *
     * @param reqDTO        äº‹åŠ¡è¯·æ±‚
     * @param stock         ç›®æ ‡åº“存汇总记录
     * @param transactionId æœ¬ç¬”事务 ID
     * @return è¢‹ç æ˜Žç»†
     */
    MesWmMaterialStockBagDO inbound(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO stock, Long transactionId);
    /**
     * å‡ºåº“:将指定袋码明细置为已出库。
     *
     * @param reqDTO        äº‹åŠ¡è¯·æ±‚ï¼ˆéœ€æºå¸¦ bagCode/bagCodeId)
     * @param transactionId æœ¬ç¬”事务 ID
     */
    void outbound(MesWmTransactionSaveReqDTO reqDTO, Long transactionId);
    /**
     * ç§»å…¥/迁移:更新活动袋码明细的位置与库存状态(移库、调拨、质检合格迁移)。
     *
     * @param reqDTO        äº‹åŠ¡è¯·æ±‚ï¼ˆéœ€æºå¸¦ bagCode)
     * @param targetStock   ç›®æ ‡åº“存汇总记录
     * @param transactionId æœ¬ç¬”事务 ID
     */
    void moveIn(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO targetStock, Long transactionId);
    /**
     * æŠ¥åºŸï¼šå°†æŒ‡å®šè¢‹ç æ˜Žç»†ç½®ä¸ºæŠ¥åºŸã€‚
     *
     * @param reqDTO        äº‹åŠ¡è¯·æ±‚ï¼ˆéœ€æºå¸¦ bagCode)
     * @param transactionId æœ¬ç¬”事务 ID
     */
    void scrap(MesWmTransactionSaveReqDTO reqDTO, Long transactionId);
    /**
     * æ ¹æ®åº“存汇总 ID æŸ¥è¯¢åœ¨åº“袋码明细。
     *
     * @param materialStockId åº“存汇总 ID
     * @return è¢‹ç æ˜Žç»†ï¼ˆæ— åˆ™ null)
     */
    MesWmMaterialStockBagDO getActiveByMaterialStockId(Long materialStockId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstockbag/MesWmMaterialStockBagServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,165 @@
package cn.iocoder.yudao.module.mes.service.wm.materialstockbag;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstockbag.MesWmMaterialStockBagDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstockbag.MesWmMaterialStockBagMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmMaterialStockBagStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
 * MES åº“存袋码明细 Service å®žçŽ°ç±»
 *
 * è¢‹ç æ˜Žç»†ç”Ÿå‘½å‘¨æœŸï¼šé¦–次入库建立(ACTIVE)→ ç§»åº“/质检迁移位置状态 â†’ å‡ºåº“/报废结束(OUT/SCRAPPED)。
 * å”¯ä¸€çº¦æŸ bag_code ä¿è¯æ¯ä¸ªè¢‹ç ä»…一条明细,复用同一行完成全生命周期流转。
 */
@Service
@Validated
public class MesWmMaterialStockBagServiceImpl implements MesWmMaterialStockBagService {
    @Resource
    private MesWmMaterialStockBagMapper materialStockBagMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesWmMaterialStockBagDO inbound(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO stock, Long transactionId) {
        String bagCode = reqDTO.getBagCode();
        if (StrUtil.isBlank(bagCode)) {
            throw exception(WM_MATERIAL_STOCK_BAG_REQUIRED);
        }
        // 1. æŸ¥è¯¢è¯¥è¢‹ç æ—¢æœ‰æ˜Žç»†ï¼šä¼˜å…ˆæŒ‰ bagCodeId,其次按 bagCode
        MesWmMaterialStockBagDO existing = materialStockBagMapper.selectByBagCode(bagCode);
        if (existing == null && reqDTO.getBagCodeId() != null) {
            existing = materialStockBagMapper.selectByBagCodeId(reqDTO.getBagCodeId());
        }
        if (existing != null) {
            // 2. å·²åœ¨åº“ â†’ é‡å¤å…¥åº“,禁止
            if (MesWmMaterialStockBagStatusEnum.ACTIVE.getStatus().equals(existing.getStatus())) {
                throw exception(WM_MATERIAL_STOCK_BAG_DUPLICATE, bagCode);
            }
            // 3. å·²å‡ºåº“/报废 â†’ é‡æ–°æ¿€æ´»å¹¶æ›´æ–°ä½ç½®
            return reactivate(existing, reqDTO, stock, transactionId);
        }
        // 4. é¦–次入库 â†’ æ–°å»ºæ˜Žç»†
        MesWmMaterialStockBagDO bag = MesWmMaterialStockBagDO.builder()
                .materialStockId(stock.getId())
                .bagCodeId(reqDTO.getBagCodeId())
                .bagCode(bagCode)
                .itemId(stock.getItemId())
                .batchId(stock.getBatchId())
                .batchCode(stock.getBatchCode())
                .vendorId(stock.getVendorId())
                .warehouseId(stock.getWarehouseId())
                .locationId(stock.getLocationId())
                .areaId(stock.getAreaId())
                .stockState(ObjUtil.defaultIfNull(reqDTO.getStockState(), stock.getStockState()))
                .status(MesWmMaterialStockBagStatusEnum.ACTIVE.getStatus())
                .quantity(BigDecimal.ONE)
                .frozen(false)
                .lastTransactionId(transactionId)
                .inboundTime(LocalDateTime.now())
                .build();
        materialStockBagMapper.insert(bag);
        return bag;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void outbound(MesWmTransactionSaveReqDTO reqDTO, Long transactionId) {
        MesWmMaterialStockBagDO bag = requireActiveBag(reqDTO);
        MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO();
        update.setId(bag.getId());
        update.setStatus(MesWmMaterialStockBagStatusEnum.OUT.getStatus());
        update.setStockState(MesWmStockStateEnum.OUT.getState());
        update.setLastTransactionId(transactionId);
        update.setOutboundTime(LocalDateTime.now());
        materialStockBagMapper.updateById(update);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void moveIn(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO targetStock, Long transactionId) {
        MesWmMaterialStockBagDO bag = requireActiveBag(reqDTO);
        MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO();
        update.setId(bag.getId());
        update.setMaterialStockId(targetStock.getId());
        update.setWarehouseId(targetStock.getWarehouseId());
        update.setLocationId(targetStock.getLocationId());
        update.setAreaId(targetStock.getAreaId());
        update.setStockState(ObjUtil.defaultIfNull(reqDTO.getStockState(), targetStock.getStockState()));
        update.setLastTransactionId(transactionId);
        materialStockBagMapper.updateById(update);
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void scrap(MesWmTransactionSaveReqDTO reqDTO, Long transactionId) {
        MesWmMaterialStockBagDO bag = requireActiveBag(reqDTO);
        MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO();
        update.setId(bag.getId());
        update.setStatus(MesWmMaterialStockBagStatusEnum.SCRAPPED.getStatus());
        update.setStockState(MesWmStockStateEnum.OUT.getState());
        update.setQuantity(BigDecimal.ZERO);
        update.setLastTransactionId(transactionId);
        update.setOutboundTime(LocalDateTime.now());
        materialStockBagMapper.updateById(update);
    }
    @Override
    public MesWmMaterialStockBagDO getActiveByMaterialStockId(Long materialStockId) {
        return materialStockBagMapper.selectOneActiveByMaterialStockId(materialStockId);
    }
    /**
     * æŸ¥è¯¢æŒ‡å®šè¢‹ç çš„æ´»åŠ¨æ˜Žç»†ï¼Œä¸å­˜åœ¨æˆ–å·²å‡ºåº“/报废则抛异常。
     */
    private MesWmMaterialStockBagDO requireActiveBag(MesWmTransactionSaveReqDTO reqDTO) {
        MesWmMaterialStockBagDO bag = null;
        if (reqDTO.getBagCodeId() != null) {
            bag = materialStockBagMapper.selectActiveByBagCodeId(reqDTO.getBagCodeId());
        }
        if (bag == null && StrUtil.isNotBlank(reqDTO.getBagCode())) {
            bag = materialStockBagMapper.selectActiveByBagCode(reqDTO.getBagCode());
        }
        if (bag == null) {
            String code = StrUtil.blankToDefault(reqDTO.getBagCode(), String.valueOf(reqDTO.getBagCodeId()));
            throw exception(WM_MATERIAL_STOCK_BAG_NOT_ACTIVE, code);
        }
        return bag;
    }
    private MesWmMaterialStockBagDO reactivate(MesWmMaterialStockBagDO existing, MesWmTransactionSaveReqDTO reqDTO,
                                               MesWmMaterialStockDO stock, Long transactionId) {
        MesWmMaterialStockBagDO update = new MesWmMaterialStockBagDO();
        update.setId(existing.getId());
        update.setMaterialStockId(stock.getId());
        update.setItemId(stock.getItemId());
        update.setBatchId(stock.getBatchId());
        update.setBatchCode(stock.getBatchCode());
        update.setVendorId(stock.getVendorId());
        update.setWarehouseId(stock.getWarehouseId());
        update.setLocationId(stock.getLocationId());
        update.setAreaId(stock.getAreaId());
        update.setStockState(ObjUtil.defaultIfNull(reqDTO.getStockState(), stock.getStockState()));
        update.setStatus(MesWmMaterialStockBagStatusEnum.ACTIVE.getStatus());
        update.setQuantity(BigDecimal.ONE);
        update.setFrozen(false);
        update.setLastTransactionId(transactionId);
        update.setInboundTime(LocalDateTime.now());
        update.setOutboundTime(null);
        materialStockBagMapper.updateById(update);
        return existing;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -27,6 +27,7 @@
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeQcStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmSalesNoticeStatusEnum;
@@ -43,8 +44,6 @@
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.mes.service.qc.oqc.MesQcOqcService;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
@@ -104,9 +103,6 @@
    @Resource
    private MesTraceIntegrityService integrityService;
    @Resource
    private ErpSaleOrderApi saleOrderApi;
    @Override
    public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) {
@@ -242,6 +238,15 @@
        if (pallet != null && pallet.getBatchId() == null) {
            throw exception(PRO_PALLET_NOT_BIND_BATCH);
        }
        // ç§å­è´¨æ£€æŒ‰è¢‹ç ç²’度流转后,出库只允许已质检合格的袋码/托盘
        if (pallet != null) {
            List<MesProBagCodeDO> palletBags = bagCodeService.getBagCodeListByPalletId(pallet.getId());
            if (palletBags.stream().anyMatch(b -> !MesProBagCodeQcStatusEnum.QUALIFIED.getStatus().equals(b.getQcStatus()))) {
                throw exception(WM_PRODUCT_SALES_SCAN_PALLET_NOT_ALL_QUALIFIED);
            }
        } else if (!MesProBagCodeQcStatusEnum.QUALIFIED.getStatus().equals(bag.getQcStatus())) {
            throw exception(WM_PRODUCT_SALES_SCAN_BAG_NOT_QUALIFIED);
        }
        Long codeId = pallet != null ? pallet.getId() : bag.getId();
        MesWmProductSalesDetailDO existed = pallet != null
                ? productSalesDetailMapper.selectBySalesIdAndPalletCodeId(reqVO.getSalesId(), codeId)
@@ -270,7 +275,8 @@
        if (quantity == null) {
            quantity = line.getQuantity();
        }
        List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockListByItemAndBatch(batch.getItemId(), wmBatch.getId());
        // åªä»Žåˆæ ¼åº“记录中选库存(同批次暂存库记录不可出库)
        List<MesWmMaterialStockDO> stocks = materialStockService.getQualifiedMaterialStockListByItemAndBatch(batch.getItemId(), wmBatch.getId());
        BigDecimal outboundQuantity = quantity;
        MesWmMaterialStockDO stock = stocks.stream().filter(item -> item.getQuantity().compareTo(outboundQuantity) >= 0).findFirst().orElse(null);
        if (stock == null) {
@@ -338,12 +344,6 @@
        // æ‰§è¡Œæ‹£è´§ï¼ˆå¾…拣货 â†’ å¾…填写运单)
        productSalesMapper.updateById(new MesWmProductSalesDO()
                .setId(id).setStatus(MesWmProductSalesStatusEnum.SHIPPING.getStatus()));
        // å›žå†™ ERP é”€å”®è®¢å•出库状态为"部分出库"(表示已拣货/占用)
        if (sales.getSaleOrderId() != null) {
            saleOrderApi.updateOrderOutStatusTo(sales.getSaleOrderId(),
                    ErpSaleOrderOutStatusEnum.PART_OUT.getStatus());
        }
    }
    @Override
@@ -392,10 +392,7 @@
            updateSalesNoticeOutCount(sales);
        }
        // 6. å›žå†™ ERP é”€å”®è®¢å•的出库数量和状态
        updateErpSaleOrderOutCount(sales);
        // 7. å›žå†™è¢‹ç /托盘出库去向(供窜货稽查定位经销商地区),并生成出库完整性摘要
        // 6. å›žå†™è¢‹ç /托盘出库去向(供窜货稽查定位经销商地区),并生成出库完整性摘要
        List<MesWmProductSalesDetailDO> outDetails = productSalesDetailService.getProductSalesDetailListBySalesId(id);
        for (MesWmProductSalesDetailDO detail : outDetails) {
            if (detail.getBagCodeId() != null) {
@@ -425,43 +422,13 @@
        }
    }
    /**
     * å›žå†™ ERP é”€å”®è®¢å•的出库数量和状态
     */
    private void updateErpSaleOrderOutCount(MesWmProductSalesDO sales) {
        // èŽ·å–å‡ºåº“å•è¡Œ
        List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(sales.getId());
        if (CollUtil.isEmpty(lines)) {
            return;
        }
        // ç”¨äºŽå­˜å‚¨é”€å”®è®¢å• ID,避免重复更新
        Long saleOrderId = null;
        // éåŽ†è¡Œï¼ŒæŒ‰ saleOrderItemId æ›´æ–°å‡ºåº“数量
        for (MesWmProductSalesLineDO line : lines) {
            if (line.getSaleOrderItemId() != null && line.getQuantity() != null) {
                saleOrderApi.updateOrderItemOutCount(line.getSaleOrderItemId(), line.getQuantity());
                // èŽ·å–é”€å”®è®¢å• ID(从第一个有效的明细项获取)
                if (saleOrderId == null) {
                    cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderItemRespDTO itemDTO =
                            saleOrderApi.getSaleOrderItem(line.getSaleOrderItemId()).getCheckedData();
                    if (itemDTO != null && itemDTO.getOrderId() != null) {
                        saleOrderId = itemDTO.getOrderId();
                    }
                }
            }
        }
        // æ›´æ–°é”€å”®è®¢å•的出库状态
        if (saleOrderId != null) {
            saleOrderApi.updateOrderOutStatus(saleOrderId);
        }
    }
    private void createTransactionList(MesWmProductSalesDO sales) {
        List<MesWmProductSalesDetailDO> details = productSalesDetailService.getProductSalesDetailListBySalesId(sales.getId());
        wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
                .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId())
                .setQuantity(detail.getQuantity().negate()) // å‡ºåº“数量为负数
                .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
                .setBagCodeId(detail.getBagCodeId()) // è¢‹ç åž‹æ˜Žç»†å‡ºåº“时置袋码已出库;托盘型明细不逐袋处理
                .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
                .setBizType(MesBizTypeConstants.WM_PRODUCT_SALES).setBizId(sales.getId())
                .setBizCode(sales.getCode()).setBizLineId(detail.getLineId())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeLineServiceImpl.java
@@ -7,16 +7,19 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.line.MesWmSalesNoticeLinePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.salesnotice.vo.line.MesWmSalesNoticeLineSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeLineDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.salesnotice.MesWmSalesNoticeLineMapper;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -38,6 +41,8 @@
    private MesWmSalesNoticeService salesNoticeService;
    @Resource
    private MesWmBatchService batchService;
    @Resource
    private MesWmMaterialStockService materialStockService;
    @Resource
    private MesMdmItemService mdmItemService;
@@ -118,12 +123,36 @@
    }
    private void validateLineSaveData(MesWmSalesNoticeLineSaveReqVO reqVO) {
        // æ ¡éªŒçˆ¶å•据存在且为草稿状态
        validateNoticeStatusDraft(reqVO.getNoticeId());
        // æ ¡éªŒç‰©æ–™å­˜åœ¨
        if (reqVO.getItemId() != null) {
            mdmItemService.validateItemExistsAndEnable(reqVO.getItemId());
        mdmItemService.validateItemExistsAndEnable(reqVO.getItemId());
        MesWmBatchDO batch = resolveBatch(reqVO);
        if (batch == null) {
            throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
        }
        if (ObjUtil.notEqual(batch.getItemId(), reqVO.getItemId())) {
            throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
        }
        BigDecimal qualifiedQuantity = materialStockService
                .getQualifiedMaterialStockListByItemAndBatch(reqVO.getItemId(), batch.getId()).stream()
                .map(MesWmMaterialStockDO::getQuantity)
                .filter(quantity -> quantity != null)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        if (qualifiedQuantity.compareTo(reqVO.getQuantity()) < 0) {
            throw exception(WM_MATERIAL_STOCK_INSUFFICIENT);
        }
        reqVO.setBatchId(batch.getId());
        reqVO.setBatchCode(batch.getCode());
    }
    private MesWmBatchDO resolveBatch(MesWmSalesNoticeLineSaveReqVO reqVO) {
        if (reqVO.getBatchId() != null) {
            return batchService.getBatch(reqVO.getBatchId());
        }
        if (StrUtil.isNotBlank(reqVO.getBatchCode())) {
            return batchService.getBatchByCode(reqVO.getBatchCode());
        }
        return null;
    }
    /**
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeService.java
@@ -89,14 +89,6 @@
    }
    /**
     * ä»Žé”€å”®è®¢å•生成发货通知单
     *
     * @param saleOrderId é”€å”®è®¢å•ID
     * @return å‘货通知单ID
     */
    Long generateSalesNoticeFromSaleOrder(Long saleOrderId);
    /**
     * å®Œæˆå‘货通知单(出库完成后调用)
     * ç´¯åŠ å‡ºåº“æ•°é‡å¹¶è®¡ç®—å‡ºåº“çŠ¶æ€
     *
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
@@ -18,10 +18,6 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.erp.api.sale.ErpSaleOrderApi;
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderItemRespDTO;
import cn.iocoder.yudao.module.erp.api.sale.dto.ErpSaleOrderRespDTO;
import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -53,8 +49,6 @@
    @Resource
    private CrmCustomerApi customerApi;
    @Resource
    private ErpSaleOrderApi saleOrderApi;
    @Resource
    private MesMdAutoCodeRecordService autoCodeRecordService;
@@ -125,7 +119,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void submitSalesNotice(Long id) {
        // 1.1 æ ¡éªŒå­˜åœ¨ + è‰ç¨¿çŠ¶æ€
        MesWmSalesNoticeDO notice = validateSalesNoticeExistsAndDraft(id);
        validateSalesNoticeExistsAndDraft(id);
        // 1.2 æ£€æŸ¥æ˜¯å¦æœ‰è¡Œé¡¹ç›®
        List<MesWmSalesNoticeLineDO> lines = salesNoticeLineService.getSalesNoticeLineListByNoticeId(id);
        if (CollUtil.isEmpty(lines)) {
@@ -135,12 +129,6 @@
        // 2. æ›´æ–°çŠ¶æ€ï¼šè‰ç¨¿ â†’ å¾…出库
        salesNoticeMapper.updateById(new MesWmSalesNoticeDO()
                .setId(id).setStatus(APPROVED.getStatus()));
        // 3. å›žå†™ ERP é”€å”®è®¢å•出库状态为"已预留"
        if (notice.getSaleOrderId() != null) {
            saleOrderApi.updateOrderOutStatusTo(notice.getSaleOrderId(),
                    ErpSaleOrderOutStatusEnum.RESERVED.getStatus());
        }
    }
    @Override
@@ -188,54 +176,6 @@
        if (ObjUtil.notEqual(id, notice.getId())) {
            throw exception(WM_SALES_NOTICE_CODE_DUPLICATE);
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long generateSalesNoticeFromSaleOrder(Long saleOrderId) {
        // 1. æ ¡éªŒæ˜¯å¦å·²ç”Ÿæˆè¿‡å‘货通知单(防止重复生成)
        MesWmSalesNoticeDO existNotice = salesNoticeMapper.selectBySaleOrderId(saleOrderId);
        if (existNotice != null) {
            throw exception(WM_SALES_NOTICE_ALREADY_EXISTS);
        }
        // 2. èŽ·å–é”€å”®è®¢å•ä¿¡æ¯
        ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(saleOrderId).getCheckedData();
        if (saleOrder == null) {
            throw exception(WM_SALES_NOTICE_SALE_ORDER_NOT_EXISTS);
        }
        // 3. èŽ·å–é”€å”®è®¢å•æ˜Žç»†
        List<ErpSaleOrderItemRespDTO> saleOrderItems = saleOrderApi.getSaleOrderItemList(saleOrderId).getCheckedData();
        if (CollUtil.isEmpty(saleOrderItems)) {
            throw exception(WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY);
        }
        // 4. ç”Ÿæˆå‘货通知单编码
        String code = autoCodeRecordService.generateAutoCode("SALESNOTICE_CODE", null);
        // 5. åˆ›å»ºå‘货通知单主表
        MesWmSalesNoticeDO notice = new MesWmSalesNoticeDO();
        notice.setCode(code);
        notice.setName("销售订单[" + saleOrder.getNo() + "]发货通知");
        notice.setClientId(saleOrder.getCustomerId());
        notice.setSaleOrderId(saleOrderId);
        notice.setSaleOrderCode(saleOrder.getNo());
        notice.setSalesDate(java.time.LocalDateTime.now());
        notice.setStatus(PREPARE.getStatus());
        salesNoticeMapper.insert(notice);
        // 6. éåŽ†æ˜Žç»†åˆ›å»ºå‘è´§é€šçŸ¥å•è¡Œ
        for (ErpSaleOrderItemRespDTO saleOrderItem : saleOrderItems) {
            MesWmSalesNoticeLineDO line = new MesWmSalesNoticeLineDO();
            line.setNoticeId(notice.getId());
            line.setSaleOrderItemId(saleOrderItem.getId());
            line.setItemId(saleOrderItem.getProductId());
            line.setQuantity(saleOrderItem.getCount());
            salesNoticeLineService.createSalesNoticeLine(line);
        }
        return notice.getId();
    }
    @Override
@@ -350,8 +290,6 @@
        productSales.setCode(code);
        productSales.setName("发货通知单[" + notice.getCode() + "]出库");
        productSales.setClientId(notice.getClientId());
        productSales.setSaleOrderId(notice.getSaleOrderId());
        productSales.setSalesOrderCode(notice.getSaleOrderCode());
        productSales.setNoticeId(noticeId);
        productSales.setSalesDate(notice.getSalesDate());
        // æ”¶è´§ä¿¡æ¯ä»Žå‘货通知单带入
@@ -365,7 +303,6 @@
        for (MesWmSalesNoticeLineDO noticeLine : noticeLines) {
            MesWmProductSalesLineDO salesLine = new MesWmProductSalesLineDO();
            salesLine.setSalesId(productSales.getId());
            salesLine.setSaleOrderItemId(noticeLine.getSaleOrderItemId());
            salesLine.setNoticeLineId(noticeLine.getId());
            salesLine.setItemId(noticeLine.getItemId());
            salesLine.setQuantity(noticeLine.getQuantity());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -16,6 +16,7 @@
import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
@@ -23,6 +24,7 @@
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import cn.iocoder.yudao.module.mes.service.wm.materialstockbag.MesWmMaterialStockBagService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -57,6 +59,9 @@
    private MesWmWarehouseLocationService locationService;
    @Resource
    private MesWmWarehouseAreaService areaService;
    @Resource
    private MesWmMaterialStockBagService materialStockBagService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -127,11 +132,20 @@
        materialStockService.updateMaterialStockQuantity(materialStock.getId(), reqDTO.getQuantity(), checkFlag);
        MesWmMaterialStockDO updatedStock = materialStockService.getMaterialStock(materialStock.getId());
        BigDecimal newQuantity = updatedStock.getQuantity();
        // åº“存状态:显式指定优先(如扫码入暂存库),否则按数量自动判定(≤0 å·²å‡ºåº“,>0 åˆæ ¼åº“)
        // åº“存状态:显式指定优先(如扫码入暂存库),否则自动判定——≤0 å·²å‡ºåº“ï¼›
        // >0 æ—¶ä»…目标仓库为合格库(20)才置合格态,否则保持暂存态(10),
        // é¿å…ç›˜ç‚¹/调整/调拨等操作把暂存仓库库存翻成合格态
        Integer stockState = reqDTO.getStockState();
        if (stockState == null) {
            stockState = newQuantity.signum() <= 0
                    ? MesWmStockStateEnum.OUT.getState() : MesWmStockStateEnum.QUALIFIED.getState();
            if (newQuantity.signum() <= 0) {
                stockState = MesWmStockStateEnum.OUT.getState();
            } else {
                MesWmWarehouseDO warehouse = warehouseService.getWarehouse(reqDTO.getWarehouseId());
                boolean qualifiedWarehouse = warehouse != null
                        && MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(warehouse.getType());
                stockState = qualifiedWarehouse
                        ? MesWmStockStateEnum.QUALIFIED.getState() : MesWmStockStateEnum.TEMP.getState();
            }
        }
        materialStockService.updateMaterialStockState(updatedStock.getId(), stockState);
@@ -139,14 +153,46 @@
        MesWmTransactionDO transaction = MesWmTransactionDO.builder()
                .type(reqDTO.getType()).quantity(reqDTO.getQuantity()).transactionTime(LocalDateTime.now())
                .itemId(reqDTO.getItemId()).batchId(reqDTO.getBatchId()).batchCode(reqDTO.getBatchCode())
                .bagCodeId(reqDTO.getBagCodeId()).bagCode(reqDTO.getBagCode())
                .warehouseId(reqDTO.getWarehouseId()).locationId(reqDTO.getLocationId()).areaId(reqDTO.getAreaId())
                .bizType(reqDTO.getBizType()).bizId(reqDTO.getBizId()).bizCode(reqDTO.getBizCode()).bizLineId(reqDTO.getBizLineId())
                .materialStockId(materialStock.getId()).relatedTransactionId(reqDTO.getRelatedTransactionId())
                .build();
        transactionMapper.insert(transaction);
        // 4. å¸¦è¢‹ç æ—¶ï¼ŒåŒæ­¥ç»´æŠ¤åº“存袋码明细(入库建立/激活、出库置已出库、移入迁移、报废)
        boolean skipBagManage = ObjUtil.defaultIfNull(reqDTO.getSkipBagManage(), false);
        if (!skipBagManage && (StrUtil.isNotBlank(reqDTO.getBagCode()) || reqDTO.getBagCodeId() != null)) {
            handleStockBag(reqDTO, materialStock, transaction.getId(), transactionType);
        }
        return transaction.getId();
    }
    /**
     * æŒ‰äº‹åŠ¡ç±»åž‹ç»´æŠ¤è¢‹ç æ˜Žç»†ï¼Œä¿è¯æ±‡æ€»åº“å­˜ã€è¢‹ç æ˜Žç»†ã€äº‹åŠ¡æµæ°´ä¸‰è€…ä¸€è‡´ã€‚
     */
    private void handleStockBag(MesWmTransactionSaveReqDTO reqDTO, MesWmMaterialStockDO materialStock,
                                Long transactionId, MesWmTransactionTypeEnum transactionType) {
        switch (transactionType) {
            case MOVE_IN:
                // ç§»å…¥/调拨/质检迁移:更新明细位置与状态
                materialStockBagService.moveIn(reqDTO, materialStock, transactionId);
                break;
            case IN:
            case ADJUST_IN:
                // å…¥åº“/盘盈:建立或激活明细
                materialStockBagService.inbound(reqDTO, materialStock, transactionId);
                break;
            case MOVE_OUT:
            case OUT:
            case ADJUST_OUT:
                // ç§»å‡º/出库/盘亏:置为已出库
                materialStockBagService.outbound(reqDTO, transactionId);
                break;
            default:
                break;
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void createTransactionList(List<MesWmTransactionSaveReqDTO> reqDTOs) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
@@ -69,6 +69,10 @@
     * æ‰¹æ¬¡å·
     */
    private String batchCode;
    /** è¢‹ç ID */
    private Long bagCodeId;
    /** è¢‹ç  */
    private String bagCode;
    // ===== åº“存位置 =====
@@ -128,4 +132,12 @@
     */
    private Integer stockState;
    /**
     * æ˜¯å¦è·³è¿‡è¢‹ç æ˜Žç»†è‡ªåŠ¨ç»´æŠ¤ï¼ˆé»˜è®¤ false)。
     * <p>
     * è´¨æ£€ç­‰éœ€è¦è‡ªè¡ŒæŽ§åˆ¶è¢‹ç ç”Ÿå‘½å‘¨æœŸï¼ˆå¦‚暂存出库与合格入库成对发生、袋码仅在最终落点迁移)的场景,
     * åœ¨å†™äº‹åŠ¡æ—¶ç½® true,避免 createTransaction æŒ‰å•笔事务类型自动置已出库/迁移,导致袋码状态错乱。
     */
    private Boolean skipBagManage;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseService.java
@@ -90,4 +90,26 @@
     */
    MesWmWarehouseDO getWarehouseByCode(String code);
    /**
     * æŒ‰ä»“库类型获得仓库列表(按 id å‡åºï¼‰
     *
     * @param type ä»“库类型,见 {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum}
     * @return ä»“库列表
     */
    List<MesWmWarehouseDO> getWarehouseListByType(Integer type);
    /**
     * èŽ·å¾—åˆæ ¼åº“ï¼ˆtype=QUALIFIED)的编号集合,用于按仓库类型过滤合格库存
     *
     * @return åˆæ ¼ä»“库 id é›†åˆ
     */
    List<Long> getQualifiedWarehouseIds();
    /**
     * èŽ·å¾—ç¬¬ä¸€ä¸ªåˆæ ¼åº“ï¼ˆtype=QUALIFIED,按 id å‡åºï¼‰
     *
     * @return åˆæ ¼ä»“库,未配置返回 null
     */
    MesWmWarehouseDO getFirstQualifiedWarehouse();
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warehouse/MesWmWarehouseServiceImpl.java
@@ -9,6 +9,7 @@
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.warehouse.MesWmWarehouseMapper;
import cn.iocoder.yudao.module.mes.enums.wm.BarcodeBizTypeEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmWarehouseTypeEnum;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService;
import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService;
@@ -22,6 +23,7 @@
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -175,6 +177,7 @@
        if (MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE.equals(code)) {
            MesWmWarehouseDO newWarehouse = MesWmWarehouseDO.builder()
                    .code(code).name("虚拟线边仓库").frozen(false)
                    .type(MesWmWarehouseTypeEnum.TEMP.getType())
                    .remark("系统自动初始化的虚拟线边仓库(用于生产报工与在制品管理解耦)")
                    .build();
            warehouseMapper.insert(newWarehouse);
@@ -183,4 +186,21 @@
        return null;
    }
    @Override
    public List<MesWmWarehouseDO> getWarehouseListByType(Integer type) {
        return warehouseMapper.selectListByType(type);
    }
    @Override
    public List<Long> getQualifiedWarehouseIds() {
        return convertList(getWarehouseListByType(MesWmWarehouseTypeEnum.QUALIFIED.getType()),
                MesWmWarehouseDO::getId);
    }
    @Override
    public MesWmWarehouseDO getFirstQualifiedWarehouse() {
        List<MesWmWarehouseDO> list = getWarehouseListByType(MesWmWarehouseTypeEnum.QUALIFIED.getType());
        return CollUtil.isNotEmpty(list) ? list.get(0) : null;
    }
}
yudao-module-mes/src/main/resources/mapper/home/MesBagCodeHomeStatisticsMapper.xml
@@ -62,6 +62,45 @@
        WHERE deleted = 0
    </select>
    <!-- ========== å‡ºåº“流通 / ä»“储 / è´¨é‡ ========== -->
    <!-- ç»Ÿè®¡å‘货通知单数(mes_wm_sales_notice) -->
    <select id="selectSalesNoticeCount" resultType="java.lang.Long">
        SELECT COUNT(*)
        FROM mes_wm_sales_notice
        WHERE deleted = 0
    </select>
    <!-- ç»Ÿè®¡é”€å”®å‡ºåº“单数(mes_wm_product_sales) -->
    <select id="selectProductSalesCount" resultType="java.lang.Long">
        SELECT COUNT(*)
        FROM mes_wm_product_sales
        WHERE deleted = 0
    </select>
    <!-- ç»Ÿè®¡é”€å”®é€€è´§å•数(mes_wm_return_sales) -->
    <select id="selectReturnSalesCount" resultType="java.lang.Long">
        SELECT COUNT(*)
        FROM mes_wm_return_sales
        WHERE deleted = 0
    </select>
    <!-- ç»Ÿè®¡æœªå¤„理库存预警数(mes_wm_stock_warning.status=10) -->
    <select id="selectStockWarningPendingCount" resultType="java.lang.Long">
        SELECT COUNT(*)
        FROM mes_wm_stock_warning
        WHERE deleted = 0
          AND status = 10
    </select>
    <!-- ç»Ÿè®¡å¾…评审不合格品单数(mes_qc_ncr.status=0) -->
    <select id="selectNcrPendingCount" resultType="java.lang.Long">
        SELECT COUNT(*)
        FROM mes_qc_ncr
        WHERE deleted = 0
          AND status = 0
    </select>
    <!-- ========== çŠ¶æ€åˆ†å¸ƒ ========== -->
    <!-- æŒ‰çŠ¶æ€ç»Ÿè®¡æ‰¹æ¬¡æ•°é‡ -->