docs/warehouse_stock_management_integration.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,202 @@ # ä»å¨ä¼å - ä¸åºåç¶ææµè½¬ + é¢è¦/æ±æ»/çç¹ å端èè°æ¹æ¡ ## æ¶åé¡µé¢ - åºåå°è´¦ï¼`/mes/wm/material-stock/page`ï¼ - ç产线æ«ç å ¥åºï¼`/mes/pro/scan-in`ï¼ - ç§åè´¨æ£ï¼ç§åè´¨æ£æäº¤çé¢ï¼ - éå®åºåºï¼é宿«ç æ£è´§ï¼ - åºåé¢è¦ï¼æ°å¢é¡µé¢ï¼ - åºåå¤ç»´æ±æ»ï¼æ°å¢é¡µé¢/颿¿ï¼ - PDA æ«ç çç¹ï¼çç¹ä»»å¡æ«ç çé¢ï¼ ## ä¸å¡è§åï¼ä¸åºåç¶ææµè½¬ | æä½ | åºåç¶æåå | 说æ | |------|-------------|------| | ç产线æ«ç å ¥åº | â æååºï¼TEMP=10ï¼ | æ«ç è¢ç å ¥åºæååï¼åºåè¿å ¥æååºï¼å¾ è´¨æ£ï¼ï¼ä¸åç´æ¥è¿å ¥åæ ¼åº | | ç§åè´¨æ£æäº¤ãåæ ¼ã | æååº â åæ ¼åºï¼QUALIFIED=20ï¼ | è´¨æ£ç»æ=åæ ¼æ¶ï¼ææ¹æ¬¡å°æååºåºåæ´æ¹è½¬ä¸ºåæ ¼åºï¼æ£å¼åºåï¼ | | é宿«ç åºåº | åæ ¼åº â å·²åºåºï¼OUT=30ï¼ | æ£ååæ ¼åºåºåï¼æ°éâ¤0 æ¶ç¶æèªå¨å为已åºåº | | ä¸åæ ¼ï¼è´¨æ£ç»æ=ä¸åæ ¼ï¼ | ç卿ååº | æ¬æä¸åéè´§/æ¥åºæµï¼ç±åç»äººå·¥å¤ç | > **æ ¸å¿åå**ï¼ç产线æ«ç å ¥åºä»ãç´æ¥è¿åæ ¼åºãæ¹ä¸ºãå è¿æååºå¾ è´¨æ£ãï¼è´¨æ£åæ ¼åæè¿å ¥åæ ¼åºãå端éå¨åºåå°è´¦é¡µæç¶æç»´åº¦åºåå±ç¤ºä¸ä¸ªåºã ### ç产线æ«ç å ¥åºï¼å ³é®ä¿®å¤ï¼å端æ åååï¼ - æ¥å£ï¼`POST /mes/pro/scan-in/inbound`ï¼è¯·æ±åæ°**ä¿æä¸å**ã - å端å é¨ååï¼æéçäº§æ¹æ¬¡ä½ç³»ä¸ä»åºæ¹æ¬¡ä½ç³»ï¼å ¥åºæ¶èªå¨æ¡¥æ¥ä»åºæ¹æ¬¡å¹¶åå ¥ `batchId`ï¼åºåè¿å ¥**æååº**ã - å端影åï¼å ¥åºæååï¼åºåä¸ä½ç°å¨ãåæ ¼åºãçéä¸ï¼èæ¯ä½ç°å¨ãæååºãçéä¸ï¼éçå¾ ç§åè´¨æ£æäº¤åæ ¼åæä¼åºç°å¨åæ ¼åºã ## API ### ä¸ãåºåå°è´¦ï¼å·²ææ¥å£ï¼å»ºè®®å¢å¼ºæ¥è¯¢æ¡ä»¶å±ç¤ºï¼ | æ¹æ³ | è·¯å¾ | 说æ | |------|------|------| | GET | /mes/wm/material-stock/page | åºåå°è´¦åé¡µï¼æ°å¢æ `stockState` è¿æ»¤ | **请æ±åæ°ï¼æ°å¢/强è°ï¼ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | stockState | Integer | å¦ | åºåç¶æï¼10-æååºï¼20-åæ ¼åºï¼30-å·²åºåº | | itemId | Long | å¦ | åç§ï¼ç©æï¼ID | | batchCode | String | å¦ | æ¹æ¬¡å· | | warehouseId | Long | å¦ | ä»åº ID | **ååºå段ï¼å·²æï¼æ éæ°å¢ï¼ï¼** å« itemCodeãitemNameãbatchCodeãwarehouseNameãquantityãstockState çã ### äºãåºåå¤ç»´æ±æ»ï¼æ°å¢ï¼ | æ¹æ³ | è·¯å¾ | 说æ | |------|------|------| | GET | /mes/wm/material-stock/summary-by-item | æåç§æ±æ»æ°é | | GET | /mes/wm/material-stock/summary-by-batch | ææ¹æ¬¡æ±æ»æ°é | | GET | /mes/wm/material-stock/summary-by-warehouse | æä»åºæ±æ»æ°é | **请æ±åæ°ï¼åå¯éï¼å¤ç¨åºåæ¥è¯¢æ¡ä»¶ï¼ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | stockState | Integer | å¦ | åºåç¶æè¿æ»¤ï¼ä¸ä¼ åæç¶æåå¼å±ç¤º | | itemId | Long | å¦ | åç§è¿æ»¤ | | batchCode | String | å¦ | æ¹æ¬¡å·è¿æ»¤ | | warehouseId | Long | å¦ | ä»åºè¿æ»¤ | | areaId / locationId | Long | å¦ | åºä½/åºåºè¿æ»¤ | **ååºå段ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | itemId / itemCode / itemName | Long/String/String | åç§ç»´åº¦ï¼æåç§æ±æ»æ¶è¿åï¼ | | batchId / batchCode | Long/String | æ¹æ¬¡ç»´åº¦ï¼ææ¹æ¬¡æ±æ»æ¶è¿åï¼ | | warehouseId / warehouseName | Long/String | ä»åºç»´åº¦ï¼æä»åºæ±æ»æ¶è¿åï¼ | | stockState / stockStateName | Integer/String | åºåç¶æååç§° | | totalQuantity | BigDecimal | æ±æ»æ°é | **ååºç¤ºä¾ï¼** ```json { "code": 0, "data": [ { "itemId": 1, "itemCode": "ITEM001", "itemName": "æ©ç¨»ç§å", "stockState": 10, "stockStateName": "æååº", "totalQuantity": 500.00 }, { "itemId": 1, "itemCode": "ITEM001", "itemName": "æ©ç¨»ç§å", "stockState": 20, "stockStateName": "åæ ¼åº", "totalQuantity": 400.00 } ] } ``` ### ä¸ãåºåé¢è¦ï¼æ°å¢ï¼ | æ¹æ³ | è·¯å¾ | 说æ | |------|------|------| | GET | /mes/wm/stock-warning/page | é¢è¦å页ï¼è½åºè®°å½ï¼å«å·²å¤çåå²ï¼ | | GET | /mes/wm/stock-warning/current | 宿¶è®¡ç®å½åé¢è¦ï¼ä¸è½åºï¼ç§çº§ï¼ | | POST | /mes/wm/stock-warning/refresh | éæ°è®¡ç®å¹¶è½åºå½åé¢è¦ | | PUT | /mes/wm/stock-warning/handle | å¤çé¢è¦ï¼æ è®°å·²å¤çï¼ | **GET /page 请æ±åæ°ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | itemCode | String | å¦ | åç§ç¼ç æ¨¡ç³æ¥è¯¢ | | batchCode | String | å¦ | æ¹æ¬¡å·æ¨¡ç³æ¥è¯¢ | | warningType | Integer | å¦ | é¢è¦ç±»åï¼10-ä½åºåï¼20-临æï¼30-åºé¾ | | status | Integer | å¦ | å¤çç¶æï¼10-æªå¤çï¼20-å·²å¤ç | | pageNo / pageSize | Integer | æ¯ | å页 | **GET /page ååºå段ï¼** | åæ®µ | ç±»å | 说æ | |------|------|------| | id | Long | é¢è¦è®°å½ç¼å· | | itemCode / itemName | String | åç§ç¼ç /åç§° | | batchCode | String | æ¹æ¬¡å· | | warehouseName | String | ä»åºåç§° | | warningType / warningTypeName | Integer/String | é¢è¦ç±»åååç§° | | thresholdValue / currentValue | BigDecimal | éå¼ï¼ä½åºå=æä½åºåã临æ=å©ä½å¤©æ°éå¼ãåºé¾=è¶ é¾å¤©æ°ï¼/ å½åå¼ | | expireDate | String(datetime) | è¿ææ¥æï¼ä¸´æé¢è¦ææï¼ | | receiptTime | String(datetime) | å ¥åºæ¶é´ï¼åºé¾é¢è¦ææï¼ | | status / statusName | Integer/String | å¤çç¶æååç§° | | handleTime / handleRemark | String/String | å¤çæ¶é´/夿³¨ | **PUT /handle 请æ±ä½ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | id | Long | æ¯ | é¢è¦è®°å½ç¼å· | | handleRemark | String | å¦ | å¤ç夿³¨ | **ä¸å¡è§åï¼** - é¢è¦é对**åæ ¼åº**ï¼æ£å¼åºåï¼è®¡ç®ï¼ä½åºå=å¨åºæ°é ⤠åç§æä½åºåï¼ä¸´æ=æ¹æ¬¡å©ä½å¤©æ° ⤠åç§æææå¤©æ°ï¼åºé¾=å ¥åºå¤©æ° ⥠365 天ã - é¢è¦åééï¼`/current` 宿¶è®¡ç®ï¼éåçæ¿å·æ°ï¼ï¼`/page` ä¸ºå®æ¶è½åºè®°å½ï¼éåå表ï¼å«å¤çåå²ï¼ã - 宿¶ä»»å¡ `MesWmStockWarningJob`ï¼Quartzï¼JobHandlerï¼æ¯æ¥éç®ï¼å 餿§æªå¤çé¢è¦ãåå ¥å½åé¢è¦ï¼å·²å¤çè®°å½ä¿ç为åå²ã ### åãPDA æ«ç çç¹ï¼æ°å¢ï¼ | æ¹æ³ | è·¯å¾ | 说æ | |------|------|------| | POST | /mes/wm/stock-taking-task/scan | PDA æ«ç çç¹ï¼è§£æè¿½æº¯ç å®ä½æ¹æ¬¡ï¼åå ¥çç¹ç»æï¼ | | POST | /mes/wm/stock-taking-task/process-difference | å¤ççç¹å·®å¼ï¼ççå ¥åº/çäºåºåºï¼è°æ´åºåï¼ | | POST | /mes/wm/stock-taking-task/finish | 宿çç¹ä»»å¡ï¼å·²æï¼ | **POST /scan 请æ±ä½ï¼** | åæ° | ç±»å | å¿ å¡« | 说æ | |------|------|------|------| | taskId | Long | æ¯ | çç¹ä»»å¡ç¼å· | | scanCode | String | æ¯ | 追溯ç /è¢ç /æçç /æ¹æ¬¡å· | | takingQuantity | BigDecimal | æ¯ | çç¹æ°é | | remark | String | å¦ | 夿³¨ | **ååºï¼** è¿åçç¹ç»æç¼å·ï¼Longï¼ã **ä¸å¡è§åï¼** - ä» **çç¹ä¸**ç¶æçä»»å¡å 许æ«ç ï¼æäº¤çç¹ä»»å¡åï¼ã - æ«ç è§£æé¡ºåºï¼æ¹æ¬¡å· â è¢ç â æçç ï¼æç»å®ä½å°ä»åºæ¹æ¬¡ã - åä¸ä»»å¡è¡**åªå 许çç¹ä¸æ¬¡**ï¼é夿«ç æ¥éã该çç¹æ¸ åè¡å·²æçç¹è®°å½ï¼ä¸è½éå¤çç¹ãï¼å¦éä¿®æ¹å å é¤å¯¹åºçç¹ç»æåéæ°æ«ç ã - æ«ç æ¹æ¬¡ä¸å¨ä»»å¡èå´å æ¶æ¥ãæ«ç æ¹æ¬¡ä¸å¨çç¹ä»»å¡èå´å ãã - çç¹æµç¨ï¼åå»ºä»»å¡ â æäº¤ï¼è¿å ¥çç¹ä¸ï¼â PDA æ«ç éä¸ªæ¹æ¬¡çç¹ â 宿ï¼ä»»å¡â已宿ï¼è§£å»åºåï¼â å¤çå·®å¼ï¼åçç/çäºåºåæµæ°´ï¼ã ## æ°æ®å¸¦å ¥è¯´æ | ä¸ä¸æ¥ | ä¸ä¸æ¥ | å¸¦å ¥åæ®µ | |--------|--------|----------| | ç产线æ«ç å ¥åº | åºåå°è´¦ï¼æååºï¼ | è¢ç âçäº§æ¹æ¬¡âä»åºæ¹æ¬¡ï¼batchId/batchCode/itemId/warehouseId èªå¨æ¡¥æ¥ï¼ | | ç§åè´¨æ£æäº¤åæ ¼ | åºåå°è´¦ï¼åæ ¼åºï¼ | ææ¹æ¬¡å·ï¼batchCodeï¼å°æååºæ´æ¹è½¬åæ ¼åºï¼æ éåç«¯ä¼ åºå ID | | é宿«ç åºåº | åºåå°è´¦ï¼å·²åºåºï¼ | é宿«ç è§£ææ¹æ¬¡ â å®ä½åæ ¼åºåºå â æ£å | | PDA æ«ç çç¹ | çç¹ç»æ/ä»»å¡è¡ | 追溯ç â æ¹æ¬¡ â ä»»å¡è¡ï¼taskId+batchId å®ä½ï¼ | ## å端修æ¹ç¹ ### 1. åºåå°è´¦é¡µ - é¡¶é¨çéå¢å ãåºåç¶æãä¸æï¼æååº/åæ ¼åº/å·²åºåºï¼ï¼å¯¹åº `stockState` åæ°ã - å表å¢å ãåºåç¶æãåå±ç¤ºï¼ç¶æåç§°å¯ç¨æ¥å£è¿åç `stockStateName`ï¼å°è´¦ï¼æå端åå ¸ã ### 2. ç产线æ«ç å ¥åºé¡µ - æç¤ºææ¡ï¼å ¥åºæåè¿å ¥ãæååºï¼å¾ è´¨æ£ï¼ãï¼åãç´æ¥ä¸ºåæ ¼åºåãçå±ç¤ºé»è¾éè°æ´ã ### 3. ç§åè´¨æ£æäº¤ - æäº¤ãåæ ¼ãåï¼å¯å·æ°åºåå°è´¦ãåæ ¼åºãçå°è¯¥æ¹æ¬¡åºåï¼è¥æäº¤ãä¸åæ ¼ãï¼åºåä»åçå¨ãæååºãã ### 4. é宿«ç æ£è´§ - åºåºæååå¯å·æ°åºåå°è´¦ï¼ãåæ ¼åºãæ£åãæ°éâ¤0 çæ¹æ¬¡åºç°å¨ãå·²åºåºãã ### 5. åºåé¢è¦é¡µï¼æ°å¢ï¼ - å表å±ç¤º `/page` æ°æ®ï¼æä¾ã宿¶è®¡ç®ãæé®è° `/current`ï¼ãå¤çãæé®å¼¹çªå¡«å¤æ³¨è° `/handle`ï¼ãéæ°è®¡ç®ãæé®è° `/refresh`ã - é¢è¦ç±»å/å¤çç¶æåå±ç¤º `warningTypeName` / `statusName`ã ### 6. åºåæ±æ»é¢æ¿ï¼æ°å¢ï¼ - ä¸ä¸ªæ±æ»æ¥å£åå«åºåç§/æ¹æ¬¡/ä»åºç»´åº¦çæ°é颿¿ï¼æ¯ææ `stockState` çéï¼å±ç¤º `totalQuantity`ã ### 7. PDA çç¹æ«ç - çç¹ä»»å¡è¯¦æ è¿å ¥ãçç¹ä¸ãåï¼æä¾æ«ç è¾å ¥æ¡ï¼`taskId` åºå®ä¸ºå½åä»»å¡ï¼`scanCode` æ«ç ã`takingQuantity` æè¾ï¼ã - æ«ç æåæç¤ºè¿åççç¹ç»æç¼å·ï¼éå¤çç¹éå å ç»æã ## 注æäºé¡¹ - æææ¥å£å为管çåå°æ¥å£ï¼é带ç»å½ token 访é®ï¼æéç¹ï¼`mes:wm-stock-warning:query/update`ã`mes:wm-stock-taking-task:update`ã`mes:wm-material-stock:query`ã - é¢è¦ä» 对**åæ ¼åº**计ç®ï¼æååºä¸å·²åºåºä¸è®¡å ¥é¢è¦ã - ç产线æ«ç å ¥åºä¿®å¤åï¼å岿°æ®ä¸å·²åå¨ä½æªæ¡¥æ¥çæ¹æ¬¡ï¼ä¼å¨ä¸æ¬¡æ«ç /è´¨æ£/é宿¶æçäº§æ¹æ¬¡å·èªå¨æ¡¥æ¥å建ä»åºæ¹æ¬¡ï¼èæ°æ®æ éè¿ç§»ã - é¢è¦å®æ¶ä»»å¡éå¨ç®¡çåå°ãç³»ç»ç®¡ç-宿¶ä»»å¡ã注å JobHandlerï¼`MesWmStockWarningJob`ï¼æ¯æ¥æ§è¡ä¸æ¬¡ï¼ã - æ±æ»æ¥å£ä¸ºå åèåï¼æ°æ®é大æ¶å»ºè®®å端æéè¿æ»¤ï¼ä¼ stockState/warehouseId çï¼ã tmp_img_view.png
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/MesWmMaterialStockController.java
@@ -13,6 +13,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO; 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.MesWmStockSummaryRespVO; import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi; import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO; import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; @@ -123,6 +125,30 @@ return success(true); } @GetMapping("/summary-by-item") @Operation(summary = "æåç§æ±æ»åºåæ°é") @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:query')") public CommonResult<List<MesWmStockSummaryRespVO>> getStockSummaryByItem( MesWmMaterialStockListReqVO reqVO) { return success(materialStockService.getStockSummaryByItem(reqVO)); } @GetMapping("/summary-by-batch") @Operation(summary = "ææ¹æ¬¡æ±æ»åºåæ°é") @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:query')") public CommonResult<List<MesWmStockSummaryRespVO>> getStockSummaryByBatch( MesWmMaterialStockListReqVO reqVO) { return success(materialStockService.getStockSummaryByBatch(reqVO)); } @GetMapping("/summary-by-warehouse") @Operation(summary = "æä»åºæ±æ»åºåæ°é") @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:query')") public CommonResult<List<MesWmStockSummaryRespVO>> getStockSummaryByWarehouse( MesWmMaterialStockListReqVO reqVO) { return success(materialStockService.getStockSummaryByWarehouse(reqVO)); } @GetMapping("/export-excel") @Operation(summary = "导åºåºåå°è´¦ Excel") @PreAuthorize("@ss.hasPermission('mes:wm-material-stock:export')") yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/materialstock/vo/MesWmStockSummaryRespVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,42 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; @Schema(description = "管çåå° - åºåæ±æ» Response VO") @Data public class MesWmStockSummaryRespVO { @Schema(description = "åç§IDï¼æåç§æ±æ»æ¶è¿åï¼") private Long itemId; @Schema(description = "åç§ç¼ç ") private String itemCode; @Schema(description = "åç§åç§°") private String itemName; @Schema(description = "æ¹æ¬¡IDï¼ææ¹æ¬¡æ±æ»æ¶è¿åï¼") private Long batchId; @Schema(description = "æ¹æ¬¡å·") private String batchCode; @Schema(description = "ä»åºIDï¼æä»åºæ±æ»æ¶è¿åï¼") private Long warehouseId; @Schema(description = "ä»åºåç§°") private String warehouseName; @Schema(description = "åºåç¶æï¼10-æååºï¼20-åæ ¼åºï¼30-å·²åºåº") private Integer stockState; @Schema(description = "åºåç¶æåç§°") private String stockStateName; @Schema(description = "æ±æ»æ°é") private BigDecimal totalQuantity; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/MesWmStockTakingTaskController.java
@@ -130,6 +130,22 @@ return success(true); } @PostMapping("/scan") @Operation(summary = "PDA æ«ç çç¹") @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:update')") public CommonResult<Long> scanStockTakingTask(@Valid @RequestBody MesWmStockTakingTaskScanReqVO scanReqVO) { return success(stockTakingTaskService.scanStockTakingTask(scanReqVO)); } @PostMapping("/process-difference") @Operation(summary = "å¤ççç¹å·®å¼ï¼è°æ´åºå") @Parameter(name = "id", description = "ç¼å·", required = true) @PreAuthorize("@ss.hasPermission('mes:wm-stock-taking-task:update')") public CommonResult<Boolean> processStockTakingDifference(@RequestParam("id") Long id) { stockTakingTaskService.processStockTakingDifference(id); return success(true); } // ==================== æ¼æ¥ VO ==================== private List<MesWmStockTakingTaskRespVO> buildTaskRespVOList(List<MesWmStockTakingTaskDO> list) { yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/stocktaking/task/vo/MesWmStockTakingTaskScanReqVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,29 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; import java.math.BigDecimal; @Schema(description = "管çåå° - MES çç¹ä»»å¡ PDA æ«ç Request VO") @Data public class MesWmStockTakingTaskScanReqVO { @Schema(description = "çç¹ä»»å¡ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "çç¹ä»»å¡ç¼å·ä¸è½ä¸ºç©º") private Long taskId; @Schema(description = "追溯ç /è¢ç /æçç /æ¹æ¬¡å·", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty(message = "æ«ç å 容ä¸è½ä¸ºç©º") private String scanCode; @Schema(description = "çç¹æ°é", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") @NotNull(message = "çç¹æ°éä¸è½ä¸ºç©º") private BigDecimal takingQuantity; @Schema(description = "夿³¨") private String remark; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/MesWmStockWarningController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,60 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.warning; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningHandleReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningRespVO; import cn.iocoder.yudao.module.mes.service.wm.warning.MesWmStockWarningService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @Tag(name = "管çåå° - MES åºåé¢è¦") @RestController @RequestMapping("/mes/wm/stock-warning") @Validated public class MesWmStockWarningController { @Resource private MesWmStockWarningService stockWarningService; @GetMapping("/page") @Operation(summary = "è·å¾åºåé¢è¦å页ï¼è½åºè®°å½ï¼") @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:query')") public CommonResult<PageResult<MesWmStockWarningRespVO>> getWarningPage( @Valid MesWmStockWarningPageReqVO pageReqVO) { return success(stockWarningService.getWarningPage(pageReqVO)); } @GetMapping("/current") @Operation(summary = "宿¶è®¡ç®å½ååºåé¢è¦ï¼ä¸è½åºï¼") @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:query')") public CommonResult<List<MesWmStockWarningRespVO>> getCurrentWarnings() { return success(stockWarningService.getCurrentWarnings()); } @PostMapping("/refresh") @Operation(summary = "éæ°è®¡ç®å¹¶è½åºå½ååºåé¢è¦") @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:update')") public CommonResult<Integer> refreshWarnings() { return success(stockWarningService.persistCurrentWarnings()); } @PutMapping("/handle") @Operation(summary = "å¤çåºåé¢è¦ï¼æ è®°å·²å¤çï¼") @PreAuthorize("@ss.hasPermission('mes:wm-stock-warning:update')") public CommonResult<Boolean> handleWarning(@Valid @RequestBody MesWmStockWarningHandleReqVO reqVO) { stockWarningService.handle(reqVO); return success(true); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningHandleReqVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,18 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; @Schema(description = "管çåå° - åºåé¢è¦å¤ç Request VO") @Data public class MesWmStockWarningHandleReqVO { @Schema(description = "é¢è¦è®°å½ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED) @NotNull(message = "é¢è¦è®°å½ç¼å·ä¸è½ä¸ºç©º") private Long id; @Schema(description = "å¤ç夿³¨") private String handleRemark; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningPageReqVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,27 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo; import cn.iocoder.yudao.framework.common.pojo.PageParam; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; @Schema(description = "管çåå° - åºåé¢è¦å页 Request VO") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class MesWmStockWarningPageReqVO extends PageParam { @Schema(description = "åç§ç¼ç ") private String itemCode; @Schema(description = "æ¹æ¬¡å·") private String batchCode; @Schema(description = "é¢è¦ç±»åï¼10-ä½åºåï¼20-临æï¼30-åºé¾") private Integer warningType; @Schema(description = "å¤çç¶æï¼10-æªå¤çï¼20-å·²å¤ç") private Integer status; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/warning/vo/MesWmStockWarningRespVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,70 @@ package cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo; 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 MesWmStockWarningRespVO { @Schema(description = "ç¼å·") private Long id; @Schema(description = "åç§ID") private Long itemId; @Schema(description = "åç§ç¼ç ") private String itemCode; @Schema(description = "åç§åç§°") private String itemName; @Schema(description = "æ¹æ¬¡ID") private Long batchId; @Schema(description = "æ¹æ¬¡å·") private String batchCode; @Schema(description = "ä»åºID") private Long warehouseId; @Schema(description = "ä»åºåç§°") private String warehouseName; @Schema(description = "é¢è¦ç±»åï¼10-ä½åºåï¼20-临æï¼30-åºé¾") private Integer warningType; @Schema(description = "é¢è¦ç±»ååç§°") private String warningTypeName; @Schema(description = "éå¼ï¼ä½åºå=æä½åºåï¼ä¸´æ=å©ä½å¤©æ°ï¼åºé¾=è¶ é¾å¤©æ°ï¼") private BigDecimal thresholdValue; @Schema(description = "å½åå¼ï¼ä½åºå=å¨åºæ°éï¼ä¸´æ=å©ä½å¤©æ°ï¼åºé¾=åºé¾å¤©æ°ï¼") private BigDecimal currentValue; @Schema(description = "è¿ææ¥æï¼ä¸´æé¢è¦ææï¼") private LocalDateTime expireDate; @Schema(description = "å ¥åºæ¶é´ï¼åºé¾é¢è¦ææï¼") private LocalDateTime receiptTime; @Schema(description = "å¤çç¶æï¼10-æªå¤çï¼20-å·²å¤ç") private Integer status; @Schema(description = "å¤çç¶æåç§°") private String statusName; @Schema(description = "å¤ç人ID") private Long handleUserId; @Schema(description = "å¤çæ¶é´") private LocalDateTime handleTime; @Schema(description = "å¤ç夿³¨") private String handleRemark; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/MesProTracePublicController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,41 @@ package cn.iocoder.yudao.module.mes.controller.pub.trace; 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.tags.Tag; import jakarta.annotation.Resource; import jakarta.annotation.security.PermitAll; import jakarta.validation.constraints.NotEmpty; 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; /** * H5 æ«ç æº¯æº - å ¬å¼æ¥è¯¢ * <p> * å ç»å½ï¼@PermitAllï¼é¢åå ¬ä¼æä¾å ¨ç¨æº¯æºæ¥è¯¢ï¼ä½ä¸ºè¿½æº¯ä½ç³»çæ ¸å¿äº§åè½½ä½ã * åç»æ°å¢æ°æ®æºå¨ {@link MesProTracePublicRespVO} ä¸è¿½å ä¿¡æ¯åºåå³å¯ã */ @Tag(name = "H5 æ«ç æº¯æº - å ¬å¼æ¥è¯¢") @RestController @RequestMapping("/public/mes/trace") @Validated public class MesProTracePublicController { @Resource private MesProTracePublicService service; @GetMapping("/query") @PermitAll @Operation(summary = "æ ¹æ®è¿½æº¯ç æ¥è¯¢å ¨ç¨æº¯æºä¿¡æ¯") public CommonResult<MesProTracePublicRespVO> query(@RequestParam("code") @NotEmpty(message = "追溯ç ä¸è½ä¸ºç©º") String code) { return success(service.queryTrace(code)); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/pub/trace/vo/MesProTracePublicRespVO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,212 @@ package cn.iocoder.yudao.module.mes.controller.pub.trace.vo; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; /** * H5 æ«ç æº¯æº - å ¨ç¨æº¯æºä¿¡æ¯ Response VO * <p> * 以ãä¿¡æ¯åºåãæ¨¡ååç»ç»ï¼æ ¸å¿ä¿¡æ¯ï¼åç§/æ¹æ¬¡/人å/è´¨æ£/å ¥åºï¼å§ç»è¿åï¼ * éè´æ¥æºãéå®å»åãå å·¥æµè½¬ä¸ºå¯éæ©å±åºåï¼ææ°æ®æé空ï¼ãåç»æ°å¢æ°æ®æºæå䏿¨¡å¼è¿½å åºååæ®µã */ @Schema(description = "H5 æ«ç æº¯æº - å ¨ç¨æº¯æºä¿¡æ¯ Response VO") @Data public class MesProTracePublicRespVO { @Schema(description = "è¿½æº¯å ¥å£ç±»åï¼BAG-è¢ç / PALLET-æçç / BATCH-æ¹æ¬¡ç ", example = "BAG") private String sourceType; @Schema(description = "请æ±ç追溯ç ï¼åå§æ«ç å¼ï¼", example = "PC20260828twoITEM_001") private String requestCode; // ====== åç§ä¿¡æ¯åºå ====== @Schema(description = "åç§ç¼ç ") private String itemCode; @Schema(description = "åç§åç§°") private String itemName; @Schema(description = "åç§è§æ ¼") private String itemSpecification; // ====== çäº§æ¹æ¬¡ä¿¡æ¯åºå ====== @Schema(description = "æ¹æ¬¡å·") private String batchCode; @Schema(description = "ä¸´æ¶æ¹æ¬¡ç ") private String tempCode; @Schema(description = "æ¹æ¬¡ç¶æ", example = "20") private Integer batchStatus; @Schema(description = "æ¹æ¬¡ç¶æåç§°", example = "ç产ä¸") private String batchStatusName; @Schema(description = "çäº§æ¥æ") private LocalDateTime produceDate; @Schema(description = "产线ç¼ç ") private String lineCode; @Schema(description = "产线åç§°") private String lineName; @Schema(description = "è´£ä»»äººç¨æ·ç¼å·") private Long ownerUserId; @Schema(description = "责任人å§å") private String ownerUserName; @Schema(description = "è®¡åæ°é") private BigDecimal planQuantity; @Schema(description = "å·²çæè¢ç æ°é") private Integer bagQuantity; @Schema(description = "å·²çææçæ°é") private Integer palletQuantity; @Schema(description = "å ¥åºæ¶é´") private LocalDateTime inboundTime; @Schema(description = "夿³¨") private String remark; // ====== ç产人ååºå ====== @Schema(description = "ç产人åå表") private List<WorkerVO> workers; // ====== æçåºå ====== @Schema(description = "æçæ»æ°") private Integer palletCount; @Schema(description = "æçå表") private List<PalletVO> pallets; // ====== è¢ç åºå ====== @Schema(description = "è¢ç æ»æ°") private Long bagTotal; @Schema(description = "è¢ç åè¡¨ï¼æå¤ 100 æ¡ï¼") private List<BagVO> bags; // ====== å ¥åºäºä»¶åºå ====== @Schema(description = "ææ°å ¥åºäºä»¶") private InboundVO inbound; // ====== è´¨æ£ä¿¡æ¯åºå ====== @Schema(description = "ç§åè´¨æ£ä¿¡æ¯") private QualityVO quality; // ====== å¯éæ©å±åºåï¼ææ°æ®æè¿åï¼ç©ºè¡¨è¿å空åè¡¨ï¼ ====== @Schema(description = "éè´æ¥æºï¼å¯éï¼") private List<MesWmBatchPurchaseSourceRespVO> purchaseSources; @Schema(description = "éå®å»åï¼å¯éï¼") private List<MesWmBatchSalesTargetRespVO> salesTargets; @Schema(description = "å å·¥æµè½¬ï¼å¯éï¼") private List<MesWmBatchProcessTraceRespVO> processTraces; @Schema(description = "ç产人å") @Data public static class WorkerVO { @Schema(description = "å²ä½ç±»å", example = "20") private Integer workerType; @Schema(description = "å²ä½ç±»ååç§°", example = "æä½å·¥") private String workerTypeName; @Schema(description = "å§å") private String userName; @Schema(description = "夿³¨") private String remark; } @Schema(description = "æçä¿¡æ¯") @Data public static class PalletVO { @Schema(description = "æçç ") private String code; @Schema(description = "æçåºå·") private Integer palletNo; @Schema(description = "å·²è£ è¢æ°") private Integer bagCount; @Schema(description = "å¯è£ è¢æ°") private Integer capacity; } @Schema(description = "è¢ç ä¿¡æ¯") @Data public static class BagVO { @Schema(description = "è¢ç ") private String code; @Schema(description = "è¢åºå·") private Integer bagNo; @Schema(description = "ç»å®æçç ") private String palletCode; @Schema(description = "ç¶æ", example = "20") private Integer status; @Schema(description = "ç¶æåç§°", example = "已导åºå°å·") private String statusName; @Schema(description = "å ¥åºæ¶é´") private LocalDateTime inboundTime; } @Schema(description = "å ¥åºäºä»¶") @Data public static class InboundVO { @Schema(description = "äºä»¶å·") private String eventId; @Schema(description = "设å¤ç¼ç ") private String deviceCode; @Schema(description = "产线ç¼ç ") private String lineCode; @Schema(description = "å ¥åºæ°é") private BigDecimal quantity; @Schema(description = "ç¶æ", example = "1") private String status; @Schema(description = "äºä»¶æ¶é´") private LocalDateTime eventTime; } @Schema(description = "ç§åè´¨æ£ä¿¡æ¯") @Data public static class QualityVO { @Schema(description = "è´¨æ£ç»æ", example = "1") private Integer result; @Schema(description = "è´¨æ£ç»æåç§°", example = "åæ ¼") private String resultName; @Schema(description = "ä¸åæ ¼åå ") private String reason; @Schema(description = "æ£éªåå§å") private String inspectorName; @Schema(description = "æ£éªæ¶é´") private LocalDateTime inspectedAt; @Schema(description = "è´¨æ£ææ å表") private List<IndicatorVO> indicators; } @Schema(description = "è´¨æ£ææ ") @Data public static class IndicatorVO { @Schema(description = "ææ ç¼ç ", example = "purity") private String indicatorCode; @Schema(description = "ææ åç§°", example = "纯度") private String indicatorName; @Schema(description = "æ£æµå¼") private BigDecimal value; @Schema(description = "ä¸é") private BigDecimal minValue; @Schema(description = "ä¸é") private BigDecimal maxValue; @Schema(description = "æ¯å¦åæ ¼") private Boolean pass; } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/warning/MesWmStockWarningDO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,105 @@ package cn.iocoder.yudao.module.mes.dal.dataobject.wm.warning; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; 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 * <p> * é¢è¦ç±»åè§ {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmStockWarningTypeEnum}ï¼ * 10-ä½åºåï¼20-临æï¼30-åºé¾ãç±å®æ¶è®¡ç®æ¥è¯¢ + 宿¶ä»»å¡è½åºåéé产çã * * @author è¶ çº§ç®¡çå */ @TableName("mes_wm_stock_warning") @KeySequence("mes_wm_stock_warning_seq") @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) @Builder @NoArgsConstructor @AllArgsConstructor public class MesWmStockWarningDO extends BaseDO { /** * ç¼å· */ @TableId private Long id; /** * åç§ID * * å ³è {@link MesMdmItemDO#getId()} */ private Long itemId; /** * åç§ç¼ç */ private String itemCode; /** * æ¹æ¬¡ID * * å ³è {@link MesWmBatchDO#getId()} */ private Long batchId; /** * æ¹æ¬¡å· */ private String batchCode; /** * ä»åºID * * å ³è {@link MesWmWarehouseDO#getId()} */ private Long warehouseId; /** * ä»åºåç§° */ private String warehouseName; /** * é¢è¦ç±»åï¼10-ä½åºåï¼20-临æï¼30-åºé¾ */ private Integer warningType; /** * éå¼ï¼ä½åºå=æä½åºåï¼ä¸´æ=å©ä½å¤©æ°ï¼åºé¾=è¶ é¾å¤©æ°ï¼ */ private BigDecimal thresholdValue; /** * å½åå¼ï¼ä½åºå=å¨åºæ°éï¼ä¸´æ=å©ä½å¤©æ°ï¼åºé¾=åºé¾å¤©æ°ï¼ */ private BigDecimal currentValue; /** * è¿ææ¥æï¼ä¸´æé¢è¦ææï¼ */ private LocalDateTime expireDate; /** * å ¥åºæ¶é´ï¼åºé¾é¢è¦ææï¼ */ private LocalDateTime receiptTime; /** * å¤çç¶æï¼10-æªå¤çï¼20-å·²å¤ç */ private Integer status; /** * å¤ç人ID */ private Long handleUserId; /** * å¤çæ¶é´ */ private LocalDateTime handleTime; /** * å¤ç夿³¨ */ private String handleRemark; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java
@@ -7,6 +7,8 @@ import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface MesProScanEventMapper extends BaseMapperX<MesProScanEventDO> { default MesProScanEventDO selectByEventIdForUpdate(String eventId) { @@ -29,4 +31,10 @@ .eqIfPresent(MesProScanEventDO::getDeviceCode, deviceCode) .orderByDesc(MesProScanEventDO::getEventTime).last("LIMIT 1")); } default List<MesProScanEventDO> selectListByBatchId(Long batchId) { return selectList(new LambdaQueryWrapperX<MesProScanEventDO>() .eq(MesProScanEventDO::getBatchId, batchId) .orderByDesc(MesProScanEventDO::getEventTime)); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java
@@ -1,5 +1,14 @@ package cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO; import org.apache.ibatis.annotations.Mapper; @Mapper public interface MesQcSeedQualityIndicatorMapper extends BaseMapperX<MesQcSeedQualityIndicatorDO> {} import java.util.List; @Mapper public interface MesQcSeedQualityIndicatorMapper extends BaseMapperX<MesQcSeedQualityIndicatorDO> { default List<MesQcSeedQualityIndicatorDO> selectListByQualityId(Long qualityId) { return selectList(new LambdaQueryWrapperX<MesQcSeedQualityIndicatorDO>() .eq(MesQcSeedQualityIndicatorDO::getQualityId, qualityId) .orderByAsc(MesQcSeedQualityIndicatorDO::getId)); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java
@@ -5,9 +5,17 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO; import org.apache.ibatis.annotations.Mapper; import java.util.List; @Mapper public interface MesQcSeedQualityMapper extends BaseMapperX<MesQcSeedQualityDO> { default MesQcSeedQualityDO selectByCode(String code) { return selectOne(new LambdaQueryWrapperX<MesQcSeedQualityDO>().eq(MesQcSeedQualityDO::getCode, code)); } default List<MesQcSeedQualityDO> selectListByBatchCode(String batchCode) { return selectList(new LambdaQueryWrapperX<MesQcSeedQualityDO>() .eq(MesQcSeedQualityDO::getBatchCode, batchCode) .orderByDesc(MesQcSeedQualityDO::getInspectedAt)); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -206,4 +206,19 @@ .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); } /** * ææ¹æ¬¡å·æ´æ°åºåç¶æï¼ç¨äºç§åè´¨æ£åæ ¼åï¼å°æååºæ´æ¹è½¬ä¸ºåæ ¼åºï¼ * * @param batchId ä»åºæ¹æ¬¡ID * @param toState ç®æ åºåç¶æ * @param fromState ååºåç¶æï¼éå®åªæ´æ°è¯¥ç¶æçè®°å½ï¼ * @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)); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/stocktaking/task/MesWmStockTakingTaskLineMapper.java
@@ -40,4 +40,11 @@ .eqIfPresent(MesWmStockTakingTaskLineDO::getAreaId, areaId)); } default MesWmStockTakingTaskLineDO selectByTaskIdAndBatchId(Long taskId, Long batchId) { return selectOne(new LambdaQueryWrapperX<MesWmStockTakingTaskLineDO>() .eq(MesWmStockTakingTaskLineDO::getTaskId, taskId) .eq(MesWmStockTakingTaskLineDO::getBatchId, batchId) .last("LIMIT 1")); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/warning/MesWmStockWarningMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,25 @@ package cn.iocoder.yudao.module.mes.dal.mysql.wm.warning; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warning.MesWmStockWarningDO; import org.apache.ibatis.annotations.Mapper; /** * MES åºåé¢è¦ Mapper */ @Mapper public interface MesWmStockWarningMapper extends BaseMapperX<MesWmStockWarningDO> { default PageResult<MesWmStockWarningDO> selectPage(MesWmStockWarningPageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX<MesWmStockWarningDO>() .likeIfPresent(MesWmStockWarningDO::getItemCode, reqVO.getItemCode()) .likeIfPresent(MesWmStockWarningDO::getBatchCode, reqVO.getBatchCode()) .eqIfPresent(MesWmStockWarningDO::getWarningType, reqVO.getWarningType()) .eqIfPresent(MesWmStockWarningDO::getStatus, reqVO.getStatus()) .orderByDesc(MesWmStockWarningDO::getCreateTime)); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -682,6 +682,8 @@ ErrorCode WM_STOCK_TAKING_TASK_RESULT_NOT_EXISTS = new ErrorCode(1_040_714_119, "çç¹ç»æä¸åå¨"); ErrorCode WM_STOCK_TAKING_TASK_LINE_ALREADY_TAKEN = new ErrorCode(1_040_714_120, "该çç¹æ¸ åè¡å·²æçç¹è®°å½ï¼ä¸è½éå¤çç¹"); ErrorCode WM_STOCK_TAKING_TASK_NOT_FINISHED = new ErrorCode(1_040_714_121, "åªæå·²å®æç¶æççç¹ä»»å¡æè½å¤çå·®å¼"); ErrorCode WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_FOUND = new ErrorCode(1_040_714_122, "çç¹æ«ç æªå®ä½å°å¯¹åºæ¹æ¬¡ï¼è¯·æ ¸å¯¹è¿½æº¯ç "); ErrorCode WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_IN_TASK = new ErrorCode(1_040_714_123, "æ«ç æ¹æ¬¡ä¸å¨çç¹ä»»å¡èå´å "); // ========== MES ä»åºç®¡ç-éå®åºåºåï¼1-040-714-000ï¼ ========== ErrorCode WM_PRODUCT_SALES_NOT_EXISTS = new ErrorCode(1_040_714_000, "éå®åºåºåä¸åå¨"); @@ -787,6 +789,9 @@ ErrorCode WM_PACKAGE_HAS_CHILDREN = new ErrorCode(1_040_740_008, "åå¨åç®±ï¼ä¸å 许å é¤"); ErrorCode WM_PACKAGE_LINE_NOT_EXISTS = new ErrorCode(1_040_740_100, "è£ ç®±æç»ä¸åå¨"); // ========== MES ä»åºç®¡ç-åºåé¢è¦ï¼1-040-756-000ï¼ ========== ErrorCode WM_STOCK_WARNING_NOT_EXISTS = new ErrorCode(1_040_756_000, "åºåé¢è¦è®°å½ä¸åå¨"); // ========== MES 产å设计-设计项ç®ï¼1-040-800-000ï¼ ========== ErrorCode PD_PROJECT_NOT_EXISTS = new ErrorCode(1_040_800_000, "设计项ç®ä¸åå¨"); ErrorCode PD_PROJECT_NOT_INITIATE = new ErrorCode(1_040_800_001, "设计项ç®ä¸æ¯å¾ åå¸ç¶æï¼ä¸å è®¸ä¿®æ¹æå é¤"); @@ -869,4 +874,7 @@ ErrorCode WMS_APPROVAL_STATUS_INVALID = new ErrorCode(1_040_900_004, "åæ®ç¶æä¸æ£ç¡®ï¼æ æ³æäº¤å®¡æ¹"); ErrorCode WMS_APPROVAL_ALREADY_SUBMITTED = new ErrorCode(1_040_900_005, "忮已æäº¤å®¡æ¹ï¼è¯·å¿éå¤æäº¤"); // ========== MES å ¬å¼æ«ç 溯æºï¼1-040-760-000ï¼ ========== ErrorCode MES_TRACE_CODE_NOT_FOUND = new ErrorCode(1_040_760_000, "æªæ¥è¯¢å°è¯¥è¿½æº¯ç ï¼è¯·æ ¸å¯¹äºç»´ç æèç³»å®æ¹"); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/wm/MesWmStockWarningTypeEnum.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,19 @@ package cn.iocoder.yudao.module.mes.enums.wm; import lombok.AllArgsConstructor; import lombok.Getter; /** * MES åºåé¢è¦ç±»åæä¸¾ */ @Getter @AllArgsConstructor public enum MesWmStockWarningTypeEnum { LOW_STOCK(10, "ä½åºå"), EXPIRING(20, "临æ"), AGING(30, "åºé¾"); private final Integer type; private final String name; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesProperties.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,21 @@ package cn.iocoder.yudao.module.mes.framework.config; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * MES èªå®ä¹é 置项 */ @Data @Component @ConfigurationProperties(prefix = "mes") public class MesProperties { /** * H5 溯æºé¡µé¢å°åï¼trace.htmlï¼åºç¡ URLï¼ç¨äºçææºå¸¦è¿½æº¯ç åæ°çäºç»´ç å 容ã * 为空æ¶äºç»´ç ä»ç¼ç 纯ç å¼ï¼å ¼å®¹æ§ç ï¼ã */ private String traceBaseUrl = ""; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/config/MesQrCodeConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,22 @@ package cn.iocoder.yudao.module.mes.framework.config; import cn.iocoder.yudao.module.mes.framework.qrcode.MesProQrLabelBuilder; import jakarta.annotation.PostConstruct; import jakarta.annotation.Resource; import org.springframework.context.annotation.Configuration; /** * MES äºç»´ç ç¸å ³é ç½®ï¼å¯å¨æ¶å° traceBaseUrl æ³¨å ¥äºç»´ç æ ç¾æå»ºå¨ï¼é¿å ä¾µå ¥ Controller è°ç¨ç¹ */ @Configuration(proxyBeanMethods = false) public class MesQrCodeConfig { @Resource private MesProperties mesProperties; @PostConstruct public void init() { MesProQrLabelBuilder.setTraceBaseUrl(mesProperties.getTraceBaseUrl()); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/MesProQrLabelBuilder.java
@@ -8,16 +8,39 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import java.nio.charset.StandardCharsets; import java.net.URLEncoder; import java.util.LinkedHashMap; import java.util.Map; /** * MES äºç»´ç æ ç¾æå»ºå¨ï¼ç»ä¸"äºç»´ç ç¼ç 纯ç å¼ + å¾çå å å¯è¯»ææ¬"çææ¬ç»è£ è§åï¼ * ä¿è¯æ¹æ¬¡ãæçãè¢ç ä¸ç§æ ç¾çå ³é®ä¿¡æ¯ä¿çä¸è´ * MES äºç»´ç æ ç¾æå»ºå¨ï¼ç»ä¸"äºç»´ç ç¼ç å 容 + å¾çå å å¯è¯»ææ¬"çææ¬ç»è£ è§åï¼ * ä¿è¯æ¹æ¬¡ãæçãè¢ç ä¸ç§æ ç¾çå ³é®ä¿¡æ¯ä¿çä¸è´ã * <p> * é ç½®äº {@code mes.trace-base-url} æ¶ï¼äºç»´ç å 容为 H5 溯æºå°åï¼å¸¦ code åæ°ï¼ï¼å¦ååé为纯ç å¼ï¼å ¼å®¹æ§ç ï¼ */ public final class MesProQrLabelBuilder { private MesProQrLabelBuilder() { } /** * H5 溯æºé¡µé¢åºç¡å°åï¼ç± {@code MesQrCodeConfig} å¯å¨æ¶æ³¨å ¥ï¼ä¸ºç©ºæ¶äºç»´ç ç¼ç 纯ç å¼ */ private static volatile String traceBaseUrl = ""; public static void setTraceBaseUrl(String traceBaseUrl) { MesProQrLabelBuilder.traceBaseUrl = StrUtil.blankToDefault(traceBaseUrl, ""); } /** * ç»è£ äºç»´ç å 容ï¼é ç½®äºæº¯æºå°ååæ¼ URLï¼å¦åè¿å纯ç å¼ */ private static String buildQrCodeContent(String code) { if (StrUtil.isEmpty(traceBaseUrl)) { return code; } return traceBaseUrl + "?code=" + URLEncoder.encode(code, StandardCharsets.UTF_8); } /** @@ -51,7 +74,7 @@ if (user != null) { keyValues.put("责任人", user.getNickname()); } return QrLabelInfo.builder().fileName(code).code(code).keyValues(keyValues).build(); return QrLabelInfo.builder().fileName(code).code(buildQrCodeContent(code)).keyValues(keyValues).build(); } /** @@ -78,7 +101,7 @@ if (batch != null) { fillBatchKeyValues(keyValues, batch, itemMap, userMap); } return QrLabelInfo.builder().fileName(code).code(code).keyValues(keyValues).build(); return QrLabelInfo.builder().fileName(code).code(buildQrCodeContent(code)).keyValues(keyValues).build(); } /** @@ -98,7 +121,7 @@ if (batch != null) { fillBatchKeyValues(keyValues, batch, itemMap, userMap); } return QrLabelInfo.builder().fileName(bagCode.getCode()).code(bagCode.getCode()).keyValues(keyValues).build(); return QrLabelInfo.builder().fileName(bagCode.getCode()).code(buildQrCodeContent(bagCode.getCode())).keyValues(keyValues).build(); } /** yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/job/wm/MesWmStockWarningJob.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,28 @@ package cn.iocoder.yudao.module.mes.job.wm; import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; import cn.iocoder.yudao.module.mes.service.wm.warning.MesWmStockWarningService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** * MES åºåé¢è¦å®æ¶è½åº Job * <p> * éæ°è®¡ç®å½ååºåé¢è¦ï¼ä½åºå/临æ/åºé¾ï¼ï¼å 餿§çæªå¤çé¢è¦ååå ¥ï¼ä¿çå·²å¤çåå²ã */ @Component @Slf4j public class MesWmStockWarningJob implements JobHandler { @Resource private MesWmStockWarningService stockWarningService; @Override public String execute(String param) { int count = stockWarningService.persistCurrentWarnings(); log.info("[execute][åºåé¢è¦å®æ¶è½åº {} æ¡]", count); return String.format("åºåé¢è¦å®æ¶è½åº %s æ¡", count); } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO; import jakarta.validation.Valid; import java.util.List; import java.util.Map; public interface MesProScanEventService { @@ -14,4 +15,12 @@ PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO); MesProScanEventDO getLatest(String deviceCode); Map<String, Object> getDashboard(); /** * è·å¾æ¹æ¬¡ä¸çæ«ç å ¥åºäºä»¶åè¡¨ï¼æäºä»¶æ¶é´ååºï¼ï¼ç¨äºå ¬å¼æº¯æºå±ç¤ºææ°å ¥åºäºä»¶ * * @param batchId æ¹æ¬¡ç¼å· * @return äºä»¶å表 */ List<MesProScanEventDO> getByBatchId(Long batchId); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
@@ -6,13 +6,16 @@ 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.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.scan.MesProScanEventMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.MesProScanEventStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; 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.MesWmWarehouseService; @@ -23,6 +26,7 @@ import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; @Service @@ -33,6 +37,7 @@ @Resource private MesProBatchMapper batchMapper; @Resource private MesWmTransactionService transactionService; @Resource private MesWmWarehouseService warehouseService; @Resource private MesWmBatchService mesWmBatchService; @Override @Transactional(rollbackFor = Exception.class) @@ -60,12 +65,19 @@ if (warehouse == null) return failed(event, "MES_SCAN_WIP_WAREHOUSE_NOT_EXISTS", "WIPèæä»ä¸åå¨"); event.setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId()); try { // çäº§æ¹æ¬¡æ¡¥æ¥ä»åºæ¹æ¬¡ï¼åºå/æµæ°´ç»ä¸æå¨ mes_wm_batch ä¸ï¼æ«ç å ¥æååºå¾ è´¨æ£ MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(batch); if (wmBatch == null) { return failed(event, "MES_SCAN_WM_BATCH_NOT_EXISTS", "ä»åºæ¹æ¬¡æ¡¥æ¥å¤±è´¥"); } 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()).setBatchCode(batch.getCode()) .setItemId(batch.getItemId()).setQuantity(req.getQuantity()) .setBatchId(wmBatch.getId()).setBatchCode(batch.getCode()) .setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId()) .setReceiptTime(LocalDateTime.now()).setCheckFlag(true)); .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()); @@ -86,6 +98,7 @@ @Override public MesProScanEventDO getByEventId(String eventId) { return eventMapper.selectOne(MesProScanEventDO::getEventId, eventId); } @Override public PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO) { return eventMapper.selectPage(reqVO); } @Override public MesProScanEventDO getLatest(String deviceCode) { return eventMapper.selectLatest(deviceCode); } @Override public List<MesProScanEventDO> getByBatchId(Long batchId) { return eventMapper.selectListByBatchId(batchId); } @Override public Map<String, Object> getDashboard() { Map<String, Object> result = new HashMap<>(); result.put("latest", eventMapper.selectLatest(null)); yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java
@@ -1,3 +1,29 @@ package cn.iocoder.yudao.module.mes.service.qc.seedquality; import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO; public interface MesQcSeedQualityService { Long submit(MesQcSeedQualitySubmitReqVO reqVO); } import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO; import java.util.List; public interface MesQcSeedQualityService { Long submit(MesQcSeedQualitySubmitReqVO reqVO); /** * æ ¹æ®æ¹æ¬¡å·æ¥è¯¢è´¨æ£è®°å½ï¼ææ£éªæ¶é´ååºï¼åææ°ï¼ï¼ç¨äºå ¬å¼æº¯æºã * ç§åè´¨æ£è¡¨æªåå§åæ¶è¿å空å表ï¼å®¹éï¼ä¸å½±å追溯ï¼ã * * @param batchCode æ¹æ¬¡å· * @return è´¨æ£è®°å½å表 */ List<MesQcSeedQualityDO> getQualityByBatchCode(String batchCode); /** * æ ¹æ®è´¨æ£è®°å½ç¼å·æ¥è¯¢è´¨æ£ææ å表 * * @param qualityId è´¨æ£è®°å½ç¼å· * @return è´¨æ£ææ å表 */ List<MesQcSeedQualityIndicatorDO> getIndicatorsByQualityId(Long qualityId); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
@@ -2,20 +2,31 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO; 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.mysql.qc.seedquality.*; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; 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 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.time.LocalDateTime; import java.util.*; @Service @Validated @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 MesProBatchService proBatchService; @Resource private MesWmBatchService mesWmBatchService; @Resource private MesWmMaterialStockService materialStockService; @Override @Transactional(rollbackFor = Exception.class) public Long submit(MesQcSeedQualitySubmitReqVO reqVO) { @@ -36,7 +47,41 @@ if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("åé¡¹ææ ä¸å®æ´"); quality.setResult(passed ? 1 : 2); qualityMapper.updateById(quality); // è´¨æ£åæ ¼ â åºåæ´æ¹ç±æååºï¼å¾ è´¨æ£ï¼è½¬ä¸ºåæ ¼åºï¼æ£å¼åºåï¼ 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); } } } // åºä½ç±åå°é ç½®æå¡æ¥ç®¡ï¼å½åçæ¬ä¿ç审计éç¯ï¼ç¦æ¢åç«¯ä¼ å ¥åºä½ã return quality.getId(); } @Override public List<MesQcSeedQualityDO> getQualityByBatchCode(String batchCode) { try { return qualityMapper.selectListByBatchCode(batchCode); } catch (BadSqlGrammarException ex) { // ç§åè´¨æ£è¡¨æªåå§åæ¶å®¹éï¼ä¸é»å¡å ¬å¼æº¯æº log.warn("[getQualityByBatchCode][ç§åè´¨æ£è¡¨æªåå§åï¼æ¹æ¬¡ {} æ¥è¯¢å¤±è´¥: {}]", batchCode, ex.getMessage()); return List.of(); } } @Override public List<MesQcSeedQualityIndicatorDO> getIndicatorsByQualityId(Long qualityId) { try { return indicatorMapper.selectListByQualityId(qualityId); } catch (BadSqlGrammarException ex) { // ç§åè´¨æ£è¡¨æªåå§åæ¶å®¹é log.warn("[getIndicatorsByQualityId][ç§åè´¨æ£ææ 表æªåå§åï¼è´¨æ£ {} æ¥è¯¢å¤±è´¥: {}]", qualityId, ex.getMessage()); return List.of(); } } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,21 @@ package cn.iocoder.yudao.module.mes.service.trace; import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO; /** * å ¬å¼æ«ç æº¯æº Service æ¥å£ * <p> * é¢åå ¬ä¼ï¼å ç»å½ï¼ï¼æ ¹æ®è¢ç / æçç / æ¹æ¬¡ç æ¥è¯¢å ¨ç¨æº¯æºä¿¡æ¯ãä½ä¸ºè¿½æº¯ä½ç³»çæ ¸å¿èåå ¥å£ï¼ * åç»æ°å¢æ°æ®æºï¼è´¨æ£æ©å±ãé²ä¼ªéªè¯ãå¨è¿ä¿¡æ¯çï¼å¨èåå®ç°ä¸è¿½å ä¿¡æ¯åºåå³å¯ã */ public interface MesProTracePublicService { /** * æ ¹æ®è¿½æº¯ç æ¥è¯¢å ¨ç¨æº¯æºä¿¡æ¯ * * @param code 追溯ç ï¼è¢ç / æçç / æ¹æ¬¡ç ï¼å ¼å®¹æ´ H5 URLï¼ * @return å ¨ç¨æº¯æºä¿¡æ¯ */ MesProTracePublicRespVO queryTrace(String code); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/trace/MesProTracePublicServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,348 @@ package cn.iocoder.yudao.module.mes.service.trace; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO; import cn.iocoder.yudao.module.mes.controller.pub.trace.vo.MesProTracePublicRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; 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.batch.MesProBatchWorkerDO; 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.qc.seedquality.MesQcSeedQualityDO; import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO; import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchWorkerTypeEnum; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService; import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService; import cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletService; import cn.iocoder.yudao.module.mes.service.pro.scan.MesProScanEventService; import cn.iocoder.yudao.module.mes.service.qc.seedquality.MesQcSeedQualityService; import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; import java.util.Set; 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.MES_TRACE_CODE_NOT_FOUND; /** * å ¬å¼æ«ç æº¯æº Service å®ç° * <p> * 追溯ç è§£æé¡ºåºï¼è¢ç â æçç â æ¹æ¬¡ç ï¼å«ä¸´æ¶æ¹æ¬¡ç ï¼ã以çäº§æ¹æ¬¡ä¸ºä¸»æ°æ®æºèåå ¨ç¨ä¿¡æ¯ã */ @Service @Validated @Slf4j public class MesProTracePublicServiceImpl implements MesProTracePublicService { /** * H5 è¢ç å表å±ç¤ºä¸éï¼å ¨éç»è®¡ + ééæç»ï¼ */ private static final int BAG_LIST_LIMIT = 100; @Resource private MesProBagCodeService bagCodeService; @Resource private MesProPalletService palletService; @Resource private MesProBatchService batchService; @Resource private MesProScanEventService scanEventService; @Resource private MesQcSeedQualityService qcSeedQualityService; @Resource private MesMdmItemService mdmItemService; @Resource private AdminUserApi adminUserApi; @Resource private MesWmBatchService mesWmBatchService; @Override public MesProTracePublicRespVO queryTrace(String code) { String rawCode = StrUtil.trim(StrUtil.nullToEmpty(code)); String traceCode = normalize(rawCode); if (StrUtil.isEmpty(traceCode)) { throw exception(MES_TRACE_CODE_NOT_FOUND); } // 1. è§£æè¿½æº¯å ¥å£ï¼å®ä½æ¹æ¬¡ TraceEntry entry = resolveEntry(traceCode); if (entry.batchId == null) { throw exception(MES_TRACE_CODE_NOT_FOUND); } // 2. èåå ¨ç¨ä¿¡æ¯ return buildRespVO(rawCode, traceCode, entry.sourceType, entry.batchId); } // ==================== 追溯ç è§£æ ==================== /** * å ¼å®¹æ´ URL æ«ç ï¼äºç»´ç å 容为 H5 å°åæ¶ï¼ï¼ä» æå code åæ°ï¼å¦ååæ ·è¿å */ private String normalize(String code) { if (StrUtil.startWithIgnoreCase(code, "http")) { String query = StrUtil.subAfter(code, "?", false); if (StrUtil.isNotEmpty(query)) { // 廿 # éç¹ query = StrUtil.subBefore(query, "#", false); for (String pair : StrUtil.split(query, "&")) { String[] kv = pair.split("=", 2); if (kv.length == 2 && "code".equals(kv[0])) { return StrUtil.trim(URLDecoder.decode(kv[1], StandardCharsets.UTF_8)); } } } return ""; } return code; } private TraceEntry resolveEntry(String traceCode) { // â è¢ç MesProBagCodeDO bag = bagCodeService.getBagCodeByCode(traceCode); if (bag != null) { return new TraceEntry("BAG", bag.getBatchId()); } // â¡ æçç MesProPalletDO pallet = palletService.getPalletByCode(traceCode); if (pallet != null) { return new TraceEntry("PALLET", pallet.getBatchId()); } // â¢ æ¹æ¬¡ç ï¼å«ä¸´æ¶æ¹æ¬¡ç ï¼getBatchByCode å é¨å ¼å®¹ï¼ MesProBatchDO batch = batchService.getBatchByCode(traceCode); if (batch != null) { return new TraceEntry("BATCH", batch.getId()); } return new TraceEntry("", null); } // ==================== èåæå»º ==================== private MesProTracePublicRespVO buildRespVO(String requestCode, String traceCode, String sourceType, Long batchId) { MesProBatchDO batch = batchService.getBatch(batchId); if (batch == null) { throw exception(MES_TRACE_CODE_NOT_FOUND); } MesProTracePublicRespVO resp = new MesProTracePublicRespVO(); resp.setSourceType(sourceType); resp.setRequestCode(requestCode); // åç§åºå fillItem(resp, batch); // æ¹æ¬¡åºå resp.setBatchCode(batch.getCode()).setTempCode(batch.getTempCode()) .setBatchStatus(batch.getStatus()).setBatchStatusName(batchStatusName(batch.getStatus())) .setProduceDate(batch.getProduceDate()).setLineCode(batch.getLineCode()).setLineName(batch.getLineName()) .setOwnerUserId(batch.getOwnerUserId()).setOwnerUserName(userName(batch.getOwnerUserId())) .setPlanQuantity(batch.getPlanQuantity()).setBagQuantity(batch.getBagQuantity()) .setPalletQuantity(batch.getPalletQuantity()).setInboundTime(batch.getInboundTime()) .setRemark(batch.getRemark()); // 人ååºå resp.setWorkers(buildWorkers(batchId)); // æçåºå fillPallets(resp, batchId); // è¢ç åºå fillBags(resp, batchId); // å ¥åºäºä»¶åºå resp.setInbound(buildInbound(batchId)); // è´¨æ£åºå resp.setQuality(buildQuality(batch)); // å¯éæ©å±åºåï¼ææ°æ®æè¿åï¼ fillOptionalModules(resp, batch); return resp; } private void fillItem(MesProTracePublicRespVO resp, MesProBatchDO batch) { if (batch.getItemId() == null) { return; } MesMdmItemDO item = mdmItemService.getItem(batch.getItemId()); if (item == null) { return; } resp.setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification()); } private List<MesProTracePublicRespVO.WorkerVO> buildWorkers(Long batchId) { List<MesProBatchWorkerDO> workers = batchService.getBatchWorkerListByBatchId(batchId); if (CollUtil.isEmpty(workers)) { return List.of(); } Set<Long> userIds = convertSet(workers, MesProBatchWorkerDO::getUserId); Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); return convertList(workers, worker -> { MesProTracePublicRespVO.WorkerVO vo = new MesProTracePublicRespVO.WorkerVO(); vo.setWorkerType(worker.getWorkerType()); vo.setWorkerTypeName(workerTypeName(worker.getWorkerType())); vo.setRemark(worker.getRemark()); AdminUserRespDTO user = userMap.get(worker.getUserId()); vo.setUserName(user == null ? null : user.getNickname()); return vo; }); } private void fillPallets(MesProTracePublicRespVO resp, Long batchId) { List<MesProPalletDO> pallets = palletService.getPalletListByBatchId(batchId); resp.setPalletCount(CollUtil.isEmpty(pallets) ? 0 : pallets.size()); resp.setPallets(convertList(pallets, pallet -> { MesProTracePublicRespVO.PalletVO vo = new MesProTracePublicRespVO.PalletVO(); vo.setCode(pallet.getCode()).setPalletNo(pallet.getPalletNo()) .setBagCount(pallet.getBagCount()).setCapacity(pallet.getCapacity()); return vo; })); } private void fillBags(MesProTracePublicRespVO resp, Long batchId) { List<MesProBagCodeDO> bags = bagCodeService.getBagCodeListByBatchId(batchId); List<MesProBagCodeDO> safeBags = bags == null ? List.of() : bags; resp.setBagTotal((long) safeBags.size()); resp.setBags(convertList(safeBags.stream().limit(BAG_LIST_LIMIT).toList(), bag -> { MesProTracePublicRespVO.BagVO vo = new MesProTracePublicRespVO.BagVO(); vo.setCode(bag.getCode()).setBagNo(bag.getBagNo()).setPalletCode(bag.getPalletCode()) .setStatus(bag.getStatus()).setStatusName(bagCodeStatusName(bag.getStatus())) .setInboundTime(bag.getInboundTime()); return vo; })); } private MesProTracePublicRespVO.InboundVO buildInbound(Long batchId) { List<MesProScanEventDO> events = scanEventService.getByBatchId(batchId); if (CollUtil.isEmpty(events)) { return null; } MesProScanEventDO event = events.get(0); MesProTracePublicRespVO.InboundVO vo = new MesProTracePublicRespVO.InboundVO(); vo.setEventId(event.getEventId()).setDeviceCode(event.getDeviceCode()).setLineCode(event.getLineCode()) .setQuantity(event.getQuantity()).setStatus(event.getStatus()).setEventTime(event.getEventTime()); return vo; } private MesProTracePublicRespVO.QualityVO buildQuality(MesProBatchDO batch) { List<MesQcSeedQualityDO> list = qcSeedQualityService.getQualityByBatchCode(batch.getCode()); if (CollUtil.isEmpty(list)) { return null; } MesQcSeedQualityDO quality = list.get(0); MesProTracePublicRespVO.QualityVO vo = new MesProTracePublicRespVO.QualityVO(); vo.setResult(quality.getResult()).setResultName(qualityResultName(quality.getResult())) .setReason(quality.getReason()).setInspectorName(userName(quality.getInspectorId())) .setInspectedAt(quality.getInspectedAt()); List<MesQcSeedQualityIndicatorDO> indicators = qcSeedQualityService.getIndicatorsByQualityId(quality.getId()); vo.setIndicators(convertList(indicators, ind -> { MesProTracePublicRespVO.IndicatorVO i = new MesProTracePublicRespVO.IndicatorVO(); i.setIndicatorCode(ind.getIndicatorCode()).setIndicatorName(indicatorName(ind.getIndicatorCode())) .setValue(ind.getValue()).setMinValue(ind.getMinValue()).setMaxValue(ind.getMaxValue()); i.setPass(ind.getValue() != null && ind.getMinValue() != null && ind.getMaxValue() != null && ind.getValue().compareTo(ind.getMinValue()) >= 0 && ind.getValue().compareTo(ind.getMaxValue()) <= 0); return i; })); return vo; } private void fillOptionalModules(MesProTracePublicRespVO resp, MesProBatchDO batch) { try { List<MesWmBatchPurchaseSourceRespVO> purchaseSources = mesWmBatchService.getPurchaseSourceList(batch.getCode()); List<MesWmBatchSalesTargetRespVO> salesTargets = mesWmBatchService.getSalesTargetList(batch.getCode()); List<MesWmBatchProcessTraceRespVO> processTraces = mesWmBatchService.getProcessTraceList(batch.getCode()); resp.setPurchaseSources(CollUtil.isNotEmpty(purchaseSources) ? purchaseSources : List.of()); resp.setSalesTargets(CollUtil.isNotEmpty(salesTargets) ? salesTargets : List.of()); resp.setProcessTraces(CollUtil.isNotEmpty(processTraces) ? processTraces : List.of()); } catch (Exception ex) { // ä»åºæ¹æ¬¡ä½ç³»ï¼éè´/éå®/æµè½¬ï¼æªå¯ç¨æ¶å®¹éï¼ä¸å½±åæ ¸å¿è¿½æº¯ log.warn("[fillOptionalModules][æ¹æ¬¡ {} æ©å±æ¨¡åæ¥è¯¢å¤±è´¥: {}]", batch.getCode(), ex.getMessage()); resp.setPurchaseSources(List.of()); resp.setSalesTargets(List.of()); resp.setProcessTraces(List.of()); } } // ==================== åç§°æ å° ==================== private String userName(Long userId) { if (userId == null) { return null; } AdminUserRespDTO user = adminUserApi.getUser(userId); return user == null ? null : user.getNickname(); } private String batchStatusName(Integer status) { if (status == null) { return ""; } for (MesProBatchStatusEnum e : MesProBatchStatusEnum.values()) { if (e.getStatus().equals(status)) { return e.getName(); } } return String.valueOf(status); } private String bagCodeStatusName(Integer status) { if (status == null) { return ""; } for (MesProBagCodeStatusEnum e : MesProBagCodeStatusEnum.values()) { if (e.getStatus().equals(status)) { return e.getName(); } } return String.valueOf(status); } private String workerTypeName(Integer workerType) { if (workerType == null) { return ""; } for (MesProBatchWorkerTypeEnum e : MesProBatchWorkerTypeEnum.values()) { if (e.getType().equals(workerType)) { return e.getName(); } } return String.valueOf(workerType); } private String qualityResultName(Integer result) { if (result == null) { return ""; } return switch (result) { case 0 -> "å¤çä¸"; case 1 -> "åæ ¼"; case 2 -> "ä¸åæ ¼"; default -> String.valueOf(result); }; } private String indicatorName(String code) { if (code == null) { return ""; } return switch (code) { case "purity" -> "纯度"; case "cleanliness" -> "å度"; case "germination_rate" -> "åè½ç"; case "moisture" -> "æ°´å"; default -> code; }; } private record TraceEntry(String sourceType, Long batchId) { } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; import java.util.List; @@ -131,4 +132,15 @@ */ MesWmBatchDO getBatchBySn(String sn); /** * æ ¹æ®çäº§æ¹æ¬¡è·åæå建ä»åºæ¹æ¬¡ï¼æéçäº§æ¹æ¬¡ä½ç³»ä¸ä»åºæ¹æ¬¡ä½ç³»ï¼ * <p> * ç产线æ«ç å ¥åºãéå®åºåºãè´¨æ£è½¬åæ ¼çæµç¨ï¼åºå䏿µæ°´ç»ä¸æå¨ä»åºæ¹æ¬¡ï¼mes_wm_batchï¼ä¸ï¼ * éè¿çäº§æ¹æ¬¡å·ï¼mes_pro_batch.codeï¼æ¡¥æ¥ï¼å·²åå¨åç´æ¥å¤ç¨ï¼å¦åæ°å»ºã * * @param proBatch çäº§æ¹æ¬¡ * @return ä»åºæ¹æ¬¡ï¼å« idï¼ */ MesWmBatchDO getOrCreateByProBatch(MesProBatchDO proBatch); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
@@ -10,12 +10,14 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.sn.MesWmSnMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; import cn.iocoder.yudao.module.mes.service.wm.barcode.MesWmBarcodeService; @@ -415,4 +417,28 @@ return batchMapper.selectByCode(snDO.getBatchCode()); } @Override @Transactional(rollbackFor = Exception.class) public MesWmBatchDO getOrCreateByProBatch(MesProBatchDO proBatch) { if (proBatch == null || StrUtil.isBlank(proBatch.getCode())) { return null; } // 1. å¤ç¨å·²æä»åºæ¹æ¬¡ï¼åä¸çäº§æ¹æ¬¡å·ï¼ MesWmBatchDO existing = batchMapper.selectByCode(proBatch.getCode()); if (existing != null) { return existing; } // 2. æ°å»ºä»åºæ¹æ¬¡ï¼ä»¥çäº§æ¹æ¬¡å·ä½ä¸ºä»åºæ¹æ¬¡å·ï¼è´¨éç¶æç½®ä¸ºå¾ æ£éª MesWmBatchDO batch = MesWmBatchDO.builder() .code(proBatch.getCode()) .itemId(proBatch.getItemId()) .produceDate(proBatch.getProduceDate()) .receiptDate(java.time.LocalDateTime.now()) .qualityStatus(MesWmQualityStatusEnum.PENDING.getStatus()) .remark("çäº§æ¹æ¬¡èªå¨æ¡¥æ¥") .build(); batchMapper.insert(batch); return batch; } } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/materialstock/MesWmMaterialStockService.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.MesWmMaterialStockMoveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmStockSummaryRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; import jakarta.validation.Valid; @@ -142,6 +143,40 @@ void updateMaterialStockState(Long id, Integer stockState); /** * æåç§ç»´åº¦æ±æ»åºåæ°éï¼å åèåï¼æ¯æ stockState çè¿æ»¤æ¡ä»¶ï¼ * * @param reqVO è¿æ»¤æ¡ä»¶ * @return åç§æ±æ»å表 */ List<MesWmStockSummaryRespVO> getStockSummaryByItem(MesWmMaterialStockListReqVO reqVO); /** * ææ¹æ¬¡ç»´åº¦æ±æ»åºåæ°éï¼å åèåï¼æ¯æ stockState çè¿æ»¤æ¡ä»¶ï¼ * * @param reqVO è¿æ»¤æ¡ä»¶ * @return æ¹æ¬¡æ±æ»å表 */ List<MesWmStockSummaryRespVO> getStockSummaryByBatch(MesWmMaterialStockListReqVO reqVO); /** * æä»åºç»´åº¦æ±æ»åºåæ°éï¼å åèåï¼æ¯æ stockState çè¿æ»¤æ¡ä»¶ï¼ * * @param reqVO è¿æ»¤æ¡ä»¶ * @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
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockMoveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmStockSummaryRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO; @@ -39,6 +40,8 @@ import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.*; import java.util.function.BiConsumer; 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.convertSet; @@ -170,6 +173,86 @@ } @Override public List<MesWmStockSummaryRespVO> getStockSummaryByItem(MesWmMaterialStockListReqVO reqVO) { List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO); List<MesWmStockSummaryRespVO> list = buildStockSummary(stocks, stock -> String.valueOf(stock.getItemId()), (vo, stock) -> vo.setItemId(stock.getItemId())); Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap( convertSet(stocks, MesWmMaterialStockDO::getItemId)); list.forEach(vo -> { MesMdmItemDO item = itemMap.get(vo.getItemId()); if (item != null) { vo.setItemCode(item.getCode()).setItemName(item.getName()); } }); return list; } @Override public List<MesWmStockSummaryRespVO> getStockSummaryByBatch(MesWmMaterialStockListReqVO reqVO) { List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO); return buildStockSummary(stocks, stock -> stock.getBatchId() + "|" + (stock.getBatchCode() != null ? stock.getBatchCode() : ""), (vo, stock) -> vo.setBatchId(stock.getBatchId()).setBatchCode(stock.getBatchCode())); } @Override public List<MesWmStockSummaryRespVO> getStockSummaryByWarehouse(MesWmMaterialStockListReqVO reqVO) { List<MesWmMaterialStockDO> stocks = getMaterialStockList(reqVO); List<MesWmStockSummaryRespVO> list = buildStockSummary(stocks, stock -> String.valueOf(stock.getWarehouseId()), (vo, stock) -> vo.setWarehouseId(stock.getWarehouseId())); Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap( convertSet(stocks, MesWmMaterialStockDO::getWarehouseId)); list.forEach(vo -> { MesWmWarehouseDO warehouse = warehouseMap.get(vo.getWarehouseId()); if (warehouse != null) { vo.setWarehouseName(warehouse.getName()); } }); return list; } /** * å åèååºåæ°éï¼æç»´åº¦é® + åºåç¶æåç»ï¼ç´¯å å¨åºæ°é */ private List<MesWmStockSummaryRespVO> buildStockSummary(List<MesWmMaterialStockDO> stocks, Function<MesWmMaterialStockDO, String> dimKeyFn, BiConsumer<MesWmStockSummaryRespVO, MesWmMaterialStockDO> dimSetter) { if (CollUtil.isEmpty(stocks)) { return Collections.emptyList(); } Map<String, MesWmStockSummaryRespVO> map = new LinkedHashMap<>(); for (MesWmMaterialStockDO stock : stocks) { String key = dimKeyFn.apply(stock) + "|" + stock.getStockState(); MesWmStockSummaryRespVO vo = map.computeIfAbsent(key, k -> { MesWmStockSummaryRespVO newVo = new MesWmStockSummaryRespVO(); dimSetter.accept(newVo, stock); newVo.setStockState(stock.getStockState()); newVo.setStockStateName(stockStateName(stock.getStockState())); newVo.setTotalQuantity(BigDecimal.ZERO); return newVo; }); BigDecimal qty = stock.getQuantity() != null ? stock.getQuantity() : BigDecimal.ZERO; vo.setTotalQuantity(vo.getTotalQuantity().add(qty)); } return new ArrayList<>(map.values()); } private String stockStateName(Integer state) { if (state == null) { return ""; } for (MesWmStockStateEnum e : MesWmStockStateEnum.values()) { if (e.getState().equals(state)) { return e.getName(); } } return String.valueOf(state); } @Override public MesWmMaterialStockDO getOrCreateMaterialStock(Long itemId, Long warehouseId, Long locationId, Long areaId, Long batchId, String batchCode, Long vendorId, LocalDateTime receiptTime) { // 1. æ¥æ¾å·²æåºåè®°å½ï¼vendorId åä¸å¯ä¸é®ï¼ @@ -222,6 +305,11 @@ } @Override public int updateStockStateByBatchId(Long batchId, Integer toState, Integer fromState) { return materialStockMapper.updateStockStateByBatchId(batchId, toState, fromState); } @Override public void checkAreaMixingRule(Long areaId, Long itemId, Long batchId) { // 1.1 æ ¡éªåºä½å¿ é¡»åå¨ MesWmWarehouseAreaDO area = areaService.validateWarehouseAreaExists(areaId); yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesServiceImpl.java
@@ -14,6 +14,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO; import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO; 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.mysql.wm.productsales.MesWmProductSalesDetailMapper; import cn.iocoder.yudao.module.mes.service.pro.bagcode.MesProBagCodeService; @@ -34,6 +35,7 @@ import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; import cn.iocoder.yudao.module.mes.service.wm.salesnotice.MesWmSalesNoticeService; 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.stockreserve.MesWmStockReserveService; import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; @@ -75,7 +77,9 @@ @Resource private MesProPalletService palletService; @Resource private MesProBatchService wmBatchService; private MesProBatchService proBatchService; @Resource private MesWmBatchService mesWmBatchService; @Resource private MesWmProductSalesLineService productSalesLineService; @@ -241,9 +245,14 @@ .setCodeId(codeId).setScanContent(reqVO.getScanContent()).setAlreadyScanned(true) .setDetailId(existed.getId()).setOutboundQuantity(existed.getQuantity()).setMessage("该ç å·²æ«æ"); } Long batchId = pallet != null ? pallet.getBatchId() : bag.getBatchId(); MesProBatchDO batch = wmBatchService.getBatch(batchId); Long proBatchId = pallet != null ? pallet.getBatchId() : bag.getBatchId(); MesProBatchDO batch = proBatchService.getBatch(proBatchId); if (batch == null) { throw exception(WM_TRANSACTION_BATCH_NOT_EXISTS); } // çäº§æ¹æ¬¡æ¡¥æ¥ä»åºæ¹æ¬¡ï¼æ«ç å ¥åºåå ¥çæ¯ä»åºæ¹æ¬¡ï¼mes_wm_batchï¼ï¼æ¤å¤å¿ é¡»ç¨ä»åºæ¹æ¬¡ id æ¥åºå MesWmBatchDO wmBatch = mesWmBatchService.getOrCreateByProBatch(batch); if (wmBatch == null) { throw exception(WM_TRANSACTION_BATCH_NOT_EXISTS); } List<MesWmProductSalesLineDO> lines = productSalesLineService.getProductSalesLineListBySalesId(reqVO.getSalesId()); @@ -255,7 +264,7 @@ if (quantity == null) { quantity = line.getQuantity(); } List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockListByItemAndBatch(batch.getItemId(), batchId); List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockListByItemAndBatch(batch.getItemId(), wmBatch.getId()); BigDecimal outboundQuantity = quantity; MesWmMaterialStockDO stock = stocks.stream().filter(item -> item.getQuantity().compareTo(outboundQuantity) >= 0).findFirst().orElse(null); if (stock == null) { @@ -268,13 +277,13 @@ } MesWmProductSalesDetailDO detail = new MesWmProductSalesDetailDO().setLineId(line.getId()) .setSalesId(reqVO.getSalesId()).setItemId(batch.getItemId()).setQuantity(quantity) .setMaterialStockId(stock.getId()).setBatchId(batchId).setBatchCode(batch.getCode()) .setMaterialStockId(stock.getId()).setBatchId(wmBatch.getId()).setBatchCode(wmBatch.getCode()) .setWarehouseId(stock.getWarehouseId()).setLocationId(stock.getLocationId()).setAreaId(stock.getAreaId()) .setBagCodeId(pallet == null ? codeId : null).setPalletCodeId(pallet != null ? codeId : null); productSalesDetailMapper.insert(detail); return new MesWmProductSalesScanRespVO().setScanType(pallet != null ? "PALLET" : "BAG") .setCodeId(codeId).setScanContent(reqVO.getScanContent()).setItemId(batch.getItemId()) .setBatchId(batchId).setBatchCode(batch.getCode()).setMaterialStockId(stock.getId()) .setBatchId(wmBatch.getId()).setBatchCode(wmBatch.getCode()).setMaterialStockId(stock.getId()) .setAvailableQuantity(stock.getQuantity()).setScanQuantity(quantity).setOutboundQuantity(quantity) .setAlreadyScanned(false).setDetailId(detail.getId()).setWarehouseId(stock.getWarehouseId()) .setLocationId(stock.getLocationId()).setAreaId(stock.getAreaId()).setMessage("æ«ç æå"); yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskService.java
@@ -3,6 +3,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskScanReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO; import jakarta.validation.Valid; @@ -97,6 +98,14 @@ PageResult<MesWmStockTakingTaskDO> getStockTakingTaskPage(MesWmStockTakingTaskPageReqVO pageReqVO); /** * PDA æ«ç çç¹ï¼è§£æè¿½æº¯ç ï¼è¢ç /æçç /æ¹æ¬¡å·ï¼å®ä½æ¹æ¬¡ï¼æ´æ°çç¹ç»æä¸ä»»å¡è¡ * * @param scanReqVO æ«ç çç¹è¯·æ± * @return çç¹ç»æç¼å· */ Long scanStockTakingTask(MesWmStockTakingTaskScanReqVO scanReqVO); /** * å¤ççç¹å·®å¼ï¼è°æ´åºå * * è¯¥æ¹æ³ä¼æ¯è¾çç¹ç»æä¸åºåå°è´¦çå·®å¼ï¼ yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/stocktaking/task/MesWmStockTakingTaskServiceImpl.java
@@ -2,17 +2,29 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; 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.wm.stocktaking.task.vo.MesWmStockTakingTaskPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.MesWmStockTakingTaskScanReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.stocktaking.task.vo.result.MesWmStockTakingTaskResultSaveReqVO; 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.wm.batch.MesWmBatchDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskLineDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.stocktaking.task.MesWmStockTakingTaskResultDO; import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper; import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.task.MesWmStockTakingTaskLineMapper; import cn.iocoder.yudao.module.mes.dal.mysql.wm.stocktaking.task.MesWmStockTakingTaskMapper; import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockTakingTaskStatusEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; 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.stocktaking.plan.MesWmStockTakingPlanService; import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; @@ -56,6 +68,17 @@ @Resource private MesWmTransactionService wmTransactionService; @Resource private MesWmStockTakingTaskLineMapper stockTakingTaskLineMapper; @Resource private MesWmBatchService mesWmBatchService; @Resource private MesProBatchService proBatchService; @Resource private MesProBagCodeMapper bagCodeMapper; @Resource private MesProPalletMapper palletMapper; @Resource private AdminUserApi adminUserApi; @@ -231,6 +254,69 @@ @Override @Transactional(rollbackFor = Exception.class) public Long scanStockTakingTask(MesWmStockTakingTaskScanReqVO scanReqVO) { // 1. æ ¡éªä»»å¡åå¨ä¸å¤äºçç¹ä¸ç¶æ validateStockTakingTaskExistsAndApproving(scanReqVO.getTaskId()); // 2. è§£æè¿½æº¯ç å®ä½ä»åºæ¹æ¬¡ï¼æ¯ææ¹æ¬¡å·/è¢ç /æçç ï¼èµ°çäº§æ¹æ¬¡æ¡¥æ¥ï¼ MesWmBatchDO wmBatch = resolveWmBatchByScanCode(scanReqVO.getScanCode()); if (wmBatch == null) { throw exception(WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_FOUND); } // 3. å¨ä»»å¡èå´å å®ä½ä»»å¡è¡ MesWmStockTakingTaskLineDO line = stockTakingTaskLineMapper .selectByTaskIdAndBatchId(scanReqVO.getTaskId(), wmBatch.getId()); if (line == null) { throw exception(WM_STOCK_TAKING_TASK_SCAN_BATCH_NOT_IN_TASK); } // 4. åå ¥çç¹ç»æï¼å¤ç¨ createStockTakingTaskResultï¼å«éå¤çç¹æ ¡éªï¼è卿´æ°ä»»å¡è¡çç¹æ°éä¸çäºç¶æï¼ MesWmStockTakingTaskResultSaveReqVO resultReqVO = new MesWmStockTakingTaskResultSaveReqVO() .setTaskId(line.getTaskId()).setLineId(line.getId()) .setMaterialStockId(line.getMaterialStockId()) .setItemId(line.getItemId()).setBatchId(line.getBatchId()).setBatchCode(line.getBatchCode()) .setWarehouseId(line.getWarehouseId()).setLocationId(line.getLocationId()).setAreaId(line.getAreaId()) .setTakingQuantity(scanReqVO.getTakingQuantity()) .setRemark(scanReqVO.getRemark()); return stockTakingTaskResultService.createStockTakingTaskResult(resultReqVO); } /** * è§£æè¿½æº¯ç å®ä½ä»åºæ¹æ¬¡ï¼ä¾æ¬¡å¹é æ¹æ¬¡å·ãè¢ç ãæçç ï¼çäº§æ¹æ¬¡ç»æ¡¥æ¥è¿åä»åºæ¹æ¬¡ */ private MesWmBatchDO resolveWmBatchByScanCode(String scanCode) { if (StrUtil.isBlank(scanCode)) { return null; } // 1. ç´æ¥å½ä¸ä»åºæ¹æ¬¡å· MesWmBatchDO wmBatch = mesWmBatchService.getBatchByCode(scanCode); if (wmBatch != null) { return wmBatch; } // 2. å½ä¸è¢ç MesProBagCodeDO bag = bagCodeMapper.selectByCode(scanCode); if (bag != null && bag.getBatchId() != null) { return bridgeProBatch(bag.getBatchId()); } // 3. å½ä¸æçç MesProPalletDO pallet = palletMapper.selectByCode(scanCode); if (pallet != null && pallet.getBatchId() != null) { return bridgeProBatch(pallet.getBatchId()); } return null; } private MesWmBatchDO bridgeProBatch(Long proBatchId) { MesProBatchDO proBatch = proBatchService.getBatch(proBatchId); if (proBatch == null) { return null; } return mesWmBatchService.getOrCreateByProBatch(proBatch); } @Override @Transactional(rollbackFor = Exception.class) public void processStockTakingDifference(Long id) { // 1. æ ¡éªä»»å¡åå¨ä¸å·²å®æ MesWmStockTakingTaskDO task = validateStockTakingTaskExists(id); yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -127,8 +127,13 @@ materialStockService.updateMaterialStockQuantity(materialStock.getId(), reqDTO.getQuantity(), checkFlag); MesWmMaterialStockDO updatedStock = materialStockService.getMaterialStock(materialStock.getId()); BigDecimal newQuantity = updatedStock.getQuantity(); materialStockService.updateMaterialStockState(updatedStock.getId(), newQuantity.signum() <= 0 ? MesWmStockStateEnum.OUT.getState() : MesWmStockStateEnum.QUALIFIED.getState()); // åºåç¶æï¼æ¾å¼æå®ä¼å ï¼å¦æ«ç å ¥æååºï¼ï¼å¦åææ°éèªå¨å¤å®ï¼â¤0 å·²åºåºï¼>0 åæ ¼åºï¼ Integer stockState = reqDTO.getStockState(); if (stockState == null) { stockState = newQuantity.signum() <= 0 ? MesWmStockStateEnum.OUT.getState() : MesWmStockStateEnum.QUALIFIED.getState(); } materialStockService.updateMaterialStockState(updatedStock.getId(), stockState); // 3. æå ¥äºå¡æµæ°´ MesWmTransactionDO transaction = MesWmTransactionDO.builder() yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/dto/MesWmTransactionSaveReqDTO.java
@@ -118,4 +118,14 @@ */ private Boolean checkFlag; // ===== åºåç¶ææ§å¶ï¼éåå¨åæ®µï¼ ===== /** * æå®åºåç¶æï¼å ¥åºæ¶æ¾å¼æå®ï¼ä¸ºç©ºæ¶ææ°éèªå¨å¤å®ï¼â¤0 å·²åºåºï¼>0 åæ ¼åºï¼ * <p> * ç产线æ«ç å ¥åºä¼ {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum#TEMP}ï¼ * å ¥åºå å ¥æååºå¾ è´¨æ£ã */ private Integer stockState; } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,46 @@ package cn.iocoder.yudao.module.mes.service.wm.warning; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningHandleReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningRespVO; import java.util.List; /** * MES åºåé¢è¦ Service æ¥å£ * <p> * é¢è¦åééï¼å®æ¶è®¡ç®æ¥è¯¢ + 宿¶ä»»å¡è½åºã */ public interface MesWmStockWarningService { /** * è·å¾åºåé¢è¦å页ï¼è½åºè®°å½ï¼ * * @param pageReqVO å页æ¥è¯¢ * @return é¢è¦å页 */ PageResult<MesWmStockWarningRespVO> getWarningPage(MesWmStockWarningPageReqVO pageReqVO); /** * 宿¶è®¡ç®å½ååºåé¢è¦ï¼ä½åºå / 临æ / åºé¾ï¼ï¼ä¸è½åº * * @return å½åé¢è¦å表 */ List<MesWmStockWarningRespVO> getCurrentWarnings(); /** * è½åºå½åé¢è¦ï¼å®æ¶ä»»å¡è°ç¨ï¼ï¼å 餿§çæªå¤çé¢è¦ï¼åå ¥å½åé¢è¦ï¼ä¿çå·²å¤çåå² * * @return æ¬æ¬¡è½åºæ¡æ° */ int persistCurrentWarnings(); /** * å¤çé¢è¦ï¼æ è®°å·²å¤çï¼ * * @param reqVO å¤çè¯·æ± */ void handle(MesWmStockWarningHandleReqVO reqVO); } yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/warning/MesWmStockWarningServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,237 @@ package cn.iocoder.yudao.module.mes.service.wm.warning; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.controller.admin.wm.materialstock.vo.MesWmMaterialStockListReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningHandleReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningPageReqVO; import cn.iocoder.yudao.module.mes.controller.admin.wm.warning.vo.MesWmStockWarningRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; 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.warning.MesWmStockWarningDO; import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; import cn.iocoder.yudao.module.mes.dal.mysql.wm.warning.MesWmStockWarningMapper; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockStateEnum; import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockWarningTypeEnum; 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 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.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; 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.WM_STOCK_WARNING_NOT_EXISTS; /** * MES åºåé¢è¦ Service å®ç°ç±» * <p> * ä½åºåã临æãåºé¾ä¸ç±»é¢è¦ãåªéå¯¹åæ ¼åºï¼æ£å¼åºåï¼ï¼æååºï¼å¾ è´¨æ£ï¼ä¸å·²åºåºä¸è®¡å ¥é¢è¦ã */ @Service @Validated @Slf4j public class MesWmStockWarningServiceImpl implements MesWmStockWarningService { /** * æªå¤ç */ private static final Integer STATUS_UNHANDLED = 10; /** * å·²å¤ç */ private static final Integer STATUS_HANDLED = 20; /** * åºé¾é¢è¦éå¼ï¼å¤©ï¼ */ private static final int AGING_DAYS = 365; @Resource private MesWmStockWarningMapper warningMapper; @Resource private MesWmMaterialStockService materialStockService; @Resource private MesMdmItemService mdmItemService; @Resource private MesWmWarehouseService warehouseService; @Resource private MesWmBatchService batchService; @Override public PageResult<MesWmStockWarningRespVO> getWarningPage(MesWmStockWarningPageReqVO pageReqVO) { PageResult<MesWmStockWarningDO> pageResult = warningMapper.selectPage(pageReqVO); Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap( convertSet(pageResult.getList(), MesWmStockWarningDO::getItemId)); return new PageResult<>(convertList(pageResult.getList(), warning -> toRespVO(warning, itemMap.get(warning.getItemId()))), pageResult.getTotal()); } @Override public List<MesWmStockWarningRespVO> getCurrentWarnings() { List<MesWmStockWarningDO> warnings = computeCurrentWarnings(); if (CollUtil.isEmpty(warnings)) { return List.of(); } Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap( convertSet(warnings, MesWmStockWarningDO::getItemId)); return convertList(warnings, warning -> toRespVO(warning, itemMap.get(warning.getItemId()))); } @Override @Transactional(rollbackFor = Exception.class) public int persistCurrentWarnings() { // 1. 宿¶è®¡ç®å½åé¢è¦ List<MesWmStockWarningDO> current = computeCurrentWarnings(); // 2. å 餿§çæªå¤çé¢è¦ï¼å·²å¤çè®°å½ä½ä¸ºåå²ä¿çï¼ warningMapper.delete(new LambdaQueryWrapperX<MesWmStockWarningDO>() .eq(MesWmStockWarningDO::getStatus, STATUS_UNHANDLED)); // 3. åå ¥å½åé¢è¦ if (CollUtil.isNotEmpty(current)) { current.forEach(warningMapper::insert); } log.info("[persistCurrentWarnings][åºåé¢è¦è½åº {} æ¡]", current.size()); return current.size(); } @Override public void handle(MesWmStockWarningHandleReqVO reqVO) { MesWmStockWarningDO warning = warningMapper.selectById(reqVO.getId()); if (warning == null) { throw exception(WM_STOCK_WARNING_NOT_EXISTS); } warningMapper.updateById(new MesWmStockWarningDO() .setId(warning.getId()) .setStatus(STATUS_HANDLED) .setHandleUserId(SecurityFrameworkUtils.getLoginUserId()) .setHandleTime(LocalDateTime.now()) .setHandleRemark(reqVO.getHandleRemark())); } // ==================== ç§ææ¹æ³ ==================== /** * 宿¶è®¡ç®å½åé¢è¦ï¼ä½åºå / 临æ / åºé¾ï¼ï¼éå¯¹åæ ¼åºå */ private List<MesWmStockWarningDO> computeCurrentWarnings() { List<MesWmStockWarningDO> result = new ArrayList<>(); // ä» æ«æåæ ¼åºï¼æ£å¼åºåï¼ List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockList( new MesWmMaterialStockListReqVO().setStockState(MesWmStockStateEnum.QUALIFIED.getState())); if (CollUtil.isEmpty(stocks)) { return result; } LocalDateTime now = LocalDateTime.now(); Set<Long> itemIds = convertSet(stocks, MesWmMaterialStockDO::getItemId); Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap(itemIds); Set<Long> warehouseIds = convertSet(stocks, MesWmMaterialStockDO::getWarehouseId); Map<Long, MesWmWarehouseDO> warehouseMap = warehouseService.getWarehouseMap(warehouseIds); for (MesWmMaterialStockDO stock : stocks) { MesMdmItemDO item = itemMap.get(stock.getItemId()); MesWmWarehouseDO warehouse = warehouseMap.get(stock.getWarehouseId()); if (item == null) { continue; } MesWmBatchDO batch = stock.getBatchId() != null ? batchService.getBatch(stock.getBatchId()) : null; BigDecimal quantity = stock.getQuantity() != null ? stock.getQuantity() : BigDecimal.ZERO; // 1. ä½åºåï¼å¨åºæ°é <= æä½åºå if (item.getMinStock() != null && quantity.compareTo(item.getMinStock()) <= 0) { result.add(buildWarning(stock, item, warehouse, MesWmStockWarningTypeEnum.LOW_STOCK, item.getMinStock(), quantity, batch != null ? batch.getExpireDate() : null)); } // 2. 临æï¼æ¹æ¬¡ææææï¼ä¸å©ä½å¤©æ° <= ç©æé ç½®çæææå¤©æ° if (item.getExpiryDay() != null && batch != null && batch.getExpireDate() != null) { long remainDays = ChronoUnit.DAYS.between(now.toLocalDate(), batch.getExpireDate().toLocalDate()); if (remainDays <= item.getExpiryDay()) { result.add(buildWarning(stock, item, warehouse, MesWmStockWarningTypeEnum.EXPIRING, BigDecimal.valueOf(item.getExpiryDay()), BigDecimal.valueOf(Math.max(remainDays, 0)), batch.getExpireDate())); } } // 3. åºé¾ï¼å ¥åºæ¶é´è¶ è¿éå¼ if (stock.getReceiptTime() != null) { long ageDays = ChronoUnit.DAYS.between(stock.getReceiptTime().toLocalDate(), now.toLocalDate()); if (ageDays >= AGING_DAYS) { result.add(buildWarning(stock, item, warehouse, MesWmStockWarningTypeEnum.AGING, BigDecimal.valueOf(AGING_DAYS), BigDecimal.valueOf(ageDays), null)); } } } return result; } private MesWmStockWarningDO buildWarning(MesWmMaterialStockDO stock, MesMdmItemDO item, MesWmWarehouseDO warehouse, MesWmStockWarningTypeEnum type, BigDecimal threshold, BigDecimal current, LocalDateTime expireDate) { return MesWmStockWarningDO.builder() .itemId(item.getId()).itemCode(item.getCode()) .batchId(stock.getBatchId()).batchCode(stock.getBatchCode()) .warehouseId(stock.getWarehouseId()) .warehouseName(warehouse != null ? warehouse.getName() : null) .warningType(type.getType()).thresholdValue(threshold).currentValue(current) .expireDate(expireDate).receiptTime(stock.getReceiptTime()) .status(STATUS_UNHANDLED) .build(); } private MesWmStockWarningRespVO toRespVO(MesWmStockWarningDO warning, MesMdmItemDO item) { MesWmStockWarningRespVO vo = new MesWmStockWarningRespVO(); vo.setId(warning.getId()); vo.setItemId(warning.getItemId()); vo.setItemCode(warning.getItemCode()); vo.setItemName(item != null ? item.getName() : null); vo.setBatchId(warning.getBatchId()); vo.setBatchCode(warning.getBatchCode()); vo.setWarehouseId(warning.getWarehouseId()); vo.setWarehouseName(warning.getWarehouseName()); vo.setWarningType(warning.getWarningType()); vo.setWarningTypeName(warningTypeName(warning.getWarningType())); vo.setThresholdValue(warning.getThresholdValue()); vo.setCurrentValue(warning.getCurrentValue()); vo.setExpireDate(warning.getExpireDate()); vo.setReceiptTime(warning.getReceiptTime()); vo.setStatus(warning.getStatus()); vo.setStatusName(statusName(warning.getStatus())); vo.setHandleUserId(warning.getHandleUserId()); vo.setHandleTime(warning.getHandleTime()); vo.setHandleRemark(warning.getHandleRemark()); return vo; } private String warningTypeName(Integer type) { if (type == null) { return ""; } for (MesWmStockWarningTypeEnum e : MesWmStockWarningTypeEnum.values()) { if (e.getType().equals(type)) { return e.getName(); } } return String.valueOf(type); } private String statusName(Integer status) { if (status == null) { return ""; } return status.equals(STATUS_HANDLED) ? "å·²å¤ç" : "æªå¤ç"; } } yudao-server/src/main/resources/application-dev.yaml
@@ -1,6 +1,10 @@ server: port: 48080 # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å 容åç¼ï¼ï¼éä¸ºå ¬ä¼æ«ç å¯è®¿é®çå°å mes: trace-base-url: https://dev.your-domain.com/trace.html --- #################### æ°æ®åºç¸å ³é ç½® #################### spring: yudao-server/src/main/resources/application-jhhg.yaml
@@ -1,6 +1,10 @@ server: port: 48080 # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å 容åç¼ï¼ï¼éä¸ºå ¬ä¼æ«ç å¯è®¿é®çå°å mes: trace-base-url: http://42.63.70.90:9003/trace.html --- #################### æ°æ®åºç¸å ³é ç½® #################### spring: autoconfigure: yudao-server/src/main/resources/application-local.yaml
@@ -1,6 +1,10 @@ server: port: 48080 # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å 容åç¼ï¼ï¼éä¸ºå ¬ä¼æ«ç å¯è®¿é®çå°å mes: trace-base-url: http://192.168.0.10:48080/trace.html --- #################### æ°æ®åºç¸å ³é ç½® #################### spring: autoconfigure: yudao-server/src/main/resources/application-test.yaml
@@ -1,6 +1,10 @@ server: port: 48080 # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å 容åç¼ï¼ï¼éä¸ºå ¬ä¼æ«ç å¯è®¿é®çå°å mes: trace-base-url: http://36.213.180.217:9500/trace.html --- #################### æ°æ®åºç¸å ³é ç½® #################### spring: autoconfigure: yudao-server/src/main/resources/application-thzy.yaml
@@ -1,6 +1,10 @@ server: port: 48080 # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å 容åç¼ï¼ï¼éä¸ºå ¬ä¼æ«ç å¯è®¿é®çå°å mes: trace-base-url: http://47.97.181.254:9001/trace.html --- #################### æ°æ®åºç¸å ³é ç½® #################### spring: autoconfigure: @@ -187,7 +191,7 @@ storage: # æä»¶ä¸ä¼ é ç½® path: /home/mom/javaWork/file # æä»¶å卿 ¹ç®å½ url-prefix: /admin-api/system/storage-blob # URLè·¯å¾åç¼ domain: http://47.97.181.254:9002 # åååç¼ï¼æå®é ååé ç½®ï¼ï¼å端é 置好åä¸å¤ªå¯¹ç°å¨æ¯ç´è¿åç«¯ï¼ææè¦åç«¯ç«¯å£ domain: http://47.97.181.254:9001 # åååç¼ï¼æå®é ååé ç½®ï¼ï¼å端é 置好åä¸å¤ªå¯¹ç°å¨æ¯ç´è¿åç«¯ï¼ææè¦åç«¯ç«¯å£ expired: 120 # tokenè¿ææ¶é´ï¼åéï¼ use-limit: 10 # tokenä½¿ç¨æ¬¡æ°ä¸é jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWTå¯é¥ yudao-server/src/main/resources/application-yzfx.yaml
@@ -1,6 +1,10 @@ server: port: 48080 # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å 容åç¼ï¼ï¼éä¸ºå ¬ä¼æ«ç å¯è®¿é®çå°å mes: trace-base-url: http://42.63.70.231:9001/trace.html --- #################### æ°æ®åºç¸å ³é ç½® #################### spring: autoconfigure: yudao-server/src/main/resources/application.yaml
@@ -1,3 +1,8 @@ # H5 溯æºé¡µé¢åºç¡å°åï¼äºç»´ç å 容åç¼ï¼å½¢å¦ http://åå:端å£/trace.htmlï¼ï¼ # éä¸ºå ¬ä¼æ«ç å¯è®¿é®çå°åï¼å profile å¯è¦çãç空åäºç»´ç ç¼ç 纯ç å¼ï¼å ¼å®¹æ§ç ï¼ã mes: trace-base-url: http://localhost:48080/trace.html spring: application: name: yudao-server yudao-server/src/main/resources/static/trace.html
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,433 @@ <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> <meta name="format-detection" content="telephone=no"> <title>äº§åæº¯æº</title> <style> :root { --primary: #2e7d32; --primary-light: #43a047; --primary-bg: #e8f5e9; --text: #1f2933; --text-sub: #6b7280; --border: #e5e7eb; --bg: #f5f7f4; --card-shadow: 0 1px 4px rgba(0,0,0,.06); --green: #2e7d32; --red: #d32f2f; --blue: #1565c0; --gray: #6b7280; } * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; } html, body { height: 100%; } body { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", "Microsoft YaHei", sans-serif; background: var(--bg); color: var(--text); line-height: 1.5; -webkit-font-smoothing: antialiased; } .wrap { max-width: 480px; margin: 0 auto; padding: 0 12px 32px; } /* é¡¶é¨åçåº */ .brand { max-width: 480px; margin: 0 auto; padding: 20px 16px 14px; text-align: center; } .brand .logo { display: inline-flex; align-items: center; gap: 8px; font-size: 20px; font-weight: 700; color: var(--primary); } .brand .logo .mark { width: 26px; height: 26px; border-radius: 50%; background: linear-gradient(135deg, var(--primary-light), var(--primary)); position: relative; } .brand .logo .mark::after { content: ""; position: absolute; left: 50%; top: 50%; width: 10px; height: 10px; border: 2px solid #fff; border-radius: 50%; transform: translate(-50%, -50%); } .brand .sub { margin-top: 4px; font-size: 12px; color: var(--text-sub); } /* æ¥è¯¢æ */ .query-bar { display: flex; gap: 8px; margin-bottom: 14px; } .query-bar input { flex: 1; height: 42px; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; font-size: 14px; background: #fff; outline: none; } .query-bar input:focus { border-color: var(--primary); } .query-bar button { height: 42px; padding: 0 18px; border: none; border-radius: 8px; background: var(--primary); color: #fff; font-size: 14px; cursor: pointer; } .query-bar button:active { background: var(--primary-light); } /* ç¶æ */ .state { padding-top: 8px; } .loading { text-align: center; padding: 48px 0; color: var(--text-sub); } .spinner { width: 32px; height: 32px; margin: 0 auto 12px; border: 3px solid var(--primary-bg); border-top-color: var(--primary); border-radius: 50%; animation: spin .8s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .empty-tip { padding: 12px 16px; border-radius: 10px; background: #fff; box-shadow: var(--card-shadow); font-size: 14px; color: var(--text-sub); text-align: center; } .empty-tip.error { color: var(--red); background: #fdecec; } .empty-tip.primary { color: var(--primary); background: var(--primary-bg); } /* å¡ç */ .card { background: #fff; border-radius: 12px; box-shadow: var(--card-shadow); margin-bottom: 12px; overflow: hidden; } .card .card-hd { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 15px; font-weight: 600; } .card .card-hd .bar { width: 4px; height: 16px; border-radius: 2px; background: var(--primary); } .card .card-hd .count { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--text-sub); } .card .card-bd { padding: 12px 16px; } /* ä¿¡æ¯è¡ */ .kv { display: flex; padding: 6px 0; font-size: 14px; } .kv .k { flex: 0 0 88px; color: var(--text-sub); } .kv .v { flex: 1; word-break: break-all; } .kv .v.dim { color: var(--text-sub); } /* å¾½ç« */ .badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 12px; line-height: 18px; } .badge.green { color: var(--green); background: #e8f5e9; } .badge.red { color: var(--red); background: #fdecec; } .badge.blue { color: var(--blue); background: #e3f2fd; } .badge.gray { color: var(--gray); background: #f1f2f4; } .badge-big { display: inline-block; padding: 4px 12px; border-radius: 16px; font-size: 14px; } .badge-big.green { color: #fff; background: var(--green); } .badge-big.red { color: #fff; background: var(--red); } .badge-big.gray { color: #fff; background: var(--gray); } /* å表 */ .mini-table { width: 100%; border-collapse: collapse; font-size: 13px; } .mini-table th, .mini-table td { text-align: left; padding: 8px 6px; border-bottom: 1px solid var(--border); } .mini-table th { color: var(--text-sub); font-weight: 400; background: #fafbf9; white-space: nowrap; } .mini-table tr:last-child td { border-bottom: none; } .mini-table td { word-break: break-all; } .pill-list { display: flex; flex-wrap: wrap; gap: 8px; } .pill { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 16px; font-size: 13px; background: #f3f4f6; } .pill .dot { width: 6px; height: 6px; border-radius: 50%; } .scroll-list { max-height: 300px; overflow-y: auto; } .scroll-list::-webkit-scrollbar { width: 4px; } .scroll-list::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; } footer { max-width: 480px; margin: 0 auto; padding: 20px 16px 28px; text-align: center; font-size: 12px; color: #9ca3af; } </style> </head> <body> <div class="brand"> <div class="logo"><span class="mark"></span>ç§åäº§åæº¯æº</div> <div class="sub">æ¥æºå¯æ¥ · å»åå¯è¿½ · 责任å¯ç©¶</div> </div> <div class="wrap"> <!-- æå¨æ¥è¯¢æ ï¼æ code åæ°æ¶æç¤ºè¾å ¥ï¼æåæ°æ¶èªå¨æ¥è¯¢ --> <div class="query-bar"> <input id="codeInput" type="text" placeholder="请è¾å ¥è¿½æº¯ç ï¼è¢ç / æçç / æ¹æ¬¡ç ï¼" autocomplete="off"> <button id="searchBtn" type="button">æ¥è¯¢</button> </div> <section id="state" class="state"></section> </div> <footer>æ¬ç³»ç»æ°æ®ä» ç¨äºäº§å溯æºï¼è¯·éè¿æ£è§æ¸ éè´ä¹°ã</footer> <script> (function () { 'use strict'; var STATE = document.getElementById('state'); var CODE_INPUT = document.getElementById('codeInput'); var SEARCH_BTN = document.getElementById('searchBtn'); var BATCH_STATUS = { 10: ['è稿', 'gray'], 20: ['ç产ä¸', 'blue'], 30: ['已宿', 'green'], 40: ['å·²ä½åº', 'red'] }; var BAG_STATUS = { 10: ['å·²çæ', 'gray'], 20: ['已导åºå°å·', 'blue'], 30: ['å·²ä½åº', 'red'] }; var QUALITY_RESULT = { 0: ['å¤çä¸', 'gray'], 1: ['åæ ¼', 'green'], 2: ['ä¸åæ ¼', 'red'] }; var WORKER_TYPE = { 10: 'ç»é¿', 20: 'æä½å·¥', 30: 'å è£ å·¥', 40: 'è´¨æ£å', 50: 'å ¶ä»' }; // ========== å·¥å ·å½æ° ========== function esc(v) { if (v === null || v === undefined) return ''; return String(v).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function text(v) { return (v === null || v === undefined || v === '') ? '-' : esc(v); } function fmtDate(s) { if (!s) return '-'; return esc(String(s).replace('T', ' ').replace(/\.\d+$/, '').slice(0, 16)); } function fmtNum(v) { return (v === null || v === undefined) ? '-' : esc(v); } function statusBadge(map, key) { var item = map[key]; if (!item) return '<span class="badge gray">' + (key === null || key === undefined ? '' : esc(key)) + '</span>'; return '<span class="badge ' + item[1] + '">' + esc(item[0]) + '</span>'; } function card(title, countHtml, bodyHtml) { return '<div class="card">' + '<div class="card-hd"><span class="bar"></span>' + esc(title) + (countHtml ? '<span class="count">' + countHtml + '</span>' : '') + '</div>' + '<div class="card-bd">' + bodyHtml + '</div>' + '</div>'; } function kv(k, v, dim) { return '<div class="kv"><span class="k">' + esc(k) + '</span><span class="v' + (dim ? ' dim' : '') + '">' + v + '</span></div>'; } function isEmptyList(list) { return !list || list.length === 0; } // ========== æ¥è¯¢ ========== function query(code) { var value = (code || '').trim(); if (!value) { renderState('<div class="empty-tip primary">请è¾å ¥è¿½æº¯ç ååæ¥è¯¢</div>'); return; } renderState('<div class="loading"><div class="spinner"></div>æ£å¨æ¥è¯¢è¿½æº¯ä¿¡æ¯...</div>'); fetch('/public/mes/trace/query?code=' + encodeURIComponent(value)) .then(function (resp) { return resp.json(); }) .then(function (res) { if (res.code === 0) { renderResult(res.data); } else { var msg = res.msg || 'æ¥è¯¢å¤±è´¥ï¼è¯·ç¨åéè¯'; renderState('<div class="empty-tip error">' + esc(msg) + '</div>'); } }) .catch(function () { renderState('<div class="empty-tip error">ç½ç»å¼å¸¸ï¼è¯·æ£æ¥ç½ç»åéè¯</div>'); }); } function renderState(html) { STATE.innerHTML = html; window.scrollTo({ top: 0, behavior: 'smooth' }); } // ========== ç»ææ¸²æï¼ä¿¡æ¯åºå模ååï¼åç»æ°æ°æ®æºå¨æ¤è¿½å åºåï¼ ========== function renderResult(d) { if (!d) { renderState('<div class="empty-tip error">æªæ¥è¯¢å°è¯¥è¿½æº¯ç ï¼è¯·æ ¸å¯¹äºç»´ç æèç³»å®æ¹</div>'); return; } var html = ''; html += buildItemCard(d); html += buildBatchCard(d); html += buildQualityCard(d); html += buildWorkerCard(d); html += buildInboundCard(d); html += buildPalletCard(d); html += buildBagCard(d); html += buildOptionalCard(d); STATE.innerHTML = html || '<div class="empty-tip">ææ è¿½æº¯æ°æ®</div>'; window.scrollTo({ top: 0, behavior: 'smooth' }); } // åºå1ï¼åç§ä¿¡æ¯ function buildItemCard(d) { if (!d.itemCode && !d.itemName && !d.itemSpecification) return ''; return card('åç§ä¿¡æ¯', null, kv('åç§åç§°', text(d.itemName)) + kv('è§æ ¼', text(d.itemSpecification)) + kv('åç§ç¼ç ', text(d.itemCode)) ); } // åºå2ï¼çäº§æ¹æ¬¡ function buildBatchCard(d) { if (!d.batchCode && !d.tempCode) return ''; var status = statusBadge(BATCH_STATUS, d.batchStatus); return card('çäº§æ¹æ¬¡', status, kv('æ¹æ¬¡å·', text(d.batchCode)) + kv('ç¶æ', status) + kv('çäº§æ¥æ', fmtDate(d.produceDate)) + kv('产线', text(d.lineName ? (d.lineName + (d.lineCode ? ' / ' + d.lineCode : '')) : d.lineCode)) + kv('责任人', text(d.ownerUserName)) + kv('è®¡åæ°é', fmtNum(d.planQuantity) === '-' ? '-' : fmtNum(d.planQuantity)) + kv('å·²çæè¢ç ', fmtNum(d.bagQuantity)) + kv('å·²çææç', fmtNum(d.palletQuantity)) + kv('å ¥åºæ¶é´', fmtDate(d.inboundTime)) + (d.remark ? kv('夿³¨', text(d.remark)) : '') ); } // åºå3ï¼ç§åè´¨æ£ function buildQualityCard(d) { var q = d.quality; if (!q) return ''; var result = QUALITY_RESULT[q.result]; var badge = result ? '<span class="badge-big ' + result[1] + '">' + esc(result[0]) + '</span>' : '<span class="badge-big gray">æªç¥</span>'; var rows = ''; if (q.indicators && q.indicators.length) { rows = '<table class="mini-table"><thead><tr><th>ææ </th><th>æ£æµå¼</th><th>æ åèå´</th><th>ç»æ</th></tr></thead><tbody>'; q.indicators.forEach(function (i) { var range = (i.minValue !== null && i.minValue !== undefined && i.maxValue !== null && i.maxValue !== undefined) ? esc(i.minValue) + ' ~ ' + esc(i.maxValue) : '-'; var pass = (i.pass === true) ? '<span class="badge green">åæ ¼</span>' : (i.pass === false ? '<span class="badge red">ä¸åæ ¼</span>' : '<span class="badge gray">--</span>'); rows += '<tr><td>' + text(i.indicatorName) + '</td><td>' + text(i.value) + '</td><td>' + range + '</td><td>' + pass + '</td></tr>'; }); rows += '</tbody></table>'; } return card('ç§åè´¨æ£', badge, kv('æ£éªç»è®º', badge) + kv('æ£éªå', text(q.inspectorName)) + kv('æ£éªæ¶é´', fmtDate(q.inspectedAt)) + (q.reason ? kv('ä¸åæ ¼åå ', text(q.reason)) : '') + (rows ? '<div style="margin-top:8px">' + rows + '</div>' : '') ); } // åºå4ï¼ç产人å function buildWorkerCard(d) { if (isEmptyList(d.workers)) return ''; var html = '<div class="pill-list">'; d.workers.forEach(function (w) { var name = w.userName || '-'; var type = w.workerTypeName || (WORKER_TYPE[w.workerType] || ''); var color = { 10: 'var(--blue)', 20: 'var(--green)', 30: 'var(--primary-light)', 40: 'var(--red)', 50: 'var(--gray)' }[w.workerType] || 'var(--gray)'; html += '<span class="pill"><span class="dot" style="background:' + color + '"></span>' + esc(type) + 'ï¼' + esc(name) + '</span>'; }); html += '</div>'; return card('ç产人å', null, html); } // åºå5ï¼æ«ç å ¥åºäºä»¶ function buildInboundCard(d) { var e = d.inbound; if (!e) return ''; return card('æ«ç å ¥åº', null, kv('äºä»¶å·', text(e.eventId)) + kv('å ¥åºæ°é', fmtNum(e.quantity)) + kv('设å¤ç¼ç ', text(e.deviceCode)) + kv('产线', text(e.lineCode)) + kv('å ¥åºæ¶é´', fmtDate(e.eventTime)) ); } // åºå6ï¼æç function buildPalletCard(d) { if (isEmptyList(d.pallets)) return ''; var countHtml = 'å ± ' + esc(d.palletCount || d.pallets.length) + ' 个'; var rows = '<table class="mini-table"><thead><tr><th>æçç </th><th>åºå·</th><th>å·²è£ /å¯è£ </th></tr></thead><tbody>'; d.pallets.forEach(function (p) { rows += '<tr><td>' + text(p.code) + '</td><td>' + fmtNum(p.palletNo) + '</td><td>' + fmtNum(p.bagCount) + ' / ' + fmtNum(p.capacity) + '</td></tr>'; }); rows += '</tbody></table>'; return card('æçä¿¡æ¯', countHtml, rows); } // åºå7ï¼è¢ç ï¼ä¸è¢ä¸ç ï¼ function buildBagCard(d) { if (d.bagTotal === null || d.bagTotal === undefined || d.bagTotal === 0) return ''; var countHtml = 'å ± ' + esc(d.bagTotal) + ' 个' + (d.bags && d.bags.length < d.bagTotal ? 'ï¼å±ç¤ºå ' + esc(d.bags.length) + ' 个ï¼' : ''); var rows = '<table class="mini-table"><thead><tr><th>è¢ç </th><th>è¢å·</th><th>æç</th><th>ç¶æ</th></tr></thead><tbody>'; (d.bags || []).forEach(function (b) { rows += '<tr><td>' + text(b.code) + '</td><td>' + fmtNum(b.bagNo) + '</td><td>' + text(b.palletCode) + '</td><td>' + statusBadge(BAG_STATUS, b.status) + '</td></tr>'; }); rows += '</tbody></table>'; return card('è¢ç ä¿¡æ¯', countHtml, '<div class="scroll-list">' + rows + '</div>' ); } // åºå8ï¼å¯éæ©å±ï¼éè´æ¥æº / éå®å»å / å å·¥æµè½¬ï¼ï¼ææ°æ®æå±ç¤º function buildOptionalCard(d) { var html = ''; if (!isEmptyList(d.purchaseSources)) { var rows = '<table class="mini-table"><thead><tr><th>éè´å ¥åºå</th><th>ä¾åºå</th><th>æ°é</th><th>æ¥æ</th></tr></thead><tbody>'; d.purchaseSources.forEach(function (s) { rows += '<tr><td>' + text(s.receiptCode) + '</td><td>' + text(s.vendorName) + '</td><td>' + fmtNum(s.receivedQuantity) + '</td><td>' + fmtDate(s.receiptDate) + '</td></tr>'; }); rows += '</tbody></table>'; html += card('éè´æ¥æº', null, rows); } if (!isEmptyList(d.salesTargets)) { var rows2 = '<table class="mini-table"><thead><tr><th>éå®åºåºå</th><th>客æ·</th><th>æ°é</th><th>æ¥æ</th></tr></thead><tbody>'; d.salesTargets.forEach(function (s) { rows2 += '<tr><td>' + text(s.outCode || s.salesOrderCode || '') + '</td><td>' + text(s.customerName) + '</td><td>' + fmtNum(s.quantity) + '</td><td>' + fmtDate(s.outTime) + '</td></tr>'; }); rows2 += '</tbody></table>'; html += card('éå®å»å', null, rows2); } if (!isEmptyList(d.processTraces)) { var rows3 = '<table class="mini-table"><thead><tr><th>èç¹</th><th>说æ</th><th>æ¶é´</th></tr></thead><tbody>'; d.processTraces.forEach(function (t) { rows3 += '<tr><td>' + text(t.nodeName) + '</td><td>' + text(t.description) + '</td><td>' + fmtDate(t.processTime) + '</td></tr>'; }); rows3 += '</tbody></table>'; html += card('å å·¥æµè½¬', null, rows3); } return html; } // ========== åå§å ========== SEARCH_BTN.addEventListener('click', function () { query(CODE_INPUT.value); }); CODE_INPUT.addEventListener('keydown', function (e) { if (e.key === 'Enter') query(CODE_INPUT.value); }); var initialCode = new URLSearchParams(location.search).get('code') || ''; CODE_INPUT.value = initialCode; if (initialCode) { query(initialCode); } else { renderState('<div class="empty-tip">æ«ç æè¾å ¥è¿½æº¯ç ï¼å³å¯æ¥è¯¢äº§åå ¨ç¨æº¯æºä¿¡æ¯</div>'); CODE_INPUT.focus(); } })(); </script> </body> </html>