feat(ncr): 完善NCR处置联动逻辑与关闭校验
- 新增重新生产处置方式并按检验类型过滤可选项
- 完善出货检验返工返修的库存扣减与原单取消联动
- 增加不合格品关闭前的复检单绑定与状态校验逻辑
- 移除检验完成时的附件强制上传校验
- 重构销售合同Word模板渲染为XML节点精确填充
- 将销售报价单导出格式由Word更改为Excel
| | |
| | | ## ä¸å¡æµç¨ä¸æ°æ®å¸¦å
¥ |
| | | |
| | | 1. **æ£éªå¤å®ä¸åæ ¼ â NCR å建**ï¼å¨åæ£éªå详æ
页ï¼å½æ£éªç»æä¸º"ä¸åæ ¼éè´§"æ"ä¸åæ ¼æ¥åº"æ¶ï¼å¯ç¹å»"çæNCR"æé®ï¼èªå¨å¸¦å
¥æ£éªç±»åãæ£éªåID/ç¼å·ãç©æä¿¡æ¯ãä¸åæ ¼æ°éãä¸åæ ¼æè¿°ï¼è·³è½¬å° NCR æ°å»ºé¡µé¢ã |
| | | 2. **NCR å¾
è¯å®¡ â è¯å®¡**ï¼å¡«åå¤ç½®æ¹å¼ï¼éè´§/æ¥åº/è¿å·¥/è®©æ¥æ¥æ¶/é级使ç¨ï¼ã责任é¨é¨ã缺é·ç级ãè¯å®¡æè§ã |
| | | 2. **NCR å¾
è¯å®¡ â è¯å®¡**ï¼ææ£éªç±»åéæ©å¤ç½®æ¹å¼ï¼IQC éè´§/è®©æ¥æ¾è¡ï¼IPQC æ¥åº/è¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®/éæ°ç产ï¼OQC è¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®/éæ°ç产ï¼RQC æ¥åº/è¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®ï¼ï¼å¹¶å¡«å责任é¨é¨ã缺é·ç级ãè¯å®¡æè§ã |
| | | 3. **NCR å·²è¯å®¡ â å¤ç½®**ï¼å¡«åå¤ç人ãå¤çç»æè¯´æã |
| | | 4. **NCR å·²å¤ç½® â å
³é**ï¼å¡«åå
³éæè§ã |
| | | |
| | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | NCRç¼å· | |
| | | | disposition | Integer | æ¯ | å¤ç½®æ¹å¼ï¼1=éè´§/2=æ¥åº/3=è¿å·¥/4=è®©æ¥æ¥æ¶/5=é级使ç¨ï¼ | |
| | | | disposition | Integer | æ¯ | å¤ç½®æ¹å¼ï¼1=éè´§/2=æ¥åº/3=è¿å·¥/4=è®©æ¥æ¥æ¶/5=é级使ç¨/6=è¿ä¿®/7=éæ°çäº§ï¼ | |
| | | | responsibleDeptId | Long | å¦ | 责任é¨é¨ID | |
| | | | defectLevel | Integer | å¦ | 缺é·ç级 | |
| | | | reviewOpinion | String | å¦ | è¯å®¡æè§ | |
| | |
| | | |
| | | **PUT** `/mes/qc/ncr/dispose` |
| | | |
| | | å¤ç½®ä¼èå¨çæä¸å¡åæ®ï¼è®©æ¥æ¾è¡(4)/æ¥åº(2) çææé¡¹å
¥åºåå¹¶èªå¨æäº¤ã宿å
¥åºï¼è¿å·¥(3)/è¿ä¿®(6) çæèªè¡ç产类åç产订åï¼è稿ï¼ã |
| | | å¤ç½®ä¼èå¨çæä¸å¡åæ®ï¼è®©æ¥æ¾è¡(4)/æ¥åº(2) çææé¡¹å
¥åºåå¹¶èªå¨æäº¤ã宿å
¥åºï¼è¿å·¥(3)/è¿ä¿®(6) çæèªè¡ç产类åç产订åï¼è稿ï¼ï¼åºè´§æ£éªï¼OQCï¼çè¿å·¥/è¿ä¿®ä¼æ£ååæ¹æ¬¡æååºåãèªå¨åæ¶åéå®åºåºåå¹¶çæå·²ä¸è¾¾çè¿å·¥å·¥åï¼éæ°ç产(7) å¤å¶åå·¥åå·¥åºä¸ BOM ä¸è¾¾æ°å·¥åã |
| | | |
| | | å¯éçå¤ç½®æ¹å¼åå³äºæ£éªæ¥æºï¼`qcType`ï¼ï¼ |
| | | |
| | | | qcType | å¯å¤ç½®æ¹å¼ | |
| | | |---|---| |
| | | | 1 IQC | éè´§(1)ãè®©æ¥æ¾è¡(4) | |
| | | | 2 IPQC | æ¥åº(2)ãè¿å·¥(3)ãè®©æ¥æ¾è¡(4)ãè¿ä¿®(6)ãéæ°ç产(7) | |
| | | | 3 OQC | è¿å·¥(3)ãè®©æ¥æ¾è¡(4)ãè¿ä¿®(6)ãéæ°ç产(7) | |
| | | | 4 RQC | æ¥åº(2)ãè¿å·¥(3)ãè®©æ¥æ¾è¡(4)ãè¿ä¿®(6) | |
| | | |
| | | 请æ±åæ°ï¼ |
| | | |
| | | | åæ° | ç±»å | å¿
å¡« | 说æ | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | NCRç¼å· | |
| | | | disposition | Integer | æ¯ | å¤ç½®æ¹å¼ï¼ä»
2=æ¥åº/3=è¿å·¥/4=è®©æ¥æ¾è¡/6=è¿ä¿®ï¼ | |
| | | | disposition | Integer | æ¯ | å¤ç½®æ¹å¼ï¼åå¼èå´è§ä¸æ¹ãå¯éå¤ç½®æ¹å¼ã | |
| | | | warehouseId | Long | æ¡ä»¶å¿
å¡« | å
¥åºä»åºï¼è®©æ¥æ¾è¡/æ¥åºæ¶å¿
å¡«ï¼å端é»è®¤å¸¦åºåææä» | |
| | | | locationId | Long | æ¡ä»¶å¿
å¡« | å
¥åºåºåºï¼è®©æ¥æ¾è¡/æ¥åºæ¶å¿
å¡« | |
| | | | areaId | Long | æ¡ä»¶å¿
å¡« | å
¥åºåºä½ï¼è®©æ¥æ¾è¡/æ¥åºæ¶å¿
å¡« | |
| | | | batchCode | String | æ¡ä»¶å¿
å¡« | æ¹æ¬¡å·ï¼ç©æå¯ç¨æ¹æ¬¡ç®¡çæ¶å¿
å¡«ï¼æ¹æ¬¡ä¸åå¨ä¼èªå¨å建 | |
| | | | materialStockId | Long | æ¡ä»¶å¿
å¡« | åæ¹æ¬¡æååºåè®°å½IDï¼OQC è¿å·¥/è¿ä¿®å¿
å¡«ãæªä¼ æ¶åç«¯è¦æ±åç©æåæ¹æ¬¡ä»
䏿¡å¯ç¨åºåï¼å¦åæç¤ºå
éæ©å
·ä½åºå | |
| | | | handlerUserId | Long | å¦ | å¤ç人ID | |
| | | | handleResult | String | å¦ | å¤çç»æè¯´æ | |
| | | |
| | |
| | | |------|------|------|------| |
| | | | id | Long | æ¯ | NCRç¼å· | |
| | | | closeOpinion | String | å¦ | å
³éæè§ | |
| | | |
| | | å
³éåæ ¡éªï¼æå¤ç½®æ¹å¼ï¼ï¼ |
| | | |
| | | | å¤ç½®æ¹å¼ | å
³éåå¿
须满足 | |
| | | |---|---| |
| | | | è¿å·¥(3)/è¿ä¿®(6) | 对åºè¿å·¥å·¥å已宿并æ¥å·¥ï¼å¤æ£åå¿
é¡»ç»å®è¯¥è¿å·¥å·¥åçæ¥å·¥äº§åºè¡ï¼æ£æµæ°é=ä¸åæ ¼æ°éï¼åæ ¼äº§åºæå¯¹åºå
¥åºæµæ°´ï¼å¤æ£è¥æä¸åæ ¼æ°éï¼é¡»å·²çæå¯ä¸çæ° NCR | |
| | | | OQC è¿å·¥(3)/è¿ä¿®(6) | å¨ä¸ä¸è¡åºç¡ä¸ï¼åéå®åºåºå已为ã已忶ãï¼è¿å·¥é¢ç¨åºåºå已宿ä¸åºåºæ°é=ä¸åæ ¼æ°é | |
| | | | IQC éè´§(1) | éè´éè´§å已确认éè´§ | |
| | | | OQC è®©æ¥æ¾è¡(4) | åéå®åºåºå已宿æ£è´§ååºåº | |
| | | | éæ°ç产(7) | å½åçæ¬æä¸å
许å
³é | |
| | | |
| | | ### å页æ¥è¯¢ |
| | | |
| | |
| | | | 4 | è®©æ¥æ¥æ¶ | æé¡¹å
¥åºå°åææä» | |
| | | | 5 | éçº§ä½¿ç¨ | å岿䏾ï¼è¯å®¡ä¸åå¯é | |
| | | | 6 | è¿ä¿® | çæç产订å | |
| | | | 7 | éæ°ç产 | å¤å¶åå·¥åå·¥åºå BOMï¼ä¸è¾¾æ°ç产订å | |
| | | |
| | | ### å¤ç½®èå¨è§å |
| | | |
| | |
| | | |----------|----------|-----------| |
| | | | è®©æ¥æ¾è¡(4) | æé¡¹å
¥åºåï¼type=2 è®©æ¥æ¾è¡å
¥åºï¼ï¼èªå¨æäº¤å¹¶å®æå
¥åºï¼åºåå¢å | é»è®¤åææä»ï¼å¯æ¹ä»åº/åºåº/åºä½ | |
| | | | æ¥åº(2) | æé¡¹å
¥åºåï¼type=3 æ¥åºå
¥åºï¼ï¼èªå¨æäº¤å¹¶å®æå
¥åºï¼åºåå¢å | é»è®¤ä¸åæ ¼åä»ï¼å¯æ¹ä»åº/åºåº/åºä½ | |
| | | | è¿å·¥(3)/è¿ä¿®(6) | ç产订åï¼èªè¡ç产ï¼è稿æï¼ | 产å=NCRç©æãæ°é=ä¸åæ ¼æ°éï¼BOM/æ¡ç èªå¨çæï¼å·¥èºè·¯çº¿é人工补å
åä¸è¾¾ | |
| | | | è¿å·¥(3)/è¿ä¿®(6)ï¼IPQC/RQCï¼ | ç产订åï¼èªè¡ç产ï¼è稿æï¼ | 产å=NCRç©æãæ°é=ä¸åæ ¼æ°éï¼BOM/æ¡ç èªå¨çæï¼å·¥èºè·¯çº¿é人工补å
åä¸è¾¾ | |
| | | | è¿å·¥(3)/è¿ä¿®(6)ï¼OQCï¼ | è¿å·¥é¢ç¨åºåºåï¼æé¡¹åºåº type=3ï¼+ åéå®åºåºåèªå¨åæ¶ + è¿å·¥å·¥åï¼å·²ç¡®è®¤å¹¶çæä»»å¡ï¼ | å¿
é¡»éå®åæ¹æ¬¡æååºåï¼æ£åæ°é=ä¸åæ ¼æ°éï¼è¿å·¥å·¥åå¤å¶åå·¥åå·¥èºè·¯çº¿ä¸ BOM | |
| | | | éæ°ç产(7)ï¼IPQC/OQCï¼ | ç产订åï¼èªè¡ç产ï¼å·²ç¡®è®¤å¹¶çæä»»å¡ï¼ | æä¸åæ ¼æ°éå¤å¶åå·¥åçå·¥åºå¿«ç
§ä¸ BOMï¼BOM ç¨éææ¯ä¾æ¢ç® | |
| | | |
| | | ### 缺é·ç级æä¸¾ |
| | | |
| | |
| | | - å·²å¤ç½®ï¼å
³é |
| | | - å·²å
³éï¼ä»
æ¥ç |
| | | - 详æ
页建议ååºåå±ç¤ºï¼ä¸åæ ¼åä¿¡æ¯ãè¯å®¡ä¿¡æ¯ãå¤ç½®ä¿¡æ¯ãå
³éä¿¡æ¯ |
| | | - ç¶æåå
¸ç±»å为 `mes_ncr_status`ï¼å¤ç½®æ¹å¼åå
¸ç±»å为 `mes_ncr_disposition` |
| | | - å¤ç½®å¼¹çªçå¤ç½®æ¹å¼éæ `qcType` è¿æ»¤ï¼OQC éæ©è¿å·¥/è¿ä¿®æ¶éå±ç¤ºãè¿å·¥åºåãéæ©å¨ï¼`materialStockId`ï¼ |
| | | - åºè´§æ£éªä¸åæ ¼çåºåºè¡ä¼é»æ¢éå®åºåºåã确认ãï¼å¿
é¡»å
å¨ä¸åæ ¼åå¤çåä¸å®æå¤ç½®ï¼å«è®©æ¥æ¾è¡ï¼åæè½ç»§ç»åºåº |
| | | - ç¶æåå
¸ç±»å为 `mes_ncr_status`ï¼å¤ç½®æ¹å¼åå
¸ç±»å为 `mes_ncr_disposition`ï¼æé¡¹åºåºç±»ååå
¸ç±»å为 `mes_wm_misc_issue_type` |
| | |
| | | }, |
| | | { |
| | | "stage": "ä¸åæ ¼", |
| | | "action": "NCR: å¾
è¯å®¡(0)âæäº¤BPM审æ¹(2)â审æ¹éè¿å·²è¯å®¡(6)/驳ååå¾
è¯å®¡âè´¨éé¨å¤ç½®(è®©æ¥æ¾è¡/è¿ä¿®/è¿å·¥/æ¥åº)âå·²å¤ç½®(7)âå
³é(8)ï¼å¤ç½®èå¨ï¼è®©æ¥æ¾è¡/æ¥åºçææé¡¹å
¥åºåå¹¶èªå¨æäº¤å®æå
¥åº(å¤ç½®å¼¹çªéä»åº/åºåº/åºä½ï¼è®©æ¥æ¾è¡é»è®¤åææä»ãæ¥åºé»è®¤ä¸åæ ¼åä»ï¼æ¹æ¬¡ç®¡çç©æå¿
å¡«æ¹æ¬¡å·)ï¼è¿å·¥/è¿ä¿®çæèªè¡ç产类åç产订å(产å=NCRç©æãæ°é=ä¸åæ ¼æ°éãBOM䏿¡ç èªå¨çæãå·¥èºè·¯çº¿äººå·¥è¡¥å
)ï¼å®¡æ¹æè§ç± BPM æµè½¬è®°å½æ¿è½½ï¼æªå叿µç¨å®ä¹æå
¨å±å
å®¡æ¹æ¶æäº¤åèªå¨éè¿", |
| | | "action": "NCR: å¾
è¯å®¡(0)âæäº¤BPM审æ¹(2)â审æ¹éè¿å·²è¯å®¡(6)/驳ååå¾
è¯å®¡âè´¨éé¨å¤ç½®âå·²å¤ç½®(7)âå
³é(8)ï¼å¤ç½®é¡¹ææ£éªç±»åå±ç¤º(IQCéè´§/è®©æ¥æ¾è¡ï¼IPQCæ¥åº/è¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®/éæ°ç产ï¼OQCè¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®/éæ°ç产ï¼RQCæ¥åº/è¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®)ï¼è®©æ¥æ¾è¡/æ¥åºçææé¡¹å
¥åºåå¹¶èªå¨æäº¤å®æå
¥åº(å¤ç½®å¼¹çªéä»åº/åºåº/åºä½ï¼è®©æ¥æ¾è¡é»è®¤åææä»ãæ¥åºé»è®¤ä¸åæ ¼åä»ï¼æ¹æ¬¡ç®¡çç©æå¿
å¡«æ¹æ¬¡å·)ï¼IPQC/RQCè¿å·¥/è¿ä¿®çæèªè¡ç产类åç产订å(产å=NCRç©æãæ°é=ä¸åæ ¼æ°éãBOM䏿¡ç èªå¨çæãå·¥èºè·¯çº¿äººå·¥è¡¥å
)ï¼OQCè¿å·¥/è¿ä¿®å
éå®åæ¹æ¬¡æååºåæ£åãçæè¿å·¥é¢ç¨åºåºå(type=3)并宿åºåºãåéå®åºåºåæ´åèªå¨åæ¶ï¼åå¤ç¨åå·¥åå·¥åºä¸BOMå
éè¿å·¥å·¥åå¹¶ä¸è¾¾ï¼éæ°ç产æä¸åæ ¼æ°éå¤å¶åå·¥åå·¥åºä¸BOMä¸è¾¾æ°å·¥åï¼å®¡æ¹æè§ç± BPM æµè½¬è®°å½æ¿è½½ï¼æªå叿µç¨å®ä¹æå
¨å±å
å®¡æ¹æ¶æäº¤åèªå¨éè¿", |
| | | "roles": [ |
| | | "è´¨éå·¥ç¨å¸" |
| | | ] |
| | |
| | | "name": "NCRä¸åæ ¼å", |
| | | "module": "è´¨é", |
| | | "table": "mes_qc_ncr", |
| | | "doc": "å¾
è¯å®¡0âæäº¤BPM审æ¹ä¸2â审æ¹éè¿å·²è¯å®¡6(驳å/åæ¶åå¾
è¯å®¡)âå·²å¤ç½®7âå·²å
³é8ï¼å®¡æ¹æè§ç± BPM æµè½¬è®°å½æ¿è½½ï¼è´¨é鍿ç»å¤ç½®:è®©æ¥æ¾è¡/è¿ä¿®/è¿å·¥/æ¥åº(éè´§/é级ä»
åå²å±ç¤º)", |
| | | "doc": "å¾
è¯å®¡0âæäº¤BPM审æ¹ä¸2â审æ¹éè¿å·²è¯å®¡6(驳å/åæ¶åå¾
è¯å®¡)âå·²å¤ç½®7âå·²å
³é8ï¼å®¡æ¹æè§ç± BPM æµè½¬è®°å½æ¿è½½ï¼æç»å¤ç½®ææ£éªç±»å卿å¯éï¼IQCéè´§/è®©æ¥æ¾è¡ï¼IPQCæ¥åº/è¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®/éæ°ç产ï¼OQCè¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®/éæ°ç产ï¼RQCæ¥åº/è¿å·¥/è®©æ¥æ¾è¡/è¿ä¿®(é级ä»
åå²å±ç¤º)ï¼OQCè¿å·¥/è¿ä¿®ééå®åæ¹æ¬¡æååºåï¼æ£åå¹¶çæè¿å·¥é¢ç¨åºåºåãèªå¨åæ¶åéå®åºåºååå
éè¿å·¥å·¥åï¼å
³éåæ ¸éªè¿å·¥å®æãåºåºåæ¶ä¸é¢ç¨æ°é", |
| | | "upstream": [ |
| | | "IQC/IPQC/OQC/RQC" |
| | | ], |
| | | "downstream": [ |
| | | "éè´§/æ¥åº/è¿å·¥(å°è´¦)" |
| | | "éè´éè´§å/æé¡¹å
¥åºå/è¿å·¥é¢ç¨åºåºå/ç产订å(å°è´¦)" |
| | | ], |
| | | "keywords": "NCR ä¸åæ ¼ å¤ç½® ç¹é æ¥åº" |
| | | }, |
| | |
| | | N1A -->|BPM 驳å/åæ¶Â·ä¿çæµç¨å®ä¾å¯æ¥æè§| N1 |
| | | N1A -->|BPM 审æ¹éè¿Â·å®¡æ¹èç¹ãè´¨éé¨é¨å®¡æ¹ãæè´¨éé¨é¨è§è²æç¾Â·æè§ç± BPM æµè½¬è®°å½æ¿è½½| N2[å·²è¯å®¡6·质éé¨å¾
å¤ç½®] |
| | | N2 -->|è´¨é鍿ç»å¤ç½®Â·è®©æ¥æ¾è¡4/æ¥åº2| N3A[çææé¡¹å
¥åºå·èªå¨æäº¤å¹¶å®æå
¥åºÂ·å¤ç½®å¼¹çªéä»åº/åºåº/åºä½Â·è®©æ¥æ¾è¡é»è®¤åææä»Â·æ¥åºé»è®¤ä¸åæ ¼åä»Â·æ¹æ¬¡ç®¡çç©æå¿
å¡«æ¹æ¬¡å·] |
| | | N2 -->|è´¨é鍿ç»å¤ç½®Â·è¿å·¥3/è¿ä¿®6| N3B[çæèªè¡ç产类åç产订å(è稿)·产å=NCRç©æÂ·æ°é=ä¸åæ ¼æ°é·BOM䏿¡ç èªå¨çæÂ·å·¥èºè·¯çº¿äººå·¥è¡¥å
åä¸è¾¾] |
| | | N2 -->|è´¨é鍿ç»å¤ç½®Â·è¿ç¨/éè´§æ£éª(IPQC/RQC)è¿å·¥3/è¿ä¿®6| N3B[çæèªè¡ç产类åç产订å(è稿)·产å=NCRç©æÂ·æ°é=ä¸åæ ¼æ°é·BOM䏿¡ç èªå¨çæÂ·å·¥èºè·¯çº¿äººå·¥è¡¥å
åä¸è¾¾] |
| | | N2 -->|è´¨é鍿ç»å¤ç½®Â·åºè´§æ£éª(OQC)è¿å·¥3/è¿ä¿®6| N3C[éå®åæ¹æ¬¡æååºåå¹¶æ£å·çæè¿å·¥é¢ç¨åºåºåtype=3并宿åºåºÂ·åéå®åºåºåæ´åèªå¨åæ¶Â·å¤ç¨åå·¥åå·¥åºä¸BOMå
éè¿å·¥å·¥åå¹¶ä¸è¾¾] |
| | | N2 -->|è´¨é鍿ç»å¤ç½®Â·éæ°ç产7(IPQC/OQC)| N3D[æä¸åæ ¼æ°éå¤å¶åå·¥åå·¥åºå¿«ç
§ä¸BOM·çæå¹¶ç¡®è®¤æ°ç产订å·èªå¨çæä»»å¡è稿] |
| | | N3A --> N3[å·²å¤ç½®7] |
| | | N3B --> N3 |
| | | N3C --> N3 |
| | | N3D --> N3 |
| | | N3C -.->|å
³éåæ ¸éª| N4C[è¿å·¥å·¥åå·²å®æÂ·åéå®åºåºåå·²åæ¶Â·è¿å·¥é¢ç¨åºåºæµæ°´æ°é=ä¸åæ ¼æ°éÂ·å¤æ£ç»å®è¿å·¥æ¥å·¥äº§åºè¡] |
| | | N2 -.->|å¤ç½®é¡¹ææ£éªç±»åå±ç¤ºÂ·IQCéè´§/让æ¥Â·IPQCæ¥åº/è¿å·¥/让æ¥/è¿ä¿®/éæ°ç产·OQCè¿å·¥/让æ¥/è¿ä¿®/éæ°ç产·RQCæ¥åº/è¿å·¥/让æ¥/è¿ä¿®| N3E[ååç«¯åæºè¿æ»¤] |
| | | N3 -->|å
³é| N4[å·²å
³é8] |
| | | N1 -.->|æªå叿µç¨å®ä¹æå
¨å±å
å®¡æ¹æ¶Â·æäº¤åèªå¨ç½®ä¸ºå·²è¯å®¡| N2 |
| | | Q8[æ¯ææ°æ®:è´¨æ£æ¹æ¡Â·æ£éªææ ·缺é·åºÂ·ææ ç»æ] --- A1 |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- è¿å·¥/è¿ä¿®å
³éæ¶è®°å½å¤æ£è¯æ®ï¼é¨ç½²æ°ä»£ç åæ§è¡ã |
| | | ALTER TABLE `mes_qc_ncr` |
| | | ADD COLUMN `reinspection_ipqc_id` bigint NULL COMMENT 'è¿å·¥/è¿ä¿®å
³éæ¶ç夿£å ID'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 客æ·éè´§ä¸åæ ¼è¡è½¬äº¤ NCRï¼é¿å
åéè´§å忬¡å
¥åºã |
| | | ALTER TABLE `mes_wm_return_sales_line` |
| | | ADD COLUMN `ncr_disposition_id` bigint NULL COMMENT 'æ¿æ¥ä¸åæ ¼æ°éç NCR ID'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | INSERT INTO `system_dict_data` |
| | | (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | SELECT 3, 'è¿å·¥é¢ç¨', '3', 'mes_wm_misc_issue_type', 0, 'warning', '', 'è¿å·¥é¢ç¨åºåº', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM `system_dict_data` WHERE `dict_type` = 'mes_wm_misc_issue_type' AND `value` = '3' AND `deleted` = b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | INSERT INTO `system_dict_data` |
| | | (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) |
| | | SELECT 7, 'éæ°ç产', '7', 'mes_ncr_disposition', 0, '', '', 'åºè´§ä¸åæ ¼éæ°ç产', '1', NOW(), '1', NOW(), b'0' |
| | | WHERE NOT EXISTS (SELECT 1 FROM `system_dict_data` WHERE `dict_type` = 'mes_ncr_disposition' AND `value` = '7' AND `deleted` = b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ä¸åæ ¼äº§åºè¡è¢« NCR æ¶è´¹åä¿çå¤ç½®å
³èï¼é²æ¢å¤ä¸ª NCR éå¤å
¥åºã |
| | | ALTER TABLE `mes_wm_product_produce_line` |
| | | ADD COLUMN `ncr_disposition_id` bigint NULL COMMENT 'å¤ç½®è¯¥ä¸åæ ¼äº§åºç NCR ID'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- åºè´§ä¸åæ ¼è¡åªè½è½¬äº¤ä¸å¼ å¤çåã |
| | | ALTER TABLE `mes_wm_product_sales_line` |
| | | ADD COLUMN `ncr_disposition_id` bigint NULL COMMENT 'æ¿æ¥ä¸åæ ¼åºè´§ç NCR ID'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- è®°å½å·²æéè´å
¥åºè¿è´¦çè®©æ¥æ°éï¼åç»å°è´§å
¥åºåªæ¶ååæ ¼æ°éã |
| | | CREATE TABLE IF NOT EXISTS `mes_qc_purchase_concession` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT, |
| | | `ncr_id` bigint NOT NULL, |
| | | `iqc_id` bigint NOT NULL, |
| | | `notice_id` bigint NOT NULL, |
| | | `notice_line_id` bigint NOT NULL, |
| | | `purchase_order_item_id` bigint NOT NULL, |
| | | `receipt_id` bigint NOT NULL, |
| | | `quantity` decimal(20,4) NOT NULL, |
| | | `creator` varchar(64) DEFAULT '', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | `updater` varchar(64) DEFAULT '', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0', |
| | | `tenant_id` bigint NOT NULL DEFAULT 0, |
| | | PRIMARY KEY (`id`), UNIQUE KEY `uk_qc_purchase_concession_ncr` (`ncr_id`), |
| | | UNIQUE KEY `uk_qc_purchase_concession_iqc` (`iqc_id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='æ¥æè®©æ¥éè´å
¥åºå
³è'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- æªå
¥åºä¸åæ ¼åçç´æ¥éè´éè´§ï¼ä¸çæåºååºå
¥åºï¼ä¹ä¸å ç¨å·²å
¥åºéè´§æ°éã |
| | | CREATE TABLE IF NOT EXISTS `mes_qc_purchase_return` ( |
| | | `id` bigint NOT NULL AUTO_INCREMENT, |
| | | `code` varchar(64) NOT NULL, |
| | | `ncr_id` bigint NOT NULL, |
| | | `iqc_id` bigint NOT NULL, |
| | | `notice_id` bigint NOT NULL, |
| | | `notice_line_id` bigint NOT NULL, |
| | | `purchase_order_id` bigint NOT NULL, |
| | | `purchase_order_item_id` bigint NOT NULL, |
| | | `vendor_id` bigint NOT NULL, |
| | | `item_id` bigint NOT NULL, |
| | | `vendor_batch` varchar(100) DEFAULT NULL, |
| | | `quantity` decimal(20,4) NOT NULL, |
| | | `status` tinyint NOT NULL DEFAULT 0 COMMENT '0å¾
ä»åºéè´§ 1已确认éè´§', |
| | | `confirmed_by` bigint DEFAULT NULL, |
| | | `confirmed_at` datetime DEFAULT NULL, |
| | | `confirmation_remark` varchar(500) DEFAULT NULL, |
| | | `creator` varchar(64) DEFAULT '', |
| | | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | `updater` varchar(64) DEFAULT '', |
| | | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, |
| | | `deleted` bit(1) NOT NULL DEFAULT b'0', |
| | | `tenant_id` bigint NOT NULL DEFAULT 0, |
| | | PRIMARY KEY (`id`), UNIQUE KEY `uk_qc_purchase_return_ncr` (`ncr_id`), |
| | | UNIQUE KEY `uk_qc_purchase_return_iqc` (`iqc_id`), |
| | | KEY `idx_qc_purchase_return_order_item` (`purchase_order_item_id`) |
| | | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='å¾
å
¥åºæ¥æä¸åæ ¼éè´éè´§'; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- 客æ·é货产ççåºææ²¡æç产工åï¼ä»¥åºæåç¼ç å夿³¨è¿½æº¯ä¸åæ ¼å¤çåã |
| | | ALTER TABLE `mes_wm_scrap_receipt` |
| | | MODIFY COLUMN `work_order_id` bigint NULL COMMENT 'ç产工åç¼å·ï¼å®¢æ·é货为空', |
| | | MODIFY COLUMN `work_order_code` varchar(64) NULL COMMENT 'ç产工åå·å¿«ç
§'; |
| | |
| | | import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; |
| | | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; |
| | | import cn.iocoder.yudao.module.crm.enums.quotation.CrmSaleQuotationStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationDocumentRenderer; |
| | | import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationExcelRenderer; |
| | | import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; |
| | | import cn.iocoder.yudao.module.crm.service.quotation.CrmSaleQuotationService; |
| | | import cn.iocoder.yudao.module.system.service.companyconfig.SystemCompanyConfigService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | |
| | | private MdmUnitMeasureApi mdmUnitMeasureApi; |
| | | @Resource |
| | | private CrmCustomerService customerService; |
| | | @Resource |
| | | private CrmContactService contactService; |
| | | @Resource |
| | | private CrmBusinessService businessService; |
| | | @Resource |
| | |
| | | ExcelUtils.write(response, "é宿¥ä»·å.xls", "æ°æ®", CrmSaleQuotationRespVO.class, list); |
| | | } |
| | | |
| | | @GetMapping("/export-word") |
| | | @Operation(summary = "导åºé宿¥ä»·å Wordï¼å¸¦çµåç« ï¼") |
| | | @GetMapping({"/export-word", "/export-quotation-excel"}) |
| | | @Operation(summary = "导åºé宿¥ä»·å Excelï¼å¸¦çµåç« ï¼") |
| | | @Parameter(name = "id", description = "æ¥ä»·åç¼å·", required = true) |
| | | @PreAuthorize("@ss.hasPermission('crm:sale-quotation:export')") |
| | | @ApiAccessLog(operateType = EXPORT) |
| | |
| | | saleQuotationService.getSaleQuotationProductListByQuotationId(id)); |
| | | var companyConfig = companyConfigService.getCompanyConfig(); |
| | | String companyName = companyConfig != null ? companyConfig.getCompanyName() : null; |
| | | byte[] content = CrmSaleQuotationDocumentRenderer.render(respVO, companyName); |
| | | var customer = saleQuotation.getCustomerId() == null ? null : customerService.getCustomer(saleQuotation.getCustomerId()); |
| | | var contact = saleQuotation.getContactId() == null ? null : contactService.getContact(saleQuotation.getContactId()); |
| | | String phone = contact == null ? null : (contact.getMobile() != null && !contact.getMobile().isBlank() |
| | | ? contact.getMobile() : contact.getTelephone()); |
| | | byte[] content = CrmSaleQuotationExcelRenderer.render(respVO, companyName, |
| | | customer == null ? null : customer.getDetailAddress(), contact == null ? null : contact.getName(), phone); |
| | | |
| | | String filename = "æ¥ä»·å-" + saleQuotation.getNo() |
| | | + (respVO.getCustomerName() != null ? "-" + respVO.getCustomerName() : "") + ".docx"; |
| | | + (respVO.getCustomerName() != null ? "-" + respVO.getCustomerName() : "") + ".xlsx"; |
| | | String encoded = URLEncoder.encode(filename, StandardCharsets.UTF_8).replace("+", "%20"); |
| | | response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document"); |
| | | response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
| | | response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + encoded); |
| | | response.getOutputStream().write(content); |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; |
| | | import org.springframework.core.io.ClassPathResource; |
| | | import org.w3c.dom.*; |
| | | |
| | | import javax.xml.XMLConstants; |
| | | import javax.xml.parsers.DocumentBuilderFactory; |
| | | import javax.xml.transform.TransformerFactory; |
| | |
| | | import java.util.*; |
| | | import java.util.zip.*; |
| | | |
| | | /** ä¿çç¨æ·å忍¡æ¿ç OOXML å
ï¼ä»
å¡«åä¸å¡æ§½ä½å¹¶æ©å±äº§åæç»è¡ã */ |
| | | /** å¨éå®å忍¡æ¿ä¸å¡«åååä¿¡æ¯ï¼ä¿ç模æ¿çå
¶ä»å
å®¹åæ ¼å¼ã */ |
| | | public final class CrmContractDocumentRenderer { |
| | | private static final String W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; |
| | | private CrmContractDocumentRenderer() { } |
| | |
| | | factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); |
| | | factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); |
| | | var document = factory.newDocumentBuilder().parse(new ByteArrayInputStream(xml)); |
| | | Node rowTemplate = null; |
| | | var rows = document.getElementsByTagNameNS(W, "tr"); |
| | | for (int i = 0; i < rows.getLength(); i++) { |
| | | if (rows.item(i).getTextContent().contains("${itemName}")) { rowTemplate = rows.item(i); break; } |
| | | var paragraphs = document.getElementsByTagNameNS(W, "p"); |
| | | boolean numberFilled = false, dateFilled = false, buyerFilled = false; |
| | | for (int i = 0; i < paragraphs.getLength(); i++) { |
| | | Node p = paragraphs.item(i); |
| | | String value = p.getTextContent(); |
| | | if (value.contains("ååç¼å·ï¼SHHT20260102902-F")) { |
| | | setParagraphText(p, "ååç¼å·ï¼" + text(contract.getNo())); |
| | | alignRight(p); |
| | | numberFilled = true; |
| | | } else if (value.trim().equals("ç¾è®¢æ¥æï¼")) { |
| | | setParagraphText(p, "ç¾è®¢æ¥æï¼" + date(contract.getOrderDate())); |
| | | alignRight(p); |
| | | dateFilled = true; |
| | | } else if (value.contains("ä¹°æ¹ï¼ç²æ¹ï¼ï¼") && value.contains("ç¾è®¢å°ç¹ï¼æ±èåé")) { |
| | | fillBuyer(p, text(contract.getCustomerName())); |
| | | buyerFilled = true; |
| | | } else if (value.equals("乿¹ï¼çç« ï¼ï¼")) { |
| | | // 忍¡æ¿ç空ç½ä¹°æ¹ç¾ç« æ¡è¯¯åä¸ºä¹æ¹ï¼åæ¶å¤ç DrawingML å VML 两个å
¼å®¹åæ¯ã |
| | | setParagraphText(p, "ç²æ¹ï¼çç« ï¼ï¼" + text(contract.getCustomerName())); |
| | | } |
| | | if (rowTemplate == null) throw new IOException("å忍¡æ¿ç¼ºå°äº§åæç»æ§½ä½"); |
| | | int index = 0; |
| | | } |
| | | if (!numberFilled || !dateFilled || !buyerFilled) throw new IOException("éå®å忍¡æ¿è¡¨å¤´ä¸å®æ´"); |
| | | |
| | | Node table = document.getElementsByTagNameNS(W, "tbl").item(0); |
| | | List<Node> rows = children(table, "tr"); |
| | | if (rows.size() != 4 || children(rows.get(1), "tc").size() != 7 |
| | | || !rows.get(3).getTextContent().contains("å计ï¼å«13%å¢å¼ç¨ï¼")) { |
| | | throw new IOException("éå®å忍¡æ¿äº§åè¡¨æ ¼ä¸å®æ´"); |
| | | } |
| | | Node rowTemplate = rows.get(1); |
| | | BigDecimal productTotal = BigDecimal.ZERO; |
| | | int index = 0; |
| | | for (var product : contract.getProducts()) { |
| | | Node row = rowTemplate.cloneNode(true); |
| | | BigDecimal price = Objects.requireNonNull(product.getContractPrice(), "åååä»·ä¸è½ä¸ºç©º"); |
| | |
| | | productTotal = productTotal.add(amount); |
| | | String name = text(product.getItemName()); |
| | | if (name.isBlank()) name = text(product.getItemCode()); |
| | | String specification = text(product.getItemSpecification()); |
| | | if (!specification.isBlank()) name += "ï¼" + specification + "ï¼"; |
| | | replace(row, Map.of("index", String.valueOf(++index), "itemName", name, |
| | | "unitName", text(product.getItemUnitName()), "count", count.stripTrailingZeros().toPlainString(), |
| | | "price", money(price), "amount", money(amount))); |
| | | List<Node> cells = children(row, "tc"); |
| | | String[] values = {String.valueOf(++index), name, text(product.getItemSpecification()), |
| | | text(product.getItemUnitName()), count.stripTrailingZeros().toPlainString(), money(price), money(amount)}; |
| | | for (int i = 0; i < values.length; i++) { |
| | | Node cell = cells.get(i); |
| | | Node properties = firstChild(cell, "tcPr"); |
| | | Node noWrap = firstChild(properties, "noWrap"); |
| | | if (noWrap != null) properties.removeChild(noWrap); |
| | | setParagraphText(firstChild(cell, "p"), values[i]); |
| | | } |
| | | Node rowProperties = firstChild(row, "trPr"); |
| | | if (firstChild(rowProperties, "cantSplit") == null) { |
| | | rowProperties.appendChild(document.createElementNS(W, "w:cantSplit")); |
| | | } |
| | | rowTemplate.getParentNode().insertBefore(row, rowTemplate); |
| | | } |
| | | rowTemplate.getParentNode().removeChild(rowTemplate); |
| | | BigDecimal total = Objects.requireNonNull(contract.getTotalPrice(), "ååæ»éé¢ä¸è½ä¸ºç©º"); |
| | | BigDecimal discount = productTotal.subtract(total); |
| | | replace(document, Map.of("customerName", text(contract.getCustomerName()), |
| | | "contractNo", text(contract.getNo()), |
| | | "contractName", text(contract.getName()), |
| | | "contractDate", contract.getOrderDate() == null ? "____________" : contract.getOrderDate().format(DateTimeFormatter.ofPattern("yyyyå¹´MMæddæ¥")), |
| | | "startDate", date(contract.getStartTime()), |
| | | "endDate", date(contract.getEndTime()), |
| | | "totalPrice", money(total), "totalUpper", Convert.digitToChinese(total), |
| | | "discountNote", discount.signum() == 0 ? "" : "ï¼æç»å计 " + money(productTotal) + " å
ï¼ä¸ååéé¢çå·®é¢ " + money(discount) + " å
ï¼")); |
| | | if (document.getDocumentElement().getTextContent().contains("${")) throw new IOException("å忍¡æ¿å卿ªå¡«åæ§½ä½"); |
| | | String summary = "å计ï¼å«13%å¢å¼ç¨ï¼ 人æ°å¸ï¼" + money(total) + "å
大åï¼" + Convert.digitToChinese(total); |
| | | if (discount.signum() != 0) summary += "ï¼æç»å计 " + money(productTotal) + " å
ï¼ä¸ååéé¢çå·®é¢ " + money(discount) + " å
ï¼"; |
| | | Node totalCell = children(rows.get(3), "tc").get(0); |
| | | Node totalProperties = firstChild(totalCell, "tcPr"); |
| | | Node noWrap = firstChild(totalProperties, "noWrap"); |
| | | if (noWrap != null) totalProperties.removeChild(noWrap); |
| | | setParagraphText(firstChild(totalCell, "p"), summary); |
| | | var transformerFactory = TransformerFactory.newInstance(); |
| | | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); |
| | | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, ""); |
| | |
| | | } catch (Exception exception) { throw new IOException("çæåå Word æä»¶å¤±è´¥", exception); } |
| | | } |
| | | |
| | | private static void replace(Node node, Map<String, String> values) { |
| | | if (node.getNodeType() == Node.ELEMENT_NODE && "t".equals(node.getLocalName()) && W.equals(node.getNamespaceURI())) { |
| | | private static void fillBuyer(Node paragraph, String buyer) { |
| | | var nodes = ((Element) paragraph).getElementsByTagNameNS(W, "t"); |
| | | boolean afterColon = false, inserted = false; |
| | | for (int i = 0; i < nodes.getLength(); i++) { |
| | | Node node = nodes.item(i); |
| | | String value = node.getTextContent(); |
| | | for (var entry : values.entrySet()) value = value.replace("${" + entry.getKey() + "}", entry.getValue()); |
| | | node.setTextContent(value); |
| | | return; |
| | | if (value.contains("ç¾è®¢å°ç¹ï¼")) break; |
| | | if (afterColon) { |
| | | node.setTextContent(inserted ? "" : buyer + " "); |
| | | inserted = true; |
| | | } else if (value.equals("ï¼")) afterColon = true; |
| | | } |
| | | for (Node child = node.getFirstChild(); child != null; child = child.getNextSibling()) replace(child, values); |
| | | if (!inserted) throw new IllegalStateException("å忍¡æ¿ç¼ºå°ä¹°æ¹å¡«åä½ç½®"); |
| | | } |
| | | |
| | | private static void alignRight(Node paragraph) { |
| | | Node properties = firstChild(paragraph, "pPr"); |
| | | Node indent = firstChild(properties, "ind"); |
| | | if (indent != null) properties.removeChild(indent); |
| | | Node alignment = firstChild(properties, "jc"); |
| | | if (alignment == null) { |
| | | alignment = paragraph.getOwnerDocument().createElementNS(W, "w:jc"); |
| | | properties.appendChild(alignment); |
| | | } |
| | | ((Element) alignment).setAttributeNS(W, "w:val", "right"); |
| | | } |
| | | |
| | | private static void setParagraphText(Node paragraph, String value) { |
| | | var nodes = ((Element) paragraph).getElementsByTagNameNS(W, "t"); |
| | | if (nodes.getLength() == 0) { |
| | | var document = paragraph.getOwnerDocument(); |
| | | Node run = document.createElementNS(W, "w:r"); |
| | | Node text = document.createElementNS(W, "w:t"); |
| | | run.appendChild(text); |
| | | paragraph.appendChild(run); |
| | | text.setTextContent(value); |
| | | } else { |
| | | nodes.item(0).setTextContent(value); |
| | | for (int i = 1; i < nodes.getLength(); i++) nodes.item(i).setTextContent(""); |
| | | } |
| | | } |
| | | |
| | | private static List<Node> children(Node parent, String localName) { |
| | | List<Node> result = new ArrayList<>(); |
| | | if (parent == null) return result; |
| | | for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { |
| | | if (W.equals(node.getNamespaceURI()) && localName.equals(node.getLocalName())) result.add(node); |
| | | } |
| | | return result; |
| | | } |
| | | private static Node firstChild(Node parent, String localName) { |
| | | List<Node> result = children(parent, localName); |
| | | return result.isEmpty() ? null : result.get(0); |
| | | } |
| | | private static String money(BigDecimal value) { return value.setScale(2, RoundingMode.HALF_UP).toPlainString(); } |
| | | private static String date(java.time.LocalDateTime value) { return value == null ? "____________" : value.format(DateTimeFormatter.ofPattern("yyyyå¹´MMæddæ¥")); } |
| | | private static String text(String value) { return value == null ? "" : value; } |
| | |
| | | * æå
¬å¸åç§°å®æ¶ç»å¶çº¢è²åå½¢çµåç« ï¼ååç¯ + 䏿¹ç¯ç»å
¬å¸å + ä¸å¤®äºè§æ + 䏿¹âæ¥ä»·ä¸ç¨ç« âã |
| | | * ä¸ä¾èµä»»ä½éæç« å¾çï¼é¿å
æ¢å
¬å¸åçåä»éåç¬ç»´æ¤å¾çèµæºã |
| | | */ |
| | | private static byte[] renderSeal(String companyName) throws IOException { |
| | | static byte[] renderSeal(String companyName) throws IOException { |
| | | int size = 400; |
| | | var image = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); |
| | | Graphics2D g = image.createGraphics(); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.quotation; |
| | | |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationRespVO; |
| | | import org.apache.poi.ss.usermodel.Cell; |
| | | import org.apache.poi.ss.usermodel.Workbook; |
| | | import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| | | import org.springframework.core.io.ClassPathResource; |
| | | |
| | | import java.io.ByteArrayOutputStream; |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.time.format.DateTimeFormatter; |
| | | |
| | | /** 使ç¨å®¢æ·æä¾ç Excel 模æ¿çæåå¼ é宿¥ä»·åã */ |
| | | public final class CrmSaleQuotationExcelRenderer { |
| | | |
| | | private static final String TEMPLATE = "templates/quotation/sale-quotation.xlsx"; |
| | | private static final String DEFAULT_COMPANY = "èç©çº¿ç¼åéæéå
¬å¸"; |
| | | |
| | | private CrmSaleQuotationExcelRenderer() { |
| | | } |
| | | |
| | | public static byte[] render(CrmSaleQuotationRespVO quotation, String companyName, |
| | | String address, String contactName, String contactPhone) throws IOException { |
| | | if (quotation == null || quotation.getItems() == null || quotation.getItems().isEmpty()) { |
| | | throw new IllegalArgumentException("æ¥ä»·åä¸åå¨ææ²¡æäº§åæç»ï¼æ æ³çææ¥ä»·æä»¶"); |
| | | } |
| | | String company = companyName == null || companyName.isBlank() ? DEFAULT_COMPANY : companyName; |
| | | try (var input = new ClassPathResource(TEMPLATE).getInputStream(); |
| | | var workbook = new XSSFWorkbook(input); |
| | | var output = new ByteArrayOutputStream()) { |
| | | var sheet = workbook.getSheetAt(0); |
| | | int extraRows = quotation.getItems().size() - 1; |
| | | if (extraRows > 0) { |
| | | sheet.shiftRows(11, sheet.getLastRowNum(), extraRows, true, false); |
| | | } |
| | | var templateRow = sheet.getRow(10); |
| | | for (int i = 0; i < quotation.getItems().size(); i++) { |
| | | var row = i == 0 ? templateRow : sheet.createRow(10 + i); |
| | | if (i > 0) { |
| | | row.setHeight(templateRow.getHeight()); |
| | | for (int col = 0; col < 8; col++) { |
| | | row.createCell(col).setCellStyle(templateRow.getCell(col).getCellStyle()); |
| | | } |
| | | } |
| | | var item = quotation.getItems().get(i); |
| | | string(row.getCell(0), item.getItemName() == null || item.getItemName().isBlank() |
| | | ? item.getItemCode() : item.getItemName()); |
| | | string(row.getCell(1), item.getItemSpecification()); |
| | | string(row.getCell(2), item.getItemUnitName()); |
| | | number(row.getCell(3), item.getCount()); |
| | | number(row.getCell(4), item.getQuotationPrice()); |
| | | BigDecimal amount = item.getTotalPrice(); |
| | | if (amount == null && item.getCount() != null && item.getQuotationPrice() != null) { |
| | | amount = item.getCount().multiply(item.getQuotationPrice()); |
| | | } |
| | | number(row.getCell(5), amount); |
| | | string(row.getCell(6), null); // è°ä»·æ²¡æå¯¹åºçç³»ç»å段 |
| | | string(row.getCell(7), item.getRemark()); |
| | | } |
| | | string(sheet.getRow(0).getCell(0), company); |
| | | string(sheet.getRow(2).getCell(0), "ç¼å·ï¼" + text(quotation.getNo())); |
| | | string(sheet.getRow(4).getCell(1), quotation.getCustomerName()); |
| | | string(sheet.getRow(5).getCell(1), address); |
| | | string(sheet.getRow(6).getCell(1), contactName); |
| | | string(sheet.getRow(7).getCell(1), contactPhone); |
| | | string(sheet.getRow(13 + extraRows).getCell(6), "æ¥ä»·åä½ï¼" + company); |
| | | string(sheet.getRow(14 + extraRows).getCell(6), "è ç³» 人ï¼" + text(quotation.getOwnerUserName())); |
| | | string(sheet.getRow(15 + extraRows).getCell(6), "æ¥ä»·æ¥æï¼" + (quotation.getQuotationTime() == null |
| | | ? "" : quotation.getQuotationTime().format(DateTimeFormatter.ofPattern("yyyyå¹´MMæddæ¥")))); |
| | | workbook.setPrintArea(0, 0, 7, 0, 17 + extraRows); |
| | | |
| | | int picture = workbook.addPicture(CrmSaleQuotationDocumentRenderer.renderSeal(company), Workbook.PICTURE_TYPE_PNG); |
| | | var anchor = workbook.getCreationHelper().createClientAnchor(); |
| | | anchor.setCol1(3); |
| | | anchor.setCol2(5); |
| | | anchor.setRow1(13 + extraRows); |
| | | anchor.setRow2(17 + extraRows); |
| | | sheet.createDrawingPatriarch().createPicture(anchor, picture); |
| | | workbook.write(output); |
| | | return output.toByteArray(); |
| | | } |
| | | } |
| | | |
| | | private static void string(Cell cell, String value) { |
| | | cell.setCellValue(text(value)); |
| | | } |
| | | |
| | | private static void number(Cell cell, BigDecimal value) { |
| | | if (value == null) { |
| | | cell.setBlank(); |
| | | } else { |
| | | cell.setCellValue(value.doubleValue()); |
| | | } |
| | | } |
| | | |
| | | private static String text(String value) { |
| | | return value == null ? "" : value; |
| | | } |
| | | } |
| | |
| | | String xml = new String(parts.get("word/document.xml"), StandardCharsets.UTF_8); |
| | | assertTrue(xml.contains("HT20261015001")); assertTrue(xml.contains("2026å¹´10æ15æ¥")); |
| | | assertTrue(xml.contains("10000.00")); assertTrue(xml.contains("壹ä¸å
æ´")); |
| | | assertTrue(xml.contains("å»å¹²å¬è«å¤èï¼2500级ä¸çï¼")); assertTrue(xml.contains("交ä»ä¸éªæ¶")); |
| | | assertFalse(xml.contains("å京åä»å ")); assertFalse(xml.contains("28504001040001454")); |
| | | assertTrue(xml.contains("å»å¹²å¬è«å¤è")); assertTrue(xml.contains("2500级ä¸ç")); assertTrue(xml.contains("äº¤è´§æ¡æ¬¾")); |
| | | assertTrue(xml.contains("è´¨éå¼è®®æé")); assertFalse(xml.contains("SHHT20260102902-F")); |
| | | assertTrue(xml.contains("ç²æ¹ï¼çç« ï¼ï¼ä¸å½ç§å¦é¢è¥¿åé«åçç©ç ç©¶æ")); |
| | | assertFalse(xml.contains("${")); |
| | | try(var template = new ClassPathResource("templates/contract/sale-contract.docx").getInputStream()) { |
| | | var original = unzip(template.readAllBytes()); assertEquals(original.keySet(),parts.keySet()); |
| | |
| | | contract.setTotalPrice(new BigDecimal("249000")); |
| | | String xml = new String(unzip(CrmContractDocumentRenderer.render(contract)).get("word/document.xml"),StandardCharsets.UTF_8); |
| | | assertTrue(xml.contains("客æ·<&>å
¬å¸")); assertTrue(xml.contains("ä¸ååéé¢çå·®é¢ 1000.00 å
")); |
| | | assertEquals(25,xml.split("å»å¹²å¬è«å¤èï¼2500级ä¸çï¼",-1).length-1); |
| | | assertEquals(25,xml.split("å»å¹²å¬è«å¤è",-1).length-1); |
| | | assertEquals(25,xml.split("<w:cantSplit",-1).length-1); |
| | | assertTrue(xml.contains("249000.00")); assertFalse(xml.contains("${")); |
| | | assertTrue(xml.contains("乿¹ï¼éæ¹ï¼ï¼å®¢æ·<&>å
¬å¸")); |
| | | assertTrue(xml.contains("ç²æ¹ï¼çç« ï¼ï¼å®¢æ·<&>å
¬å¸")); |
| | | String output = System.getProperty("contract.document.sample"); |
| | | if (output != null) Files.write(Path.of(output).resolveSibling("generated-contract-many.docx"), CrmContractDocumentRenderer.render(contract)); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.quotation; |
| | | |
| | | import cn.iocoder.yudao.module.crm.controller.admin.quotation.vo.CrmSaleQuotationRespVO; |
| | | import org.apache.poi.xssf.usermodel.XSSFWorkbook; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | |
| | | class CrmSaleQuotationExcelRendererTest { |
| | | |
| | | @Test |
| | | void fillsMultipleItemsAndPreservesTemplateTermsAndPrintArea() throws Exception { |
| | | var quote = new CrmSaleQuotationRespVO(); |
| | | quote.setNo("BJ-123"); |
| | | quote.setCustomerName("客æ·ç²"); |
| | | quote.setOwnerUserName("éå®å"); |
| | | quote.setQuotationTime(LocalDateTime.of(2026, 11, 10, 0, 0)); |
| | | var first = new CrmSaleQuotationRespVO.Item(); |
| | | first.setItemName("çµç¼A"); |
| | | first.setCount(new BigDecimal("2.5")); |
| | | first.setQuotationPrice(new BigDecimal("3.20")); |
| | | var second = new CrmSaleQuotationRespVO.Item(); |
| | | second.setItemName("çµç¼B"); |
| | | second.setRemark("夿³¨B"); |
| | | quote.setItems(List.of(first, second)); |
| | | |
| | | byte[] bytes = CrmSaleQuotationExcelRenderer.render(quote, "æµè¯å
¬å¸", "客æ·å°å", "è系人", "123456"); |
| | | try (var workbook = new XSSFWorkbook(new ByteArrayInputStream(bytes))) { |
| | | var sheet = workbook.getSheetAt(0); |
| | | assertEquals("ç¼å·ï¼BJ-123", sheet.getRow(2).getCell(0).getStringCellValue()); |
| | | assertEquals("客æ·ç²", sheet.getRow(4).getCell(1).getStringCellValue()); |
| | | assertEquals("客æ·å°å", sheet.getRow(5).getCell(1).getStringCellValue()); |
| | | assertEquals("è系人", sheet.getRow(6).getCell(1).getStringCellValue()); |
| | | assertEquals("123456", sheet.getRow(7).getCell(1).getStringCellValue()); |
| | | assertEquals(2.5, sheet.getRow(10).getCell(3).getNumericCellValue()); |
| | | assertEquals(8.0, sheet.getRow(10).getCell(5).getNumericCellValue()); |
| | | assertEquals("çµç¼B", sheet.getRow(11).getCell(0).getStringCellValue()); |
| | | assertEquals("夿³¨B", sheet.getRow(11).getCell(7).getStringCellValue()); |
| | | String terms = sheet.getRow(13).getCell(0).getStringCellValue(); |
| | | assertTrue(terms.contains("110å
/å
¬æ¤")); |
| | | assertTrue(terms.contains("è´§å°45天å
仿¬¾")); |
| | | assertEquals("æ¥ä»·æ¥æï¼2026å¹´11æ10æ¥", sheet.getRow(16).getCell(6).getStringCellValue()); |
| | | assertTrue(workbook.getPrintArea(0).contains("$H$19")); |
| | | assertEquals(2, workbook.getAllPictures().size()); |
| | | } |
| | | } |
| | | } |
| | |
| | | @Operation(summary = "å
³éä¸åæ ¼åå¤çå") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-ncr:close')") |
| | | public CommonResult<Boolean> closeNcr(@Valid @RequestBody MesQcNcrCloseReqVO closeReqVO) { |
| | | ncrService.closeNcr(closeReqVO.getId(), closeReqVO.getCloseOpinion()); |
| | | ncrService.closeNcr(closeReqVO.getId(), closeReqVO.getCloseOpinion(), closeReqVO.getReinspectionIpqcId()); |
| | | return success(true); |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseReturnDO; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ncr.MesQcPurchaseReturnService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.security.access.prepost.PreAuthorize; |
| | | import org.springframework.web.bind.annotation.*; |
| | | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| | | |
| | | @Tag(name = "管çåå° - æ¥æä¸åæ ¼éè´éè´§") |
| | | @RestController |
| | | @RequestMapping("/mes/qc-purchase-return") |
| | | public class MesQcPurchaseReturnController { |
| | | @Resource private MesQcPurchaseReturnService service; |
| | | |
| | | @GetMapping("/get-by-ncr") |
| | | @Operation(summary = "æä¸åæ ¼åæ¥çéè´éè´§å") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-ncr:query') or @ss.hasPermission('mes:wm-return-vendor:finish')") |
| | | public CommonResult<MesQcPurchaseReturnDO> getByNcr(@RequestParam Long ncrId) { |
| | | return success(service.getByNcrId(ncrId)); |
| | | } |
| | | |
| | | @GetMapping("/page") |
| | | @Operation(summary = "éè´ä»åºæ¥è¯¢å¾
éåå·²éåæ®") |
| | | @PreAuthorize("@ss.hasPermission('mes:wm-return-vendor:finish')") |
| | | public CommonResult<PageResult<MesQcPurchaseReturnDO>> page(PageParam page, @RequestParam(required = false) Integer status) { |
| | | return success(service.getPage(page, status)); |
| | | } |
| | | |
| | | @PutMapping("/confirm") |
| | | @Operation(summary = "éè´ä»åºç¡®è®¤å·²éåä¾åºå") |
| | | @PreAuthorize("@ss.hasPermission('mes:wm-return-vendor:finish')") |
| | | public CommonResult<Boolean> confirm(@RequestParam Long id, @RequestParam(required = false) String remark) { |
| | | service.confirm(id, remark); |
| | | return success(true); |
| | | } |
| | | } |
| | |
| | | @Schema(description = "å
³éæè§", example = "å¤ç½®å®æï¼äºä»¥å
³é") |
| | | private String closeOpinion; |
| | | |
| | | @Schema(description = "è¿å·¥æè¿ä¿®å®æåçæ´æ¹å¤æ£è¿ç¨æ£éªå ID") |
| | | private Long reinspectionIpqcId; |
| | | |
| | | } |
| | |
| | | @NotNull(message = "NCRç¼å·ä¸è½ä¸ºç©º") |
| | | private Long id; |
| | | |
| | | @Schema(description = "å¤ç½®æ¹å¼ï¼2-æ¥åº 3-è¿å·¥ 4-è®©æ¥æ¾è¡ 6-è¿ä¿®ï¼", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| | | @NotNull(message = "å¤ç½®æ¹å¼ä¸è½ä¸ºç©ºï¼è¯·ä»[è®©æ¥æ¾è¡ãè¿ä¿®ãè¿å·¥ãæ¥åº]ä¸éæ©ä¸é¡¹") |
| | | @Schema(description = "å¤ç½®æ¹å¼ï¼1éè´§ã2æ¥åºã3è¿å·¥ã4è®©æ¥æ¾è¡ã6è¿ä¿®ã7éæ°ç产ï¼å¯é项åå³äºæ£éªæ¥æº", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| | | @NotNull(message = "å¤ç½®æ¹å¼ä¸è½ä¸ºç©º") |
| | | private Integer disposition; |
| | | |
| | | @Schema(description = "å¤ç人ID", example = "2") |
| | |
| | | @Schema(description = "æ¹æ¬¡å·ï¼ç©æå¯ç¨æ¹æ¬¡ç®¡çæ¶å¿
å¡«ï¼ä¸åå¨ä¼èªå¨åå»ºæ¹æ¬¡ï¼", example = "BATCH20261109001") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "åºè´§è®©æ¥æ¾è¡æè¿å·¥/è¿ä¿®æ¶éå®çå®é
åºåè®°å½ ID") |
| | | private Long materialStockId; |
| | | |
| | | } |
| | |
| | | @ExcelIgnoreUnannotated |
| | | public class MesQcNcrRespVO { |
| | | |
| | | @Schema(description = "è¿å·¥æè¿ä¿®å
³éæ¶ä½¿ç¨ç夿£å ID") |
| | | private Long reinspectionIpqcId; |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | | @ExcelProperty("ç¼å·") |
| | | private Long id; |
| | |
| | | @Schema(description = "ç产æ¹å·", example = "PB20260110") |
| | | private String lotNumber; |
| | | |
| | | @Schema(description = "æå®åºåæ¹æ¬¡å·ï¼ä¸ºç©ºæ¶æè§åçæ") |
| | | private String batchCode; |
| | | |
| | | @Schema(description = "夿³¨", example = "夿³¨") |
| | | private String remark; |
| | | |
| | |
| | | */ |
| | | private String closeOpinion; |
| | | |
| | | /** å
³éè¿å·¥/è¿ä¿®åæ¶éç¨ç夿£è¯æ®ã */ |
| | | private Long reinspectionIpqcId; |
| | | |
| | | // ========== ç¶æä¸æµç¨ ========== |
| | | |
| | | /** |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr; |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import java.math.BigDecimal; |
| | | |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @TableName("mes_qc_purchase_concession") |
| | | public class MesQcPurchaseConcessionDO extends BaseDO { |
| | | @TableId private Long id; |
| | | private Long ncrId; |
| | | private Long iqcId; |
| | | private Long noticeId; |
| | | private Long noticeLineId; |
| | | private Long purchaseOrderItemId; |
| | | private Long receiptId; |
| | | private BigDecimal quantity; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** æªå
¥åºçæ¥æä¸åæ ¼åç´æ¥éåä¾åºåï¼ä¸äº§çåºååºåºã */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = true) |
| | | @TableName("mes_qc_purchase_return") |
| | | public class MesQcPurchaseReturnDO extends BaseDO { |
| | | @TableId private Long id; |
| | | private String code; |
| | | private Long ncrId; |
| | | private Long iqcId; |
| | | private Long noticeId; |
| | | private Long noticeLineId; |
| | | private Long purchaseOrderId; |
| | | private Long purchaseOrderItemId; |
| | | private Long vendorId; |
| | | private Long itemId; |
| | | private String vendorBatch; |
| | | private BigDecimal quantity; |
| | | /** 0 å¾
ä»åºéè´§ï¼1 ä»åºå·²ç¡®è®¤éè´§ã */ |
| | | private Integer status; |
| | | private Long confirmedBy; |
| | | private LocalDateTime confirmedAt; |
| | | private String confirmationRemark; |
| | | } |
| | |
| | | * æä¸¾ {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum} |
| | | */ |
| | | private Integer qualityStatus; |
| | | /** å¤ç½®ä¸åæ ¼äº§åºæä½¿ç¨ç NCRï¼ç©ºè¡¨ç¤ºå°æªå¤çã */ |
| | | private Long ncrDispositionId; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | |
| | | * æä¸¾ {@link MesWmQualityStatusEnum} |
| | | */ |
| | | private Integer qualityStatus; |
| | | /** ä¸åæ ¼åºè´§è¡çå¤ç½®å½å±ï¼é²æ¢å¤ä¸ª NCR éå¤å¤çã */ |
| | | private Long ncrDispositionId; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | |
| | | * æä¸¾ {@link cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum} |
| | | */ |
| | | private Integer qualityStatus; |
| | | /** å·²å
¨é转交ä¸åæ ¼å¤ççéè´§è¡ï¼åéè´§åä¸å¾å次å
¥åºè¯¥è¡ã */ |
| | | private Long ncrDispositionId; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | |
| | | return selectOne(MesQcNcrDO::getCode, code); |
| | | } |
| | | |
| | | /** 串è¡ååä¸å¤çåçç¶ææµè½¬åå
¶ä¸æ¸¸åæ®å建ã */ |
| | | default MesQcNcrDO selectByIdForUpdate(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcNcrDO>() |
| | | .eq(MesQcNcrDO::getId, id).last("FOR UPDATE")); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr; |
| | | 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.ncr.MesQcPurchaseConcessionDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface MesQcPurchaseConcessionMapper extends BaseMapperX<MesQcPurchaseConcessionDO> { |
| | | default MesQcPurchaseConcessionDO selectByIqcId(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcPurchaseConcessionDO>() |
| | | .eq(MesQcPurchaseConcessionDO::getIqcId, id)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseReturnDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface MesQcPurchaseReturnMapper extends BaseMapperX<MesQcPurchaseReturnDO> { |
| | | default MesQcPurchaseReturnDO selectByNcrId(Long ncrId) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcPurchaseReturnDO>() |
| | | .eq(MesQcPurchaseReturnDO::getNcrId, ncrId)); |
| | | } |
| | | default MesQcPurchaseReturnDO selectByIdForUpdate(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcPurchaseReturnDO>() |
| | | .eq(MesQcPurchaseReturnDO::getId, id).last("FOR UPDATE")); |
| | | } |
| | | default MesQcPurchaseReturnDO selectConfirmedByIqcId(Long iqcId) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcPurchaseReturnDO>() |
| | | .eq(MesQcPurchaseReturnDO::getIqcId, iqcId) |
| | | .eq(MesQcPurchaseReturnDO::getStatus, 1).last("LIMIT 1")); |
| | | } |
| | | default MesQcPurchaseReturnDO selectByIqcId(Long iqcId) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcPurchaseReturnDO>() |
| | | .eq(MesQcPurchaseReturnDO::getIqcId, iqcId)); |
| | | } |
| | | default PageResult<MesQcPurchaseReturnDO> selectPage(PageParam page, Integer status) { |
| | | return selectPage(page, new LambdaQueryWrapperX<MesQcPurchaseReturnDO>() |
| | | .eqIfPresent(MesQcPurchaseReturnDO::getStatus, status) |
| | | .orderByDesc(MesQcPurchaseReturnDO::getId)); |
| | | } |
| | | } |
| | |
| | | .eq(MesWmReturnSalesDO::getCode, code)); |
| | | } |
| | | |
| | | default MesWmReturnSalesDO selectByIdForUpdate(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<MesWmReturnSalesDO>() |
| | | .eq(MesWmReturnSalesDO::getId, id).last("FOR UPDATE")); |
| | | } |
| | | |
| | | } |
| | |
| | | REWORK(3, "è¿å·¥"), |
| | | CONCESSION(4, "è®©æ¥æ¾è¡"), |
| | | DOWNGRADE(5, "é级使ç¨"), |
| | | REPAIR(6, "è¿ä¿®"); |
| | | REPAIR(6, "è¿ä¿®"), |
| | | REPRODUCTION(7, "éæ°ç产"); |
| | | |
| | | public static final Integer[] ARRAYS = Arrays.stream(values()) |
| | | .map(MesNcrDispositionEnum::getType).toArray(Integer[]::new); |
| | |
| | | public enum MesWmMiscIssueTypeEnum implements ArrayValuable<Integer> { |
| | | |
| | | ADJUST(1, "åºåè°æ´"), |
| | | SCRAP(2, "æ¥åºåºåº"); |
| | | SCRAP(2, "æ¥åºåºåº"), |
| | | REWORK(3, "è¿å·¥é¢ç¨"); |
| | | |
| | | private final Integer type; |
| | | private final String name; |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | import java.math.BigDecimal; |
| | |
| | | /** ææ°éæ±æ»è´¨æ£ç»æï¼ä¸æ¨æä¸åæ ¼åçåç»å¤ç½®æ¹å¼ã */ |
| | | @Component |
| | | public class MesQcCompletionHelper { |
| | | @Resource private StorageAttachmentApi storageApi; |
| | | @Resource private org.springframework.jdbc.core.JdbcTemplate jdbcTemplate; |
| | | |
| | | public void lockDocument(String type, Long id) { |
| | |
| | | if (total.signum() <= 0 || checked == null || total.compareTo(checked) != 0) { |
| | | throw new ServiceException(400, "åæ ¼æ°éä¸ä¸åæ ¼æ°éä¹å须大äºé¶ä¸çäºæ£æµæ°é"); |
| | | } |
| | | var attachments = storageApi.listAttachments("mes_qc_" + type + "_attachment", id); |
| | | if (attachments == null || attachments.isEmpty()) throw new ServiceException(400, "请å
ä¸ä¼ æ£éªéä»¶"); |
| | | // æ£éªéä»¶æ¹ä¸ºéå¿
å¡«ï¼å®ææ£éªæ¶ä¸åå¼ºå¶æ ¡éªæ¯å¦å·²ä¸ä¼ ã |
| | | // ä¸ç°æ mes_qc_check_result åå
¸ä¸è´ï¼1 éè¿ï¼2 ä¸éè¿ã |
| | | return unqualified.signum() == 0 ? 1 : 2; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseReturnMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseConcessionMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import java.math.BigDecimal; |
| | | import java.util.Objects; |
| | | |
| | | /** 便®åè¡åå§è´¨æ£ç»è®ºåçå®éè´§ç¡®è®¤éæ°è®¡ç®å°è´§åç¶æã */ |
| | | @Service |
| | | public class MesQcArrivalDispositionService { |
| | | @Resource private MesWmArrivalNoticeMapper arrivalNoticeMapper; |
| | | @Resource private MesWmArrivalNoticeLineMapper lineMapper; |
| | | @Resource private MesQcPurchaseReturnMapper returnMapper; |
| | | @Resource private MesQcPurchaseConcessionMapper concessionMapper; |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void refresh(Long noticeId) { |
| | | var notice = arrivalNoticeMapper.selectOne(new LambdaQueryWrapperX<MesWmArrivalNoticeDO>() |
| | | .eq(MesWmArrivalNoticeDO::getId, noticeId).last("FOR UPDATE")); |
| | | if (notice == null || !Objects.equals(notice.getStatus(), MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus())) return; |
| | | var lines = lineMapper.selectListByNoticeId(noticeId); |
| | | if (lines.isEmpty()) return; |
| | | boolean hasReceiptQuantity = false; |
| | | for (var line : lines) { |
| | | if (Boolean.TRUE.equals(line.getIqcCheckFlag())) { |
| | | if (line.getIqcId() == null || line.getIqcCheckResult() == null || line.getQualifiedQuantity() == null) return; |
| | | if (!MesQcCheckResultEnum.canInbound(line.getIqcCheckResult())) { |
| | | var returned = returnMapper.selectConfirmedByIqcId(line.getIqcId()); |
| | | var concession = concessionMapper.selectByIqcId(line.getIqcId()); |
| | | boolean returnDone = returned != null && Objects.equals(returned.getNoticeLineId(), line.getId()); |
| | | boolean concessionDone = concession != null && Objects.equals(concession.getNoticeLineId(), line.getId()); |
| | | if (!returnDone && !concessionDone) return; |
| | | } |
| | | } |
| | | BigDecimal quantity = line.getQualifiedQuantity() != null ? line.getQualifiedQuantity() : line.getArrivalQuantity(); |
| | | if (quantity == null || quantity.signum() < 0) return; |
| | | if (quantity.signum() > 0) hasReceiptQuantity = true; |
| | | } |
| | | arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO().setId(noticeId) |
| | | .setStatus(hasReceiptQuantity ? MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus() |
| | | : MesWmArrivalNoticeStatusEnum.FINISHED.getStatus())); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.detail.MesWmProductSalesDetailSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesDetailService; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | import java.util.Objects; |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | /** å°è®©æ¥å³å®ååååºåºè¡ï¼å¿
é¡»ç± NCR å¤ç½®äºå¡è°ç¨ã */ |
| | | @Component |
| | | public class MesQcNcrOqcConcessionService { |
| | | @Resource private MesQcOqcMapper oqcMapper; |
| | | @Resource private MesWmProductSalesMapper salesMapper; |
| | | @Resource private MesWmProductSalesLineMapper lineMapper; |
| | | @Resource private MesWmProductSalesLineService lineService; |
| | | @Resource private MesWmTransactionMapper transactionMapper; |
| | | @Resource private MesWmMaterialStockService stockService; |
| | | @Resource private MesWmProductSalesDetailService detailService; |
| | | @Resource private MesWmProductSalesService salesService; |
| | | |
| | | public void release(MesQcNcrDO ncr, MesQcNcrDisposeReqVO req) { |
| | | MesQcOqcDO qc = oqcMapper.selectById(ncr.getQcId()); |
| | | if (qc == null || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES) |
| | | || qc.getSourceDocId() == null || qc.getSourceLineId() == null || StrUtil.isBlank(qc.getBatchCode()) |
| | | || !Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus()) |
| | | || !Objects.equals(qc.getCheckResult(), 2) || !Objects.equals(qc.getItemId(), ncr.getItemId()) |
| | | || qc.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity() == null |
| | | || qc.getUnqualifiedQuantity().signum() <= 0 |
| | | || qc.getUnqualifiedQuantity().compareTo(ncr.getUnqualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, "åºè´§è®©æ¥éå
³è已宿çä¸åæ ¼åºè´§æ£éªãååºåºè¡åæç¡®æ¹æ¬¡ï¼ç©æåä¸åæ ¼æ°éé¡»ä¸è´"); |
| | | } |
| | | MesWmProductSalesDO sales = salesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>() |
| | | .eq(MesWmProductSalesDO::getId, qc.getSourceDocId()).last("FOR UPDATE")); |
| | | MesWmProductSalesLineDO line = lineMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesLineDO>() |
| | | .eq(MesWmProductSalesLineDO::getId, qc.getSourceLineId()).last("FOR UPDATE")); |
| | | if (sales == null || line == null || !Objects.equals(sales.getStatus(), MesWmProductSalesStatusEnum.CONFIRMED.getStatus()) |
| | | || !Objects.equals(line.getSalesId(), sales.getId()) || !Objects.equals(line.getOqcId(), qc.getId()) |
| | | || !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | || !Objects.equals(line.getItemId(), qc.getItemId()) || line.getQuantity() == null |
| | | || qc.getCheckQuantity() == null || line.getQuantity().signum() <= 0 |
| | | || qc.getQualifiedQuantity() == null || qc.getQualifiedQuantity().signum() < 0 |
| | | || qc.getQualifiedQuantity().add(qc.getUnqualifiedQuantity()).compareTo(qc.getCheckQuantity()) != 0 |
| | | || (StrUtil.isNotBlank(line.getBatchCode()) && !Objects.equals(line.getBatchCode(), qc.getBatchCode())) |
| | | || qc.getCheckQuantity().compareTo(line.getQuantity()) != 0) { |
| | | throw new ServiceException(400, "åºè´§è®©æ¥è¦æ±ååºåºåå¤äºå¾
æ£éªç¶æï¼åè¡ä»ç»å®æ¬æ¬¡ä¸åæ ¼æ£éªä¸æ£æµæ°éè¦çæ´è¡"); |
| | | } |
| | | MesWmMaterialStockDO stock = req.getMaterialStockId() == null ? null : stockService.getMaterialStock(req.getMaterialStockId()); |
| | | if (stock == null || !Objects.equals(stock.getItemId(), qc.getItemId()) |
| | | || !Objects.equals(stock.getBatchCode(), qc.getBatchCode()) |
| | | || (line.getMaterialStockId() != null && !Objects.equals(line.getMaterialStockId(), stock.getId())) |
| | | || stock.getWarehouseId() == null || stock.getLocationId() == null || stock.getAreaId() == null) { |
| | | throw new ServiceException(400, "è¯·éæ©åæ£éªæ¹æ¬¡çå®é
åºåå宿´åºä½ï¼åè¡æå®åºåæ¶å¿
é¡»éæ©è¯¥åºå"); |
| | | } |
| | | var currentDetails = detailService.getProductSalesDetailListByLineId(line.getId()); |
| | | if (!currentDetails.isEmpty()) { |
| | | throw new ServiceException(400, "该åºåºè¡å·²ææ£è´§æç»ï¼è¯·å
æ ¸å¯¹å¹¶æ¸
çåéæ°éæ©å®é
åºå"); |
| | | } |
| | | // å
¶ä»è¡ä¹å¿
须宿´ä¸å¯åºåºï¼æ¬æä½ä»¥æ´å¼ éå®åºåºå为äºå¡è¾¹çã |
| | | for (MesWmProductSalesLineDO other : lineService.getProductSalesLineListBySalesId(sales.getId())) { |
| | | if (Objects.equals(other.getId(), line.getId())) continue; |
| | | if (Boolean.TRUE.equals(other.getOqcCheckFlag()) |
| | | && !Objects.equals(other.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus())) { |
| | | throw new ServiceException(400, "åä¸éå®åºåºåè¿ææªéè¿è´¨æ£çè¡ï¼ä¸è½æ´ååºåº"); |
| | | } |
| | | var details = detailService.getProductSalesDetailListByLineId(other.getId()); |
| | | BigDecimal total = details.stream().map(MesWmProductSalesDetailDO::getQuantity) |
| | | .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | if (details.isEmpty() || other.getQuantity() == null || other.getQuantity().signum() <= 0 |
| | | || total.compareTo(other.getQuantity()) != 0 |
| | | || details.stream().anyMatch(d -> d.getQuantity() == null || d.getQuantity().signum() <= 0 |
| | | || !Objects.equals(d.getSalesId(), sales.getId()) || !Objects.equals(d.getLineId(), other.getId()) |
| | | || !Objects.equals(d.getItemId(), other.getItemId()))) { |
| | | throw new ServiceException(400, "åä¸éå®åºåºåå
¶ä»è¡çæ£è´§æç»æªå¤é½ï¼ä¸è½æ´ååºåº"); |
| | | } |
| | | } |
| | | lineService.updateProductSalesLineWhenOqcFinish(line.getId(), qc.getId(), 1); |
| | | MesWmProductSalesDetailSaveReqVO detail = new MesWmProductSalesDetailSaveReqVO(); |
| | | detail.setSalesId(sales.getId()); detail.setLineId(line.getId()); detail.setItemId(qc.getItemId()); |
| | | detail.setMaterialStockId(stock.getId()); detail.setQuantity(line.getQuantity()); |
| | | detail.setBatchId(stock.getBatchId()); detail.setBatchCode(stock.getBatchCode()); |
| | | detail.setWarehouseId(stock.getWarehouseId()); detail.setLocationId(stock.getLocationId()); detail.setAreaId(stock.getAreaId()); |
| | | detailService.createProductSalesDetail(detail); |
| | | if (!salesService.checkProductSalesQuantity(sales.getId())) { |
| | | throw new ServiceException(400, "éå®åºåºåæç»æ°éä¸å®æ´ï¼ä¸è½æ´ååºåº"); |
| | | } |
| | | salesService.stockProductSales(sales.getId()); |
| | | MesWmProductSalesShippingReqVO shipping = new MesWmProductSalesShippingReqVO(); |
| | | shipping.setId(sales.getId()); |
| | | salesService.shippingProductSales(shipping); |
| | | salesService.finishProductSales(sales.getId()); |
| | | } |
| | | |
| | | /** å®é
åºåºåæè½å
³é让æ¥åï¼ä¸æ¹ååå§ä¸åæ ¼æ£éªç»è®ºã */ |
| | | public void validateBeforeClose(MesQcNcrDO ncr) { |
| | | MesQcOqcDO qc = oqcMapper.selectById(ncr.getQcId()); |
| | | if (qc == null || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES) |
| | | || qc.getSourceDocId() == null || qc.getSourceLineId() == null || StrUtil.isBlank(qc.getBatchCode()) |
| | | || !Objects.equals(qc.getItemId(), ncr.getItemId()) |
| | | || qc.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity() == null |
| | | || qc.getUnqualifiedQuantity().signum() <= 0 |
| | | || qc.getUnqualifiedQuantity().compareTo(ncr.getUnqualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, "æ æ³æ ¸éªè®©æ¥åºè´§çæ¥æºæ£éªåååæ¹æ¬¡"); |
| | | } |
| | | MesWmProductSalesDO sales = salesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>() |
| | | .eq(MesWmProductSalesDO::getId, qc.getSourceDocId()).last("FOR UPDATE")); |
| | | MesWmProductSalesLineDO line = lineMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesLineDO>() |
| | | .eq(MesWmProductSalesLineDO::getId, qc.getSourceLineId()).last("FOR UPDATE")); |
| | | if (sales == null || line == null || !Objects.equals(sales.getStatus(), MesWmProductSalesStatusEnum.FINISHED.getStatus()) |
| | | || !Objects.equals(line.getSalesId(), sales.getId()) || !Objects.equals(line.getOqcId(), qc.getId()) |
| | | || !Objects.equals(line.getItemId(), qc.getItemId()) || line.getQuantity() == null |
| | | || qc.getCheckQuantity() == null || line.getQuantity().compareTo(qc.getCheckQuantity()) != 0) { |
| | | throw new ServiceException(400, "åéå®åºåºåå°æªå®æï¼æåºåºè¡ä¸æ£éªæ¥æºä¸ä¸è´ï¼ä¸è½å
³é"); |
| | | } |
| | | List<MesWmTransactionDO> flows = transactionMapper.selectList(new LambdaQueryWrapperX<MesWmTransactionDO>() |
| | | .eq(MesWmTransactionDO::getBizType, MesBizTypeConstants.WM_PRODUCT_SALES) |
| | | .eq(MesWmTransactionDO::getBizId, sales.getId()).eq(MesWmTransactionDO::getBizLineId, line.getId()) |
| | | .eq(MesWmTransactionDO::getType, MesWmTransactionTypeEnum.OUT.getType())); |
| | | BigDecimal out = BigDecimal.ZERO; |
| | | for (MesWmTransactionDO flow : flows) { |
| | | if (!Objects.equals(flow.getItemId(), qc.getItemId()) || !Objects.equals(flow.getBatchCode(), qc.getBatchCode()) |
| | | || flow.getQuantity() == null || flow.getQuantity().signum() >= 0) { |
| | | throw new ServiceException(400, "åºåºæµæ°´çç©æãæ¹æ¬¡ææ°éä¸è®©æ¥æ£éªä¸ä¸è´"); |
| | | } |
| | | out = out.subtract(flow.getQuantity()); |
| | | } |
| | | if (out.compareTo(line.getQuantity()) != 0) { |
| | | throw new ServiceException(400, "åéå®åºåºæµæ°´æ°éä¸å®æ´ï¼ä¸è½å
³é让æ¥å"); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.MesProWorkOrderSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGenerateReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderBomMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper; |
| | | 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.pro.MesProWorkOrderSourceTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | 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.pro.workorder.MesProWorkOrderProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDateTime; |
| | | import java.util.Arrays; |
| | | import java.util.Objects; |
| | | |
| | | /** åºè´§æè¿ç¨ä¸åæ ¼éæ°ç产ï¼å¤å¶æ¥æºå·¥åçå·¥åºå BOM å¿«ç
§ã */ |
| | | @Service |
| | | public class MesQcNcrOqcReproductionService { |
| | | @Resource private MesQcOqcMapper oqcMapper; |
| | | @Resource private MesWmProductSalesLineMapper salesLineMapper; |
| | | @Resource private MesWmProductSalesMapper salesMapper; |
| | | @Resource private MesWmProductProduceLineMapper produceLineMapper; |
| | | @Resource private MesQcNcrScrapService sourceService; |
| | | @Resource private MesWmBatchMapper batchMapper; |
| | | @Resource private MesProWorkOrderMapper orderMapper; |
| | | @Resource private MesProWorkOrderBomMapper bomMapper; |
| | | @Resource private MesMdItemMapper itemMapper; |
| | | @Resource private MesProWorkOrderService orderService; |
| | | @Resource private MesProWorkOrderProcessService processService; |
| | | @Resource private MesProTaskService taskService; |
| | | @Resource private MesMdAutoCodeRecordService autoCodeService; |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcNcrDisposeRespVO reproduce(MesQcNcrDO ncr, Integer disposition) { |
| | | if (!Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType()) || ncr.getQcId() == null |
| | | || ncr.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity().signum() <= 0) |
| | | throw error("éæ°ç产éè¦å
³èåºè´§è´¨æ£åææä¸åæ ¼æ°é"); |
| | | var qc = oqcMapper.selectById(ncr.getQcId()); |
| | | if (qc == null || !Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus()) |
| | | || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES) |
| | | || qc.getSourceDocId() == null || qc.getSourceLineId() == null |
| | | || !Objects.equals(qc.getItemId(), ncr.getItemId()) || !same(qc.getUnqualifiedQuantity(), ncr.getUnqualifiedQuantity())) |
| | | throw error("åºè´§æ£éªæ¥æºãç©ææä¸åæ ¼æ°éä¸ä¸è´ï¼æ æ³éæ°ç产"); |
| | | var line = salesLineMapper.selectById(qc.getSourceLineId()); |
| | | var sales = salesMapper.selectById(qc.getSourceDocId()); |
| | | if (line == null || sales == null || !Objects.equals(line.getSalesId(), sales.getId()) |
| | | || !Objects.equals(line.getOqcId(), qc.getId()) || !Objects.equals(line.getItemId(), ncr.getItemId()) |
| | | || !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | || !same(line.getQuantity(), qc.getOutQuantity()) || ncr.getUnqualifiedQuantity().compareTo(line.getQuantity()) > 0) |
| | | throw error("æ æ³å¯ä¸æ ¸å¯¹åºè´§æ£éªå¯¹åºçä¸åæ ¼åºè´§è¡åæ°é"); |
| | | if (sales.getClientId() == null || !Objects.equals(sales.getClientId(), qc.getClientId()) |
| | | || line.getBatchId() == null || StrUtil.isBlank(line.getBatchCode()) |
| | | || !Objects.equals(line.getBatchCode(), qc.getBatchCode())) |
| | | throw error("åºè´§æ£éªç¼ºå°ä¸è´çå®¢æ·ææ¹æ¬¡ï¼æ æ³ç¡®å®åç产订å"); |
| | | var batch = batchMapper.selectById(line.getBatchId()); |
| | | if (batch == null || !Objects.equals(batch.getCode(), line.getBatchCode()) |
| | | || !Objects.equals(batch.getItemId(), ncr.getItemId()) || !Objects.equals(batch.getClientId(), sales.getClientId()) |
| | | || batch.getWorkOrderId() == null || (StrUtil.isNotBlank(qc.getReelNo()) && !Objects.equals(qc.getReelNo(), batch.getReelNo()))) |
| | | throw error("åºè´§æ¹æ¬¡æªç»å®ä¸è´ç客æ·ãç©æãçå·ååç产订å"); |
| | | var original = orderMapper.selectById(batch.getWorkOrderId()); |
| | | if (original == null || !Objects.equals(original.getClientId(), sales.getClientId()) || original.getProductId() == null) |
| | | throw error("æ¹æ¬¡ç»å®çåç产订åä¸å卿客æ·ä¸ä¸è´"); |
| | | return createFromOriginal(ncr, disposition, original, sales.getClientId(), batch.getCode(), "éæ°ç产-"); |
| | | } |
| | | |
| | | /** è°ç¨åé¡»å¨åä¸äºå¡å
éè¿ claimIpqcSource éå®å¹¶æ ¸é该ä¸åæ ¼äº§åºã */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcNcrDisposeRespVO reproduceIpqc(MesQcNcrDO ncr, Integer disposition, |
| | | MesQcNcrScrapService.IpqcSource source) { |
| | | var verified = sourceService.validateIpqcSource(ncr); |
| | | if (source == null || !Objects.equals(source, verified)) throw error("è¿ç¨æ£éªæ¥æºäº§åºå·²ååï¼è¯·å·æ°åéè¯"); |
| | | var line = produceLineMapper.selectById(source.sourceLineId()); |
| | | if (line == null || ncr.getId() == null || !Objects.equals(line.getNcrDispositionId(), ncr.getId())) |
| | | throw error("è¿ç¨ä¸åæ ¼äº§åºå°æªç±è¯¥å¤çåæ ¸é"); |
| | | var original = orderMapper.selectById(source.orderId()); |
| | | if (original == null || original.getProductId() == null) throw error("è¿ç¨æ£éªæ¥æºç产订åä¸åå¨"); |
| | | return createFromOriginal(ncr, disposition, original, original.getClientId(), source.batchCode(), "éæ°ç产-"); |
| | | } |
| | | |
| | | /** |
| | | * åºè´§ä¸åæ ¼è¿å·¥/è¿ä¿®ï¼å¤ç¨åå·¥åçå·¥èºè·¯çº¿ä¸ BOM å
éä¸å¼ è¿å·¥å·¥åã |
| | | * |
| | | * <p>è°ç¨æ¹é¡»å·²å®ææ¥æºè®¤é¢ãåæååºååºåºååéå®åºåºååæ¶ã</p> |
| | | */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcNcrDisposeRespVO createReworkFromOriginal(MesQcNcrDO ncr, Integer disposition, |
| | | MesProWorkOrderDO original, Long clientId, |
| | | String batchCode, String namePrefix) { |
| | | return createFromOriginal(ncr, disposition, original, clientId, batchCode, namePrefix); |
| | | } |
| | | |
| | | private MesQcNcrDisposeRespVO createFromOriginal(MesQcNcrDO ncr, Integer disposition, |
| | | MesProWorkOrderDO original, Long clientId, String batchCode, |
| | | String namePrefix) { |
| | | var product = itemMapper.selectById(original.getProductId()); |
| | | if (product == null || !Objects.equals(product.getMdmItemId(), ncr.getItemId())) |
| | | throw error("åç产订å产åä¸ä¸åæ ¼ç©ææ å°ä¸ä¸è´"); |
| | | var processes = processService.getWorkOrderProcessListByWorkOrderId(original.getId()); |
| | | if (processes == null || processes.isEmpty()) throw error("åçäº§è®¢åæ²¡æå·¥åºå¿«ç
§ï¼æ æ³èªå¨ä¸è¾¾éæ°ç产"); |
| | | if (original.getRouteId() == null) throw error("åçäº§è®¢åæªè®°å½å·¥èºè·¯çº¿ï¼æ æ³èªå¨çæç产任å¡"); |
| | | var sourceBoms = bomMapper.selectListByWorkOrderId(original.getId()); |
| | | if (sourceBoms == null) throw error("æ æ³è¯»åå工忿 BOM"); |
| | | if (sourceBoms.stream().anyMatch(b -> b.getItemId() == null || b.getQuantity() == null || b.getQuantity().signum() < 0 |
| | | || (b.getProcessId() == null && (original.getQuantity() == null || original.getQuantity().signum() <= 0)))) |
| | | throw error("åå·¥å BOM æè®¢åæ°éæ æï¼æ æ³å¤å¶ææ"); |
| | | if (sourceBoms.stream().anyMatch(b -> b.getProcessId() != null && processes.stream() |
| | | .noneMatch(p -> Objects.equals(p.getProcessId(), b.getProcessId())))) |
| | | throw error("åå·¥å BOM å
³èäºä¸åå¨çå·¥åºï¼æ æ³èªå¨å¤å¶"); |
| | | |
| | | String code = autoCodeService.generateAutoCode(MesMdAutoCodeRuleCodeEnum.PRO_WORK_ORDER_CODE.getCode()); |
| | | var request = new MesProWorkOrderSaveReqVO(); |
| | | request.setCode(code); request.setName(namePrefix + ncr.getCode()); request.setType(MesProWorkOrderTypeEnum.SELF.getType()); |
| | | request.setOrderSourceType(MesProWorkOrderSourceTypeEnum.STORE.getType()); request.setOrderSourceCode(ncr.getCode()); |
| | | request.setProductId(original.getProductId()); request.setReelNo(original.getReelNo()); |
| | | request.setClientId(clientId); request.setQuantity(ncr.getUnqualifiedQuantity()); |
| | | request.setRequestDate(LocalDateTime.now()); request.setFeedbackApprove(original.getFeedbackApprove()); |
| | | request.setProcessDefinitionKey(original.getProcessDefinitionKey()); request.setFeedbackRole(original.getFeedbackRole()); |
| | | request.setRemark("ç±ä¸åæ ¼å[" + ncr.getCode() + "]" + StrUtil.removeSuffix(namePrefix, "-") |
| | | + "ï¼åå·¥å[" + original.getCode() + "]ï¼åæ¹æ¬¡[" + StrUtil.blankToDefault(batchCode, "æ ") + "]"); |
| | | Long id = orderService.createWorkOrder(request); |
| | | orderMapper.updateById(new MesProWorkOrderDO().setId(id).setRouteId(original.getRouteId()).setAutoFinishFlag(original.getAutoFinishFlag())); |
| | | for (var process : processes) { |
| | | var copy = BeanUtils.toBean(process, MesProWorkOrderProcessSaveReqVO.class); |
| | | copy.setId(null); copy.setWorkOrderId(id); |
| | | if (StrUtil.isNotBlank(process.getOutputItemId())) { |
| | | try { copy.setOutputItemIds(Arrays.stream(process.getOutputItemId().split(",")).map(String::trim).map(Long::valueOf).toList()); } |
| | | catch (NumberFormatException ex) { throw error("åå·¥åå·¥åºäº§åºç©æç¼å·æ ¼å¼éè¯¯ï¼æ æ³å¤å¶"); } |
| | | } |
| | | processService.createWorkOrderProcess(copy); |
| | | } |
| | | // å·¥åº BOM æ¯åä½ç¨éï¼æ®éå·¥å BOM æ¯è®¢åæ»ç¨éï¼å使ç¨åå·¥åå¿«ç
§ã |
| | | bomMapper.deleteByWorkOrderId(id); |
| | | for (var bom : sourceBoms) { |
| | | BigDecimal amount = bom.getProcessId() != null ? bom.getQuantity() |
| | | : bom.getQuantity().multiply(ncr.getUnqualifiedQuantity()).divide(original.getQuantity(), 8, RoundingMode.CEILING); |
| | | bomMapper.insert(new MesProWorkOrderBomDO().setWorkOrderId(id).setProcessId(bom.getProcessId()) |
| | | .setItemId(bom.getItemId()).setQuantity(amount).setRemark(bom.getRemark())); |
| | | } |
| | | orderService.confirmWorkOrder(id); |
| | | var taskReq = new MesProTaskAutoGenerateReqVO(); |
| | | taskReq.setWorkOrderId(id); taskReq.setRouteId(original.getRouteId()); |
| | | var taskIds = taskService.autoGenerateTasks(taskReq); |
| | | if (taskIds == null || taskIds.isEmpty()) throw error("éæ°çäº§å·¥åæªçæç产任å¡ï¼æ æ³ä¸è¾¾"); |
| | | var result = new MesQcNcrDisposeRespVO(); |
| | | result.setDisposition(disposition); result.setDocType("WORK_ORDER"); result.setDocId(id); |
| | | result.setDocCode(code); result.setDocName(request.getName()); result.setMessage("å·²ç¡®è®¤éæ°ç产订å[" + code + "]ï¼çæ " + taskIds.size() + " æ¡ç产任å¡è稿ï¼å¾
æç¨"); |
| | | return result; |
| | | } |
| | | private static boolean same(BigDecimal a, BigDecimal b) { return a != null && b != null && a.compareTo(b) == 0; } |
| | | private static ServiceException error(String message) { return new ServiceException(400, message); } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssueSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.line.MesWmMiscIssueLineSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | 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.miscissue.MesWmMiscIssueDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscissue.MesWmMiscIssueMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | 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.pro.MesProWorkOrderSourceTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.miscissue.MesWmMiscIssueLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.miscissue.MesWmMiscIssueService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * åºè´§æ£éªï¼OQCï¼ä¸åæ ¼è¿å·¥/è¿ä¿®éç¯ã |
| | | * |
| | | * <p>å¤ç½®æ¶æ£ååæååºåå¹¶çæè¿å·¥é¢ç¨åºåºåï¼èªå¨åæ¶åéå®åºåºåï¼ |
| | | * åå¤ç¨åç产工åå
éä¸å¼ è¿å·¥å·¥åï¼å¤æ£éè¿åç± NCR å
³éæµç¨æ ¸éªã</p> |
| | | */ |
| | | @Service |
| | | public class MesQcNcrOqcReworkService { |
| | | |
| | | /** è¿å·¥é¢ç¨åºåºåçæ¥æºåæ®ç±»å */ |
| | | private static final String MISC_ISSUE_SOURCE_TYPE_NCR = "QC_NCR"; |
| | | |
| | | @Resource private MesWmBatchMapper batchMapper; |
| | | @Resource private MesProWorkOrderMapper orderMapper; |
| | | @Resource private MesQcOqcMapper oqcMapper; |
| | | @Resource private MesWmMaterialStockMapper stockMapper; |
| | | @Resource private MesWmMiscIssueMapper miscIssueMapper; |
| | | @Resource private MesWmTransactionMapper transactionMapper; |
| | | @Resource private MesWmProductSalesMapper salesMapper; |
| | | |
| | | @Resource private MesWmMaterialStockService stockService; |
| | | @Resource private MesWmProductSalesService salesService; |
| | | @Resource private MesWmMiscIssueService miscIssueService; |
| | | @Resource private MesWmMiscIssueLineService miscIssueLineService; |
| | | @Resource private MesMdAutoCodeRecordService autoCodeRecordService; |
| | | @Resource private MesMdItemService itemService; |
| | | @Resource private MesQcNcrOqcReproductionService reproductionService; |
| | | @Resource private MesWmWarehouseService warehouseService; |
| | | @Resource private MesWmWarehouseLocationService warehouseLocationService; |
| | | @Resource private MesWmWarehouseAreaService warehouseAreaService; |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcNcrDisposeRespVO dispose(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source, |
| | | MesQcNcrDisposeReqVO req) { |
| | | validateSource(ncr, source); |
| | | MesWmBatchDO batch = validateBatch(ncr, source); |
| | | MesProWorkOrderDO original = validateOriginalWorkOrder(batch, source); |
| | | MesWmMaterialStockDO stock = resolveAndLockStock(ncr, source, req); |
| | | |
| | | // åéå®åºåºåæ´ååæ¶ï¼éæ¾å ç¨ï¼å¿
须卿£ååºåå宿 |
| | | salesService.cancelProductSales(source.sales().getId()); |
| | | |
| | | String issueCode = createReworkIssue(ncr, stock, source.sales().getCode()); |
| | | String namePrefix = Objects.equals(req.getDisposition(), MesNcrDispositionEnum.REWORK.getType()) |
| | | ? "è¿å·¥-" : "è¿ä¿®-"; |
| | | MesQcNcrDisposeRespVO result = reproductionService.createReworkFromOriginal( |
| | | ncr, req.getDisposition(), original, source.sales().getClientId(), batch.getCode(), namePrefix); |
| | | result.setMessage(String.format( |
| | | "å·²çæè¿å·¥é¢ç¨åºåºå[%s]并宿åºåºï¼åéå®åºåºå[%s]已忶ï¼è¿å·¥å·¥å[%s]å·²ä¸è¾¾ã" |
| | | + "è¯·å®æè¿å·¥ååè¿ç¨æ£éªå¤æ£ï¼å¤æ£åæ ¼åå
³éæ¬å", |
| | | issueCode, source.sales().getCode(), result.getDocCode())); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * å
³éè¿å·¥/è¿ä¿® NCR åæ ¸éªï¼è¿å·¥å·¥å已宿ãåéå®åºåºå已忶ãè¿å·¥é¢ç¨åºåºå·²æ§è¡ã |
| | | */ |
| | | public void validateBeforeClose(MesQcNcrDO ncr) { |
| | | List<MesProWorkOrderDO> orders = orderMapper.selectList(new LambdaQueryWrapperX<MesProWorkOrderDO>() |
| | | .eq(MesProWorkOrderDO::getOrderSourceType, MesProWorkOrderSourceTypeEnum.STORE.getType()) |
| | | .eq(MesProWorkOrderDO::getOrderSourceCode, ncr.getCode())); |
| | | if (orders.size() != 1) { |
| | | throw new ServiceException(400, String.format( |
| | | "æ æ³å¯ä¸ç¡®å®ä¸åæ ¼å[%s]对åºçè¿å·¥/è¿ä¿®ç产订åï¼å½åå¹é
[%s]å¼ ï¼ï¼è¯·å
æ ¸å¯¹æ¥æºç¼ç ", |
| | | ncr.getCode(), orders.size())); |
| | | } |
| | | MesProWorkOrderDO order = orders.get(0); |
| | | if (!Objects.equals(order.getStatus(), MesProWorkOrderStatusEnum.FINISHED.getStatus()) |
| | | || order.getFinishDate() == null) { |
| | | throw new ServiceException(400, String.format( |
| | | "è¿å·¥/è¿ä¿®ç产订å[%s]å°æªå®ææç¼ºå°å®ææ¶é´ï¼è¯·å®æè¿å·¥å¹¶æ¥å·¥ååå
³é", |
| | | order.getCode())); |
| | | } |
| | | |
| | | MesWmProductSalesDO sales = resolveOriginalSales(ncr); |
| | | if (sales == null) { |
| | | throw new ServiceException(400, "æªæ¾å°ä¸åæ ¼åå
³èçåéå®åºåºåï¼æ æ³æ ¸éªè¿å·¥éç¯"); |
| | | } |
| | | if (!Objects.equals(sales.getStatus(), MesWmProductSalesStatusEnum.CANCELED.getStatus())) { |
| | | throw new ServiceException(400, String.format( |
| | | "åéå®åºåºå[%s]å½åç¶æä¸º[%s]ï¼è¿å·¥å¤ç½®åºå·²èªå¨åæ¶è¯¥åºåºåï¼" |
| | | + "è¯·æ ¸å¯¹æ¯å¦è¢«éæ°æäº¤æäººå·¥ä¿®æ¹", sales.getCode(), salesStatusName(sales.getStatus()))); |
| | | } |
| | | |
| | | List<MesWmMiscIssueDO> issues = miscIssueMapper.selectList(new LambdaQueryWrapperX<MesWmMiscIssueDO>() |
| | | .eq(MesWmMiscIssueDO::getSourceDocType, MISC_ISSUE_SOURCE_TYPE_NCR) |
| | | .eq(MesWmMiscIssueDO::getSourceDocId, ncr.getId()) |
| | | .eq(MesWmMiscIssueDO::getType, MesWmMiscIssueTypeEnum.REWORK.getType())); |
| | | if (issues.size() != 1) { |
| | | throw new ServiceException(400, String.format( |
| | | "ä¸åæ ¼å[%s]å
³èçè¿å·¥é¢ç¨åºåºåæ°é为[%s]å¼ ï¼æ æ³æ ¸éªè¿å·¥é¢ç¨åºå", |
| | | ncr.getCode(), issues.size())); |
| | | } |
| | | MesWmMiscIssueDO issue = issues.get(0); |
| | | if (!Objects.equals(issue.getStatus(), MesWmMiscIssueStatusEnum.FINISHED.getStatus())) { |
| | | throw new ServiceException(400, String.format( |
| | | "è¿å·¥é¢ç¨åºåºå[%s]å°æªå®æåºåºï¼è¯·å
卿项åºåºä¸å®æååå
³é", issue.getCode())); |
| | | } |
| | | |
| | | List<MesWmTransactionDO> flows = transactionMapper.selectList(new LambdaQueryWrapperX<MesWmTransactionDO>() |
| | | .eq(MesWmTransactionDO::getBizType, MesBizTypeConstants.WM_MISC_ISSUE) |
| | | .eq(MesWmTransactionDO::getBizId, issue.getId()) |
| | | .eq(MesWmTransactionDO::getType, MesWmTransactionTypeEnum.OUT.getType())); |
| | | BigDecimal out = BigDecimal.ZERO; |
| | | for (MesWmTransactionDO flow : flows) { |
| | | if (!Objects.equals(flow.getItemId(), ncr.getItemId()) |
| | | || flow.getQuantity() == null || flow.getQuantity().signum() >= 0) { |
| | | throw new ServiceException(400, String.format( |
| | | "è¿å·¥é¢ç¨åºåºå[%s]åå¨ç©æææ°éä¸ä¸è´çåºåºæµæ°´ï¼è¯·æ ¸å¯¹åéè¯", issue.getCode())); |
| | | } |
| | | out = out.subtract(flow.getQuantity()); |
| | | } |
| | | if (out.compareTo(ncr.getUnqualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, String.format( |
| | | "è¿å·¥é¢ç¨åºåºæ°é[%s]ä¸ä¸åæ ¼æ°é[%s]ä¸ä¸è´ï¼è¯·æ ¸å¯¹è¿å·¥é¢ç¨åºåºå[%s]", |
| | | plain(out), plain(ncr.getUnqualifiedQuantity()), issue.getCode())); |
| | | } |
| | | } |
| | | |
| | | // ==================== æ ¡éªä¸å®ä½ ==================== |
| | | |
| | | private void validateSource(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source) { |
| | | if (source == null || source.qc() == null || source.sales() == null || source.line() == null) { |
| | | throw new ServiceException(400, "åºè´§ä¸åæ ¼è¿å·¥ç¼ºå°å·²è®¤é¢çæ¥æºæ£éªåãéå®åºåºåæåºåºè¡"); |
| | | } |
| | | MesWmProductSalesLineDO line = source.line(); |
| | | if (ncr.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity().signum() <= 0) { |
| | | throw new ServiceException(400, "ä¸åæ ¼æ°éå¿
é¡»å¤§äº 0 æè½è¿å·¥/è¿ä¿®"); |
| | | } |
| | | if (!Objects.equals(source.sales().getStatus(), MesWmProductSalesStatusEnum.CONFIRMED.getStatus()) |
| | | || !Objects.equals(line.getSalesId(), source.sales().getId()) |
| | | || !Objects.equals(line.getOqcId(), source.qc().getId()) |
| | | || !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | || !Objects.equals(line.getItemId(), ncr.getItemId()) |
| | | || line.getBatchId() == null || StrUtil.isBlank(line.getBatchCode()) |
| | | || line.getQuantity() == null |
| | | || ncr.getUnqualifiedQuantity().compareTo(line.getQuantity()) > 0) { |
| | | throw new ServiceException(400, "åºè´§æ£éªæ¥æºãéå®åºåºè¡ãç©æãæ¹æ¬¡æä¸åæ ¼æ°éä¸ä¸è´ï¼æ æ³è¿å·¥/è¿ä¿®"); |
| | | } |
| | | } |
| | | |
| | | private MesWmBatchDO validateBatch(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source) { |
| | | MesWmProductSalesLineDO line = source.line(); |
| | | MesWmBatchDO batch = batchMapper.selectById(line.getBatchId()); |
| | | if (batch == null || !Objects.equals(batch.getCode(), line.getBatchCode()) |
| | | || !Objects.equals(batch.getItemId(), line.getItemId()) |
| | | || !Objects.equals(batch.getClientId(), source.sales().getClientId()) |
| | | || batch.getWorkOrderId() == null) { |
| | | throw new ServiceException(400, String.format( |
| | | "åºè´§æ¹æ¬¡[%s]æªç»å®ä¸è´çç©æ[%s]ãå®¢æ·æåç产订åï¼æ æ³è¿å·¥/è¿ä¿®", |
| | | StrUtil.blankToDefault(line.getBatchCode(), "æªå¡«å"), itemLabel(ncr.getItemId()))); |
| | | } |
| | | return batch; |
| | | } |
| | | |
| | | private MesProWorkOrderDO validateOriginalWorkOrder(MesWmBatchDO batch, |
| | | MesQcNcrOqcSourceClaim.OqcClaimedSource source) { |
| | | MesProWorkOrderDO original = orderMapper.selectById(batch.getWorkOrderId()); |
| | | if (original == null || original.getProductId() == null |
| | | || !Objects.equals(original.getClientId(), source.sales().getClientId())) { |
| | | throw new ServiceException(400, String.format( |
| | | "åºè´§æ¹æ¬¡[%s]å
³èçåç产订åä¸åå¨ã缺å°äº§åæä¸å±äºå®¢æ·[ID=%s]ï¼æ æ³è¿å·¥/è¿ä¿®", |
| | | batch.getCode(), source.sales().getClientId())); |
| | | } |
| | | return original; |
| | | } |
| | | |
| | | /** |
| | | * å®ä½å¹¶éå®åæååºåï¼ä¼å
使ç¨å端éå®çåºåè®°å½ï¼æªæå®æ¶è¦æ±åæ¹æ¬¡ä»
䏿¡å¯ç¨åºåã |
| | | */ |
| | | private MesWmMaterialStockDO resolveAndLockStock(MesQcNcrDO ncr, MesQcNcrOqcSourceClaim.OqcClaimedSource source, |
| | | MesQcNcrDisposeReqVO req) { |
| | | MesWmProductSalesLineDO line = source.line(); |
| | | BigDecimal need = ncr.getUnqualifiedQuantity(); |
| | | MesWmMaterialStockDO stock; |
| | | if (req.getMaterialStockId() != null) { |
| | | stock = stockService.getMaterialStock(req.getMaterialStockId()); |
| | | if (stock == null) { |
| | | throw new ServiceException(400, String.format( |
| | | "æéè¿å·¥åºåè®°å½[ID=%s]ä¸åå¨ï¼è¯·éæ°éæ©è¯¥æ¹æ¬¡æååºå", req.getMaterialStockId())); |
| | | } |
| | | if (!Objects.equals(stock.getItemId(), line.getItemId()) |
| | | || !Objects.equals(stock.getBatchId(), line.getBatchId())) { |
| | | throw new ServiceException(400, String.format( |
| | | "æéåºåä¸ä¸åæ ¼åºè´§æ¹æ¬¡ä¸ä¸è´ï¼æéåºåç©æ[%s]ãæ¹æ¬¡[%s]ï¼" |
| | | + "åºè´§è¡ç©æ[%s]ãæ¹æ¬¡[%s]ãè¯·éæ°éæ©è¯¥æ¹æ¬¡æååºå", |
| | | itemLabel(stock.getItemId()), batchLabel(stock.getBatchId(), stock.getBatchCode()), |
| | | itemLabel(line.getItemId()), batchLabel(line.getBatchId(), line.getBatchCode()))); |
| | | } |
| | | if (stock.getWarehouseId() == null || stock.getLocationId() == null || stock.getAreaId() == null) { |
| | | throw new ServiceException(400, String.format( |
| | | "æéåºå[ID=%s]缺å°ä»åº/åºåº/åºä½ä¿¡æ¯ï¼æ æ³åºåºï¼è¯·éæ°éæ©å®æ´çåºåè®°å½", |
| | | stock.getId())); |
| | | } |
| | | } else { |
| | | List<MesWmMaterialStockDO> candidates = stockMapper.selectList(new LambdaQueryWrapperX<MesWmMaterialStockDO>() |
| | | .eq(MesWmMaterialStockDO::getItemId, line.getItemId()) |
| | | .eq(MesWmMaterialStockDO::getBatchId, line.getBatchId()) |
| | | .gt(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO)); |
| | | if (candidates.isEmpty()) { |
| | | throw new ServiceException(400, String.format( |
| | | "æªæ¾å°ç©æ[%s]ãæ¹æ¬¡[%s]çæååºåï¼æ æ³è¿å·¥é¢ç¨ï¼è¯·å
确认æåå·²å
¥åºæå·æ°åéè¯", |
| | | itemLabel(line.getItemId()), batchLabel(line.getBatchId(), line.getBatchCode()))); |
| | | } |
| | | List<MesWmMaterialStockDO> available = candidates.stream() |
| | | .filter(item -> available(item).compareTo(need) >= 0) |
| | | .toList(); |
| | | if (available.size() != 1) { |
| | | String detail = candidates.stream() |
| | | .map(item -> String.format("%sï¼å¯ç¨é[%s]ï¼", stockLabel(item), plain(available(item)))) |
| | | .collect(Collectors.joining("ï¼")); |
| | | throw new ServiceException(400, String.format( |
| | | "æ¹æ¬¡[%s]åºåæ æ³èªå¨ç¡®å®è¿å·¥é¢ç¨è®°å½ï¼%sã" |
| | | + "请å¨å¤ç½®å¼¹çªç[è¿å·¥åºå]ä¸éæ©å
·ä½åºåè®°å½åéè¯", |
| | | batchLabel(line.getBatchId(), line.getBatchCode()), detail)); |
| | | } |
| | | stock = available.get(0); |
| | | } |
| | | |
| | | // éæ°å éå计ç®å¯ç¨éï¼é¿å
å¹¶ååºåºå¯¼è´è¶
é¢ |
| | | MesWmMaterialStockDO locked = stockMapper.lockById(stock.getId()); |
| | | if (locked == null) { |
| | | throw new ServiceException(400, String.format( |
| | | "è¿å·¥åºåè®°å½[ID=%s]å·²ä¸åå¨ï¼è¯·å·æ°åéæ°éæ©", stock.getId())); |
| | | } |
| | | BigDecimal available = available(locked); |
| | | if (available.compareTo(need) < 0) { |
| | | throw new ServiceException(400, String.format( |
| | | "æ¹æ¬¡[%s]åºåå¯ç¨é[%s]ä¸è¶³ï¼æ¬æ¬¡è¿å·¥éé¢ç¨[%s]ï¼" |
| | | + "请å
å¤çåºåå ç¨/å»ç»ï¼ææ ¸å¯¹æåå
¥åºæ°é", |
| | | batchLabel(locked.getBatchId(), locked.getBatchCode()), plain(available), plain(need))); |
| | | } |
| | | return locked; |
| | | } |
| | | |
| | | // ==================== åæ®çæ ==================== |
| | | |
| | | private String createReworkIssue(MesQcNcrDO ncr, MesWmMaterialStockDO stock, String salesCode) { |
| | | String code = autoCodeRecordService.generateAutoCode( |
| | | MesMdAutoCodeRuleCodeEnum.WM_MISC_ISSUE_CODE.getCode()); |
| | | MesWmMiscIssueSaveReqVO issueReqVO = new MesWmMiscIssueSaveReqVO(); |
| | | issueReqVO.setCode(code); |
| | | issueReqVO.setName("è¿å·¥é¢ç¨åºåºå-" + ncr.getCode()); |
| | | issueReqVO.setType(MesWmMiscIssueTypeEnum.REWORK.getType()); |
| | | issueReqVO.setSourceDocType(MISC_ISSUE_SOURCE_TYPE_NCR); |
| | | issueReqVO.setSourceDocId(ncr.getId()); |
| | | issueReqVO.setSourceDocCode(ncr.getCode()); |
| | | issueReqVO.setIssueDate(LocalDateTime.now()); |
| | | issueReqVO.setRemark(String.format("ç±ä¸åæ ¼åå¤çå[%s]è¿å·¥é¢ç¨èªå¨çæï¼åéå®åºåºå[%s]已忶", |
| | | ncr.getCode(), salesCode)); |
| | | Long issueId = miscIssueService.createMiscIssue(issueReqVO); |
| | | |
| | | MesWmMiscIssueLineSaveReqVO lineReqVO = new MesWmMiscIssueLineSaveReqVO(); |
| | | lineReqVO.setIssueId(issueId); |
| | | lineReqVO.setItemId(ncr.getItemId()); |
| | | lineReqVO.setQuantity(ncr.getUnqualifiedQuantity()); |
| | | lineReqVO.setMaterialStockId(stock.getId()); |
| | | lineReqVO.setBatchId(stock.getBatchId()); |
| | | lineReqVO.setBatchCode(stock.getBatchCode()); |
| | | lineReqVO.setWarehouseId(stock.getWarehouseId()); |
| | | lineReqVO.setLocationId(stock.getLocationId()); |
| | | lineReqVO.setAreaId(stock.getAreaId()); |
| | | lineReqVO.setRemark(String.format("æ¥æºä¸åæ ¼åå¤çå[%s]è¿å·¥é¢ç¨", ncr.getCode())); |
| | | miscIssueLineService.createMiscIssueLine(lineReqVO); |
| | | |
| | | miscIssueService.submitMiscIssue(issueId); |
| | | miscIssueService.finishMiscIssue(issueId); |
| | | return code; |
| | | } |
| | | |
| | | /** |
| | | * éè¿æ¥æºæ£éªåå®ä½åéå®åºåºåï¼ä»
ç¨äºå
³éæ ¸éªï¼ã |
| | | */ |
| | | private MesWmProductSalesDO resolveOriginalSales(MesQcNcrDO ncr) { |
| | | if (ncr.getQcId() == null) { |
| | | return null; |
| | | } |
| | | MesQcOqcDO qc = oqcMapper.selectById(ncr.getQcId()); |
| | | return qc == null || qc.getSourceDocId() == null ? null : salesMapper.selectById(qc.getSourceDocId()); |
| | | } |
| | | |
| | | // ==================== å±ç¤ºè¾
å© ==================== |
| | | |
| | | private static BigDecimal available(MesWmMaterialStockDO stock) { |
| | | BigDecimal quantity = stock.getQuantity() == null ? BigDecimal.ZERO : stock.getQuantity(); |
| | | BigDecimal frozen = stock.getFrozenQuantity() == null ? BigDecimal.ZERO : stock.getFrozenQuantity(); |
| | | BigDecimal reserved = stock.getReservedQuantity() == null ? BigDecimal.ZERO : stock.getReservedQuantity(); |
| | | return quantity.subtract(frozen).subtract(reserved); |
| | | } |
| | | |
| | | private String itemLabel(Long itemId) { |
| | | MesMdItemDO item = itemId == null ? null : itemService.getItem(itemId); |
| | | if (item == null) { |
| | | return "ID=" + itemId; |
| | | } |
| | | String label = (StrUtil.blankToDefault(item.getCode(), "") + " " |
| | | + StrUtil.blankToDefault(item.getName(), "")).trim(); |
| | | return label.isEmpty() ? "ID=" + itemId : label; |
| | | } |
| | | |
| | | private String batchLabel(Long batchId, String batchCode) { |
| | | if (StrUtil.isNotBlank(batchCode)) { |
| | | return batchCode; |
| | | } |
| | | return batchId == null ? "æªå¡«å" : "ID=" + batchId; |
| | | } |
| | | |
| | | private String stockLabel(MesWmMaterialStockDO stock) { |
| | | return String.format("%s/%s/%s", warehouseName(stock.getWarehouseId()), |
| | | locationName(stock.getLocationId()), areaName(stock.getAreaId())); |
| | | } |
| | | |
| | | private String warehouseName(Long id) { |
| | | MesWmWarehouseDO warehouse = id == null ? null : warehouseService.getWarehouse(id); |
| | | return warehouse != null && StrUtil.isNotBlank(warehouse.getName()) ? warehouse.getName() : "ID=" + id; |
| | | } |
| | | |
| | | private String locationName(Long id) { |
| | | MesWmWarehouseLocationDO location = id == null ? null : warehouseLocationService.getWarehouseLocation(id); |
| | | return location != null && StrUtil.isNotBlank(location.getName()) ? location.getName() : "ID=" + id; |
| | | } |
| | | |
| | | private String areaName(Long id) { |
| | | MesWmWarehouseAreaDO area = id == null ? null : warehouseAreaService.getWarehouseArea(id); |
| | | return area != null && StrUtil.isNotBlank(area.getName()) ? area.getName() : "ID=" + id; |
| | | } |
| | | |
| | | private static String salesStatusName(Integer status) { |
| | | for (MesWmProductSalesStatusEnum value : MesWmProductSalesStatusEnum.values()) { |
| | | if (Objects.equals(value.getStatus(), status)) { |
| | | return value.getName(); |
| | | } |
| | | } |
| | | return "ID=" + status; |
| | | } |
| | | |
| | | private static String plain(BigDecimal value) { |
| | | return value == null ? "0" : value.stripTrailingZeros().toPlainString(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | import java.util.Objects; |
| | | |
| | | /** è°ç¨æ¹é¡»ææ NCR è¡éå¹¶å¨åä¸äºå¡å
宿åç»å¤ç½®ã */ |
| | | @Component |
| | | public class MesQcNcrOqcSourceClaim { |
| | | @Resource private MesQcOqcMapper oqcMapper; |
| | | @Resource private MesWmProductSalesMapper salesMapper; |
| | | @Resource private MesWmProductSalesLineMapper lineMapper; |
| | | |
| | | public OqcClaimedSource claim(MesQcNcrDO ncr) { |
| | | var qc = oqcMapper.selectById(ncr.getQcId()); |
| | | if (qc == null || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_PRODUCT_SALES) |
| | | || qc.getSourceDocId() == null || qc.getSourceLineId() == null |
| | | || !Objects.equals(qc.getItemId(), ncr.getItemId()) || qc.getUnqualifiedQuantity() == null |
| | | || ncr.getUnqualifiedQuantity() == null || qc.getUnqualifiedQuantity().compareTo(ncr.getUnqualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, "åºè´§ä¸åæ ¼åæ¥æºææ°éä¸ä¸è´"); |
| | | } |
| | | var sales = salesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>() |
| | | .eq(MesWmProductSalesDO::getId, qc.getSourceDocId()).last("FOR UPDATE")); |
| | | var line = lineMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesLineDO>() |
| | | .eq(MesWmProductSalesLineDO::getId, qc.getSourceLineId()).last("FOR UPDATE")); |
| | | if (sales == null || line == null || !Objects.equals(sales.getStatus(), MesWmProductSalesStatusEnum.CONFIRMED.getStatus()) |
| | | || !Objects.equals(line.getSalesId(), sales.getId()) || !Objects.equals(line.getOqcId(), qc.getId()) |
| | | || !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | || line.getNcrDispositionId() != null) { |
| | | throw new ServiceException(400, "ååºè´§è¡å·²å¤ç½®æä¸å¤äºçå¾
æ£éªå¤ç½®ç¶æ"); |
| | | } |
| | | lineMapper.updateById(new MesWmProductSalesLineDO().setId(line.getId()).setNcrDispositionId(ncr.getId())); |
| | | return new OqcClaimedSource(qc, sales, line); |
| | | } |
| | | |
| | | /** å·²éå®çåºè´§ä¸åæ ¼æ¥æºï¼æ£éªåãéå®åºåºåãéå®åºåºè¡ï¼ã */ |
| | | public record OqcClaimedSource(MesQcOqcDO qc, MesWmProductSalesDO sales, MesWmProductSalesLineDO line) { |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.Objects; |
| | | |
| | | /** éè´å¤ç½®åéå®å¹¶æ ¸å¯¹æ£éªååå°è´§è¡ï¼è°ç¨è
é¡»å¤äºäºå¡ä¸ã */ |
| | | @Component |
| | | public class MesQcNcrPurchaseSourceValidator { |
| | | @Resource private MesQcIqcMapper iqcMapper; |
| | | @Resource private MesWmArrivalNoticeMapper arrivalNoticeMapper; |
| | | @Resource private MesWmArrivalNoticeLineMapper lineMapper; |
| | | |
| | | public record Source(MesQcIqcDO iqc, MesWmArrivalNoticeDO notice, MesWmArrivalNoticeLineDO line) {} |
| | | |
| | | public Source validate(MesQcNcrDO ncr) { |
| | | if (!Objects.equals(ncr.getQcType(), MesQcTypeEnum.IQC.getType()) || ncr.getQcId() == null) |
| | | throw new ServiceException(400, "éè´ä¸åæ ¼å¤ç½®å¿
é¡»å
³èæ¥ææ£éªå"); |
| | | var iqc = iqcMapper.selectOne(new LambdaQueryWrapperX<MesQcIqcDO>() |
| | | .eq(MesQcIqcDO::getId, ncr.getQcId()).last("FOR UPDATE")); |
| | | if (iqc == null || !Objects.equals(iqc.getStatus(), MesQcStatusEnum.FINISHED.getStatus()) |
| | | || !Objects.equals(iqc.getSourceDocType(), MesBizTypeConstants.WM_ARRIVAL_NOTICE) |
| | | || iqc.getSourceDocId() == null || iqc.getSourceLineId() == null) |
| | | throw new ServiceException(400, "éè´å¤ç½®ä»
æ¯æå·²å®æä¸å
³èå°è´§éç¥åè¡çæ¥ææ£éª"); |
| | | var notice = arrivalNoticeMapper.selectOne(new LambdaQueryWrapperX<MesWmArrivalNoticeDO>() |
| | | .eq(MesWmArrivalNoticeDO::getId, iqc.getSourceDocId()).last("FOR UPDATE")); |
| | | var line = lineMapper.selectOne(new LambdaQueryWrapperX<MesWmArrivalNoticeLineDO>() |
| | | .eq(MesWmArrivalNoticeLineDO::getId, iqc.getSourceLineId()).last("FOR UPDATE")); |
| | | if (notice == null || line == null || !Objects.equals(line.getNoticeId(), notice.getId()) |
| | | || !Objects.equals(line.getIqcId(), iqc.getId()) |
| | | || !Objects.equals(notice.getStatus(), MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()) |
| | | || !Objects.equals(line.getItemId(), iqc.getItemId()) |
| | | || !Objects.equals(ncr.getItemId(), iqc.getItemId()) |
| | | || !samePositive(ncr.getUnqualifiedQuantity(), iqc.getUnqualifiedQuantity()) |
| | | || !samePositive(iqc.getCheckQuantity(), line.getArrivalQuantity())) |
| | | throw new ServiceException(400, "å°è´§åãè´¨æ£ååä¸åæ ¼åæ¥æºææ°éä¸ä¸è´ï¼æ æ³èªå¨å¤ç½®"); |
| | | return new Source(iqc, notice, line); |
| | | } |
| | | |
| | | private boolean samePositive(BigDecimal a, BigDecimal b) { |
| | | return a != null && b != null && a.signum() > 0 && a.compareTo(b) == 0; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** å
³éè¿å·¥/è¿ä¿® NCR åï¼æ ¸å®æä½è
æç¡®æå®çæ´æ¹å¤æ£è¯æ®ã */ |
| | | @Component |
| | | public class MesQcNcrReinspectionValidator { |
| | | |
| | | @Resource private MesProWorkOrderMapper workOrderMapper; |
| | | @Resource private MesQcIpqcMapper ipqcMapper; |
| | | @Resource private MesMdItemMapper itemMapper; |
| | | @Resource private MesWmProductProduceLineMapper produceLineMapper; |
| | | @Resource private MesWmProductProduceMapper produceMapper; |
| | | @Resource private MesWmTransactionMapper transactionMapper; |
| | | @Resource private MesQcNcrMapper ncrMapper; |
| | | |
| | | public void validate(MesQcNcrDO ncr, Long reinspectionIpqcId) { |
| | | if (reinspectionIpqcId == null) { |
| | | throw new ServiceException(400, "è¿å·¥/è¿ä¿®å
³éåè¯·éæ©å
³è产åºçæ´æ¹å¤æ£å"); |
| | | } |
| | | // å½å订åä»
æææ¥æºç¼ç ï¼åå¨å¤ä¸ªå¹é
项æ¶ä¸è½çæµå
³èã |
| | | List<MesProWorkOrderDO> orders = workOrderMapper.selectList(new LambdaQueryWrapperX<MesProWorkOrderDO>() |
| | | .eq(MesProWorkOrderDO::getOrderSourceType, MesProWorkOrderSourceTypeEnum.STORE.getType()) |
| | | .eq(MesProWorkOrderDO::getOrderSourceCode, ncr.getCode())); |
| | | if (orders.size() != 1) { |
| | | throw new ServiceException(400, "æ æ³å¯ä¸ç¡®å®æ¤ä¸åæ ¼å对åºçè¿å·¥/è¿ä¿®ç产订åï¼è¯·å
æ ¸å¯¹æ¥æºç¼ç "); |
| | | } |
| | | MesProWorkOrderDO order = orders.get(0); |
| | | if (!Objects.equals(order.getStatus(), MesProWorkOrderStatusEnum.FINISHED.getStatus()) |
| | | || order.getFinishDate() == null) { |
| | | throw new ServiceException(400, "è¿å·¥/è¿ä¿®ç产订åå°æªå®ææç¼ºå°å®ææ¶é´ï¼ä¸è½å
³é"); |
| | | } |
| | | MesMdItemDO product = itemMapper.selectById(order.getProductId()); |
| | | if (product == null || product.getMdmItemId() == null |
| | | || !Objects.equals(product.getMdmItemId(), ncr.getItemId())) { |
| | | throw new ServiceException(400, "è¿å·¥è®¢å产åä¸ä¸åæ ¼ç©æçæ å°ä¸æç¡®ï¼è¯·å
æ ¸å¯¹äº§åä¸»æ°æ®"); |
| | | } |
| | | MesQcIpqcDO qc = ipqcMapper.selectById(reinspectionIpqcId); |
| | | // åºè´§è¿å·¥åæ ·èµ°æ¥å·¥äº§åºå¤æ£ï¼è¿å·¥å·¥å宿åï¼éç»å®å
¶æ¥å·¥äº§åºè¡çè¿ç¨æ£éªå |
| | | if (!Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType()) |
| | | && !Objects.equals(ncr.getQcType(), MesQcTypeEnum.RQC.getType()) |
| | | && !Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType())) { |
| | | validateLegacy(qc, order, ncr); |
| | | return; |
| | | } |
| | | if (qc == null || !Objects.equals(qc.getWorkOrderId(), order.getId()) |
| | | || !Objects.equals(qc.getItemId(), order.getProductId()) |
| | | || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.PRO_FEEDBACK) |
| | | || qc.getSourceDocId() == null || qc.getSourceLineId() == null) { |
| | | throw new ServiceException(400, "è¯·éæ©å
³èè¿å·¥è®¢åæ¥å·¥äº§åºè¡çè¿ç¨æ£éªåä½ä¸ºå¤æ£è¯æ®"); |
| | | } |
| | | if (ncr.getHandleDate() == null || qc.getCreateTime() == null || qc.getCreateTime().isBefore(ncr.getHandleDate()) |
| | | || qc.getInspectDate() == null || qc.getInspectDate().isBefore(ncr.getHandleDate())) { |
| | | throw new ServiceException(400, "夿£åé¡»å¨è¿å·¥å¤ç½®åå建并æ£éªï¼è¯·æ ¸å¯¹æ£éªæ¥æ"); |
| | | } |
| | | BigDecimal quantity = ncr.getUnqualifiedQuantity(); |
| | | if (!Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus()) |
| | | || quantity == null || quantity.signum() <= 0 |
| | | || qc.getCheckQuantity() == null || qc.getCheckQuantity().compareTo(quantity) != 0 |
| | | || qc.getQualifiedQuantity() == null || qc.getQualifiedQuantity().signum() < 0 |
| | | || qc.getUnqualifiedQuantity() == null || qc.getUnqualifiedQuantity().signum() < 0 |
| | | || qc.getQualifiedQuantity().add(qc.getUnqualifiedQuantity()).compareTo(quantity) != 0 |
| | | || !Objects.equals(qc.getCheckResult(), qc.getUnqualifiedQuantity().signum() == 0 ? 1 : 2)) { |
| | | throw new ServiceException(400, "夿£å¿
须已宿ï¼ä¸åæ ¼ä¸ä¸åæ ¼æ°éä¹åé¡»çäºä¸åæ ¼åæ°é"); |
| | | } |
| | | MesWmProductProduceLineDO line = produceLineMapper.selectById(qc.getSourceLineId()); |
| | | MesWmProductProduceDO produce = line == null ? null : produceMapper.selectById(line.getProduceId()); |
| | | if (line == null || produce == null || !Objects.equals(line.getFeedbackId(), qc.getSourceDocId()) |
| | | || !Objects.equals(produce.getWorkOrderId(), order.getId()) |
| | | || !Objects.equals(line.getItemId(), qc.getItemId()) |
| | | || !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus()) |
| | | || line.getQuantity() == null || line.getQuantity().compareTo(qc.getQualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, "夿£äº§åºè¡ä¸è¿å·¥è®¢åæåæ ¼æ°éä¸ä¸è´"); |
| | | } |
| | | List<MesWmTransactionDO> flows = transactionMapper.selectList(new LambdaQueryWrapperX<MesWmTransactionDO>() |
| | | .eq(MesWmTransactionDO::getBizType, MesBizTypeConstants.WM_PRODUCT_PRODUCE) |
| | | .eq(MesWmTransactionDO::getBizId, produce.getId()) |
| | | .eq(MesWmTransactionDO::getBizLineId, line.getId()) |
| | | .eq(MesWmTransactionDO::getWorkOrderId, order.getId()) |
| | | .eq(MesWmTransactionDO::getItemId, qc.getItemId()) |
| | | .eq(MesWmTransactionDO::getType, MesWmTransactionTypeEnum.IN.getType())); |
| | | BigDecimal stocked = flows.stream().map(MesWmTransactionDO::getQuantity) |
| | | .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | if (stocked.compareTo(qc.getQualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, "è¿å·¥å¤æ£åæ ¼äº§åºçå
¥åºæµæ°´æ°éä¸å¹é
ï¼æä¸è½å
³é"); |
| | | } |
| | | if (qc.getUnqualifiedQuantity().signum() > 0) { |
| | | List<MesQcNcrDO> children = ncrMapper.selectList(new LambdaQueryWrapperX<MesQcNcrDO>() |
| | | .eq(MesQcNcrDO::getQcType, MesQcTypeEnum.IPQC.getType()) |
| | | .eq(MesQcNcrDO::getQcId, qc.getId())); |
| | | if (children.size() != 1 || !Objects.equals(children.get(0).getItemId(), ncr.getItemId()) |
| | | || children.get(0).getUnqualifiedQuantity() == null |
| | | || children.get(0).getUnqualifiedQuantity().compareTo(qc.getUnqualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, "夿£ä¸åæ ¼æ°éé¡»å
¨é¨ç§»äº¤è³å¯ä¸çæ°ä¸åæ ¼å¤çåï¼è¯·æ ¸å¯¹ç©æåæ°é"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** å
¶ä»æ£éªæ¥æºä¿æåæå
³éè§åã */ |
| | | private void validateLegacy(MesQcIpqcDO qc, MesProWorkOrderDO order, MesQcNcrDO ncr) { |
| | | if (qc == null || !Objects.equals(qc.getWorkOrderId(), order.getId()) |
| | | || !Objects.equals(qc.getItemId(), order.getProductId()) |
| | | || qc.getSourceDocType() != null || qc.getSourceDocId() != null || qc.getSourceLineId() != null) { |
| | | throw new ServiceException(400, "è¯·éæ©è¯¥è¿å·¥è®¢å产åçç¬ç«è¿ç¨æ£éªåä½ä¸ºå¤æ£è¯æ®"); |
| | | } |
| | | if (qc.getCreateTime() == null || qc.getCreateTime().isBefore(order.getFinishDate()) |
| | | || qc.getInspectDate() == null || qc.getInspectDate().isBefore(order.getFinishDate())) { |
| | | throw new ServiceException(400, "夿£åé¡»å¨è¿å·¥è®¢å宿åå建并æ£éªï¼è¯·æ ¸å¯¹æ£éªæ¥æ"); |
| | | } |
| | | BigDecimal quantity = ncr.getUnqualifiedQuantity(); |
| | | if (!Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus()) |
| | | || !Objects.equals(qc.getCheckResult(), 1) |
| | | || quantity == null || quantity.signum() <= 0 |
| | | || qc.getCheckQuantity() == null || qc.getCheckQuantity().compareTo(quantity) != 0 |
| | | || qc.getQualifiedQuantity() == null || qc.getQualifiedQuantity().compareTo(quantity) != 0 |
| | | || qc.getUnqualifiedQuantity() == null || qc.getUnqualifiedQuantity().signum() != 0) { |
| | | throw new ServiceException(400, "夿£å¿
须已宿䏿´æ¹åæ ¼ï¼æ£æµæ°éååæ ¼æ°éé¡»çäºä¸åæ ¼åæ°é"); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesDetailMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** å¨è°ç¨æ¹äºå¡å
æå®¢æ·éè´§çä¸åæ ¼è¡æ´è¡è½¬äº¤ NCRã */ |
| | | @Service |
| | | public class MesQcNcrReturnSalesLink { |
| | | @Resource private MesQcRqcMapper rqcMapper; |
| | | @Resource private MesWmReturnSalesMapper salesMapper; |
| | | @Resource private MesWmReturnSalesLineMapper lineMapper; |
| | | @Resource private MesWmReturnSalesDetailMapper detailMapper; |
| | | |
| | | public String claim(MesQcNcrDO ncr) { |
| | | MesQcRqcDO qc = rqcMapper.selectById(ncr.getQcId()); |
| | | if (qc == null || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_RETURN_SALES) |
| | | || qc.getSourceDocId() == null || qc.getSourceLineId() == null |
| | | || !Objects.equals(qc.getItemId(), ncr.getItemId()) |
| | | || qc.getUnqualifiedQuantity() == null || ncr.getUnqualifiedQuantity() == null |
| | | || qc.getUnqualifiedQuantity().compareTo(ncr.getUnqualifiedQuantity()) != 0) { |
| | | throw new ServiceException(400, "éè´§æ£éªä¸åæ ¼å¤çä»
æ¯æå¯è¿½æº¯ç客æ·éå®éè´§ï¼ç©æåæ°é须䏿£éªåä¸è´"); |
| | | } |
| | | MesWmReturnSalesDO sales = salesMapper.selectOne(new LambdaQueryWrapperX<MesWmReturnSalesDO>() |
| | | .eq(MesWmReturnSalesDO::getId, qc.getSourceDocId()).last("FOR UPDATE")); |
| | | if (sales == null || Objects.equals(sales.getStatus(), MesWmReturnSalesStatusEnum.FINISHED.getStatus()) |
| | | || Objects.equals(sales.getStatus(), MesWmReturnSalesStatusEnum.CANCELED.getStatus())) { |
| | | throw new ServiceException(400, "æ¥æºéå®éè´§åä¸å卿已宿/åæ¶ï¼ä¸è½è½¬äº¤å¤ç½®"); |
| | | } |
| | | List<MesWmReturnSalesLineDO> lines = lineMapper.selectList(new LambdaQueryWrapperX<MesWmReturnSalesLineDO>() |
| | | .eq(MesWmReturnSalesLineDO::getReturnId, sales.getId()) |
| | | .eq(MesWmReturnSalesLineDO::getRqcId, qc.getId()) |
| | | .eq(MesWmReturnSalesLineDO::getQualityStatus, MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | .last("FOR UPDATE")); |
| | | if (lines.size() != 1) { |
| | | throw new ServiceException(400, "æ æ³å¯ä¸å®ä½éè´§æ£éªå¯¹åºçä¸åæ ¼è¡ï¼è¯·æ ¸å¯¹æ¥æºå
³è"); |
| | | } |
| | | MesWmReturnSalesLineDO line = lines.get(0); |
| | | if (line.getNcrDispositionId() != null || !Objects.equals(line.getItemId(), ncr.getItemId()) |
| | | || line.getQuantity() == null || line.getQuantity().compareTo(ncr.getUnqualifiedQuantity()) != 0 |
| | | || !detailMapper.selectListByLineId(line.getId()).isEmpty()) { |
| | | throw new ServiceException(400, "éè´§ä¸åæ ¼è¡å·²è¢«å¤çãæ°éä¸ä¸è´æå·²æå
¥åºæç»ï¼ä¸è½éå¤å¤ç½®"); |
| | | } |
| | | lineMapper.updateById(new MesWmReturnSalesLineDO().setId(line.getId()).setNcrDispositionId(ncr.getId())); |
| | | return line.getBatchCode(); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeRespVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.scrap.MesWmScrapReceiptDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.scrap.MesWmScrapReceiptMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; |
| | | import jakarta.annotation.Resource; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.Objects; |
| | | |
| | | /** åªæ¥æ¶å·²ç¡®è®¤æªå
¥åºçè¿ç¨ä¸åæ ¼äº§åºæå®¢æ·éè´§ï¼ä¸æ¨æµå
¶ä»æ¥æºçåºåç¶æã */ |
| | | @Service |
| | | public class MesQcNcrScrapService { |
| | | @Resource private MesQcIpqcMapper ipqcMapper; |
| | | @Resource private MesQcRqcMapper rqcMapper; |
| | | @Resource private MesProWorkOrderMapper orderMapper; |
| | | @Resource private MesWmProductProduceLineMapper produceLineMapper; |
| | | @Resource private MesWmProductProduceMapper produceMapper; |
| | | @Resource private MesWmReturnSalesLineMapper returnLineMapper; |
| | | @Resource private MesWmScrapReceiptMapper scrapMapper; |
| | | @Resource private MesWmTransactionMapper transactionMapper; |
| | | @Resource private MesWmTransactionService transactionService; |
| | | @Resource private MesWmWarehouseService warehouseService; |
| | | @Resource private MesWmWarehouseAreaService areaService; |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcNcrDisposeRespVO receive(MesQcNcrDO ncr, MesQcNcrDisposeReqVO req) { |
| | | if (ncr.getQcId() == null || ncr.getItemId() == null || ncr.getUnqualifiedQuantity() == null |
| | | || ncr.getUnqualifiedQuantity().signum() <= 0) throw error("ä¸åæ ¼åæ¥æºãç©æææ°éä¸å®æ´"); |
| | | Long orderId = null, batchId, sourceLineId; |
| | | String orderCode = null, batchCode; |
| | | if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType())) { |
| | | var source = validateIpqcSource(ncr); |
| | | orderId = source.orderId(); orderCode = source.orderCode(); batchId = source.batchId(); |
| | | batchCode = source.batchCode(); sourceLineId = source.sourceLineId(); |
| | | } else if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.RQC.getType())) { |
| | | var qc = rqcMapper.selectById(ncr.getQcId()); |
| | | if (qc == null || !Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus()) |
| | | || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.WM_RETURN_SALES) || qc.getSourceDocId() == null |
| | | || qc.getSourceLineId() == null || !Objects.equals(qc.getItemId(), ncr.getItemId()) |
| | | || !same(qc.getUnqualifiedQuantity(), ncr.getUnqualifiedQuantity())) |
| | | throw error("ä»
å
³è客æ·éå®éè´§çä¸åæ ¼æ£éªå¯å
¥åºæï¼ä¸ç©ææ°éå¿
é¡»ä¸è´"); |
| | | var lines = returnLineMapper.selectListByReturnId(qc.getSourceDocId()).stream() |
| | | .filter(l -> Objects.equals(l.getRqcId(), qc.getId()) |
| | | && Objects.equals(l.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | && Objects.equals(l.getItemId(), ncr.getItemId()) && same(l.getQuantity(), ncr.getUnqualifiedQuantity()) |
| | | && Objects.equals(l.getBatchCode(), qc.getBatchCode())).toList(); |
| | | if (lines.size() != 1) throw error("æ æ³å¯ä¸å®ä½éè´§æ£éªå¯¹åºçä¸åæ ¼éè´§è¡ï¼è¯·æ ¸å¯¹å
³è"); |
| | | var line = lines.get(0); |
| | | requireNoTransaction(MesBizTypeConstants.WM_RETURN_SALES, qc.getSourceDocId(), line.getId()); |
| | | batchId = line.getBatchId(); batchCode = line.getBatchCode(); sourceLineId = line.getId(); |
| | | } else { |
| | | throw error("ä»
è¿ç¨æ£éªå客æ·éè´§æ£éªå¯ä»¥å
¥åºæ"); |
| | | } |
| | | if (req.getWarehouseId() == null || req.getLocationId() == null || req.getAreaId() == null) |
| | | throw error("è¯·éæ©åºæä»ãåºåºååºä½"); |
| | | var warehouse = warehouseService.validateWarehouseExists(req.getWarehouseId()); |
| | | if (!Objects.equals("åºæä»", warehouse.getWarehouseType()) && !Objects.equals("åºæ", warehouse.getWarehouseType())) |
| | | throw error("è¯·éæ©ä»åºç±»åä¸ºåºæä»çä»åº"); |
| | | areaService.validateWarehouseAreaExists(req.getWarehouseId(), req.getLocationId(), req.getAreaId()); |
| | | String code = "SCR-N" + ncr.getQcType() + "-" + sourceLineId; |
| | | if (scrapMapper.selectByCode(code) != null) throw error("该æ£éªåçä¸åæ ¼åå·²ç»è®°åºæï¼è¯·å¿éå¤å¤ç½®"); |
| | | var receipt = new MesWmScrapReceiptDO(); |
| | | receipt.setCode(code); receipt.setWorkOrderId(orderId); receipt.setWorkOrderCode(orderCode); |
| | | receipt.setItemId(ncr.getItemId()); receipt.setQuantity(ncr.getUnqualifiedQuantity()); |
| | | receipt.setWarehouseId(req.getWarehouseId()); receipt.setLocationId(req.getLocationId()); receipt.setAreaId(req.getAreaId()); |
| | | receipt.setBatchId(batchId); receipt.setBatchCode(batchCode); receipt.setReceiptTime(LocalDateTime.now()); |
| | | receipt.setRemark("æ¥æºä¸åæ ¼åå¤çå " + ncr.getCode()); |
| | | scrapMapper.insert(receipt); |
| | | Long tx = transactionService.createTransaction(new MesWmTransactionSaveReqDTO() |
| | | .setType(MesWmTransactionTypeEnum.IN.getType()).setBizType(MesBizTypeConstants.WM_SCRAP_RECEIPT) |
| | | .setBizId(receipt.getId()).setBizLineId(receipt.getId()).setBizCode(code).setWorkOrderId(orderId) |
| | | .setItemId(receipt.getItemId()).setQuantity(receipt.getQuantity()).setBatchId(batchId).setBatchCode(batchCode) |
| | | .setWarehouseId(receipt.getWarehouseId()).setLocationId(receipt.getLocationId()).setAreaId(receipt.getAreaId()) |
| | | .setReceiptTime(receipt.getReceiptTime()).setForceCreateNewStock(true)); |
| | | receipt.setTransactionId(tx); scrapMapper.updateById(receipt); |
| | | var result = new MesQcNcrDisposeRespVO(); |
| | | result.setDisposition(MesNcrDispositionEnum.SCRAP.getType()); result.setDocType("SCRAP_RECEIPT"); |
| | | result.setDocId(receipt.getId()); result.setDocCode(code); result.setDocName("åºæå
¥åºå"); |
| | | result.setMessage("å·²ç»è®°åºæå
¥åºå[" + code + "]å¹¶å
¥åºæä»"); return result; |
| | | } |
| | | |
| | | /** 让æ¥å
¥åºä¸æ¥åºå¿
é¡»å¤ç¨åä¸ä¸åæ ¼äº§åºæ¥æºåæ¹æ¬¡ï¼è°ç¨è
è´è´£äºå¡åæ¥æºæ ¸éã */ |
| | | public IpqcSource validateIpqcSource(MesQcNcrDO ncr) { |
| | | if (ncr.getQcId() == null || ncr.getItemId() == null || ncr.getUnqualifiedQuantity() == null |
| | | || ncr.getUnqualifiedQuantity().signum() <= 0 || !Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType())) |
| | | throw error("ä¸åæ ¼åè¿ç¨æ£éªæ¥æºãç©æææ°éä¸å®æ´"); |
| | | var qc = ipqcMapper.selectById(ncr.getQcId()); |
| | | if (qc == null || !Objects.equals(qc.getStatus(), MesQcStatusEnum.FINISHED.getStatus()) |
| | | || !Objects.equals(qc.getSourceDocType(), MesBizTypeConstants.PRO_FEEDBACK) |
| | | || qc.getSourceDocId() == null || qc.getSourceLineId() == null |
| | | || !Objects.equals(qc.getItemId(), ncr.getItemId()) || !same(qc.getUnqualifiedQuantity(), ncr.getUnqualifiedQuantity())) |
| | | throw error("è¿ç¨æ£éªæªå
³è宿´æ¥å·¥äº§åºæä¸åæ ¼æ°éä¸ä¸è´ï¼ä¸è½å
¥åº"); |
| | | var original = produceLineMapper.selectById(qc.getSourceLineId()); |
| | | if (original == null || original.getProduceId() == null || !Objects.equals(original.getFeedbackId(), qc.getSourceDocId())) |
| | | throw error("è¿ç¨æ£éªå产åºè¡ä¸å卿æ¥å·¥ä¸ä¸è´"); |
| | | var produce = produceMapper.selectById(original.getProduceId()); |
| | | if (produce == null || !Objects.equals(produce.getFeedbackId(), qc.getSourceDocId()) |
| | | || qc.getWorkOrderId() == null || !Objects.equals(produce.getWorkOrderId(), qc.getWorkOrderId())) |
| | | throw error("è¿ç¨æ£éªãæ¥å·¥äº§åºåç产工åå
³èä¸ä¸è´"); |
| | | var candidates = produceLineMapper.selectListByFeedbackId(qc.getSourceDocId()).stream() |
| | | .filter(l -> Objects.equals(l.getProduceId(), original.getProduceId()) |
| | | && Objects.equals(l.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | && Objects.equals(l.getItemId(), ncr.getItemId()) && same(l.getQuantity(), ncr.getUnqualifiedQuantity()) |
| | | && Objects.equals(l.getBatchId(), original.getBatchId()) && Objects.equals(l.getBatchCode(), original.getBatchCode())) |
| | | .toList(); |
| | | if (candidates.size() != 1) throw error("æ æ³å¯ä¸å®ä½æªå
¥åºçä¸åæ ¼äº§åºè¡"); |
| | | var line = candidates.get(0); |
| | | requireNoTransaction(MesBizTypeConstants.WM_PRODUCT_PRODUCE, line.getProduceId(), line.getId()); |
| | | var order = qc.getWorkOrderId() == null ? null : orderMapper.selectById(qc.getWorkOrderId()); |
| | | if (order == null) throw error("è¿ç¨æ£éªç产订åä¸åå¨"); |
| | | return new IpqcSource(order.getId(), order.getCode(), line.getId(), line.getBatchId(), line.getBatchCode()); |
| | | } |
| | | |
| | | public record IpqcSource(Long orderId, String orderCode, Long sourceLineId, Long batchId, String batchCode) {} |
| | | |
| | | /** æ¡ä»¶æ´æ°ææè¡éç´å°äºå¡ç»æï¼ååºåä¸å¡ä¸åæäº¤æåæ»ã */ |
| | | public void claimIpqcSource(MesQcNcrDO ncr, IpqcSource source) { |
| | | if (ncr.getId() == null || source == null || source.sourceLineId() == null) throw error("ä¸åæ ¼äº§åºæ¥æºä¸å®æ´"); |
| | | LambdaUpdateWrapper<MesWmProductProduceLineDO> claim = new LambdaUpdateWrapper<MesWmProductProduceLineDO>() |
| | | .set(MesWmProductProduceLineDO::getNcrDispositionId, ncr.getId()) |
| | | .eq(MesWmProductProduceLineDO::getId, source.sourceLineId()) |
| | | .eq(MesWmProductProduceLineDO::getQualityStatus, MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | .eq(MesWmProductProduceLineDO::getItemId, ncr.getItemId()) |
| | | .eq(MesWmProductProduceLineDO::getQuantity, ncr.getUnqualifiedQuantity()) |
| | | .isNull(MesWmProductProduceLineDO::getNcrDispositionId); |
| | | if (source.batchId() == null) claim.isNull(MesWmProductProduceLineDO::getBatchId); |
| | | else claim.eq(MesWmProductProduceLineDO::getBatchId, source.batchId()); |
| | | if (source.batchCode() == null) claim.isNull(MesWmProductProduceLineDO::getBatchCode); |
| | | else claim.eq(MesWmProductProduceLineDO::getBatchCode, source.batchCode()); |
| | | int changed = produceLineMapper.update(null, claim); |
| | | if (changed != 1) throw error("ä¸åæ ¼äº§åºè¡å·²å¤ç½®æåçååï¼è¯·å·æ°åæ ¸å¯¹"); |
| | | } |
| | | |
| | | private void requireNoTransaction(Integer type, Long docId, Long lineId) { |
| | | if (transactionMapper.selectCount(new LambdaQueryWrapperX<MesWmTransactionDO>() |
| | | .eq(MesWmTransactionDO::getBizType, type).eq(MesWmTransactionDO::getBizId, docId) |
| | | .eq(MesWmTransactionDO::getBizLineId, lineId)) > 0) |
| | | throw error("æ¥æºä¸åæ ¼è¡å·²æåºåæµæ°´ï¼è¯·å
对账ï¼ä¸è½éå¤å
¥åº"); |
| | | } |
| | | private static boolean same(BigDecimal a, BigDecimal b) { return a != null && b != null && a.compareTo(b) == 0; } |
| | | private static ServiceException error(String message) { return new ServiceException(400, message); } |
| | | } |
| | |
| | | * @param id ç¼å· |
| | | * @param bpmResult BPM 审æ¹ç»æ |
| | | */ |
| | | void updateNcrAuditStatus(Long id, Integer bpmResult); |
| | | void updateNcrAuditStatus(Long id, String processInstanceId, Integer bpmResult); |
| | | |
| | | /** |
| | | * å
³éä¸åæ ¼åå¤çå |
| | |
| | | * @param id ç¼å· |
| | | * @param closeOpinion å
³éæè§ |
| | | */ |
| | | void closeNcr(Long id, String closeOpinion); |
| | | void closeNcr(Long id, String closeOpinion, Long reinspectionIpqcId); |
| | | |
| | | /** |
| | | * æ ¡éªä¸åæ ¼åå¤çååå¨ |
| | |
| | | */ |
| | | private static final Set<Integer> DISPOSITION_ALLOWED_TYPES = Set.of( |
| | | MesNcrDispositionEnum.SCRAP.getType(), |
| | | MesNcrDispositionEnum.RETURN.getType(), |
| | | MesNcrDispositionEnum.REPRODUCTION.getType(), |
| | | MesNcrDispositionEnum.REWORK.getType(), |
| | | MesNcrDispositionEnum.CONCESSION.getType(), |
| | | MesNcrDispositionEnum.REPAIR.getType()); |
| | | |
| | | @Resource |
| | | private MesQcNcrMapper ncrMapper; |
| | | |
| | | @Resource |
| | | private MesQcNcrReinspectionValidator reinspectionValidator; |
| | | |
| | | @Resource |
| | | private MesQcNcrStockClosureGuard stockClosureGuard; |
| | | |
| | | @Resource private MesQcNcrScrapService scrapService; |
| | | @Resource private MesQcNcrReturnSalesLink returnSalesLink; |
| | | @Resource private MesQcPurchaseReturnService purchaseReturnService; |
| | | @Resource private MesQcPurchaseConcessionService purchaseConcessionService; |
| | | @Resource private MesQcNcrOqcSourceClaim oqcSourceClaim; |
| | | @Resource private MesQcNcrOqcConcessionService oqcConcessionService; |
| | | @Resource private MesQcNcrOqcReproductionService oqcReproductionService; |
| | | @Resource private MesQcNcrOqcReworkService oqcReworkService; |
| | | |
| | | @Resource |
| | | private MdmItemApi mdmItemApi; |
| | |
| | | public void updateNcr(MesQcNcrSaveReqVO updateReqVO) { |
| | | // 1. æ ¡éªåå¨ + åªå
许å¾
è¯å®¡ç¶æç¼è¾ |
| | | MesQcNcrDO ncr = validateNcrStatusPendingReview(updateReqVO.getId()); |
| | | if (ncr.getQcId() != null && (!Objects.equals(ncr.getItemId(), updateReqVO.getItemId()) |
| | | || ncr.getUnqualifiedQuantity() == null || updateReqVO.getUnqualifiedQuantity() == null |
| | | || ncr.getUnqualifiedQuantity().compareTo(updateReqVO.getUnqualifiedQuantity()) != 0)) { |
| | | throw new ServiceException(400, "æ¥æºè´¨æ£åçæçä¸åæ ¼ç©æåæ°éä¸è½ä¿®æ¹"); |
| | | } |
| | | validateNcrCodeUnique(updateReqVO.getId(), updateReqVO.getCode()); |
| | | validateItemExists(updateReqVO.getItemId()); |
| | | |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteNcr(Long id) { |
| | | // æ ¡éªåå¨ + åªå
许å é¤å¾
è¯å®¡ç¶æ |
| | | validateNcrStatusPendingReview(id); |
| | | MesQcNcrDO ncr = validateNcrStatusPendingReview(id); |
| | | if (ncr.getQcId() != null) { |
| | | throw new ServiceException(400, "æ¥æºè´¨æ£åçæçä¸åæ ¼å¤çåä¸è½å é¤"); |
| | | } |
| | | ncrMapper.deleteById(id); |
| | | } |
| | | |
| | |
| | | public void reviewNcr(Long id, Integer disposition, Long responsibleDeptId, |
| | | Integer defectLevel, String reviewOpinion) { |
| | | // 1. æ ¡éªåå¨ + å¾
è¯å®¡ç¶æ |
| | | validateNcrStatusPendingReview(id); |
| | | MesQcNcrDO ncr = validateNcrStatusPendingReview(id); |
| | | validateDisposition(disposition); |
| | | validateDispositionForSource(ncr, disposition); |
| | | |
| | | // 2. æ´æ°è¯å®¡ä¿¡æ¯ |
| | | MesQcNcrDO updateObj = new MesQcNcrDO() |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcNcrDisposeRespVO disposeNcr(MesQcNcrDisposeReqVO disposeReqVO) { |
| | | // 1. æ ¡éªåå¨ + å·²è¯å®¡ï¼å®¡æ¹éè¿ï¼ç¶æ |
| | | MesQcNcrDO ncr = validateNcrExists(disposeReqVO.getId()); |
| | | MesQcNcrDO ncr = validateNcrExistsForUpdate(disposeReqVO.getId()); |
| | | if (!MesNcrStatusEnum.REVIEWED.getStatus().equals(ncr.getStatus())) { |
| | | throw exception(QC_NCR_DISPOSE_STATUS_INVALID, ncr.getCode(), resolveStatusName(ncr.getStatus())); |
| | | } |
| | | // 2. å¤ç½®æ¹å¼å¿
å¡«ï¼ä¸å¿
é¡»æ¯åæ³æä¸¾å¼ |
| | | validateDisposition(disposeReqVO.getDisposition()); |
| | | validateDispositionForSource(ncr, disposeReqVO.getDisposition()); |
| | | // 3. ä¸åæ ¼æ°éå¿
é¡»å¤§äº 0ï¼å¦åæ æ³çæå
¥åºå / ç产订å |
| | | if (ncr.getUnqualifiedQuantity() == null |
| | | || ncr.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) <= 0) { |
| | |
| | | |
| | | // 4. æå¤ç½®æ¹å¼èå¨çæä¸å¡åæ® |
| | | MesQcNcrDisposeRespVO respVO; |
| | | if (Objects.equals(MesNcrDispositionEnum.CONCESSION.getType(), disposeReqVO.getDisposition()) |
| | | || Objects.equals(MesNcrDispositionEnum.SCRAP.getType(), disposeReqVO.getDisposition())) { |
| | | // è®©æ¥æ¾è¡ / æ¥åºï¼çææé¡¹å
¥åºåå¹¶èªå¨å®æå
¥åº |
| | | MesQcNcrScrapService.IpqcSource processSource = null; |
| | | MesQcNcrOqcSourceClaim.OqcClaimedSource oqcSource = null; |
| | | if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType())) { |
| | | oqcSource = oqcSourceClaim.claim(ncr); |
| | | } |
| | | if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType())) { |
| | | processSource = scrapService.validateIpqcSource(ncr); |
| | | scrapService.claimIpqcSource(ncr, processSource); |
| | | if (StrUtil.isNotBlank(processSource.batchCode()) && StrUtil.isNotBlank(disposeReqVO.getBatchCode()) |
| | | && !Objects.equals(processSource.batchCode(), StrUtil.trim(disposeReqVO.getBatchCode()))) { |
| | | throw new ServiceException(400, "å¤ç½®æ¹æ¬¡å¿
é¡»ä¸è¿ç¨æ£éªåæ¹æ¬¡ä¸è´"); |
| | | } |
| | | if (StrUtil.isNotBlank(processSource.batchCode())) disposeReqVO.setBatchCode(processSource.batchCode()); |
| | | } |
| | | if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.RQC.getType())) { |
| | | String originalBatch = returnSalesLink.claim(ncr); |
| | | if (StrUtil.isNotBlank(originalBatch) && StrUtil.isNotBlank(disposeReqVO.getBatchCode()) |
| | | && !Objects.equals(originalBatch, StrUtil.trim(disposeReqVO.getBatchCode()))) { |
| | | throw new ServiceException(400, "å¤ç½®æ¹æ¬¡å¿
é¡»ä¸å®¢æ·éè´§åæ¹æ¬¡ä¸è´"); |
| | | } |
| | | if (StrUtil.isNotBlank(originalBatch)) disposeReqVO.setBatchCode(originalBatch); |
| | | } |
| | | if (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.RETURN.getType())) { |
| | | var doc = purchaseReturnService.createForNcr(ncr); |
| | | respVO = new MesQcNcrDisposeRespVO(); |
| | | respVO.setDisposition(disposeReqVO.getDisposition()); |
| | | respVO.setDocType("PURCHASE_RETURN"); |
| | | respVO.setDocId(doc.getId()); |
| | | respVO.setDocCode(doc.getCode()); |
| | | respVO.setDocName("éè´éè´§å"); |
| | | respVO.setMessage("éè´éè´§åå·²çæï¼è¯·ç±ä»åºç¡®è®¤éè´§"); |
| | | } else if (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.REPRODUCTION.getType())) { |
| | | respVO = processSource != null |
| | | ? oqcReproductionService.reproduceIpqc(ncr, disposeReqVO.getDisposition(), processSource) |
| | | : oqcReproductionService.reproduce(ncr, disposeReqVO.getDisposition()); |
| | | } else if (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.CONCESSION.getType()) |
| | | && Objects.equals(ncr.getQcType(), MesQcTypeEnum.IQC.getType())) { |
| | | respVO = purchaseConcessionService.dispose(ncr, disposeReqVO); |
| | | } else if (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.CONCESSION.getType()) |
| | | && Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType())) { |
| | | oqcConcessionService.release(ncr, disposeReqVO); |
| | | respVO = new MesQcNcrDisposeRespVO(); |
| | | respVO.setDisposition(disposeReqVO.getDisposition()); |
| | | respVO.setDocType("PRODUCT_SALES"); |
| | | MesQcOqcDO sourceOqc = oqcService.getOqc(ncr.getQcId()); |
| | | respVO.setDocId(sourceOqc.getSourceDocId()); |
| | | respVO.setDocCode(sourceOqc.getSourceDocCode()); |
| | | respVO.setDocName("éå®åºåºå"); |
| | | respVO.setMessage("åºè´§è®©æ¥å·²å®æåéå®åºåºåæ£è´§ååºåº"); |
| | | } else if (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.SCRAP.getType()) |
| | | && (Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType()) |
| | | || Objects.equals(ncr.getQcType(), MesQcTypeEnum.RQC.getType()))) { |
| | | respVO = scrapService.receive(ncr, disposeReqVO); |
| | | } else if (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.CONCESSION.getType()) |
| | | && (Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType()) |
| | | || Objects.equals(ncr.getQcType(), MesQcTypeEnum.RQC.getType()))) { |
| | | respVO = createMiscReceiptForDispose(ncr, disposeReqVO); |
| | | } else if (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.CONCESSION.getType()) |
| | | || Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.SCRAP.getType())) { |
| | | stockClosureGuard.validateBeforeDispose(disposeReqVO.getDisposition()); |
| | | throw new ServiceException(400, "è¯¥æ¥æºæä¸æ¯ææ¤å¤ç½®æ¹å¼"); |
| | | } else if (oqcSource != null && (Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.REWORK.getType()) |
| | | || Objects.equals(disposeReqVO.getDisposition(), MesNcrDispositionEnum.REPAIR.getType()))) { |
| | | // åºè´§è¿å·¥/è¿ä¿®ï¼æ£ååæååºå â åæ¶åéå®åºåºå â å
éè¿å·¥å·¥å |
| | | respVO = oqcReworkService.dispose(ncr, oqcSource, disposeReqVO); |
| | | } else { |
| | | // è¿å·¥ / è¿ä¿®ï¼çæèªè¡ç产类åçç产订å |
| | | respVO = createReworkWorkOrderForDispose(ncr, disposeReqVO); |
| | |
| | | // è¯¥æ¹æ³ä¼æææå¼å¸¸ç»ä¸å
æ RuntimeExceptionï¼å æ¤è¿éä¸è½ç´æ¥ catch ServiceExceptionï¼ |
| | | // å¿
须沿 cause é¾å¤ææ¯å¦ä¸ºãæµç¨å®ä¹ä¸åå¨ãã |
| | | // 1. æ ¡éªåå¨ + å¾
è¯å®¡ï¼è稿/审æ¹é©³ååï¼ç¶æ |
| | | MesQcNcrDO ncr = validateNcrExists(id); |
| | | MesQcNcrDO ncr = validateNcrExistsForUpdate(id); |
| | | if (!MesNcrStatusEnum.PENDING_REVIEW.getStatus().equals(ncr.getStatus())) { |
| | | throw exception(QC_NCR_SUBMIT_STATUS_INVALID, ncr.getCode(), resolveStatusName(ncr.getStatus())); |
| | | } |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateNcrAuditStatus(Long id, Integer bpmResult) { |
| | | public void updateNcrAuditStatus(Long id, String processInstanceId, Integer bpmResult) { |
| | | // 1. æ ¡éªåå¨ + 审æ¹ä¸ï¼é¿å
å¹¶åæéå¤åè°è¦çå·²å¤ç½®/å·²å
³éç¶æï¼ |
| | | MesQcNcrDO ncr = validateNcrExists(id); |
| | | if (!MesNcrStatusEnum.APPROVING.getStatus().equals(ncr.getStatus())) { |
| | | MesQcNcrDO ncr = validateNcrExistsForUpdate(id); |
| | | if (!MesNcrStatusEnum.APPROVING.getStatus().equals(ncr.getStatus()) |
| | | || StrUtil.isBlank(processInstanceId) |
| | | || !Objects.equals(processInstanceId, ncr.getProcessInstanceId())) { |
| | | return; |
| | | } |
| | | |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void closeNcr(Long id, String closeOpinion) { |
| | | public void closeNcr(Long id, String closeOpinion, Long reinspectionIpqcId) { |
| | | // 1. æ ¡éªåå¨ + å·²å¤ç½®ç¶æ |
| | | MesQcNcrDO ncr = validateNcrExists(id); |
| | | MesQcNcrDO ncr = validateNcrExistsForUpdate(id); |
| | | if (!MesNcrStatusEnum.DISPOSED.getStatus().equals(ncr.getStatus())) { |
| | | throw exception(QC_NCR_CLOSE_STATUS_INVALID, ncr.getCode(), resolveStatusName(ncr.getStatus())); |
| | | } |
| | | |
| | | if (Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.RETURN.getType()) |
| | | && Objects.equals(ncr.getQcType(), MesQcTypeEnum.IQC.getType())) { |
| | | purchaseReturnService.validateConfirmed(ncr.getId()); |
| | | } else if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType()) |
| | | && Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.CONCESSION.getType())) { |
| | | oqcConcessionService.validateBeforeClose(ncr); |
| | | } else if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.IQC.getType()) |
| | | && Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.CONCESSION.getType())) { |
| | | // éè´è®©æ¥çå
¥åºå ERP ååä¸å¤ç½®å¨åä¸äºå¡å®æã |
| | | } else if (Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.REPRODUCTION.getType())) { |
| | | throw new ServiceException(400, "éæ°ç产åé宿夿£å¹¶å¤çååºåºåï¼æä¸è½å
³é"); |
| | | } else if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType()) |
| | | && (Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.REWORK.getType()) |
| | | || Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.REPAIR.getType()))) { |
| | | oqcReworkService.validateBeforeClose(ncr); |
| | | } else if (!Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType()) |
| | | && !Objects.equals(ncr.getQcType(), MesQcTypeEnum.RQC.getType())) { |
| | | stockClosureGuard.validateBeforeClose(ncr); |
| | | } |
| | | |
| | | if (Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.REWORK.getType()) |
| | | || Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.REPAIR.getType())) { |
| | | reinspectionValidator.validate(ncr, reinspectionIpqcId); |
| | | } else { |
| | | reinspectionIpqcId = null; |
| | | } |
| | | |
| | | // 2. æ´æ°å
³éä¿¡æ¯ |
| | |
| | | .setCloseUserId(getLoginUserId()) |
| | | .setCloseDate(LocalDateTime.now()) |
| | | .setCloseOpinion(closeOpinion) |
| | | .setReinspectionIpqcId(reinspectionIpqcId) |
| | | .setStatus(MesNcrStatusEnum.CLOSED.getStatus())); |
| | | } |
| | | |
| | |
| | | return ncr; |
| | | } |
| | | |
| | | private MesQcNcrDO validateNcrExistsForUpdate(Long id) { |
| | | MesQcNcrDO ncr = ncrMapper.selectByIdForUpdate(id); |
| | | if (ncr == null) { |
| | | throw exception(QC_NCR_NOT_EXISTS, id); |
| | | } |
| | | return ncr; |
| | | } |
| | | |
| | | /** |
| | | * æ ¡éª NCR åå¨ä¸ä¸ºå¾
è¯å®¡ç¶æ |
| | | */ |
| | | private MesQcNcrDO validateNcrStatusPendingReview(Long id) { |
| | | MesQcNcrDO ncr = validateNcrExists(id); |
| | | MesQcNcrDO ncr = validateNcrExistsForUpdate(id); |
| | | if (!MesNcrStatusEnum.PENDING_REVIEW.getStatus().equals(ncr.getStatus())) { |
| | | throw exception(QC_NCR_STATUS_NOT_PENDING, ncr.getCode(), resolveStatusName(ncr.getStatus())); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private void validateDispositionForSource(MesQcNcrDO ncr, Integer disposition) { |
| | | if (Objects.equals(ncr.getQcType(), MesQcTypeEnum.IQC.getType())) { |
| | | if (!Objects.equals(disposition, MesNcrDispositionEnum.RETURN.getType()) |
| | | && !Objects.equals(disposition, MesNcrDispositionEnum.CONCESSION.getType())) { |
| | | throw new ServiceException(400, "éè´æ£éªä¸åæ ¼ä»
æ¯æéè´§æè®©æ¥æ¾è¡"); |
| | | } |
| | | } else if (Objects.equals(disposition, MesNcrDispositionEnum.RETURN.getType())) { |
| | | throw new ServiceException(400, "åªæéè´æ£éªä¸åæ ¼æ¯æéè´éè´§"); |
| | | } |
| | | if (Objects.equals(disposition, MesNcrDispositionEnum.REPRODUCTION.getType()) |
| | | && !Objects.equals(ncr.getQcType(), MesQcTypeEnum.OQC.getType()) |
| | | && !Objects.equals(ncr.getQcType(), MesQcTypeEnum.IPQC.getType())) { |
| | | throw new ServiceException(400, "éæ°ç产ä»
æ¯æè¿ç¨æåºè´§æ£éªä¸åæ ¼"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * ç¶æå¼è½¬åç§°ï¼è§£æå¤±è´¥åé为 ID å符串 |
| | | */ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | import java.util.Objects; |
| | | |
| | | /** åºåé离å鿝éç¯å°æªå»ºç«åï¼ç¦æ¢éè¿å
¥åºä¼ªé 宿å¤ç½®ã */ |
| | | @Component |
| | | public class MesQcNcrStockClosureGuard { |
| | | |
| | | public void validateBeforeDispose(Integer disposition) { |
| | | if (Objects.equals(disposition, MesNcrDispositionEnum.SCRAP.getType())) { |
| | | throw new ServiceException(400, "æ¥åºå¤ç½®æä¸å¯æ§è¡ï¼éå
建ç«ä¸åæ ¼åé离å
¥åºãéæ¯ç¡®è®¤å鿝åºåºéç¯ï¼é¿å
éå¤å
¥åºæè´¦å®ä¸ç¬¦"); |
| | | } |
| | | if (Objects.equals(disposition, MesNcrDispositionEnum.CONCESSION.getType())) { |
| | | throw new ServiceException(400, "è®©æ¥æ¾è¡æä¸å¯æ§è¡ï¼éå
å®ææ¥æºåæ®ãåæ¹æ¬¡ååºåçæ¾è¡éç¯ï¼é¿å
éå¤å
¥åºææ¾è¡éè¯¯æ¹æ¬¡"); |
| | | } |
| | | } |
| | | |
| | | public void validateBeforeClose(MesQcNcrDO ncr) { |
| | | if (Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.SCRAP.getType())) { |
| | | throw new ServiceException(400, "æ¥åºå¤çåæä¸å¯å
³éï¼å°æªå»ºç«é离åºåçéæ¯ç¡®è®¤å鿝åºåºéç¯ï¼è¯·ä¿çåæ®çå¾
å¤ç"); |
| | | } |
| | | if (Objects.equals(ncr.getDisposition(), MesNcrDispositionEnum.CONCESSION.getType())) { |
| | | throw new ServiceException(400, "è®©æ¥æ¾è¡å¤çåæä¸å¯å
³éï¼å°æªå®ææ¥æºåæ®ãåæ¹æ¬¡ååºåçæ¾è¡æ ¸éªï¼è¯·ä¿çåæ®çå¾
å¤ç"); |
| | | } |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.line.MesWmItemReceiptLineSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.detail.MesWmItemReceiptDetailSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseConcessionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseConcessionMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseReturnMapper; |
| | | import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptDetailService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** è®©æ¥æ°éç»éè´å
¥åºè¿è´¦ï¼åæ ¼ä½éä»ç±åå°è´§åå
¥åºã */ |
| | | @Service |
| | | public class MesQcPurchaseConcessionService { |
| | | @Resource private MesQcNcrPurchaseSourceValidator validator; |
| | | @Resource private MesQcPurchaseConcessionMapper concessionMapper; |
| | | @Resource private MesQcPurchaseReturnMapper returnMapper; |
| | | @Resource private MesWmItemReceiptService receipts; |
| | | @Resource private MesWmItemReceiptLineService lines; |
| | | @Resource private MesWmItemReceiptDetailService details; |
| | | @Resource private MesQcArrivalDispositionService arrivalDispositionService; |
| | | |
| | | /** å¿
é¡»ç±éå® NCR çå¤ç½®äºå¡è°ç¨ã */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcNcrDisposeRespVO dispose(MesQcNcrDO ncr, MesQcNcrDisposeReqVO request) { |
| | | var source = validator.validate(ncr); |
| | | if (returnMapper.selectByIqcId(ncr.getQcId()) != null || concessionMapper.selectByIqcId(ncr.getQcId()) != null) |
| | | throw new ServiceException(400, "è¯¥æ¥ææ£éªå·²æéè´å¤ç½®åï¼ä¸è½éå¤å¤ç½®"); |
| | | if (source.notice().getVendorId() == null || source.notice().getPurchaseOrderId() == null |
| | | || source.line().getPurchaseOrderItemId() == null) |
| | | throw new ServiceException(400, "å°è´§å缺å°ä¾åºåæéè´è®¢åæç»ï¼æ æ³çæéè´å
¥åº"); |
| | | if (request.getWarehouseId() == null || request.getLocationId() == null || request.getAreaId() == null) |
| | | throw new ServiceException(400, "让æ¥å
¥åºè¯·éæ©ä»åºãåºåºååºä½"); |
| | | String code = "NCR-IR-" + ncr.getId(); |
| | | var head = new MesWmItemReceiptSaveReqVO(); |
| | | head.setCode(code); head.setName("让æ¥å
¥åº-" + ncr.getCode()); |
| | | head.setIqcId(ncr.getQcId()); head.setVendorId(source.notice().getVendorId()); |
| | | head.setPurchaseOrderCode(source.notice().getPurchaseOrderCode()); |
| | | head.setReceiptDate(LocalDateTime.now()); |
| | | head.setRemark("æ¥æè®©æ¥å
¥åºï¼æ¥æºä¸åæ ¼å " + ncr.getCode()); |
| | | Long receiptId = receipts.createItemReceipt(head); |
| | | var line = new MesWmItemReceiptLineSaveReqVO(); |
| | | line.setReceiptId(receiptId); line.setItemId(ncr.getItemId()); |
| | | line.setPurchaseOrderItemId(source.line().getPurchaseOrderItemId()); |
| | | line.setReceivedQuantity(ncr.getUnqualifiedQuantity()); line.setBatchCode(request.getBatchCode()); |
| | | line.setLotNumber(source.iqc().getVendorBatch()); |
| | | Long lineId = lines.createItemReceiptLine(line); |
| | | var storedLine = lines.getItemReceiptLine(lineId); |
| | | var detail = new MesWmItemReceiptDetailSaveReqVO(); |
| | | detail.setReceiptId(receiptId); detail.setLineId(lineId); detail.setItemId(ncr.getItemId()); |
| | | detail.setQuantity(ncr.getUnqualifiedQuantity()); detail.setBatchId(storedLine.getBatchId()); |
| | | detail.setWarehouseId(request.getWarehouseId()); detail.setLocationId(request.getLocationId()); detail.setAreaId(request.getAreaId()); |
| | | details.createItemReceiptDetail(detail); |
| | | receipts.submitItemReceipt(receiptId); receipts.stockItemReceipt(receiptId); receipts.finishItemReceipt(receiptId); |
| | | var record = new MesQcPurchaseConcessionDO(); |
| | | record.setNcrId(ncr.getId()); record.setIqcId(ncr.getQcId()); |
| | | record.setNoticeId(source.notice().getId()); record.setNoticeLineId(source.line().getId()); |
| | | record.setPurchaseOrderItemId(source.line().getPurchaseOrderItemId()); |
| | | record.setReceiptId(receiptId); record.setQuantity(ncr.getUnqualifiedQuantity()); |
| | | concessionMapper.insert(record); |
| | | arrivalDispositionService.refresh(source.notice().getId()); |
| | | var response = new MesQcNcrDisposeRespVO(); |
| | | response.setDisposition(request.getDisposition()); response.setDocType("ITEM_RECEIPT"); |
| | | response.setDocId(receiptId); response.setDocCode(code); response.setDocName(head.getName()); |
| | | response.setMessage("è®©æ¥æ°é已宿éè´å
¥åºï¼ååæ ¼æ°éç»§ç»ç±å°è´§åå
¥åº"); |
| | | return response; |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageParam; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseReturnDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseReturnMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseConcessionMapper; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import java.time.LocalDateTime; |
| | | import java.util.Objects; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | |
| | | @Service |
| | | public class MesQcPurchaseReturnService { |
| | | @Resource private MesQcNcrPurchaseSourceValidator sourceValidator; |
| | | @Resource private MesQcPurchaseReturnMapper mapper; |
| | | @Resource private MesQcPurchaseConcessionMapper concessionMapper; |
| | | @Resource private MesQcArrivalDispositionService arrivalDispositionService; |
| | | |
| | | /** å¿
é¡»ç±å·²éå® NCR çå¤ç½®äºå¡è°ç¨ã */ |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public MesQcPurchaseReturnDO createForNcr(MesQcNcrDO ncr) { |
| | | if (mapper.selectByNcrId(ncr.getId()) != null) |
| | | throw new ServiceException(400, "该ä¸åæ ¼åå·²çæéè´éè´§å"); |
| | | var source = sourceValidator.validate(ncr); |
| | | if (concessionMapper.selectByIqcId(ncr.getQcId()) != null || mapper.selectByIqcId(ncr.getQcId()) != null) |
| | | throw new ServiceException(400, "è¯¥æ¥ææ£éªå·²æéè´å¤ç½®åï¼ä¸è½éå¤å¤ç½®"); |
| | | if (source.notice().getVendorId() == null || source.notice().getPurchaseOrderId() == null |
| | | || source.line().getPurchaseOrderItemId() == null) |
| | | throw new ServiceException(400, "å°è´§å缺å°ä¾åºåãéè´è®¢åæéè´æç»ï¼æ æ³çæå¯è¿½æº¯éè´§å"); |
| | | MesQcPurchaseReturnDO doc = new MesQcPurchaseReturnDO(); |
| | | doc.setCode("NCR-RET-" + ncr.getId()); |
| | | doc.setNcrId(ncr.getId()); |
| | | doc.setIqcId(ncr.getQcId()); |
| | | doc.setNoticeId(source.notice().getId()); |
| | | doc.setNoticeLineId(source.line().getId()); |
| | | doc.setPurchaseOrderId(source.notice().getPurchaseOrderId()); |
| | | doc.setPurchaseOrderItemId(source.line().getPurchaseOrderItemId()); |
| | | doc.setVendorId(source.notice().getVendorId()); |
| | | doc.setItemId(ncr.getItemId()); |
| | | doc.setVendorBatch(source.iqc().getVendorBatch()); |
| | | doc.setQuantity(ncr.getUnqualifiedQuantity()); |
| | | doc.setStatus(0); |
| | | mapper.insert(doc); |
| | | return doc; |
| | | } |
| | | |
| | | public MesQcPurchaseReturnDO getByNcrId(Long ncrId) { return mapper.selectByNcrId(ncrId); } |
| | | |
| | | public PageResult<MesQcPurchaseReturnDO> getPage(PageParam page, Integer status) { |
| | | if (status != null && status != 0 && status != 1) throw new ServiceException(400, "éè´éè´§ç¶ææ æ"); |
| | | return mapper.selectPage(page, status); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void confirm(Long id, String remark) { |
| | | MesQcPurchaseReturnDO doc = mapper.selectByIdForUpdate(id); |
| | | if (doc == null) throw new ServiceException(400, "éè´éè´§åä¸åå¨"); |
| | | if (!Objects.equals(doc.getStatus(), 0)) throw new ServiceException(400, "éè´éè´§å已确认éè´§"); |
| | | mapper.updateById(new MesQcPurchaseReturnDO().setId(id).setStatus(1) |
| | | .setConfirmedBy(getLoginUserId()).setConfirmedAt(LocalDateTime.now()).setConfirmationRemark(remark)); |
| | | arrivalDispositionService.refresh(doc.getNoticeId()); |
| | | } |
| | | |
| | | public void validateConfirmed(Long ncrId) { |
| | | MesQcPurchaseReturnDO doc = mapper.selectByNcrId(ncrId); |
| | | if (doc == null || !Objects.equals(doc.getStatus(), 1)) |
| | | throw new ServiceException(400, "请å
ç±éè´ä»åºç¡®è®¤éè´éè´§å®æï¼åå
³éä¸åæ ¼å"); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | // 4. æ´æ°å®¡æ¹ç¶æï¼Service å
é¨ä¼åæ¬¡æ ¡éªä»
审æ¹ä¸ç¶æå¯æ´æ°ï¼ |
| | | ncrService.updateNcrAuditStatus(ncrId, event.getStatus()); |
| | | ncrService.updateNcrAuditStatus(ncrId, event.getId(), event.getStatus()); |
| | | log.info("[onEvent] NCR({}) 审æ¹ç¶ææ´æ°å®æï¼BPM ç¶æï¼{}", ncrId, event.getStatus()); |
| | | } |
| | | |
| | |
| | | rqc.getQualifiedQuantity(), rqc.getUnqualifiedQuantity()); |
| | | } else if (Objects.equals(rqc.getSourceDocType(), MesBizTypeConstants.WM_RETURN_SALES)) { |
| | | // ååéå®éè´§åè¡ï¼æåè¡ + æ´æ°è´¨éç¶æ + èå¨ä¸»åç¶æ |
| | | returnSalesLineService.bindRqcForFinish(rqc.getSourceLineId(), rqc.getSourceDocId(), |
| | | rqc.getId(), rqc.getItemId(), rqc.getCheckQuantity()); |
| | | returnSalesLineService.updateReturnSalesLineWhenRqcFinish( |
| | | rqc.getSourceLineId(), rqc.getSourceDocId(), rqc.getCheckResult(), |
| | | rqc.getQualifiedQuantity(), rqc.getUnqualifiedQuantity()); |
| | |
| | | public class MesWmArrivalNoticeServiceImpl implements MesWmArrivalNoticeService { |
| | | |
| | | @Resource |
| | | private cn.iocoder.yudao.module.mes.service.qc.ncr.MesQcArrivalDispositionService arrivalDispositionService; |
| | | |
| | | @Resource |
| | | private MesWmArrivalNoticeMapper arrivalNoticeMapper; |
| | | |
| | | @Resource |
| | |
| | | // 2. æ´æ°å°è´§éç¥åè¡ï¼ç»å® iqcId + è´¨æ£ç»æ + åæ ¼æ°é |
| | | arrivalNoticeLineService.updateArrivalNoticeLineWhenIqcFinish(lineId, iqcId, checkResult, qualifiedQuantity); |
| | | |
| | | // 3.1 夿æ¯å¦ææéæ£è¡é½å·²å®æè´¨æ£ |
| | | List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id); |
| | | boolean hasUnchecked = CollectionUtils.anyMatch(lines, |
| | | line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcCheckResult() == null); |
| | | if (hasUnchecked) { |
| | | log.info("[updateArrivalNoticeWhenIqcFinish][å°è´§éç¥å({}) è¿ææªå®ææ£éªçè¡ï¼æä¸æ´æ°ç¶æ]", id); |
| | | return; |
| | | } |
| | | |
| | | // 3.2 夿æ¯å¦ææéæ£è¡é½å¯ä»¥å
¥åºï¼åæ ¼æç¹éï¼ |
| | | boolean hasNonInbound = CollectionUtils.anyMatch(lines, |
| | | line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) |
| | | && !MesQcCheckResultEnum.canInbound(line.getIqcCheckResult())); |
| | | if (hasNonInbound) { |
| | | log.info("[updateArrivalNoticeWhenIqcFinish][å°è´§éç¥å({}) åå¨ä¸å¯å
¥åºçè¡ï¼éè´§/æ¥åºï¼ï¼ç¶æä¿æå¾
è´¨æ£]", id); |
| | | return; |
| | | } |
| | | |
| | | // 3.3 ææè¡æ£éªå®æä¸å¯å
¥åºï¼æ´æ°ä¸»è¡¨ç¶æ PENDING_QC â PENDING_RECEIPT |
| | | arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO() |
| | | .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus())); |
| | | // åæ¶è®¤å¯ä»åºå·²ç¡®è®¤çæ¥æéè´§ï¼ä¿çåå§ä¸åæ ¼ç»è®ºã |
| | | arrivalDispositionService.refresh(id); |
| | | } |
| | | |
| | | @Override |
| | |
| | | MesWmBatchGenerateReqVO batchReqVO = new MesWmBatchGenerateReqVO(); |
| | | // ä»å
¥åºåè¡è·å |
| | | batchReqVO.setItemId(reqVO.getItemId()).setProduceDate(reqVO.getProductionDate()) |
| | | .setExpireDate(reqVO.getExpireDate()).setLotNumber(reqVO.getLotNumber()); |
| | | .setExpireDate(reqVO.getExpireDate()).setLotNumber(reqVO.getLotNumber()) |
| | | .setBatchCode(reqVO.getBatchCode()); |
| | | // ä»ç¶åæ®è·å |
| | | batchReqVO.setVendorId(receipt.getVendorId()).setReceiptDate(receipt.getReceiptDate()) |
| | | .setPurchaseOrderCode(receipt.getPurchaseOrderCode()); |
| | |
| | | |
| | | // 6. éåè¡å建å
¥åºåè¡ |
| | | for (MesWmArrivalNoticeLineDO noticeLine : lines) { |
| | | BigDecimal receivable = noticeLine.getQualifiedQuantity() != null |
| | | ? noticeLine.getQualifiedQuantity() : noticeLine.getArrivalQuantity(); |
| | | // æ´è¡å·²éä¾åºåæ¶ä¸çæé¶æ°éå
¥åºè¡ï¼å
¶ä½è¡ä»æ£å¸¸æ¶è´§ã |
| | | if (receivable == null || receivable.signum() <= 0) { |
| | | continue; |
| | | } |
| | | MesWmItemReceiptLineDO receiptLine = new MesWmItemReceiptLineDO(); |
| | | receiptLine.setReceiptId(receipt.getId()); |
| | | receiptLine.setArrivalNoticeLineId(noticeLine.getId()); |
| | | receiptLine.setPurchaseOrderItemId(noticeLine.getPurchaseOrderItemId()); |
| | | receiptLine.setItemId(noticeLine.getItemId()); |
| | | // 使ç¨åæ ¼æ°éï¼å¦ææ IQC åæ¯åæ ¼æ°éï¼å¦åæ¯å°è´§æ°éï¼ |
| | | receiptLine.setReceivedQuantity(noticeLine.getQualifiedQuantity() != null ? noticeLine.getQualifiedQuantity() : noticeLine.getArrivalQuantity()); |
| | | receiptLine.setReceivedQuantity(receivable); |
| | | // æéè´è®¢åè¡åä»·å¿«ç
§éé¢ï¼ä¾æ¥ç¥¨/å¯¹è´¦ä½¿ç¨ |
| | | fillReceiptLinePrice(receiptLine); |
| | | itemReceiptLineService.createItemReceiptLineFromArrivalNoticeLine(receiptLine); |
| | |
| | | .setQuantity(line.getQuantity().negate()) // åºåºæ°éä¸ºè´æ° |
| | | .setBatchId(line.getBatchId()).setBatchCode(line.getBatchCode()) |
| | | .setWarehouseId(line.getWarehouseId()).setLocationId(line.getLocationId()).setAreaId(line.getAreaId()) |
| | | .setMaterialStockId(line.getMaterialStockId()) // æå®åºåè®°å½IDï¼é¿å
éæ°å¹é
æ£éåºå |
| | | .setBizType(MesBizTypeConstants.WM_MISC_ISSUE).setBizId(issue.getId()).setBizCode(issue.getCode()).setBizLineId(line.getId()))); |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.wm.productsales; |
| | | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Component; |
| | | import java.math.BigDecimal; |
| | | import java.util.Objects; |
| | | |
| | | /** 已让æ¥çåºè´§è¡åªè½ä»åæ£éªæ¹æ¬¡æ£è´§åºåºã */ |
| | | @Component |
| | | public class MesWmOqcConcessionGuard { |
| | | @Resource private MesQcNcrMapper ncrMapper; |
| | | @Resource private MesQcOqcMapper oqcMapper; |
| | | @Resource private MesWmMaterialStockMapper stockMapper; |
| | | |
| | | private MesQcOqcDO releasedQc(MesWmProductSalesLineDO line) { |
| | | if (line == null || line.getOqcId() == null) return null; |
| | | Long count = ncrMapper.selectCount(new LambdaQueryWrapperX<MesQcNcrDO>() |
| | | .eq(MesQcNcrDO::getQcType, MesQcTypeEnum.OQC.getType()) |
| | | .eq(MesQcNcrDO::getQcId, line.getOqcId()) |
| | | .eq(MesQcNcrDO::getDisposition, MesNcrDispositionEnum.CONCESSION.getType()) |
| | | .in(MesQcNcrDO::getStatus, MesNcrStatusEnum.DISPOSED.getStatus(), MesNcrStatusEnum.CLOSED.getStatus())); |
| | | if (count == 0) return null; |
| | | MesQcOqcDO qc = oqcMapper.selectById(line.getOqcId()); |
| | | if (qc == null || !Objects.equals(qc.getSourceLineId(), line.getId()) |
| | | || !Objects.equals(qc.getItemId(), line.getItemId()) || StrUtil.isBlank(qc.getBatchCode())) { |
| | | throw new ServiceException(400, "让æ¥åºè´§ç忣éªãç©æææ¹æ¬¡æ æ³æ ¸éª"); |
| | | } |
| | | return qc; |
| | | } |
| | | |
| | | public void validate(MesWmProductSalesLineDO line, Long stockId, String batchCode, BigDecimal quantity) { |
| | | MesQcOqcDO qc = releasedQc(line); |
| | | if (qc == null) return; |
| | | MesWmMaterialStockDO stock = stockId == null ? null : stockMapper.selectById(stockId); |
| | | if (quantity == null || quantity.signum() <= 0 || stock == null || !Objects.equals(stock.getItemId(), qc.getItemId()) |
| | | || (line.getMaterialStockId() != null && !Objects.equals(line.getMaterialStockId(), stockId)) |
| | | || !Objects.equals(stock.getBatchCode(), qc.getBatchCode()) |
| | | || !Objects.equals(batchCode, qc.getBatchCode())) { |
| | | throw new ServiceException(400, "让æ¥åºè´§å¿
须使ç¨ååºè´§æ£éªå¯¹åºçç©æåæ¹æ¬¡åºå"); |
| | | } |
| | | } |
| | | |
| | | public void validateQuantity(MesWmProductSalesLineDO line, BigDecimal picked) { |
| | | if (releasedQc(line) == null) return; |
| | | if (picked == null || line.getQuantity() == null || picked.compareTo(line.getQuantity()) != 0) { |
| | | throw new ServiceException(400, "让æ¥åºè´§çæ£è´§æ°éå¿
é¡»çäºåæ£éªè¡æ°é"); |
| | | } |
| | | } |
| | | |
| | | public void validateEditable(MesWmProductSalesLineDO line, Integer salesStatus) { |
| | | if (releasedQc(line) != null && !Objects.equals(salesStatus, MesWmProductSalesStatusEnum.APPROVING.getStatus())) { |
| | | throw new ServiceException(400, "让æ¥åºè´§æç»ä»
å¯å¨å¾
æ£è´§æ¶ä¿®æ¹ï¼åºåå ç¨åä¸å¯åæ´"); |
| | | } |
| | | } |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesDetailMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | | import java.util.List; |
| | |
| | | private MesWmWarehouseAreaService warehouseAreaService; |
| | | @Resource |
| | | private MesWmSalesReelGuard salesReelGuard; |
| | | @Resource |
| | | private MesWmOqcConcessionGuard oqcConcessionGuard; |
| | | @Resource |
| | | private MesWmProductSalesMapper salesMapper; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createProductSalesDetail(MesWmProductSalesDetailSaveReqVO createReqVO) { |
| | | validateEditable(createReqVO.getSalesId(), createReqVO.getLineId()); |
| | | // æ ¡éªæ°æ® |
| | | validateProductSalesDetailSaveData(createReqVO); |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateProductSalesDetail(MesWmProductSalesDetailSaveReqVO updateReqVO) { |
| | | // æ ¡éªåå¨ |
| | | validateProductSalesDetailExists(updateReqVO.getId()); |
| | | MesWmProductSalesDetailDO existing = validateProductSalesDetailExists(updateReqVO.getId()); |
| | | if (!ObjUtil.equal(existing.getSalesId(), updateReqVO.getSalesId()) || !ObjUtil.equal(existing.getLineId(), updateReqVO.getLineId())) { |
| | | throw new ServiceException(400, "ä¸å
è®¸æ´æ¹åºåºæç»æå±åæ®æè¡"); |
| | | } |
| | | validateEditable(existing.getSalesId(), existing.getLineId()); |
| | | // æ ¡éªæ°æ® |
| | | validateProductSalesDetailSaveData(updateReqVO); |
| | | |
| | |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteProductSalesDetail(Long id) { |
| | | // æ ¡éªåå¨ |
| | | validateProductSalesDetailExists(id); |
| | | MesWmProductSalesDetailDO detail = validateProductSalesDetailExists(id); |
| | | validateEditable(detail.getSalesId(), detail.getLineId()); |
| | | // å é¤ |
| | | productSalesDetailMapper.deleteById(id); |
| | | } |
| | | |
| | | private void validateEditable(Long salesId, Long lineId) { |
| | | MesWmProductSalesDO sales = salesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>() |
| | | .eq(MesWmProductSalesDO::getId, salesId).last("FOR UPDATE")); |
| | | MesWmProductSalesLineDO line = productSalesLineService.getProductSalesLine(lineId); |
| | | if (sales == null || line == null || !ObjUtil.equal(line.getSalesId(), salesId)) { |
| | | throw new ServiceException(400, "åºåºåæåºåºè¡ä¸åå¨"); |
| | | } |
| | | oqcConcessionGuard.validateEditable(line, sales.getStatus()); |
| | | } |
| | | |
| | | @Override |
| | |
| | | reqVO.getMaterialStockId(), reqVO.getItemId(), reqVO.getBatchId(), reqVO.getBatchCode(), |
| | | reqVO.getWarehouseId(), reqVO.getLocationId(), reqVO.getAreaId(), reqVO.getQuantity()); |
| | | salesReelGuard.validate(line, reqVO.getBatchId()); |
| | | oqcConcessionGuard.validate(line, reqVO.getMaterialStockId(), reqVO.getBatchCode(), reqVO.getQuantity()); |
| | | } |
| | | |
| | | } |
| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.MesWmProductSalesShippingReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.salesnotice.MesWmSalesNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDetailDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmStockReserveBizTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService; |
| | | 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.wm.stockreserve.MesWmStockReserveService; |
| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| | |
| | | @Resource |
| | | private MesWmProductSalesDetailService productSalesDetailService; |
| | | @Resource |
| | | private MesWmOqcConcessionGuard oqcConcessionGuard; |
| | | @Resource |
| | | private CrmCustomerApi customerApi; |
| | | @Resource |
| | | private MesWmSalesNoticeService salesNoticeService; |
| | |
| | | private ErpSaleOrderApi saleOrderApi; |
| | | @Resource |
| | | private MesWmSalesReelGuard salesReelGuard; |
| | | @Resource |
| | | private MesMdItemService mdItemService; |
| | | |
| | | @Override |
| | | public Long createProductSales(MesWmProductSalesSaveReqVO createReqVO) { |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void stockProductSales(Long id) { |
| | | // æ ¡éªåå¨ |
| | | MesWmProductSalesDO sales = validateProductSalesExists(id); |
| | | validateProductSalesExists(id); |
| | | MesWmProductSalesDO sales = productSalesMapper.selectOne(new LambdaQueryWrapperX<MesWmProductSalesDO>() |
| | | .eq(MesWmProductSalesDO::getId, id).last("FOR UPDATE")); |
| | | if (sales == null) throw exception(WM_PRODUCT_SALES_NOT_EXISTS); |
| | | if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.APPROVING.getStatus(), sales.getStatus())) { |
| | | throw exception(WM_PRODUCT_SALES_CANNOT_PICK); |
| | | } |
| | |
| | | if (CollUtil.isEmpty(details)) { |
| | | throw exception(WM_PRODUCT_SALES_DETAILS_EMPTY); |
| | | } |
| | | oqcConcessionGuard.validateQuantity(line, CollectionUtils.getSumValue(details, |
| | | MesWmProductSalesDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO)); |
| | | // æ ¡éªåºåå
足并å建å ç¨ |
| | | for (MesWmProductSalesDetailDO detail : details) { |
| | | salesReelGuard.validate(line, detail.getBatchId()); |
| | | oqcConcessionGuard.validate(line, detail.getMaterialStockId(), detail.getBatchCode(), detail.getQuantity()); |
| | | materialStockService.validateSelectedStock( |
| | | detail.getMaterialStockId(), |
| | | detail.getItemId(), |
| | |
| | | |
| | | private void createTransactionList(MesWmProductSalesDO sales) { |
| | | List<MesWmProductSalesDetailDO> details = productSalesDetailService.getProductSalesDetailListBySalesId(sales.getId()); |
| | | for (MesWmProductSalesLineDO line : productSalesLineService.getProductSalesLineListBySalesId(sales.getId())) { |
| | | BigDecimal quantity = details.stream().filter(detail -> ObjUtil.equal(detail.getLineId(), line.getId())) |
| | | .map(MesWmProductSalesDetailDO::getQuantity).filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | oqcConcessionGuard.validateQuantity(line, quantity); |
| | | } |
| | | for (MesWmProductSalesDetailDO detail : details) { |
| | | MesWmProductSalesLineDO line = productSalesLineService.getProductSalesLine(detail.getLineId()); |
| | | if (line == null || !ObjUtil.equal(line.getSalesId(), sales.getId())) { |
| | | throw exception(WM_PRODUCT_SALES_DETAIL_LINE_NOT_MATCH); |
| | | } |
| | | salesReelGuard.validate(line, detail.getBatchId()); |
| | | oqcConcessionGuard.validate(line, detail.getMaterialStockId(), detail.getBatchCode(), detail.getQuantity()); |
| | | } |
| | | wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO() |
| | | .setType(MesWmTransactionTypeEnum.OUT.getType()).setItemId(detail.getItemId()) |
| | |
| | | if (ObjUtil.notEqual(MesWmProductSalesStatusEnum.CONFIRMED.getStatus(), sales.getStatus())) { |
| | | throw exception(WM_PRODUCT_SALES_CANNOT_CONFIRM); |
| | | } |
| | | // åºè´§æ£éªä¸åæ ¼çåºåºè¡å¿
é¡»å
宿ä¸åæ ¼åå¤ç½®ï¼ç¦æ¢æå·¥ç¡®è®¤ç»è¿è¿å·¥/让æ¥éç¯ |
| | | List<MesWmProductSalesLineDO> lines = productSalesLineMapper.selectListBySalesId(id); |
| | | if (CollUtil.isNotEmpty(lines)) { |
| | | int rowNo = 0; |
| | | for (MesWmProductSalesLineDO line : lines) { |
| | | rowNo++; |
| | | if (Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus()) |
| | | || line.getNcrDispositionId() != null) { |
| | | throw new ServiceException(400, String.format( |
| | | "éå®åºåºå[%s]第[%d]è¡ç©æ[%s]åºè´§æ£éªä¸åæ ¼ï¼å°æªå®æä¸åæ ¼åå¤ç½®ï¼" |
| | | + "请å
å¨ä¸åæ ¼åå¤çåä¸å®æè¿å·¥/è¿ä¿®/éæ°ç产æè®©æ¥æ¾è¡åå确认åºåº", |
| | | sales.getCode(), rowNo, itemLabel(line.getItemId()))); |
| | | } |
| | | } |
| | | } |
| | | // OQC æ£éªå®æï¼æµè½¬å°å¾
æ£è´§ |
| | | productSalesMapper.updateById(new MesWmProductSalesDO() |
| | | .setId(id).setStatus(MesWmProductSalesStatusEnum.APPROVING.getStatus())); |
| | | } |
| | | |
| | | private String itemLabel(Long itemId) { |
| | | MesMdItemDO item = itemId == null ? null : mdItemService.getItem(itemId); |
| | | if (item == null) { |
| | | return "ID=" + itemId; |
| | | } |
| | | String label = (StrUtil.blankToDefault(item.getCode(), "") + " " |
| | | + StrUtil.blankToDefault(item.getName(), "")).trim(); |
| | | return label.isEmpty() ? "ID=" + itemId : label; |
| | | } |
| | | |
| | | @Override |
| | | public List<MesWmProductSalesDO> getProductSalesListByClientId(Long clientId) { |
| | | return productSalesMapper.selectListByClientId(clientId); |
| | |
| | | */ |
| | | void updateQualityStatusByReturnId(Long returnId, Integer qualityStatus); |
| | | |
| | | /** éå®å¹¶ç»å®æ¥æºæ£éªï¼ç¡®ä¿æ´è¡æ°é䏿£éªç»æå¯¹åºã */ |
| | | void bindRqcForFinish(Long lineId, Long returnId, Long rqcId, Long itemId, BigDecimal checkQuantity); |
| | | |
| | | /** |
| | | * RQC æ£éªå®æåï¼æ ¹æ®åæ ¼å/ä¸åæ ¼åæ°éæ´æ°éå®éè´§åè¡ |
| | | * |
| | |
| | | import cn.iocoder.yudao.module.aftersales.api.AfterSaleTicketApi; |
| | | import cn.iocoder.yudao.module.aftersales.api.dto.AfterSaleWarehouseReturnStageReqDTO; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWmReturnSalesLinePageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWmReturnSalesLineSaveReqVO; |
| | |
| | | } |
| | | |
| | | @Override |
| | | public void bindRqcForFinish(Long lineId, Long returnId, Long rqcId, Long itemId, BigDecimal checkQuantity) { |
| | | MesWmReturnSalesLineDO line = returnSalesLineMapper.selectOne(new LambdaQueryWrapperX<MesWmReturnSalesLineDO>() |
| | | .eq(MesWmReturnSalesLineDO::getId, lineId).last("FOR UPDATE")); |
| | | if (line == null || !Objects.equals(line.getReturnId(), returnId) |
| | | || !Objects.equals(line.getItemId(), itemId) |
| | | || !Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus()) |
| | | || (line.getRqcId() != null && !Objects.equals(line.getRqcId(), rqcId)) |
| | | || line.getQuantity() == null || checkQuantity == null |
| | | || line.getQuantity().compareTo(checkQuantity) != 0) { |
| | | throw new ServiceException(400, "éè´§æ£éªé¡»å¯¹åºå¯ä¸å¾
æ£éè´§è¡ï¼ç©æåæ£éªæ°éå¿
é¡»ä¸éè´§è¡ä¸è´"); |
| | | } |
| | | returnSalesLineMapper.updateById(new MesWmReturnSalesLineDO().setId(lineId).setRqcId(rqcId)); |
| | | } |
| | | |
| | | @Override |
| | | public void updateReturnSalesLineWhenRqcFinish(Long sourceLineId, Long sourceDocId, Integer checkResult, |
| | | BigDecimal qualifiedQuantity, BigDecimal unqualifiedQuantity) { |
| | | MesWmReturnSalesLineDO sourceLine = validateReturnSalesLineExists(sourceLineId); |
| | |
| | | import java.util.Collections; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void stockReturnSales(Long id) { |
| | | // 1.1 æ ¡éªåå¨ + å¾
䏿¶ç¶æ |
| | | MesWmReturnSalesDO returnSales = validateReturnSalesExists(id); |
| | | MesWmReturnSalesDO returnSales = returnSalesMapper.selectByIdForUpdate(id); |
| | | if (returnSales == null) throw exception(WM_RETURN_SALES_NOT_EXISTS); |
| | | if (ObjUtil.notEqual(MesWmReturnSalesStatusEnum.APPROVED.getStatus(), returnSales.getStatus())) { |
| | | throw exception(WM_RETURN_SALES_STATUS_NOT_APPROVED); |
| | | } |
| | |
| | | allDetails, MesWmReturnSalesDetailDO::getLineId); |
| | | // æ£æ¥æ¯è¡çæç»æ°é |
| | | for (MesWmReturnSalesLineDO line : lines) { |
| | | if (line.getNcrDispositionId() != null && Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())) { |
| | | if (!detailMap.getOrDefault(line.getId(), Collections.emptyList()).isEmpty()) { |
| | | throw exception(WM_RETURN_SALES_LINE_NOT_QUALIFIED); |
| | | } |
| | | continue; // ä¸åæ ¼æ°éç± NCR ç¬ç«å¤çï¼ä¸å¨åéè´§å忬¡å
¥åº |
| | | } |
| | | if (ObjUtil.notEqual(line.getQualityStatus(), MesWmQualityStatusEnum.PASS.getStatus())) { |
| | | throw exception(WM_RETURN_SALES_LINE_NOT_QUALIFIED); |
| | | } |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void cancelReturnSales(Long id) { |
| | | // æ ¡éªåå¨ |
| | | MesWmReturnSalesDO returnSales = validateReturnSalesExists(id); |
| | | MesWmReturnSalesDO returnSales = returnSalesMapper.selectByIdForUpdate(id); |
| | | if (returnSales == null) throw exception(WM_RETURN_SALES_NOT_EXISTS); |
| | | // 已宿å已忶ä¸å
è®¸åæ¶ |
| | | if (ObjectUtils.equalsAny(returnSales.getStatus(), |
| | | MesWmReturnSalesStatusEnum.FINISHED.getStatus(), |
| | |
| | | public Boolean checkReturnSalesQuantity(Long id) { |
| | | List<MesWmReturnSalesLineDO> lines = returnSalesLineService.getReturnSalesLineListByReturnId(id); |
| | | for (MesWmReturnSalesLineDO line : lines) { |
| | | if (line.getNcrDispositionId() != null && Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.FAIL.getStatus())) { |
| | | continue; |
| | | } |
| | | List<MesWmReturnSalesDetailDO> details = returnSalesDetailService.getReturnSalesDetailListByLineId(line.getId()); |
| | | BigDecimal totalDetailQty = CollectionUtils.getSumValue(details, |
| | | MesWmReturnSalesDetailDO::getQuantity, BigDecimal::add, BigDecimal.ZERO); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseReturnDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseReturnMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseConcessionMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | import static org.junit.jupiter.api.Assertions.assertEquals; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesQcArrivalDispositionServiceTest { |
| | | @Test void partialReturnEnablesOnlyQualifiedQuantityAndFullReturnFinishes() { |
| | | var service = new MesQcArrivalDispositionService(); |
| | | var notices = mock(MesWmArrivalNoticeMapper.class); |
| | | var lines = mock(MesWmArrivalNoticeLineMapper.class); |
| | | var returns = mock(MesQcPurchaseReturnMapper.class); |
| | | ReflectionTestUtils.setField(service, "arrivalNoticeMapper", notices); |
| | | ReflectionTestUtils.setField(service, "lineMapper", lines); |
| | | ReflectionTestUtils.setField(service, "returnMapper", returns); |
| | | ReflectionTestUtils.setField(service, "concessionMapper", mock(MesQcPurchaseConcessionMapper.class)); |
| | | when(notices.selectOne(any(Wrapper.class))).thenReturn(new MesWmArrivalNoticeDO().setId(1L) |
| | | .setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus())); |
| | | var line = new MesWmArrivalNoticeLineDO().setId(2L).setIqcId(3L).setIqcCheckFlag(true) |
| | | .setIqcCheckResult(2).setArrivalQuantity(BigDecimal.TEN).setQualifiedQuantity(BigDecimal.valueOf(7)); |
| | | when(lines.selectListByNoticeId(1L)).thenReturn(List.of(line)); |
| | | service.refresh(1L); |
| | | verify(notices, never()).updateById(any(MesWmArrivalNoticeDO.class)); |
| | | when(returns.selectConfirmedByIqcId(3L)).thenReturn(new MesQcPurchaseReturnDO().setNoticeLineId(2L)); |
| | | service.refresh(1L); |
| | | var changed = ArgumentCaptor.forClass(MesWmArrivalNoticeDO.class); |
| | | verify(notices).updateById(changed.capture()); |
| | | assertEquals(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus(), changed.getValue().getStatus()); |
| | | line.setQualifiedQuantity(BigDecimal.ZERO); |
| | | service.refresh(1L); |
| | | verify(notices, times(2)).updateById(changed.capture()); |
| | | assertEquals(MesWmArrivalNoticeStatusEnum.FINISHED.getStatus(), changed.getValue().getStatus()); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrStatusEnum; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.junit.jupiter.api.extension.ExtendWith; |
| | | import org.mockito.InjectMocks; |
| | | import org.mockito.Mock; |
| | | import org.mockito.junit.jupiter.MockitoExtension; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.assertThrows; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | @ExtendWith(MockitoExtension.class) |
| | | class MesQcNcrGuardTest { |
| | | @Mock private MesQcNcrMapper mapper; |
| | | @Mock private MdmItemApi mdmItemApi; |
| | | @InjectMocks private MesQcNcrServiceImpl service; |
| | | |
| | | private MesQcNcrDO sourceNcr() { |
| | | return new MesQcNcrDO().setId(1L).setCode("NCR-1").setQcId(2L) |
| | | .setItemId(3L).setUnqualifiedQuantity(new BigDecimal("10")) |
| | | .setStatus(MesNcrStatusEnum.PENDING_REVIEW.getStatus()); |
| | | } |
| | | |
| | | private MesQcNcrSaveReqVO request(Long itemId, String quantity) { |
| | | var req = new MesQcNcrSaveReqVO(); |
| | | req.setId(1L); |
| | | req.setCode("NCR-1"); |
| | | req.setItemId(itemId); |
| | | req.setUnqualifiedQuantity(new BigDecimal(quantity)); |
| | | return req; |
| | | } |
| | | |
| | | @Test |
| | | void rejectsChangingMaterialOrQuantityOfSourceInspection() { |
| | | when(mapper.selectByIdForUpdate(1L)).thenReturn(sourceNcr()); |
| | | assertThrows(ServiceException.class, () -> service.updateNcr(request(4L, "10"))); |
| | | assertThrows(ServiceException.class, () -> service.updateNcr(request(3L, "11"))); |
| | | verify(mapper, times(2)).selectByIdForUpdate(1L); |
| | | verify(mapper, never()).updateById(any(MesQcNcrDO.class)); |
| | | } |
| | | |
| | | @Test |
| | | void rejectsDeletingSourceInspectionNcr() { |
| | | when(mapper.selectByIdForUpdate(1L)).thenReturn(sourceNcr()); |
| | | assertThrows(ServiceException.class, () -> service.deleteNcr(1L)); |
| | | verify(mapper, never()).deleteById(any(Long.class)); |
| | | } |
| | | |
| | | @Test |
| | | void acceptsEquivalentQuantityWithDifferentDecimalScale() { |
| | | when(mapper.selectByIdForUpdate(1L)).thenReturn(sourceNcr()); |
| | | when(mdmItemApi.getItem(3L)).thenReturn(CommonResult.success(null)); |
| | | service.updateNcr(request(3L, "10.00")); |
| | | verify(mapper).updateById(any(MesQcNcrDO.class)); |
| | | } |
| | | |
| | | @Test |
| | | void repeatedDispositionChecksLockedCurrentStatusBeforeCreatingDocuments() { |
| | | var disposed = sourceNcr().setStatus(MesNcrStatusEnum.DISPOSED.getStatus()); |
| | | when(mapper.selectByIdForUpdate(1L)).thenReturn(disposed); |
| | | var req = new MesQcNcrDisposeReqVO(); |
| | | req.setId(1L); |
| | | req.setDisposition(2); |
| | | assertThrows(ServiceException.class, () -> service.disposeNcr(req)); |
| | | verify(mapper).selectByIdForUpdate(1L); |
| | | verify(mapper, never()).updateById(any(MesQcNcrDO.class)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesDetailService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.productsales.vo.detail.MesWmProductSalesDetailSaveReqVO; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesQcNcrOqcConcessionServiceTest { |
| | | @Test void releaseAndCloseRequireMatchingOriginalLineAndShipment() { |
| | | var service = new MesQcNcrOqcConcessionService(); |
| | | var oqcs = mock(MesQcOqcMapper.class); |
| | | var sales = mock(MesWmProductSalesMapper.class); |
| | | var lines = mock(MesWmProductSalesLineMapper.class); |
| | | var flows = mock(MesWmTransactionMapper.class); |
| | | var lineService = mock(MesWmProductSalesLineService.class); |
| | | var details = mock(MesWmProductSalesDetailService.class); |
| | | var salesService = mock(MesWmProductSalesService.class); |
| | | var stockService = mock(MesWmMaterialStockService.class); |
| | | ReflectionTestUtils.setField(service, "oqcMapper", oqcs); |
| | | ReflectionTestUtils.setField(service, "salesMapper", sales); |
| | | ReflectionTestUtils.setField(service, "lineMapper", lines); |
| | | ReflectionTestUtils.setField(service, "transactionMapper", flows); |
| | | ReflectionTestUtils.setField(service, "lineService", lineService); |
| | | ReflectionTestUtils.setField(service, "detailService", details); |
| | | ReflectionTestUtils.setField(service, "salesService", salesService); |
| | | ReflectionTestUtils.setField(service, "stockService", stockService); |
| | | var ncr = new MesQcNcrDO().setQcId(1L).setItemId(2L).setUnqualifiedQuantity(BigDecimal.valueOf(3)); |
| | | var qc = new MesQcOqcDO().setId(1L).setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES) |
| | | .setSourceDocId(4L).setSourceLineId(5L).setItemId(2L).setBatchCode("B1") |
| | | .setStatus(MesQcStatusEnum.FINISHED.getStatus()).setCheckResult(2) |
| | | .setCheckQuantity(BigDecimal.TEN).setQualifiedQuantity(BigDecimal.valueOf(7)).setUnqualifiedQuantity(BigDecimal.valueOf(3)); |
| | | var sale = new MesWmProductSalesDO().setId(4L).setStatus(MesWmProductSalesStatusEnum.CONFIRMED.getStatus()); |
| | | var line = new MesWmProductSalesLineDO().setId(5L).setSalesId(4L).setItemId(2L).setOqcId(1L) |
| | | .setQualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()).setQuantity(BigDecimal.TEN); |
| | | when(oqcs.selectById(1L)).thenReturn(qc); |
| | | when(sales.selectOne(any(Wrapper.class))).thenReturn(sale); |
| | | when(lines.selectOne(any(Wrapper.class))).thenReturn(line); |
| | | when(stockService.getMaterialStock(8L)).thenReturn(new MesWmMaterialStockDO().setId(8L).setItemId(2L) |
| | | .setBatchCode("B1").setWarehouseId(11L).setLocationId(12L).setAreaId(13L)); |
| | | when(details.getProductSalesDetailListByLineId(5L)).thenReturn(List.of()); |
| | | when(lineService.getProductSalesLineListBySalesId(4L)).thenReturn(List.of(line)); |
| | | when(salesService.checkProductSalesQuantity(4L)).thenReturn(true); |
| | | var req = new MesQcNcrDisposeReqVO(); req.setMaterialStockId(8L); |
| | | service.release(ncr, req); |
| | | verify(lineService).updateProductSalesLineWhenOqcFinish(5L, 1L, 1); |
| | | verify(details).createProductSalesDetail(any(MesWmProductSalesDetailSaveReqVO.class)); |
| | | verify(salesService).stockProductSales(4L); |
| | | verify(salesService).finishProductSales(4L); |
| | | assertThrows(ServiceException.class, () -> service.validateBeforeClose(ncr)); |
| | | sale.setStatus(MesWmProductSalesStatusEnum.FINISHED.getStatus()); |
| | | when(flows.selectList(any(Wrapper.class))).thenReturn(List.of(new MesWmTransactionDO() |
| | | .setItemId(2L).setBatchCode("B2").setQuantity(BigDecimal.TEN.negate()))); |
| | | assertThrows(ServiceException.class, () -> service.validateBeforeClose(ncr)); |
| | | when(flows.selectList(any(Wrapper.class))).thenReturn(List.of(new MesWmTransactionDO() |
| | | .setItemId(2L).setBatchCode("B1").setQuantity(BigDecimal.TEN.negate()))); |
| | | assertDoesNotThrow(() -> service.validateBeforeClose(ncr)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderBomMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | 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.pro.workorder.MesProWorkOrderProcessService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService; |
| | | import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.junit.jupiter.api.extension.ExtendWith; |
| | | import org.mockito.InjectMocks; |
| | | import org.mockito.Mock; |
| | | import org.mockito.junit.jupiter.MockitoExtension; |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | @ExtendWith(MockitoExtension.class) |
| | | class MesQcNcrOqcReproductionServiceTest { |
| | | @InjectMocks MesQcNcrOqcReproductionService service; |
| | | @Mock MesQcOqcMapper oqcMapper; |
| | | @Mock MesWmProductSalesLineMapper lineMapper; |
| | | @Mock MesWmProductSalesMapper salesMapper; |
| | | @Mock MesWmProductProduceLineMapper produceLineMapper; |
| | | @Mock MesQcNcrScrapService sourceService; |
| | | @Mock MesWmBatchMapper batchMapper; |
| | | @Mock MesProWorkOrderMapper orderMapper; |
| | | @Mock MesProWorkOrderBomMapper bomMapper; |
| | | @Mock MesMdItemMapper itemMapper; |
| | | @Mock MesProWorkOrderService orderService; |
| | | @Mock MesProWorkOrderProcessService processService; |
| | | @Mock MesProTaskService taskService; |
| | | @Mock MesMdAutoCodeRecordService autoCodeService; |
| | | private MesQcNcrDO ncr() { return new MesQcNcrDO().setId(1L).setCode("NCR1").setQcType(MesQcTypeEnum.OQC.getType()).setQcId(2L).setItemId(3L).setUnqualifiedQuantity(BigDecimal.ONE); } |
| | | private MesQcOqcDO qc() { return new MesQcOqcDO().setId(2L).setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES) |
| | | .setSourceDocId(4L).setSourceLineId(5L).setStatus(MesQcStatusEnum.FINISHED.getStatus()).setItemId(3L) |
| | | .setClientId(8L).setBatchCode("B1").setOutQuantity(BigDecimal.TEN).setUnqualifiedQuantity(BigDecimal.ONE); } |
| | | private MesWmProductSalesLineDO line() { return new MesWmProductSalesLineDO().setId(5L).setSalesId(4L).setOqcId(2L).setItemId(3L) |
| | | .setBatchId(6L).setBatchCode("B1").setQuantity(BigDecimal.TEN).setQualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()); } |
| | | |
| | | @Test void refusesClientMismatchBeforeCreatingOrder() { |
| | | when(oqcMapper.selectById(2L)).thenReturn(qc()); |
| | | when(lineMapper.selectById(5L)).thenReturn(line()); |
| | | when(salesMapper.selectById(4L)).thenReturn(new MesWmProductSalesDO().setId(4L).setClientId(9L)); |
| | | assertThrows(ServiceException.class, () -> service.reproduce(ncr(), 7)); |
| | | verify(orderService, never()).createWorkOrder(any()); |
| | | } |
| | | |
| | | @Test void ipqcReproductionRequiresClaimedSourceAndCreatesTasks() { |
| | | var item = ncr().setQcType(MesQcTypeEnum.IPQC.getType()); |
| | | var source = new MesQcNcrScrapService.IpqcSource(10L, "OLD", 12L, 6L, "B1"); |
| | | when(sourceService.validateIpqcSource(item)).thenReturn(source); |
| | | var line = new MesWmProductProduceLineDO().setId(12L).setNcrDispositionId(99L); |
| | | when(produceLineMapper.selectById(12L)).thenReturn(line); |
| | | assertThrows(ServiceException.class, () -> service.reproduceIpqc(item, 7, source)); |
| | | verify(orderService, never()).createWorkOrder(any()); |
| | | line.setNcrDispositionId(1L); |
| | | when(orderMapper.selectById(10L)).thenReturn(new MesProWorkOrderDO().setId(10L).setCode("OLD") |
| | | .setProductId(11L).setRouteId(40L)); |
| | | when(itemMapper.selectById(11L)).thenReturn(new MesMdItemDO().setId(11L).setMdmItemId(3L)); |
| | | when(processService.getWorkOrderProcessListByWorkOrderId(10L)).thenReturn(List.of( |
| | | new MesProWorkOrderProcessDO().setProcessId(21L).setSort(1))); |
| | | when(bomMapper.selectListByWorkOrderId(10L)).thenReturn(List.of()); |
| | | when(autoCodeService.generateAutoCode(anyString())).thenReturn("NEW"); |
| | | when(orderService.createWorkOrder(any())).thenReturn(30L); |
| | | when(taskService.autoGenerateTasks(any())).thenReturn(List.of(50L)); |
| | | assertEquals(30L, service.reproduceIpqc(item, 7, source).getDocId()); |
| | | verify(taskService).autoGenerateTasks(argThat(r -> r.getWorkOrderId().equals(30L))); |
| | | } |
| | | |
| | | @Test void copiesOrderProcessThenConfirms() { |
| | | when(oqcMapper.selectById(2L)).thenReturn(qc()); |
| | | when(lineMapper.selectById(5L)).thenReturn(line()); |
| | | when(salesMapper.selectById(4L)).thenReturn(new MesWmProductSalesDO().setId(4L).setClientId(8L)); |
| | | when(batchMapper.selectById(6L)).thenReturn(new MesWmBatchDO().setId(6L).setCode("B1").setItemId(3L).setClientId(8L).setWorkOrderId(10L)); |
| | | when(orderMapper.selectById(10L)).thenReturn(new MesProWorkOrderDO().setId(10L).setCode("OLD").setProductId(11L).setClientId(8L).setRouteId(40L)); |
| | | when(itemMapper.selectById(11L)).thenReturn(new MesMdItemDO().setId(11L).setMdmItemId(3L)); |
| | | when(processService.getWorkOrderProcessListByWorkOrderId(10L)).thenReturn(List.of(new MesProWorkOrderProcessDO().setId(20L) |
| | | .setProcessId(21L).setSort(1).setOutputItemId("3,4"))); |
| | | when(bomMapper.selectListByWorkOrderId(10L)).thenReturn(List.of(new MesProWorkOrderBomDO() |
| | | .setProcessId(21L).setItemId(22L).setQuantity(new BigDecimal("1.25")))); |
| | | when(autoCodeService.generateAutoCode(anyString())).thenReturn("NEW"); |
| | | when(orderService.createWorkOrder(any())).thenReturn(30L); |
| | | when(taskService.autoGenerateTasks(any())).thenReturn(List.of(50L)); |
| | | var result = service.reproduce(ncr(), 7); |
| | | assertEquals(30L, result.getDocId()); |
| | | verify(processService).createWorkOrderProcess(argThat(p -> p.getId() == null && p.getWorkOrderId().equals(30L) |
| | | && p.getProcessId().equals(21L) && p.getOutputItemIds().equals(List.of(3L, 4L)))); |
| | | verify(bomMapper).deleteByWorkOrderId(30L); |
| | | verify(bomMapper).insert(argThat((MesProWorkOrderBomDO b) -> b.getWorkOrderId().equals(30L) |
| | | && b.getProcessId().equals(21L) && b.getQuantity().compareTo(new BigDecimal("1.25")) == 0)); |
| | | var inOrder = inOrder(processService, orderService); |
| | | inOrder.verify(processService).getWorkOrderProcessListByWorkOrderId(10L); |
| | | inOrder.verify(orderService).createWorkOrder(any()); |
| | | inOrder.verify(processService).createWorkOrderProcess(any(MesProWorkOrderProcessSaveReqVO.class)); |
| | | inOrder.verify(orderService).confirmWorkOrder(30L); |
| | | verify(taskService).autoGenerateTasks(argThat(r -> r.getWorkOrderId().equals(30L) && r.getRouteId().equals(40L))); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.MesWmMiscIssueSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.miscissue.vo.line.MesWmMiscIssueLineSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | 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.miscissue.MesWmMiscIssueDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscissue.MesWmMiscIssueMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productsales.MesWmProductSalesMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmProductSalesStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.materialstock.MesWmMaterialStockService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.miscissue.MesWmMiscIssueLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.miscissue.MesWmMiscIssueService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | import org.mockito.InOrder; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesQcNcrOqcReworkServiceTest { |
| | | |
| | | private static final long NCR_ID = 100L; |
| | | private static final long QC_ID = 1L; |
| | | private static final long SALES_ID = 4L; |
| | | private static final long SALES_LINE_ID = 5L; |
| | | private static final long BATCH_ID = 6L; |
| | | private static final long ORDER_ID = 7L; |
| | | private static final long STOCK_ID = 9L; |
| | | |
| | | private MesQcNcrOqcReworkService newService() { |
| | | var service = new MesQcNcrOqcReworkService(); |
| | | ReflectionTestUtils.setField(service, "batchMapper", mock(MesWmBatchMapper.class)); |
| | | ReflectionTestUtils.setField(service, "orderMapper", mock(MesProWorkOrderMapper.class)); |
| | | ReflectionTestUtils.setField(service, "oqcMapper", mock(MesQcOqcMapper.class)); |
| | | ReflectionTestUtils.setField(service, "stockMapper", mock(MesWmMaterialStockMapper.class)); |
| | | ReflectionTestUtils.setField(service, "miscIssueMapper", mock(MesWmMiscIssueMapper.class)); |
| | | ReflectionTestUtils.setField(service, "transactionMapper", mock(MesWmTransactionMapper.class)); |
| | | ReflectionTestUtils.setField(service, "salesMapper", mock(MesWmProductSalesMapper.class)); |
| | | ReflectionTestUtils.setField(service, "stockService", mock(MesWmMaterialStockService.class)); |
| | | ReflectionTestUtils.setField(service, "salesService", mock(MesWmProductSalesService.class)); |
| | | ReflectionTestUtils.setField(service, "miscIssueService", mock(MesWmMiscIssueService.class)); |
| | | ReflectionTestUtils.setField(service, "miscIssueLineService", mock(MesWmMiscIssueLineService.class)); |
| | | ReflectionTestUtils.setField(service, "autoCodeRecordService", mock(MesMdAutoCodeRecordService.class)); |
| | | ReflectionTestUtils.setField(service, "itemService", mock(cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService.class)); |
| | | ReflectionTestUtils.setField(service, "reproductionService", mock(MesQcNcrOqcReproductionService.class)); |
| | | ReflectionTestUtils.setField(service, "warehouseService", mock(cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService.class)); |
| | | ReflectionTestUtils.setField(service, "warehouseLocationService", mock(cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseLocationService.class)); |
| | | ReflectionTestUtils.setField(service, "warehouseAreaService", mock(cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService.class)); |
| | | return service; |
| | | } |
| | | |
| | | private static <T> T field(MesQcNcrOqcReworkService service, String name, Class<T> type) { |
| | | return type.cast(ReflectionTestUtils.getField(service, name)); |
| | | } |
| | | |
| | | private MesQcNcrDO ncr() { |
| | | return new MesQcNcrDO().setId(NCR_ID).setCode("NCR1").setQcId(QC_ID) |
| | | .setItemId(2L).setUnqualifiedQuantity(BigDecimal.valueOf(3)); |
| | | } |
| | | |
| | | private MesQcNcrOqcSourceClaim.OqcClaimedSource source() { |
| | | var qc = new MesQcOqcDO().setId(QC_ID).setSourceDocId(SALES_ID).setSourceLineId(SALES_LINE_ID); |
| | | var sales = new MesWmProductSalesDO().setId(SALES_ID).setCode("SALES1").setClientId(40L) |
| | | .setStatus(MesWmProductSalesStatusEnum.CONFIRMED.getStatus()); |
| | | var line = new MesWmProductSalesLineDO().setId(SALES_LINE_ID).setSalesId(SALES_ID) |
| | | .setOqcId(QC_ID).setItemId(2L).setBatchId(BATCH_ID).setBatchCode("B1") |
| | | .setQualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()).setQuantity(BigDecimal.TEN); |
| | | return new MesQcNcrOqcSourceClaim.OqcClaimedSource(qc, sales, line); |
| | | } |
| | | |
| | | private static MesWmMaterialStockDO stock(BigDecimal quantity) { |
| | | return new MesWmMaterialStockDO().setId(STOCK_ID).setItemId(2L).setBatchId(BATCH_ID).setBatchCode("B1") |
| | | .setWarehouseId(11L).setLocationId(12L).setAreaId(13L).setQuantity(quantity) |
| | | .setFrozenQuantity(BigDecimal.ZERO).setReservedQuantity(BigDecimal.ZERO); |
| | | } |
| | | |
| | | private void stubBatchAndOrder(MesQcNcrOqcReworkService service) { |
| | | when(field(service, "batchMapper", MesWmBatchMapper.class).selectById(BATCH_ID)) |
| | | .thenReturn(new MesWmBatchDO().setId(BATCH_ID).setCode("B1").setItemId(2L) |
| | | .setClientId(40L).setWorkOrderId(ORDER_ID)); |
| | | when(field(service, "orderMapper", MesProWorkOrderMapper.class).selectById(ORDER_ID)) |
| | | .thenReturn(new MesProWorkOrderDO().setId(ORDER_ID).setProductId(8L).setClientId(40L)); |
| | | } |
| | | |
| | | @Test |
| | | void disposeDeductsSelectedStockCancelsSalesAndCreatesReworkOrder() { |
| | | var service = newService(); |
| | | var source = source(); |
| | | stubBatchAndOrder(service); |
| | | var salesService = field(service, "salesService", MesWmProductSalesService.class); |
| | | var stockService = field(service, "stockService", MesWmMaterialStockService.class); |
| | | var stockMapper = field(service, "stockMapper", MesWmMaterialStockMapper.class); |
| | | var miscIssueService = field(service, "miscIssueService", MesWmMiscIssueService.class); |
| | | var miscLineService = field(service, "miscIssueLineService", MesWmMiscIssueLineService.class); |
| | | var autoCode = field(service, "autoCodeRecordService", MesMdAutoCodeRecordService.class); |
| | | var reproduction = field(service, "reproductionService", MesQcNcrOqcReproductionService.class); |
| | | |
| | | when(stockService.getMaterialStock(STOCK_ID)).thenReturn(stock(BigDecimal.TEN)); |
| | | when(stockMapper.lockById(STOCK_ID)).thenReturn(stock(BigDecimal.TEN)); |
| | | when(autoCode.generateAutoCode(anyString())).thenReturn("MI1"); |
| | | when(miscIssueService.createMiscIssue(any(MesWmMiscIssueSaveReqVO.class))).thenReturn(20L); |
| | | var result = new MesQcNcrDisposeRespVO(); |
| | | result.setDocCode("WO1"); |
| | | when(reproduction.createReworkFromOriginal(any(), anyInt(), any(), any(), any(), any())).thenReturn(result); |
| | | |
| | | var req = new MesQcNcrDisposeReqVO(); |
| | | req.setId(NCR_ID); |
| | | req.setDisposition(MesNcrDispositionEnum.REWORK.getType()); |
| | | req.setMaterialStockId(STOCK_ID); |
| | | |
| | | var resp = service.dispose(ncr(), source, req); |
| | | |
| | | var issueCaptor = ArgumentCaptor.forClass(MesWmMiscIssueSaveReqVO.class); |
| | | verify(miscIssueService).createMiscIssue(issueCaptor.capture()); |
| | | assertEquals(MesWmMiscIssueTypeEnum.REWORK.getType(), issueCaptor.getValue().getType()); |
| | | assertEquals("QC_NCR", issueCaptor.getValue().getSourceDocType()); |
| | | assertEquals(NCR_ID, issueCaptor.getValue().getSourceDocId().longValue()); |
| | | |
| | | var lineCaptor = ArgumentCaptor.forClass(MesWmMiscIssueLineSaveReqVO.class); |
| | | verify(miscLineService).createMiscIssueLine(lineCaptor.capture()); |
| | | assertEquals(STOCK_ID, lineCaptor.getValue().getMaterialStockId().longValue()); |
| | | assertEquals(BATCH_ID, lineCaptor.getValue().getBatchId().longValue()); |
| | | assertEquals(0, BigDecimal.valueOf(3).compareTo(lineCaptor.getValue().getQuantity())); |
| | | |
| | | InOrder order = inOrder(stockMapper, salesService, miscIssueService, reproduction); |
| | | order.verify(stockMapper).lockById(STOCK_ID); |
| | | order.verify(salesService).cancelProductSales(SALES_ID); |
| | | order.verify(miscIssueService).finishMiscIssue(20L); |
| | | order.verify(reproduction).createReworkFromOriginal(any(), anyInt(), any(), any(), any(), any()); |
| | | |
| | | assertNotNull(resp.getMessage()); |
| | | assertTrue(resp.getMessage().contains("MI1")); |
| | | assertTrue(resp.getMessage().contains("SALES1")); |
| | | assertTrue(resp.getMessage().contains("WO1")); |
| | | } |
| | | |
| | | @Test |
| | | void disposeRejectsWhenStockAvailableIsInsufficient() { |
| | | var service = newService(); |
| | | stubBatchAndOrder(service); |
| | | var stockService = field(service, "stockService", MesWmMaterialStockService.class); |
| | | var stockMapper = field(service, "stockMapper", MesWmMaterialStockMapper.class); |
| | | when(stockService.getMaterialStock(STOCK_ID)).thenReturn(stock(BigDecimal.ONE)); |
| | | when(stockMapper.lockById(STOCK_ID)).thenReturn(stock(BigDecimal.ONE)); |
| | | |
| | | var req = new MesQcNcrDisposeReqVO(); |
| | | req.setDisposition(MesNcrDispositionEnum.REWORK.getType()); |
| | | req.setMaterialStockId(STOCK_ID); |
| | | |
| | | var ex = assertThrows(ServiceException.class, () -> service.dispose(ncr(), source(), req)); |
| | | assertTrue(ex.getMessage().contains("å¯ç¨é")); |
| | | verify(field(service, "salesService", MesWmProductSalesService.class), never()).cancelProductSales(any()); |
| | | } |
| | | |
| | | @Test |
| | | void disposeRejectsAmbiguousStockWhenNoStockSelected() { |
| | | var service = newService(); |
| | | stubBatchAndOrder(service); |
| | | var stockMapper = field(service, "stockMapper", MesWmMaterialStockMapper.class); |
| | | when(stockMapper.selectList(any(Wrapper.class))).thenReturn(List.of( |
| | | stock(BigDecimal.TEN).setId(9L).setWarehouseId(11L), |
| | | stock(BigDecimal.TEN).setId(10L).setWarehouseId(21L))); |
| | | |
| | | var req = new MesQcNcrDisposeReqVO(); |
| | | req.setDisposition(MesNcrDispositionEnum.REPAIR.getType()); |
| | | |
| | | var ex = assertThrows(ServiceException.class, () -> service.dispose(ncr(), source(), req)); |
| | | assertTrue(ex.getMessage().contains("è¿å·¥åºå")); |
| | | verify(field(service, "salesService", MesWmProductSalesService.class), never()).cancelProductSales(any()); |
| | | } |
| | | |
| | | @Test |
| | | void validateBeforeCloseRequiresFinishedOrderCancelledSalesAndMatchingIssue() { |
| | | var service = newService(); |
| | | var ncr = ncr(); |
| | | var orders = field(service, "orderMapper", MesProWorkOrderMapper.class); |
| | | var oqcs = field(service, "oqcMapper", MesQcOqcMapper.class); |
| | | var salesMapper = field(service, "salesMapper", MesWmProductSalesMapper.class); |
| | | var issues = field(service, "miscIssueMapper", MesWmMiscIssueMapper.class); |
| | | var flows = field(service, "transactionMapper", MesWmTransactionMapper.class); |
| | | |
| | | // 1. æ¾ä¸å°è¿å·¥å·¥å |
| | | when(orders.selectList(any(Wrapper.class))).thenReturn(List.of()); |
| | | assertThrows(ServiceException.class, () -> service.validateBeforeClose(ncr)); |
| | | |
| | | // 2. å·¥åæªå®æ |
| | | var order = new MesProWorkOrderDO().setId(ORDER_ID).setCode("WO1"); |
| | | when(orders.selectList(any(Wrapper.class))).thenReturn(List.of(order)); |
| | | assertThrows(ServiceException.class, () -> service.validateBeforeClose(ncr)); |
| | | order.setStatus(MesProWorkOrderStatusEnum.FINISHED.getStatus()) |
| | | .setFinishDate(java.time.LocalDateTime.now()); |
| | | |
| | | // 3. åéå®åºåºåæªåæ¶ |
| | | when(oqcs.selectById(QC_ID)).thenReturn(new MesQcOqcDO().setId(QC_ID).setSourceDocId(SALES_ID)); |
| | | var sales = new MesWmProductSalesDO().setId(SALES_ID).setCode("SALES1") |
| | | .setStatus(MesWmProductSalesStatusEnum.CONFIRMED.getStatus()); |
| | | when(salesMapper.selectById(SALES_ID)).thenReturn(sales); |
| | | assertThrows(ServiceException.class, () -> service.validateBeforeClose(ncr)); |
| | | sales.setStatus(MesWmProductSalesStatusEnum.CANCELED.getStatus()); |
| | | |
| | | // 4. è¿å·¥é¢ç¨åºåºå缺失 |
| | | when(issues.selectList(any(Wrapper.class))).thenReturn(List.of()); |
| | | assertThrows(ServiceException.class, () -> service.validateBeforeClose(ncr)); |
| | | var issue = new MesWmMiscIssueDO().setId(20L).setCode("MI1") |
| | | .setStatus(MesWmMiscIssueStatusEnum.FINISHED.getStatus()); |
| | | when(issues.selectList(any(Wrapper.class))).thenReturn(List.of(issue)); |
| | | |
| | | // 5. åºåºæµæ°´æ°éä¸ç¬¦ |
| | | when(flows.selectList(any(Wrapper.class))).thenReturn(List.of(new MesWmTransactionDO() |
| | | .setItemId(2L).setType(MesWmTransactionTypeEnum.OUT.getType()) |
| | | .setQuantity(BigDecimal.valueOf(-2)))); |
| | | assertThrows(ServiceException.class, () -> service.validateBeforeClose(ncr)); |
| | | |
| | | // 6. å
¨é¨æ»¡è¶³ |
| | | when(flows.selectList(any(Wrapper.class))).thenReturn(List.of(new MesWmTransactionDO() |
| | | .setItemId(2L).setType(MesWmTransactionTypeEnum.OUT.getType()) |
| | | .setQuantity(BigDecimal.valueOf(-3)))); |
| | | assertDoesNotThrow(() -> service.validateBeforeClose(ncr)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.iqc.MesQcIqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.math.BigDecimal; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesQcNcrPurchaseSourceValidatorTest { |
| | | @Test void verifiesAllSourceLinksAndQuantities() { |
| | | var validator = new MesQcNcrPurchaseSourceValidator(); |
| | | var iqcs = mock(MesQcIqcMapper.class); |
| | | var notices = mock(MesWmArrivalNoticeMapper.class); |
| | | var lines = mock(MesWmArrivalNoticeLineMapper.class); |
| | | ReflectionTestUtils.setField(validator, "iqcMapper", iqcs); |
| | | ReflectionTestUtils.setField(validator, "arrivalNoticeMapper", notices); |
| | | ReflectionTestUtils.setField(validator, "lineMapper", lines); |
| | | var ncr = new MesQcNcrDO().setQcType(MesQcTypeEnum.IQC.getType()).setQcId(1L) |
| | | .setItemId(4L).setUnqualifiedQuantity(BigDecimal.TEN); |
| | | var iqc = new MesQcIqcDO().setId(1L).setSourceDocType(MesBizTypeConstants.WM_ARRIVAL_NOTICE) |
| | | .setSourceDocId(2L).setSourceLineId(3L).setItemId(4L) |
| | | .setUnqualifiedQuantity(BigDecimal.TEN).setCheckQuantity(BigDecimal.valueOf(100)) |
| | | .setStatus(MesQcStatusEnum.FINISHED.getStatus()); |
| | | var notice = new MesWmArrivalNoticeDO().setId(2L).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()); |
| | | var line = new MesWmArrivalNoticeLineDO().setId(3L).setNoticeId(2L).setIqcId(1L) |
| | | .setItemId(4L).setArrivalQuantity(BigDecimal.valueOf(100)); |
| | | when(iqcs.selectOne(any(Wrapper.class))).thenReturn(iqc); |
| | | when(notices.selectOne(any(Wrapper.class))).thenReturn(notice); |
| | | when(lines.selectOne(any(Wrapper.class))).thenReturn(line); |
| | | assertDoesNotThrow(() -> validator.validate(ncr)); |
| | | line.setIqcId(9L); |
| | | assertThrows(ServiceException.class, () -> validator.validate(ncr)); |
| | | line.setIqcId(1L).setArrivalQuantity(BigDecimal.valueOf(101)); |
| | | assertThrows(ServiceException.class, () -> validator.validate(ncr)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.transaction.MesWmTransactionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | import java.util.List; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesQcNcrReinspectionValidatorTest { |
| | | @Test void onlyExplicitPostCompletionWholeLotInspectionCanClose() { |
| | | var validator = new MesQcNcrReinspectionValidator(); |
| | | var orders = mock(MesProWorkOrderMapper.class); |
| | | var items = mock(MesMdItemMapper.class); |
| | | var inspections = mock(MesQcIpqcMapper.class); |
| | | var lines = mock(MesWmProductProduceLineMapper.class); |
| | | var produces = mock(MesWmProductProduceMapper.class); |
| | | var transactions = mock(MesWmTransactionMapper.class); |
| | | var ncrs = mock(MesQcNcrMapper.class); |
| | | ReflectionTestUtils.setField(validator, "workOrderMapper", orders); |
| | | ReflectionTestUtils.setField(validator, "itemMapper", items); |
| | | ReflectionTestUtils.setField(validator, "ipqcMapper", inspections); |
| | | ReflectionTestUtils.setField(validator, "produceLineMapper", lines); |
| | | ReflectionTestUtils.setField(validator, "produceMapper", produces); |
| | | ReflectionTestUtils.setField(validator, "transactionMapper", transactions); |
| | | ReflectionTestUtils.setField(validator, "ncrMapper", ncrs); |
| | | var finishedAt = LocalDateTime.of(2026, 1, 1, 10, 0); |
| | | var order = new MesProWorkOrderDO().setId(1L).setProductId(2L) |
| | | .setStatus(MesProWorkOrderStatusEnum.FINISHED.getStatus()).setFinishDate(finishedAt); |
| | | var product = new MesMdItemDO().setId(2L).setMdmItemId(3L); |
| | | var ncr = new MesQcNcrDO().setCode("NCR1").setQcType(MesQcTypeEnum.IPQC.getType()).setItemId(3L).setUnqualifiedQuantity(BigDecimal.TEN) |
| | | .setHandleDate(finishedAt); |
| | | var qc = new MesQcIpqcDO().setId(4L).setWorkOrderId(1L).setItemId(2L) |
| | | .setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK).setSourceDocId(7L).setSourceLineId(8L) |
| | | .setInspectDate(finishedAt.plusMinutes(1)).setStatus(MesQcStatusEnum.FINISHED.getStatus()) |
| | | .setCheckResult(1).setCheckQuantity(BigDecimal.TEN).setQualifiedQuantity(BigDecimal.TEN) |
| | | .setUnqualifiedQuantity(BigDecimal.ZERO); |
| | | qc.setCreateTime(finishedAt.plusMinutes(1)); |
| | | when(orders.selectList(any(Wrapper.class))).thenReturn(List.of(order)); |
| | | when(items.selectById(2L)).thenReturn(product); |
| | | when(inspections.selectById(4L)).thenReturn(qc); |
| | | var line = new MesWmProductProduceLineDO().setId(8L).setProduceId(9L) |
| | | .setFeedbackId(7L).setItemId(2L).setQuantity(BigDecimal.TEN).setQualityStatus(MesWmQualityStatusEnum.PASS.getStatus()); |
| | | when(lines.selectById(8L)).thenReturn(line); |
| | | when(produces.selectById(9L)).thenReturn(new MesWmProductProduceDO().setId(9L).setWorkOrderId(1L)); |
| | | when(transactions.selectList(any(Wrapper.class))).thenReturn(List.of(new MesWmTransactionDO().setQuantity(BigDecimal.TEN))); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, null)); |
| | | assertDoesNotThrow(() -> validator.validate(ncr, 4L)); |
| | | qc.setCheckResult(2).setQualifiedQuantity(BigDecimal.valueOf(6)).setUnqualifiedQuantity(BigDecimal.valueOf(4)); |
| | | line.setQuantity(BigDecimal.valueOf(6)); |
| | | when(transactions.selectList(any(Wrapper.class))).thenReturn(List.of(new MesWmTransactionDO().setQuantity(BigDecimal.valueOf(6)))); |
| | | when(ncrs.selectList(any(Wrapper.class))).thenReturn(List.of()); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | when(ncrs.selectList(any(Wrapper.class))).thenReturn(List.of(new MesQcNcrDO().setItemId(3L).setUnqualifiedQuantity(BigDecimal.valueOf(4)))); |
| | | assertDoesNotThrow(() -> validator.validate(ncr, 4L)); |
| | | qc.setQualifiedQuantity(BigDecimal.ZERO).setUnqualifiedQuantity(BigDecimal.TEN); |
| | | line.setQuantity(BigDecimal.ZERO); |
| | | when(transactions.selectList(any(Wrapper.class))).thenReturn(List.of()); |
| | | when(ncrs.selectList(any(Wrapper.class))).thenReturn(List.of(new MesQcNcrDO().setItemId(3L).setUnqualifiedQuantity(BigDecimal.TEN))); |
| | | assertDoesNotThrow(() -> validator.validate(ncr, 4L)); |
| | | qc.setCheckResult(1).setQualifiedQuantity(BigDecimal.TEN).setUnqualifiedQuantity(BigDecimal.ZERO); |
| | | line.setQuantity(BigDecimal.TEN); |
| | | when(transactions.selectList(any(Wrapper.class))).thenReturn(List.of()); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | when(transactions.selectList(any(Wrapper.class))).thenReturn(List.of(new MesWmTransactionDO().setQuantity(BigDecimal.TEN))); |
| | | qc.setInspectDate(finishedAt.minusMinutes(1)); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | qc.setInspectDate(finishedAt.plusMinutes(1)).setQualifiedQuantity(BigDecimal.ONE); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | qc.setQualifiedQuantity(BigDecimal.TEN).setCheckResult(2).setUnqualifiedQuantity(BigDecimal.ONE); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | qc.setCheckResult(1).setUnqualifiedQuantity(BigDecimal.ZERO); |
| | | qc.setQualifiedQuantity(BigDecimal.TEN).setWorkOrderId(5L); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | qc.setWorkOrderId(1L); |
| | | // åºè´§è¿å·¥åæ ·èµ°æ¥å·¥äº§åºå¤æ£ï¼ä¸¥æ ¼åæ¯ï¼ï¼ä¸åæ¾è¡ç¬ç«è¿ç¨æ£éªå |
| | | ncr.setQcType(MesQcTypeEnum.OQC.getType()); |
| | | qc.setSourceDocType(null).setSourceDocId(null).setSourceLineId(null); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | qc.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK).setSourceDocId(7L).setSourceLineId(8L); |
| | | assertDoesNotThrow(() -> validator.validate(ncr, 4L)); |
| | | ncr.setQcType(MesQcTypeEnum.IPQC.getType()); |
| | | qc.setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK).setSourceDocId(7L).setSourceLineId(8L); |
| | | order.setFinishDate(null); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | order.setFinishDate(finishedAt); |
| | | when(orders.selectList(any(Wrapper.class))).thenReturn(List.of(order, order)); |
| | | assertThrows(RuntimeException.class, () -> validator.validate(ncr, 4L)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.rqc.MesQcRqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ipqc.MesQcIpqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.productproduce.MesWmProductProduceMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.rqc.MesQcRqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.returnsales.MesWmReturnSalesLineMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.scrap.MesWmScrapReceiptMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.transaction.MesWmTransactionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.junit.jupiter.api.BeforeAll; |
| | | import com.baomidou.mybatisplus.core.MybatisConfiguration; |
| | | import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; |
| | | import org.apache.ibatis.builder.MapperBuilderAssistant; |
| | | import org.junit.jupiter.api.extension.ExtendWith; |
| | | import org.mockito.InjectMocks; |
| | | import org.mockito.Mock; |
| | | import org.mockito.junit.jupiter.MockitoExtension; |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | @ExtendWith(MockitoExtension.class) |
| | | class MesQcNcrScrapServiceTest { |
| | | @BeforeAll static void initializeTableInfo() { |
| | | TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), "test"), |
| | | MesWmProductProduceLineDO.class); |
| | | } |
| | | @InjectMocks MesQcNcrScrapService service; |
| | | @Mock MesQcRqcMapper rqc; |
| | | @Mock MesQcIpqcMapper ipqc; |
| | | @Mock MesProWorkOrderMapper orders; |
| | | @Mock MesWmProductProduceLineMapper outputs; |
| | | @Mock MesWmProductProduceMapper produces; |
| | | @Mock MesWmReturnSalesLineMapper lines; |
| | | @Mock MesWmScrapReceiptMapper receipts; |
| | | @Mock MesWmTransactionMapper transactions; |
| | | @Mock MesWmTransactionService transactionService; |
| | | @Mock MesWmWarehouseService warehouse; |
| | | @Mock MesWmWarehouseAreaService area; |
| | | |
| | | private MesQcNcrDO ncr() { return new MesQcNcrDO().setId(1L).setCode("NCR1").setQcType(MesQcTypeEnum.RQC.getType()) |
| | | .setQcId(2L).setItemId(3L).setUnqualifiedQuantity(BigDecimal.TEN); } |
| | | private MesQcRqcDO qc(int source) { return new MesQcRqcDO().setId(2L).setItemId(3L).setSourceDocType(source) |
| | | .setSourceDocId(4L).setSourceLineId(5L).setBatchCode("B1").setStatus(MesQcStatusEnum.FINISHED.getStatus()) |
| | | .setUnqualifiedQuantity(BigDecimal.TEN); } |
| | | private MesQcNcrDisposeReqVO request() { var req = new MesQcNcrDisposeReqVO(); req.setWarehouseId(8L); req.setLocationId(9L); req.setAreaId(10L); return req; } |
| | | |
| | | @Test void rejectsProductionReturn() { |
| | | when(rqc.selectById(2L)).thenReturn(qc(MesBizTypeConstants.WM_RETURN_ISSUE)); |
| | | assertThrows(ServiceException.class, () -> service.receive(ncr(), request())); |
| | | verifyNoInteractions(receipts, transactionService); |
| | | } |
| | | |
| | | @Test void validatesUnstockedIpqcOutputAndKeepsItsOriginalBatch() { |
| | | var item = ncr().setQcType(MesQcTypeEnum.IPQC.getType()); |
| | | var qc = new MesQcIpqcDO().setId(2L).setItemId(3L).setSourceDocType(MesBizTypeConstants.PRO_FEEDBACK) |
| | | .setSourceDocId(11L).setSourceLineId(12L).setWorkOrderId(15L) |
| | | .setStatus(MesQcStatusEnum.FINISHED.getStatus()).setUnqualifiedQuantity(BigDecimal.TEN); |
| | | var original = new MesWmProductProduceLineDO().setId(12L).setFeedbackId(11L).setProduceId(13L) |
| | | .setBatchId(7L).setBatchCode("B1"); |
| | | var failed = new MesWmProductProduceLineDO().setId(14L).setFeedbackId(11L).setProduceId(13L) |
| | | .setItemId(3L).setQuantity(BigDecimal.TEN).setBatchId(7L).setBatchCode("B1") |
| | | .setQualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()); |
| | | when(ipqc.selectById(2L)).thenReturn(qc); |
| | | when(outputs.selectById(12L)).thenReturn(original); |
| | | when(produces.selectById(13L)).thenReturn(new MesWmProductProduceDO().setId(13L).setFeedbackId(11L).setWorkOrderId(15L)); |
| | | when(outputs.selectListByFeedbackId(11L)).thenReturn(List.of(failed)); |
| | | when(orders.selectById(15L)).thenReturn(new MesProWorkOrderDO().setId(15L).setCode("WO1")); |
| | | var source = service.validateIpqcSource(item); |
| | | assertEquals(14L, source.sourceLineId()); |
| | | assertEquals(7L, source.batchId()); |
| | | assertEquals("B1", source.batchCode()); |
| | | when(transactions.selectCount(any())).thenReturn(1L); |
| | | assertThrows(ServiceException.class, () -> service.validateIpqcSource(item)); |
| | | failed.setBatchCode("other"); |
| | | assertThrows(ServiceException.class, () -> service.validateIpqcSource(item)); |
| | | } |
| | | |
| | | @Test void onlyOneNcrCanClaimTheFailedOutput() { |
| | | var item = ncr().setQcType(MesQcTypeEnum.IPQC.getType()); |
| | | var source = new MesQcNcrScrapService.IpqcSource(15L, "WO1", 14L, 7L, "B1"); |
| | | when(outputs.update(isNull(), any(Wrapper.class))).thenReturn(1, 0); |
| | | assertDoesNotThrow(() -> service.claimIpqcSource(item, source)); |
| | | assertThrows(ServiceException.class, () -> service.claimIpqcSource(item.setId(99L), source)); |
| | | verify(outputs, times(2)).update(isNull(), any(Wrapper.class)); |
| | | } |
| | | |
| | | @Test void recordsCustomerReturnInScrapWarehouseWithOriginalBatch() { |
| | | when(rqc.selectById(2L)).thenReturn(qc(MesBizTypeConstants.WM_RETURN_SALES)); |
| | | var line = new MesWmReturnSalesLineDO().setId(6L).setReturnId(4L).setRqcId(2L).setItemId(3L) |
| | | .setQuantity(BigDecimal.TEN).setBatchId(7L).setBatchCode("B1").setQualityStatus(MesWmQualityStatusEnum.FAIL.getStatus()); |
| | | when(lines.selectListByReturnId(4L)).thenReturn(List.of(line)); |
| | | when(warehouse.validateWarehouseExists(8L)).thenReturn(new MesWmWarehouseDO().setWarehouseType("åºæä»")); |
| | | var response = service.receive(ncr(), request()); |
| | | assertEquals("SCRAP_RECEIPT", response.getDocType()); |
| | | assertEquals("SCR-N" + MesQcTypeEnum.RQC.getType() + "-6", response.getDocCode()); |
| | | verify(transactionService).createTransaction(argThat(tx -> tx.getItemId().equals(3L) && tx.getBatchId().equals(7L) |
| | | && tx.getQuantity().compareTo(BigDecimal.TEN) == 0 && tx.getWarehouseId().equals(8L))); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesNcrDispositionEnum; |
| | | import org.junit.jupiter.api.Test; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | |
| | | class MesQcNcrStockClosureGuardTest { |
| | | private final MesQcNcrStockClosureGuard guard = new MesQcNcrStockClosureGuard(); |
| | | |
| | | @Test |
| | | void blocksInventoryGeneratingDispositionsUntilSourceAndStockAreConnected() { |
| | | assertThrows(ServiceException.class, () -> guard.validateBeforeDispose(MesNcrDispositionEnum.SCRAP.getType())); |
| | | assertThrows(ServiceException.class, () -> guard.validateBeforeDispose(MesNcrDispositionEnum.CONCESSION.getType())); |
| | | assertDoesNotThrow(() -> guard.validateBeforeDispose(MesNcrDispositionEnum.REWORK.getType())); |
| | | assertDoesNotThrow(() -> guard.validateBeforeDispose(MesNcrDispositionEnum.REPAIR.getType())); |
| | | } |
| | | |
| | | @Test |
| | | void blocksClosingLegacyScrapWithoutDestroyEvidence() { |
| | | MesQcNcrDO ncr = new MesQcNcrDO().setDisposition(MesNcrDispositionEnum.SCRAP.getType()); |
| | | assertThrows(ServiceException.class, () -> guard.validateBeforeClose(ncr)); |
| | | assertEquals(MesNcrDispositionEnum.SCRAP.getType(), ncr.getDisposition()); |
| | | assertThrows(ServiceException.class, () -> guard.validateBeforeClose(new MesQcNcrDO().setDisposition(MesNcrDispositionEnum.CONCESSION.getType()))); |
| | | assertDoesNotThrow(() -> guard.validateBeforeClose(new MesQcNcrDO().setDisposition(MesNcrDispositionEnum.REWORK.getType()))); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.ncr.vo.MesQcNcrDisposeReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.line.MesWmItemReceiptLineSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseReturnDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseConcessionMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseReturnMapper; |
| | | import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptDetailService; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.math.BigDecimal; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesQcPurchaseConcessionServiceTest { |
| | | @Test void postsOnlyConcessionThroughPurchaseReceiptAndPreventsDuplicateReturn() { |
| | | var service = new MesQcPurchaseConcessionService(); |
| | | var validator = mock(MesQcNcrPurchaseSourceValidator.class); |
| | | var concessions = mock(MesQcPurchaseConcessionMapper.class); |
| | | var returns = mock(MesQcPurchaseReturnMapper.class); |
| | | var receipts = mock(MesWmItemReceiptService.class); |
| | | var lines = mock(MesWmItemReceiptLineService.class); |
| | | var details = mock(MesWmItemReceiptDetailService.class); |
| | | var arrival = mock(MesQcArrivalDispositionService.class); |
| | | ReflectionTestUtils.setField(service, "validator", validator); |
| | | ReflectionTestUtils.setField(service, "concessionMapper", concessions); |
| | | ReflectionTestUtils.setField(service, "returnMapper", returns); |
| | | ReflectionTestUtils.setField(service, "receipts", receipts); |
| | | ReflectionTestUtils.setField(service, "lines", lines); |
| | | ReflectionTestUtils.setField(service, "details", details); |
| | | ReflectionTestUtils.setField(service, "arrivalDispositionService", arrival); |
| | | var ncr = new MesQcNcrDO().setId(1L).setCode("NCR1").setQcId(2L).setItemId(3L).setUnqualifiedQuantity(BigDecimal.TEN); |
| | | var notice = new MesWmArrivalNoticeDO().setId(4L).setVendorId(5L).setPurchaseOrderId(6L); |
| | | var line = new MesWmArrivalNoticeLineDO().setId(7L).setPurchaseOrderItemId(8L); |
| | | when(validator.validate(ncr)).thenReturn(new MesQcNcrPurchaseSourceValidator.Source(new MesQcIqcDO(), notice, line)); |
| | | when(receipts.createItemReceipt(any())).thenReturn(9L); |
| | | when(lines.createItemReceiptLine(any())).thenReturn(10L); |
| | | when(lines.getItemReceiptLine(10L)).thenReturn(new MesWmItemReceiptLineDO().setId(10L)); |
| | | var request = new MesQcNcrDisposeReqVO(); |
| | | request.setDisposition(4); request.setWarehouseId(11L); request.setLocationId(12L); request.setAreaId(13L); |
| | | var result = service.dispose(ncr, request); |
| | | assertEquals(9L, result.getDocId()); |
| | | var lineRequest = ArgumentCaptor.forClass(MesWmItemReceiptLineSaveReqVO.class); |
| | | verify(lines).createItemReceiptLine(lineRequest.capture()); |
| | | assertEquals(BigDecimal.TEN, lineRequest.getValue().getReceivedQuantity()); |
| | | assertNull(lineRequest.getValue().getArrivalNoticeLineId()); |
| | | verify(receipts).finishItemReceipt(9L); |
| | | verify(arrival).refresh(4L); |
| | | when(returns.selectByIqcId(2L)).thenReturn(new MesQcPurchaseReturnDO()); |
| | | assertThrows(ServiceException.class, () -> service.dispose(ncr, request)); |
| | | verify(receipts, times(1)).createItemReceipt(any()); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.ncr; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcNcrDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseReturnDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.ncr.MesQcPurchaseConcessionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseReturnMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcPurchaseConcessionMapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.math.BigDecimal; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesQcPurchaseReturnServiceTest { |
| | | @Test void createsTraceableWaitingDocumentAndRequiresWarehouseConfirmation() { |
| | | var service = new MesQcPurchaseReturnService(); |
| | | var validator = mock(MesQcNcrPurchaseSourceValidator.class); |
| | | var mapper = mock(MesQcPurchaseReturnMapper.class); |
| | | var concessions = mock(MesQcPurchaseConcessionMapper.class); |
| | | ReflectionTestUtils.setField(service, "sourceValidator", validator); |
| | | ReflectionTestUtils.setField(service, "mapper", mapper); |
| | | ReflectionTestUtils.setField(service, "concessionMapper", concessions); |
| | | var ncr = new MesQcNcrDO().setId(1L).setQcId(2L).setItemId(3L).setUnqualifiedQuantity(BigDecimal.TEN); |
| | | var iqc = new MesQcIqcDO().setId(2L).setVendorBatch("B1"); |
| | | var notice = new MesWmArrivalNoticeDO().setId(4L).setVendorId(5L).setPurchaseOrderId(6L); |
| | | var line = new MesWmArrivalNoticeLineDO().setId(7L).setPurchaseOrderItemId(8L); |
| | | when(validator.validate(ncr)).thenReturn(new MesQcNcrPurchaseSourceValidator.Source(iqc, notice, line)); |
| | | var doc = service.createForNcr(ncr); |
| | | assertEquals(0, doc.getStatus()); |
| | | assertEquals(8L, doc.getPurchaseOrderItemId()); |
| | | assertEquals(BigDecimal.TEN, doc.getQuantity()); |
| | | verify(mapper).insert(any(MesQcPurchaseReturnDO.class)); |
| | | when(mapper.selectByNcrId(1L)).thenReturn(doc); |
| | | assertThrows(ServiceException.class, () -> service.createForNcr(ncr)); |
| | | assertThrows(ServiceException.class, () -> service.validateConfirmed(1L)); |
| | | doc.setStatus(1); |
| | | assertDoesNotThrow(() -> service.validateConfirmed(1L)); |
| | | when(mapper.selectByNcrId(1L)).thenReturn(null); |
| | | when(concessions.selectByIqcId(2L)).thenReturn(new MesQcPurchaseConcessionDO()); |
| | | assertThrows(ServiceException.class, () -> service.createForNcr(ncr)); |
| | | verify(mapper, times(1)).insert(any(MesQcPurchaseReturnDO.class)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.wm.miscissue; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.miscissue.MesWmMiscIssueLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.miscissue.MesWmMiscIssueMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.wm.MesWmMiscIssueStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesWmMiscIssueServiceImplTest { |
| | | |
| | | @Test |
| | | void finishMiscIssuePassesSelectedMaterialStockIdToTransaction() { |
| | | var service = new MesWmMiscIssueServiceImpl(); |
| | | var mapper = mock(MesWmMiscIssueMapper.class); |
| | | var lineService = mock(MesWmMiscIssueLineService.class); |
| | | var transactionService = mock(MesWmTransactionService.class); |
| | | ReflectionTestUtils.setField(service, "miscIssueMapper", mapper); |
| | | ReflectionTestUtils.setField(service, "miscIssueLineService", lineService); |
| | | ReflectionTestUtils.setField(service, "wmTransactionService", transactionService); |
| | | |
| | | var issue = new MesWmMiscIssueDO().setId(1L).setCode("MI1") |
| | | .setStatus(MesWmMiscIssueStatusEnum.APPROVED.getStatus()); |
| | | var line = new MesWmMiscIssueLineDO().setId(5L).setIssueId(1L).setItemId(2L) |
| | | .setQuantity(BigDecimal.valueOf(3)).setMaterialStockId(9L).setBatchId(6L) |
| | | .setBatchCode("B1").setWarehouseId(11L).setLocationId(12L).setAreaId(13L); |
| | | when(mapper.selectById(1L)).thenReturn(issue); |
| | | when(lineService.getMiscIssueLineListByIssueId(1L)).thenReturn(List.of(line)); |
| | | |
| | | service.finishMiscIssue(1L); |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | ArgumentCaptor<List<MesWmTransactionSaveReqDTO>> captor = ArgumentCaptor.forClass(List.class); |
| | | verify(transactionService).createTransactionList(captor.capture()); |
| | | var dto = captor.getValue().get(0); |
| | | assertEquals(9L, dto.getMaterialStockId().longValue()); |
| | | assertEquals(6L, dto.getBatchId().longValue()); |
| | | assertEquals(0, BigDecimal.valueOf(-3).compareTo(dto.getQuantity())); |
| | | } |
| | | |
| | | @Test |
| | | void finishMiscIssueRejectsNonApprovedStatus() { |
| | | var service = new MesWmMiscIssueServiceImpl(); |
| | | var mapper = mock(MesWmMiscIssueMapper.class); |
| | | ReflectionTestUtils.setField(service, "miscIssueMapper", mapper); |
| | | ReflectionTestUtils.setField(service, "miscIssueLineService", mock(MesWmMiscIssueLineService.class)); |
| | | ReflectionTestUtils.setField(service, "wmTransactionService", mock(MesWmTransactionService.class)); |
| | | |
| | | when(mapper.selectById(1L)).thenReturn(new MesWmMiscIssueDO().setId(1L).setCode("MI1") |
| | | .setStatus(MesWmMiscIssueStatusEnum.PREPARE.getStatus())); |
| | | |
| | | assertThrows(ServiceException.class, () -> service.finishMiscIssue(1L)); |
| | | verifyNoInteractions(ReflectionTestUtils.getField(service, "wmTransactionService")); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.wm.productsales; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productsales.MesWmProductSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ncr.MesQcNcrMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper; |
| | | import com.baomidou.mybatisplus.core.conditions.Wrapper; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | import java.math.BigDecimal; |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class MesWmOqcConcessionGuardTest { |
| | | @Test void enforcesInspectedBatchAndWholeLineQuantity() { |
| | | var guard = new MesWmOqcConcessionGuard(); |
| | | var ncrs = mock(MesQcNcrMapper.class); |
| | | var oqcs = mock(MesQcOqcMapper.class); |
| | | var stocks = mock(MesWmMaterialStockMapper.class); |
| | | ReflectionTestUtils.setField(guard, "ncrMapper", ncrs); |
| | | ReflectionTestUtils.setField(guard, "oqcMapper", oqcs); |
| | | ReflectionTestUtils.setField(guard, "stockMapper", stocks); |
| | | var line = new MesWmProductSalesLineDO().setId(1L).setItemId(2L).setOqcId(3L).setQuantity(BigDecimal.TEN); |
| | | when(ncrs.selectCount(any(Wrapper.class))).thenReturn(1L); |
| | | when(oqcs.selectById(3L)).thenReturn(new MesQcOqcDO().setSourceLineId(1L).setItemId(2L).setBatchCode("B1")); |
| | | when(stocks.selectById(4L)).thenReturn(new MesWmMaterialStockDO().setItemId(2L).setBatchCode("B1")); |
| | | when(stocks.selectById(5L)).thenReturn(new MesWmMaterialStockDO().setItemId(2L).setBatchCode("B2")); |
| | | assertDoesNotThrow(() -> guard.validate(line, 4L, "B1", BigDecimal.TEN)); |
| | | assertThrows(ServiceException.class, () -> guard.validate(line, 5L, "B1", BigDecimal.TEN)); |
| | | assertThrows(ServiceException.class, () -> guard.validate(line, 4L, "B2", BigDecimal.TEN)); |
| | | assertThrows(ServiceException.class, () -> guard.validate(line, null, "B1", BigDecimal.TEN)); |
| | | assertThrows(ServiceException.class, () -> guard.validate(line, 4L, "B1", BigDecimal.ZERO)); |
| | | assertDoesNotThrow(() -> guard.validateQuantity(line, BigDecimal.TEN)); |
| | | assertThrows(ServiceException.class, () -> guard.validateQuantity(line, BigDecimal.ONE)); |
| | | } |
| | | } |
| | |
| | | "expire_date" timestamp DEFAULT NULL, |
| | | "lot_number" varchar(128) DEFAULT NULL, |
| | | "quality_status" int DEFAULT NULL, |
| | | "ncr_disposition_id" bigint DEFAULT NULL, |
| | | "remark" varchar(500) DEFAULT NULL, |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | |
| | | "oqc_check_flag" tinyint DEFAULT NULL, |
| | | "oqc_id" bigint DEFAULT NULL, |
| | | "quality_status" int DEFAULT NULL, |
| | | "ncr_disposition_id" bigint DEFAULT NULL, |
| | | "remark" varchar(500) DEFAULT NULL, |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | |
| | | "rqc_id" bigint DEFAULT NULL, |
| | | "rqc_check_flag" bit NOT NULL DEFAULT FALSE, |
| | | "quality_status" int DEFAULT NULL, |
| | | "ncr_disposition_id" bigint DEFAULT NULL, |
| | | "remark" varchar(500) DEFAULT '', |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |