feat(mes): 完善质检流程功能
- 在 mes_qc_oqc 表增加 workflow_status 字段用于跟踪质检流程状态
- 新增 mes_qc_oqc_action 表记录质检操作流程日志
- 实现出货质检(OQC)的复检、销售部审批、公司质检审批完整流程
- 添加销售质检相关的API接口包括复检、审批和流程记录查询
- 实现种子质检复检和多级审批流程功能
- 在追溯服务中根据工作流状态计算质检结果显示
- 完善过程质检(IPQC)校验逻辑确保数据一致性
- 优化数据库查询支持按来源单据类型筛选
- 添加单元测试验证质检流程的各种场景
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # çäº§è´¨æ£æµç¨ä¸èåæ å° |
| | | |
| | | 便®ç¨æ·æä¾çç产æµç¨å¾åå½å `system_menu`ï¼ä¿çå·²æè´¨é管çèåï¼æ°å¢âéå®è´¨æ£âå
¥å£ã |
| | | |
| | | | æµç¨å¾èç¹ | ç³»ç»å
¥å£ | å®ç° | |
| | | | --- | --- | --- | |
| | | | 48/100 è¢æ«ç å
¥åºãçææçç | ä¸è¢ä¸ç / ä»å¨ç©æµ | æ¢æè¢ç ãæçç ãæ¹æ¬¡ç | |
| | | | å å·¥é¨åæ£ | è´¨é管ç / ç§åè´¨æ£ | 坿è¢ãæçãæ¹æ¬¡æäº¤ï¼æ¯è¢ç¬ç«è®°å½ | |
| | | | 忣ä¸åæ ¼ã夿£ | è´¨é管ç / è´¨æ£è®°å½ | ä¿çæååºåï¼è®°å½å¤æ£ç»æä¸åæ¬¡ææ | |
| | | | å å·¥é¨é¢å¯¼å®¡æ¹ | è´¨é管ç / è´¨æ£è®°å½ | åæ¡æåç¶ææ¹é审æ¹ï¼å¯é©³å鿣 | |
| | | | å
¬å¸è´¨æ£å®¡æ¹ | è´¨é管ç / è´¨æ£è®°å½ | åæ¡æåç¶ææ¹é审æ¹ï¼éè¿åæè¿ç§»ææ¥åºåºå | |
| | | | éå®é¨è´¨æ£ | è´¨é管ç / éå®è´¨æ£ | ç¬ç«èåï¼å¤ç¨æ¢æ OQC éå®åºåºåæ£éªå | |
| | | | éå®å¤æ£ä¸å®¡æ¹ | è´¨é管ç / éå®è´¨æ£ | ä¸åæ ¼å
夿£ï¼éå®é¨ä¸å
¬å¸è´¨æ£å®¡æ¹ååååºåºè´¨éç¶æ | |
| | | | ä¸åæ ¼åççè¯å®¡ | è´¨é管ç / ä¸åæ ¼åå¤ç | æ¢æ OQC/IPQC èªå¨å建 NCR | |
| | | | æ£å¼å
¥åºãåºåºãæ«ç 追溯 | ä»å¨ç©æµ / åºåºæµé / 追溯 | å®¡æ¹æ¾è¡åè¢ç `qc_status=20`ï¼æªæ¾è¡ä¸è½æ«ç åºåº | |
| | | |
| | | ## ç§åè´¨æ£ç¶æ |
| | | |
| | | - 10 å¾
夿£ï¼åæ£å¤±è´¥æå®¡æ¹é©³åï¼åºåä»å¨æååºã |
| | | - 20 å¾
å å·¥é¨å®¡æ¹ï¼åæ£æå¤æ£åæ ¼ã |
| | | - 30 å¾
å
¬å¸è´¨æ£å®¡æ¹ï¼å å·¥é¨åææ¾è¡ã |
| | | - 40 å·²æ¾è¡ï¼å
¬å¸è´¨æ£åæï¼äºå¡æ§å°å°è¯¥è¢ç±æååºç§»å
¥åæ ¼åºã |
| | | - 50 å¾
å å·¥é¨ä¸åæ ¼è¯å®¡ï¼å¤æ£ä¸åæ ¼ã |
| | | - 60 å¾
å
¬å¸ä¸åæ ¼å®¡æ¹ï¼å å·¥é¨åææ¥åºã |
| | | - 70 å·²æ¥åºï¼å
¬å¸è´¨æ£åæï¼äºå¡æ§å°ç§»é¤è¯¥è¢æååºåã |
| | | |
| | | 审æ¹é©³ååå° 10ãæ¯æ¬¡åæ£ã夿£å审æ¹åå
¥ `mes_qc_seed_quality_action`ï¼å
嫿ä½äººãæ¶é´ãæè§åæ£éªææ å¿«ç
§ãæ¹é审æ¹å¨å䏿°æ®åºäºå¡å
æ§è¡ï¼ä»»ä¸è¢åºåæµè½¬å¤±è´¥åå
¨é¨åæ»ã |
| | | |
| | | éå®è´¨æ£çæµç¨ç¶æä¸ç§åè´¨æ£ç¸åï¼å
¶ä¸ 20/50 ç±éå®é¨å®¡æ¹ï¼40 表示åºè´§æ£éªéè¿å¹¶ååéå®åºåºåè¡ï¼70 表示ä¸åæ ¼å®¡æ¹å®æå¹¶åå为ä¸åæ ¼ï¼åæ¶å建 NCRã忣ä¸å¤æ£ä¸åååºåºåè´¨éç»æï¼å æ¤å®¡æ¹åä»å¤äºå¾
æ£ç¶æã夿£ååæ°éåå®¡æ¹æè§åå
¥ `mes_qc_oqc_action`ã |
| | | |
| | | ## é¨ç½²é¡ºåº |
| | | |
| | | 1. å¤ä»½æ°æ®åºï¼æ§è¡ `sql/mysql/mes_seed_quality_workflow_20261019.sql`ãèæ¬æ¯ä¸æ¬¡æ§è¿ç§»ï¼åå²å·²æµè½¬çåæ ¼/ä¸åæ ¼è®°å½å嫿 记为 40/70ï¼é¿å
éå¤åºåäºå¡ã |
| | | 2. é¨ç½²å端ãå端ãå端æå»ºäº§ç©å¨ `mom-pro2-before/dist.zip`ã |
| | | 3. 为å å·¥é¨ä¸å
¬å¸è´¨æ£è§è²åå«æäº `mes:qc-seed-quality:process-approve`ã`mes:qc-seed-quality:company-approve`ï¼å¤æ£äººåæäº `mes:qc-seed-quality:retest`ã为éå®é¨ä¸å
¬å¸è´¨æ£è§è²åå«æäº `mes:qc-oqc:sales-approve`ã`mes:qc-oqc:company-approve`ï¼éå®å¤æ£äººåæäº `mes:qc-oqc:retest`ãæ ¸å¯¹âéå®è´¨æ£âèåè§è²æéã |
| | | |
| | | âéå®è´¨æ£âä¸åâåºè´§æ£éªâå
±ç¨ OQC æ£éªå忢æåºè´§æ£éªè¡¨åï¼ä½æç¬ç«èåä¸ä¸é¨ç夿£ãå®¡æ¹æä½ãåâè¿ç¨æ£éªï¼IPQCï¼â仿å¡ç产任å¡åæ¥å·¥ï¼æªå¹¶å
¥æè¢ç æµè½¬çç§åè´¨æ£å®¡æ¹é¾ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ç产质æ£ï¼åæ£ã夿£ãå å·¥é¨å®¡æ¹ãå
¬å¸è´¨æ£å®¡æ¹åææ¾è¡åºåã |
| | | -- å
é¨ç½²æ°æ®åºåæ´ï¼åé¨ç½²å¯¹åºå端ä¸å端ï¼åå²åæ ¼è®°å½å·²å®æåºåæµè½¬ã |
| | | ALTER TABLE mes_qc_seed_quality |
| | | ADD COLUMN workflow_status tinyint NOT NULL DEFAULT 40 COMMENT '10å¾
夿£ 20å¾
å å·¥é¨å®¡æ¹ 30å¾
å
¬å¸è´¨æ£å®¡æ¹ 40å·²æ¾è¡ 50å¾
å å·¥é¨ä¸åæ ¼è¯å®¡ 60å¾
å
¬å¸ä¸åæ ¼å®¡æ¹ 70å·²æ¥åº', |
| | | ADD COLUMN qualified_warehouse_id bigint DEFAULT NULL COMMENT '审æ¹éè¿åçç®æ åæ ¼åº', |
| | | ADD COLUMN retest_result tinyint DEFAULT NULL COMMENT '夿£ç»æ 1åæ ¼ 2ä¸åæ ¼', |
| | | ADD COLUMN retested_at datetime DEFAULT NULL COMMENT '夿£æ¶é´', |
| | | ADD COLUMN process_approved_at datetime DEFAULT NULL COMMENT 'å å·¥é¨å®¡æ¹æ¶é´', |
| | | ADD COLUMN company_approved_at datetime DEFAULT NULL COMMENT 'å
¬å¸è´¨æ£å®¡æ¹æ¶é´'; |
| | | CREATE INDEX idx_mes_seed_quality_workflow ON mes_qc_seed_quality (workflow_status, inspected_at); |
| | | |
| | | -- æ§æ°æ®å·²ç»ææ§æµç¨æ¾è¡ææ¥åºï¼ä¸è½éæ°å®¡æ¹æé夿£åºåã |
| | | UPDATE mes_qc_seed_quality SET workflow_status = CASE WHEN result = 1 THEN 40 WHEN result = 2 THEN 70 ELSE 10 END; |
| | | |
| | | CREATE TABLE IF NOT EXISTS mes_qc_seed_quality_action ( |
| | | id bigint NOT NULL AUTO_INCREMENT, |
| | | quality_id bigint NOT NULL, |
| | | action varchar(32) NOT NULL COMMENT 'INITIAL/RETEST/PROCESS_APPROVE/COMPANY_APPROVE/REJECT', |
| | | result tinyint DEFAULT NULL, |
| | | opinion varchar(500) DEFAULT NULL, |
| | | indicators_json text DEFAULT NULL, |
| | | operator_id bigint DEFAULT NULL, |
| | | create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_quality_time (quality_id, create_time) |
| | | ) ENGINE=InnoDB COMMENT='ç§åè´¨æ£æµç¨æä½çç'; |
| | | |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT '夿£', 'mes:qc-seed-quality:retest', 3, 3, id, '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE parent_id = 5500 AND path = 'seed-quality' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu m WHERE m.parent_id = system_menu.id AND m.permission = 'mes:qc-seed-quality:retest' AND m.deleted = b'0'); |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'å å·¥é¨å®¡æ¹', 'mes:qc-seed-quality:process-approve', 3, 4, id, '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE parent_id = 5500 AND path = 'seed-quality' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu m WHERE m.parent_id = system_menu.id AND m.permission = 'mes:qc-seed-quality:process-approve' AND m.deleted = b'0'); |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'å
¬å¸è´¨æ£å®¡æ¹', 'mes:qc-seed-quality:company-approve', 3, 5, id, '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE parent_id = 5500 AND path = 'seed-quality' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu m WHERE m.parent_id = system_menu.id AND m.permission = 'mes:qc-seed-quality:company-approve' AND m.deleted = b'0'); |
| | | |
| | | -- ç¬ç«éå®è´¨æ£å
¥å£ï¼ä¿çåâåºè´§æ£éªâèåï¼å¤ç¨å
¶å·²å®ç°çéå®åºåºåæ£éªè½åã |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'éå®è´¨æ£', '', 2, 8, 5500, 'sales-quality', 'ep:document-checked', 'mes/qc/salesQuality/index', 'MesQcSalesQuality', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE parent_id = 5500 AND path = 'sales-quality' AND deleted = b'0'); |
| | | |
| | | -- éå®è´¨æ£å¤æ£å两级审æ¹ï¼åå²å·²å®æåºè´§æ£éªåç»´ææ¢ææ¾è¡ç»æã |
| | | ALTER TABLE mes_qc_oqc |
| | | ADD COLUMN workflow_status tinyint NOT NULL DEFAULT 0 COMMENT '0è稿 10å¾
夿£ 20å¾
éå®é¨å®¡æ¹ 30å¾
å
¬å¸è´¨æ£å®¡æ¹ 40å·²æ¾è¡ 50å¾
éå®é¨ä¸åæ ¼è¯å®¡ 60å¾
å
¬å¸ä¸åæ ¼å®¡æ¹ 70å·²æç»åºè´§'; |
| | | CREATE INDEX idx_mes_oqc_workflow ON mes_qc_oqc (workflow_status, id); |
| | | UPDATE mes_qc_oqc SET workflow_status = CASE |
| | | WHEN status = 0 THEN 0 |
| | | WHEN check_result = 1 THEN 40 |
| | | ELSE 70 END; |
| | | CREATE TABLE IF NOT EXISTS mes_qc_oqc_action ( |
| | | id bigint NOT NULL AUTO_INCREMENT, |
| | | oqc_id bigint NOT NULL, |
| | | action varchar(32) NOT NULL, |
| | | result tinyint DEFAULT NULL, |
| | | opinion varchar(500) DEFAULT NULL, |
| | | quantities_json text DEFAULT NULL, |
| | | operator_id bigint DEFAULT NULL, |
| | | create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY (id), |
| | | KEY idx_oqc_time (oqc_id, create_time) |
| | | ) ENGINE=InnoDB COMMENT='éå®è´¨æ£å¤æ£å审æ¹çç'; |
| | | |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'éå®å¤æ£', 'mes:qc-oqc:retest', 3, 1, id, '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE parent_id = 5500 AND path = 'sales-quality' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu m WHERE m.parent_id = system_menu.id AND m.permission = 'mes:qc-oqc:retest' AND m.deleted = b'0'); |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'éå®é¨å®¡æ¹', 'mes:qc-oqc:sales-approve', 3, 2, id, '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE parent_id = 5500 AND path = 'sales-quality' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu m WHERE m.parent_id = system_menu.id AND m.permission = 'mes:qc-oqc:sales-approve' AND m.deleted = b'0'); |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'éå®å
¬å¸è´¨æ£å®¡æ¹', 'mes:qc-oqc:company-approve', 3, 3, id, '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_menu WHERE parent_id = 5500 AND path = 'sales-quality' AND deleted = b'0' |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu m WHERE m.parent_id = system_menu.id AND m.permission = 'mes:qc-oqc:company-approve' AND m.deleted = b'0'); |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcRetestReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcApprovalReqVO; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO; |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/retest") @Operation(summary = "éå®è´¨æ£å¤æ£") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-oqc:retest')") |
| | | public CommonResult<Boolean> retestOqc(@Valid @RequestBody MesQcOqcRetestReqVO reqVO) { |
| | | oqcService.retestOqc(reqVO); |
| | | return success(true); |
| | | } |
| | | @PostMapping("/sales-approve") @Operation(summary = "éå®é¨å®¡æ¹") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-oqc:sales-approve')") |
| | | public CommonResult<Boolean> salesApprove(@Valid @RequestBody MesQcOqcApprovalReqVO reqVO) { |
| | | oqcService.salesApprove(reqVO.getId(), reqVO.getApproved(), reqVO.getOpinion()); |
| | | return success(true); |
| | | } |
| | | @PostMapping("/company-approve") @Operation(summary = "å
¬å¸è´¨æ£å®¡æ¹éå®åºè´§") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-oqc:company-approve')") |
| | | public CommonResult<Boolean> companyApprove(@Valid @RequestBody MesQcOqcApprovalReqVO reqVO) { |
| | | oqcService.companyApprove(reqVO.getId(), reqVO.getApproved(), reqVO.getOpinion()); |
| | | return success(true); |
| | | } |
| | | @GetMapping("/actions") @Operation(summary = "éå®è´¨æ£æµç¨è®°å½") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-oqc:query')") |
| | | public CommonResult<List<cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcActionDO>> actions(@RequestParam Long id) { |
| | | return success(oqcService.getActions(id)); |
| | | } |
| | | |
| | | @DeleteMapping("/delete") |
| | | @Operation(summary = "å é¤åºè´§æ£éªå") |
| | | @Parameter(name = "id", description = "ç¼å·", required = true) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo; |
| | | |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class MesQcOqcApprovalReqVO { |
| | | @NotNull private Long id; |
| | | @NotNull private Boolean approved; |
| | | @NotBlank private String opinion; |
| | | } |
| | |
| | | |
| | | @Schema(description = "æ£æµç»æ", example = "1") |
| | | private Integer checkResult; |
| | | @Schema(description = "æ¥æºåæ®ç±»å") |
| | | private Integer sourceDocType; |
| | | |
| | | } |
| | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | | @ExcelProperty("ç¶æ") |
| | | private Integer status; |
| | | private Integer workflowStatus; |
| | | |
| | | @Schema(description = "夿³¨", example = "夿³¨") |
| | | @ExcelProperty("夿³¨") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo; |
| | | |
| | | import jakarta.validation.constraints.DecimalMin; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | import java.math.BigDecimal; |
| | | |
| | | @Data |
| | | public class MesQcOqcRetestReqVO { |
| | | @NotNull private Long id; |
| | | @NotNull @DecimalMin("0") private BigDecimal checkQuantity; |
| | | @NotNull @DecimalMin("0") private BigDecimal qualifiedQuantity; |
| | | @NotNull @DecimalMin("0") private BigDecimal unqualifiedQuantity; |
| | | @NotNull private Integer checkResult; |
| | | private String opinion; |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityApprovalReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityBatchApprovalReqVO; |
| | | import cn.iocoder.yudao.module.mes.service.qc.seedquality.MesQcSeedQualityService; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.tags.Tag; |
| | |
| | | return success(BeanUtils.toBean(service.getQualityPage(pageReqVO), MesQcSeedQualityRespVO.class)); |
| | | } |
| | | |
| | | @GetMapping("/indicators") @Operation(summary = "è·å¾ç§åè´¨æ£ææ°ææ ") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:query')") |
| | | public CommonResult<java.util.List<cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO>> indicators(@RequestParam Long id) { |
| | | return success(service.getIndicatorsByQualityId(id)); |
| | | } |
| | | @GetMapping("/actions") @Operation(summary = "è·å¾ç§åè´¨æ£æµç¨çç") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:query')") |
| | | public CommonResult<java.util.List<cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityActionDO>> actions(@RequestParam Long id) { |
| | | return success(service.getActions(id)); |
| | | } |
| | | |
| | | @PostMapping("/submit") @Operation(summary = "æäº¤ç§åè´¨æ£") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:submit')") |
| | | public CommonResult<Long> submit(@Valid @RequestBody MesQcSeedQualitySubmitReqVO reqVO) { return success(service.submit(reqVO)); } |
| | |
| | | public CommonResult<java.util.Map<String, Object>> submitGroup(@Valid @RequestBody cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityGroupSubmitReqVO reqVO) { |
| | | return success(service.submitGroup(reqVO)); |
| | | } |
| | | |
| | | @PostMapping("/retest") @Operation(summary = "ç§åè´¨æ£å¤æ£") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:retest')") |
| | | public CommonResult<Boolean> retest(@RequestParam Long id, @Valid @RequestBody MesQcSeedQualityRetestReqVO reqVO) { |
| | | service.retest(id, reqVO); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/process-approve") @Operation(summary = "å å·¥é¨å®¡æ¹") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:process-approve')") |
| | | public CommonResult<Boolean> processApprove(@Valid @RequestBody MesQcSeedQualityApprovalReqVO reqVO) { |
| | | service.processApprove(reqVO.getId(), reqVO.getApproved(), reqVO.getOpinion()); |
| | | return success(true); |
| | | } |
| | | |
| | | @PostMapping("/company-approve") @Operation(summary = "å
¬å¸è´¨æ£å®¡æ¹å¹¶æµè½¬åºå") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:company-approve')") |
| | | public CommonResult<Boolean> companyApprove(@Valid @RequestBody MesQcSeedQualityApprovalReqVO reqVO) { |
| | | service.companyApprove(reqVO.getId(), reqVO.getApproved(), reqVO.getOpinion()); |
| | | return success(true); |
| | | } |
| | | @PostMapping("/process-approve-batch") @Operation(summary = "æ¹éå å·¥é¨å®¡æ¹") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:process-approve')") |
| | | public CommonResult<Boolean> processApproveBatch(@Valid @RequestBody MesQcSeedQualityBatchApprovalReqVO reqVO) { |
| | | service.processApproveBatch(reqVO.getIds(), reqVO.getApproved(), reqVO.getOpinion()); |
| | | return success(true); |
| | | } |
| | | @PostMapping("/company-approve-batch") @Operation(summary = "æ¹éå
¬å¸è´¨æ£å®¡æ¹å¹¶æµè½¬åºå") |
| | | @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:company-approve')") |
| | | public CommonResult<Boolean> companyApproveBatch(@Valid @RequestBody MesQcSeedQualityBatchApprovalReqVO reqVO) { |
| | | service.companyApproveBatch(reqVO.getIds(), reqVO.getApproved(), reqVO.getOpinion()); |
| | | return success(true); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo; |
| | | |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class MesQcSeedQualityApprovalReqVO { |
| | | @NotNull private Long id; |
| | | @NotNull private Boolean approved; |
| | | @NotBlank private String opinion; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo; |
| | | |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | public class MesQcSeedQualityBatchApprovalReqVO { |
| | | @NotEmpty private List<@NotNull Long> ids; |
| | | @NotNull private Boolean approved; |
| | | @NotBlank private String opinion; |
| | | } |
| | |
| | | |
| | | @Schema(description = "æ£éªç»æï¼1åæ ¼ 2ä¸åæ ¼") |
| | | private Integer result; |
| | | |
| | | @Schema(description = "æµç¨ç¶æï¼10å¾
夿£ 20/50å¾
å å·¥é¨ 30/60å¾
å
¬å¸è´¨æ£ 40å·²æ¾è¡ 70å·²æ¥åº") |
| | | private Integer workflowStatus; |
| | | } |
| | |
| | | private Long batchId; |
| | | private String batchCode; |
| | | private Integer result; |
| | | private Integer workflowStatus; |
| | | private Integer retestResult; |
| | | private LocalDateTime retestedAt; |
| | | private LocalDateTime processApprovedAt; |
| | | private LocalDateTime companyApprovedAt; |
| | | private String reason; |
| | | private Long inspectorId; |
| | | private LocalDateTime inspectedAt; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo; |
| | | |
| | | import jakarta.validation.Valid; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotEmpty; |
| | | import lombok.Data; |
| | | |
| | | import java.util.List; |
| | | |
| | | @Data |
| | | public class MesQcSeedQualityRetestReqVO { |
| | | @NotBlank private String code; |
| | | @NotEmpty @Valid private List<MesQcSeedQualitySubmitReqVO.Indicator> indicators; |
| | | private String reason; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("mes_qc_oqc_action") |
| | | public class MesQcOqcActionDO { |
| | | @TableId private Long id; |
| | | private Long oqcId; |
| | | private String action; |
| | | private Integer result; |
| | | private String opinion; |
| | | private String quantitiesJson; |
| | | private Long operatorId; |
| | | private LocalDateTime createTime; |
| | | } |
| | |
| | | * æä¸¾ {@link cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum} |
| | | */ |
| | | private Integer status; |
| | | /** 0è稿ï¼10å¾
夿£ï¼20å¾
éå®é¨å®¡æ¹ï¼30å¾
å
¬å¸è´¨æ£å®¡æ¹ï¼40å·²æ¾è¡ï¼50å¾
éå®é¨ä¸åæ ¼è¯å®¡ï¼60å¾
å
¬å¸ä¸åæ ¼å®¡æ¹ï¼70å·²æç»åºè´§ã */ |
| | | private Integer workflowStatus; |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDateTime; |
| | | |
| | | @Data |
| | | @TableName("mes_qc_seed_quality_action") |
| | | public class MesQcSeedQualityActionDO { |
| | | @TableId private Long id; |
| | | private Long qualityId; |
| | | private String action; |
| | | private Integer result; |
| | | private String opinion; |
| | | private String indicatorsJson; |
| | | private Long operatorId; |
| | | private LocalDateTime createTime; |
| | | } |
| | |
| | | private String batchCode; |
| | | private Integer result; |
| | | private String reason; |
| | | /** 10 å¾
夿£ï¼20 å¾
å å·¥é¨å®¡æ¹ï¼30 å¾
å
¬å¸è´¨æ£å®¡æ¹ï¼40 å·²æ¾è¡ï¼50 å¾
å å·¥é¨ä¸åæ ¼è¯å®¡ï¼60 å¾
å
¬å¸ä¸åæ ¼å®¡æ¹ï¼70 å·²æ¥åºã */ |
| | | private Integer workflowStatus; |
| | | private Long qualifiedWarehouseId; |
| | | private Integer retestResult; |
| | | private LocalDateTime retestedAt; |
| | | private LocalDateTime processApprovedAt; |
| | | private LocalDateTime companyApprovedAt; |
| | | private Long inspectorId; |
| | | private LocalDateTime inspectedAt; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc; |
| | | |
| | | 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.oqc.MesQcOqcActionDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | import java.util.List; |
| | | |
| | | @Mapper |
| | | public interface MesQcOqcActionMapper extends BaseMapperX<MesQcOqcActionDO> { |
| | | default List<MesQcOqcActionDO> selectByOqcId(Long id) { |
| | | return selectList(new LambdaQueryWrapperX<MesQcOqcActionDO>() |
| | | .eq(MesQcOqcActionDO::getOqcId, id).orderByAsc(MesQcOqcActionDO::getId)); |
| | | } |
| | | } |
| | |
| | | .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode()) |
| | | .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId()) |
| | | .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult()) |
| | | .eqIfPresent(MesQcOqcDO::getSourceDocType, reqVO.getSourceDocType()) |
| | | .orderByDesc(MesQcOqcDO::getId)); |
| | | } |
| | | |
| | |
| | | .likeIfPresent(MesQcOqcDO::getBatchCode, reqVO.getBatchCode()) |
| | | .eqIfPresent(MesQcOqcDO::getItemId, reqVO.getItemId()) |
| | | .eqIfPresent(MesQcOqcDO::getCheckResult, reqVO.getCheckResult()) |
| | | .eqIfPresent(MesQcOqcDO::getSourceDocType, reqVO.getSourceDocType()) |
| | | .orderByDesc(MesQcOqcDO::getId); |
| | | if (permittedClientIds != null) { |
| | | if (CollUtil.isEmpty(permittedClientIds)) { |
| | |
| | | default MesQcOqcDO selectByCode(String code) { |
| | | return selectOne(MesQcOqcDO::getCode, code); |
| | | } |
| | | default MesQcOqcDO selectByIdForUpdate(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcOqcDO>().eq(MesQcOqcDO::getId, id).last("FOR UPDATE")); |
| | | } |
| | | |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality; |
| | | |
| | | import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityActionDO; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | | |
| | | @Mapper |
| | | public interface MesQcSeedQualityActionMapper extends BaseMapperX<MesQcSeedQualityActionDO> { |
| | | default java.util.List<MesQcSeedQualityActionDO> selectByQualityId(Long qualityId) { |
| | | return selectList(new LambdaQueryWrapperX<MesQcSeedQualityActionDO>() |
| | | .eq(MesQcSeedQualityActionDO::getQualityId, qualityId) |
| | | .orderByAsc(MesQcSeedQualityActionDO::getId)); |
| | | } |
| | | } |
| | |
| | | .likeIfPresent(MesQcSeedQualityDO::getCode, reqVO.getCode()) |
| | | .likeIfPresent(MesQcSeedQualityDO::getBatchCode, reqVO.getBatchCode()) |
| | | .eqIfPresent(MesQcSeedQualityDO::getResult, reqVO.getResult()) |
| | | .eqIfPresent(MesQcSeedQualityDO::getWorkflowStatus, reqVO.getWorkflowStatus()) |
| | | .orderByDesc(MesQcSeedQualityDO::getInspectedAt) |
| | | .orderByDesc(MesQcSeedQualityDO::getId)); |
| | | } |
| | |
| | | default MesQcSeedQualityDO selectByCodeForUpdate(String code) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcSeedQualityDO>().eq(MesQcSeedQualityDO::getCode, code).last("FOR UPDATE")); |
| | | } |
| | | default MesQcSeedQualityDO selectByIdForUpdate(Long id) { |
| | | return selectOne(new LambdaQueryWrapperX<MesQcSeedQualityDO>().eq(MesQcSeedQualityDO::getId, id).last("FOR UPDATE")); |
| | | } |
| | | |
| | | default List<MesQcSeedQualityDO> selectListByBatchCode(String batchCode) { |
| | | return selectList(new LambdaQueryWrapperX<MesQcSeedQualityDO>() |
| | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.ipqc.MesQcIpqcMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum; |
| | | 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.pro.MesProFeedbackStatusEnum; |
| | |
| | | if (ipqc.getCheckResult() == null) { |
| | | throw exception(QC_IPQC_CHECK_RESULT_EMPTY); |
| | | } |
| | | if (ipqc.getUnqualifiedQuantity() != null && ipqc.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) > 0 |
| | | && MesQcCheckResultEnum.QUALIFIED.getType().equals(ipqc.getCheckResult())) { |
| | | throw new IllegalArgumentException("åå¨ä¸åæ ¼åæ¶ï¼è¿ç¨æ£éªç»è®ºä¸è½ä¸ºåæ ¼"); |
| | | } |
| | | if ((ipqc.getUnqualifiedQuantity() == null || ipqc.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) == 0) |
| | | && !MesQcCheckResultEnum.QUALIFIED.getType().equals(ipqc.getCheckResult())) { |
| | | throw new IllegalArgumentException("ä¸åæ ¼æ°é为 0 æ¶ï¼è¿ç¨æ£éªç»è®ºå¿
é¡»ä¸ºåæ ¼"); |
| | | } |
| | | // 1.3 æ ¡éªè³å°åå¨ä¸æ¡æ£æµç»æ |
| | | indicatorResultService.validateIndicatorResultExistsByQcIdAndType(id, MesQcTypeEnum.IPQC.getType()); |
| | | |
| | |
| | | * @param id ç¼å· |
| | | */ |
| | | void finishOqc(Long id); |
| | | void retestOqc(cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcRetestReqVO reqVO); |
| | | void salesApprove(Long id, boolean approved, String opinion); |
| | | void companyApprove(Long id, boolean approved, String opinion); |
| | | List<cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcActionDO> getActions(Long id); |
| | | |
| | | /** |
| | | * å é¤åºè´§æ£éªå |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.oqc; |
| | | |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcRetestReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.defectrecord.MesQcDefectRecordDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcActionDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.template.MesQcTemplateItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcActionMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcDefectLevelEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum; |
| | |
| | | import java.math.RoundingMode; |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | import java.time.LocalDateTime; |
| | | import java.util.Set; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; |
| | | |
| | | /** |
| | |
| | | @Validated |
| | | public class MesQcOqcServiceImpl implements MesQcOqcService { |
| | | |
| | | private static final int WAIT_RETEST = 10, WAIT_SALES = 20, WAIT_COMPANY = 30, |
| | | RELEASED = 40, WAIT_SALES_FAIL = 50, WAIT_COMPANY_FAIL = 60, REJECTED = 70; |
| | | |
| | | @Resource |
| | | private MesQcOqcMapper oqcMapper; |
| | | @Resource |
| | | private MesQcOqcActionMapper actionMapper; |
| | | |
| | | @Resource |
| | | private MesQcTemplateItemService templateItemService; |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void finishOqc(Long id) { |
| | | // 1.1 æ ¡éªåå¨ + èç¨¿ç¶æ |
| | | MesQcOqcDO oqc = validateOqcStatusPrepare(id); |
| | | MesQcOqcDO oqc = lock(id); |
| | | if (!MesQcStatusEnum.DRAFT.getStatus().equals(oqc.getStatus())) throw exception(QC_OQC_NOT_PREPARE); |
| | | // 1.2 æ ¡éªæ£æµç»è®ºå¿
å¡« |
| | | if (oqc.getCheckResult() == null) { |
| | | throw exception(QC_OQC_CHECK_RESULT_EMPTY); |
| | | } |
| | | if (oqc.getUnqualifiedQuantity() != null && oqc.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) > 0 |
| | | && MesQcCheckResultEnum.QUALIFIED.getType().equals(oqc.getCheckResult())) { |
| | | throw new IllegalArgumentException("åå¨ä¸åæ ¼åæ¶ï¼åºè´§æ£éªç»è®ºä¸è½ä¸ºåæ ¼"); |
| | | } |
| | | if ((oqc.getUnqualifiedQuantity() == null || oqc.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) == 0) |
| | | && !MesQcCheckResultEnum.QUALIFIED.getType().equals(oqc.getCheckResult())) { |
| | | throw new IllegalArgumentException("ä¸åæ ¼æ°é为 0 æ¶ï¼åºè´§æ£éªç»è®ºå¿
é¡»ä¸ºåæ ¼"); |
| | | } |
| | | // 1.3 æ ¡éªè³å°åå¨ä¸æ¡æ£æµç»æ |
| | | indicatorResultService.validateIndicatorResultExistsByQcIdAndType(id, MesQcTypeEnum.OQC.getType()); |
| | | |
| | | // 2. æ´æ°ç¶æä¸ºå·²å®æ |
| | | MesQcOqcDO updateObj = new MesQcOqcDO() |
| | | .setId(id).setStatus(MesQcStatusEnum.FINISHED.getStatus()); |
| | | .setId(id).setStatus(MesQcStatusEnum.FINISHED.getStatus()) |
| | | .setWorkflowStatus(MesQcCheckResultEnum.QUALIFIED.getType().equals(oqc.getCheckResult()) ? WAIT_SALES : WAIT_RETEST); |
| | | oqcMapper.updateById(updateObj); |
| | | |
| | | // 3. ä¸åæ ¼æ¶èªå¨å建 NCR |
| | | generateNcrIfNeeded(oqc); |
| | | |
| | | // 4. å忥æºåæ® |
| | | writeBackSourceDoc(oqc); |
| | | recordAction(id, "INITIAL", oqc.getCheckResult(), null, oqc); |
| | | // éå®åºåºæç»ä»ä¸ºå¾
æ£ï¼å
¬å¸è´¨æ£å®¡æ¹éè¿åæååå¹¶å
许åç»åºåºã |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void retestOqc(MesQcOqcRetestReqVO req) { |
| | | MesQcOqcDO oqc = requireStatus(req.getId(), WAIT_RETEST); |
| | | if (req.getCheckQuantity().compareTo(req.getQualifiedQuantity().add(req.getUnqualifiedQuantity())) != 0) |
| | | throw new IllegalArgumentException("æ£æµæ°éå¿
é¡»çäºåæ ¼ä¸ä¸åæ ¼æ°éä¹å"); |
| | | if (req.getCheckQuantity().compareTo(oqc.getOutQuantity()) > 0) |
| | | throw new IllegalArgumentException("æ£æµæ°éä¸è½è¶
è¿åºè´§æ°é"); |
| | | if (!Set.of(1, 2).contains(req.getCheckResult())) throw new IllegalArgumentException("夿£ç»è®ºåªè½ä¸ºåæ ¼æä¸åæ ¼"); |
| | | boolean passed = req.getCheckResult() == 1; |
| | | if (passed != (req.getUnqualifiedQuantity().compareTo(BigDecimal.ZERO) == 0)) |
| | | throw new IllegalArgumentException("夿£ç»è®ºä¸ä¸åæ ¼æ°éä¸ä¸è´"); |
| | | if (!passed && (req.getOpinion() == null || req.getOpinion().isBlank())) |
| | | throw new IllegalArgumentException("夿£ä¸åæ ¼å¿
须填ååå "); |
| | | MesQcOqcDO update = new MesQcOqcDO().setId(req.getId()) |
| | | .setCheckQuantity(req.getCheckQuantity()).setQualifiedQuantity(req.getQualifiedQuantity()) |
| | | .setUnqualifiedQuantity(req.getUnqualifiedQuantity()).setCheckResult(req.getCheckResult()) |
| | | .setInspectDate(LocalDateTime.now()).setInspectorUserId(getLoginUserId()) |
| | | .setWorkflowStatus(passed ? WAIT_SALES : WAIT_SALES_FAIL); |
| | | oqcMapper.updateById(update); |
| | | recordAction(req.getId(), "RETEST", req.getCheckResult(), req.getOpinion(), update); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void salesApprove(Long id, boolean approved, String opinion) { |
| | | MesQcOqcDO oqc = lock(id); |
| | | if (!Set.of(WAIT_SALES, WAIT_SALES_FAIL).contains(oqc.getWorkflowStatus())) |
| | | throw new IllegalArgumentException("å½åç¶æä¸è½è¿è¡éå®é¨å®¡æ¹"); |
| | | requireOpinion(opinion); |
| | | int next = approved ? (oqc.getWorkflowStatus() == WAIT_SALES ? WAIT_COMPANY : WAIT_COMPANY_FAIL) : WAIT_RETEST; |
| | | oqcMapper.updateById(new MesQcOqcDO().setId(id).setWorkflowStatus(next)); |
| | | recordAction(id, approved ? "SALES_APPROVE" : "SALES_REJECT", oqc.getCheckResult(), opinion, oqc); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void companyApprove(Long id, boolean approved, String opinion) { |
| | | MesQcOqcDO oqc = lock(id); |
| | | if (!Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(oqc.getWorkflowStatus())) |
| | | throw new IllegalArgumentException("å½åç¶æä¸è½è¿è¡å
¬å¸è´¨æ£å®¡æ¹"); |
| | | requireOpinion(opinion); |
| | | if (approved) { |
| | | if (oqc.getWorkflowStatus() == WAIT_COMPANY_FAIL) generateNcrIfNeeded(oqc); |
| | | writeBackSourceDoc(oqc); |
| | | } |
| | | int next = approved ? (oqc.getWorkflowStatus() == WAIT_COMPANY ? RELEASED : REJECTED) : WAIT_RETEST; |
| | | oqcMapper.updateById(new MesQcOqcDO().setId(id).setWorkflowStatus(next)); |
| | | recordAction(id, approved ? "COMPANY_APPROVE" : "COMPANY_REJECT", oqc.getCheckResult(), opinion, oqc); |
| | | } |
| | | |
| | | private MesQcOqcDO lock(Long id) { |
| | | MesQcOqcDO oqc = oqcMapper.selectByIdForUpdate(id); |
| | | if (oqc == null) throw exception(QC_OQC_NOT_EXISTS); |
| | | return oqc; |
| | | } |
| | | |
| | | private MesQcOqcDO requireStatus(Long id, int status) { |
| | | MesQcOqcDO oqc = lock(id); |
| | | if (!Objects.equals(oqc.getWorkflowStatus(), status)) |
| | | throw new IllegalArgumentException("éå®è´¨æ£ç¶æå·²ååï¼è¯·å·æ°åéè¯"); |
| | | return oqc; |
| | | } |
| | | |
| | | private void requireOpinion(String opinion) { |
| | | if (opinion == null || opinion.isBlank()) throw new IllegalArgumentException("请填åå®¡æ¹æè§"); |
| | | } |
| | | |
| | | private void recordAction(Long id, String action, Integer result, String opinion, MesQcOqcDO snapshot) { |
| | | MesQcOqcActionDO event = new MesQcOqcActionDO(); |
| | | event.setOqcId(id);event.setAction(action);event.setResult(result);event.setOpinion(opinion); |
| | | java.util.Map<String, BigDecimal> quantities = new java.util.HashMap<>(); |
| | | quantities.put("checkQuantity", snapshot.getCheckQuantity()); |
| | | quantities.put("qualifiedQuantity", snapshot.getQualifiedQuantity()); |
| | | quantities.put("unqualifiedQuantity", snapshot.getUnqualifiedQuantity()); |
| | | event.setQuantitiesJson(JSONUtil.toJsonStr(quantities)); |
| | | event.setOperatorId(getLoginUserId());event.setCreateTime(LocalDateTime.now()); |
| | | actionMapper.insert(event); |
| | | } |
| | | |
| | | @Override |
| | | public List<MesQcOqcActionDO> getActions(Long id) { return actionMapper.selectByOqcId(id); } |
| | | |
| | | /** |
| | | * ä¸åæ ¼æ¶èªå¨å建 NCRï¼ä¸åæ ¼åå¤çåï¼ |
| | | * |
| | |
| | | java.util.Map<String, Object> resolveScan(String code); |
| | | java.util.Map<String, Object> resolveScan(String code, String scanType); |
| | | java.util.Map<String, Object> submitGroup(cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityGroupSubmitReqVO reqVO); |
| | | void retest(Long id, cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO reqVO); |
| | | void processApprove(Long id, boolean approved, String opinion); |
| | | void companyApprove(Long id, boolean approved, String opinion); |
| | | void processApproveBatch(List<Long> ids, boolean approved, String opinion); |
| | | void companyApproveBatch(List<Long> ids, boolean approved, String opinion); |
| | | |
| | | PageResult<MesQcSeedQualityDO> getQualityPage(MesQcSeedQualityPageReqVO reqVO); |
| | | |
| | |
| | | * @return è´¨æ£ææ å表 |
| | | */ |
| | | List<MesQcSeedQualityIndicatorDO> getIndicatorsByQualityId(Long qualityId); |
| | | List<cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityActionDO> getActions(Long qualityId); |
| | | } |
| | |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.hutool.json.JSONUtil; |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO; |
| | |
| | | import java.util.*; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_QUALIFIED_WAREHOUSE_NOT_CONFIGURED; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_WAREHOUSE_TYPE_NOT_QUALIFIED; |
| | |
| | | @Service @Validated @Slf4j |
| | | public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService { |
| | | private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture"); |
| | | private static final int WAIT_RETEST = 10, WAIT_PROCESS = 20, WAIT_COMPANY = 30, |
| | | RELEASED = 40, WAIT_PROCESS_FAIL = 50, WAIT_COMPANY_FAIL = 60, SCRAPPED = 70; |
| | | @Resource private MesQcSeedQualityMapper qualityMapper; |
| | | @Resource private MesQcSeedQualityActionMapper actionMapper; |
| | | @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper; |
| | | @Resource private MesProBagCodeMapper bagCodeMapper; |
| | | @Resource private cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper palletMapper; |
| | |
| | | if (!MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType())) { |
| | | throw exception(WM_WAREHOUSE_TYPE_NOT_QUALIFIED); |
| | | } |
| | | // è§£æåæ ¼åºé»è®¤åºåº/åºä½ï¼å该ä»åºä¸ç¬¬ä¸ä¸ªåºåºãå
¶ä¸ç¬¬ä¸ä¸ªåºä½ï¼æªé
ç½®å为 nullï¼ |
| | | Long qualifiedLocationId = null; |
| | | Long qualifiedAreaId = null; |
| | | List<MesWmWarehouseLocationDO> locations = warehouseLocationService.getWarehouseLocationList(qualifiedWarehouse.getId()); |
| | | if (CollUtil.isNotEmpty(locations)) { |
| | | qualifiedLocationId = locations.get(0).getId(); |
| | | List<MesWmWarehouseAreaDO> areas = warehouseAreaService.getWarehouseAreaList(qualifiedLocationId); |
| | | if (CollUtil.isNotEmpty(areas)) { |
| | | qualifiedAreaId = areas.get(0).getId(); |
| | | } |
| | | } |
| | | Set<String> codes = new HashSet<>(); |
| | | boolean passed = true; |
| | | MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(bag.getCode()).itemId(batch.getItemId()) |
| | | .batchCode(batch.getCode()).reason(reqVO.getReason()).result(0) |
| | | .batchCode(batch.getCode()).batchId(batch.getId()).reason(reqVO.getReason()).result(0) |
| | | .qualifiedWarehouseId(qualifiedWarehouse.getId()).inspectorId(getLoginUserId()) |
| | | .inspectedAt(LocalDateTime.now()).build(); |
| | | qualityMapper.insert(quality); |
| | | for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) { |
| | | if (!REQUIRED.contains(item.getCode()) || !codes.add(item.getCode())) throw new IllegalArgumentException("ææ å¿
须为纯度ãå度ãåè½çãæ°´åä¸ä¸å¯éå¤"); |
| | | if (item.getMinValue().compareTo(item.getMaxValue()) > 0 || item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false; |
| | | if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("ææ ä¸éä¸è½å¤§äºä¸é"); |
| | | if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false; |
| | | indicatorMapper.insert(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode()) |
| | | .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build()); |
| | | } |
| | | if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("åé¡¹ææ ä¸å®æ´"); |
| | | if (!passed && StrUtil.isBlank(reqVO.getReason())) throw new IllegalArgumentException("è´¨æ£ä¸åæ ¼å¿
须填ååå "); |
| | | quality.setResult(passed ? 1 : 2); |
| | | quality.setWorkflowStatus(passed ? WAIT_PROCESS : WAIT_RETEST); |
| | | qualityMapper.updateById(quality); |
| | | recordAction(quality.getId(), "INITIAL", quality.getResult(), reqVO.getReason(), reqVO.getIndicators()); |
| | | // 忣ä»
è®°å½ç»è®ºï¼è¢ä»ç卿ååºï¼å¾
夿£å两级审æ¹ååæ¾è¡ææ¥åºã |
| | | return quality.getId(); |
| | | } |
| | | |
| | | private void finalizeStock(MesQcSeedQualityDO quality, boolean passed) { |
| | | MesProBagCodeDO bag = bagCodeMapper.selectByScanCodeForUpdate(quality.getCode()); |
| | | if (bag == null || bag.getInboundEventId() == null || !Objects.equals(bag.getQcStatus(), MesProBagCodeQcStatusEnum.UNCHECKED.getStatus())) |
| | | throw new IllegalArgumentException("è¢ç ç¶æå·²ååï¼ç¦æ¢éå¤åºåæµè½¬"); |
| | | MesProBatchDO batch = proBatchService.getBatch(bag.getBatchId()); |
| | | if (batch == null) throw new IllegalArgumentException("è¢ç æå±çäº§æ¹æ¬¡ä¸åå¨"); |
| | | MesWmBatchDO wmBatch = mesWmBatchMapper.selectByCode(batch.getCode()); |
| | | if (wmBatch == null) throw new IllegalArgumentException("æååºåæå±ä»åºæ¹æ¬¡ä¸åå¨"); |
| | | MesWmWarehouseDO qualifiedWarehouse = warehouseService.getWarehouse(quality.getQualifiedWarehouseId()); |
| | | if (passed && (qualifiedWarehouse == null || Boolean.TRUE.equals(qualifiedWarehouse.getFrozen()) |
| | | || !MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType()))) |
| | | throw new IllegalArgumentException("ç®æ åæ ¼åºä¸åå¨æç±»åå·²åå"); |
| | | Long qualifiedLocationId = null; |
| | | Long qualifiedAreaId = null; |
| | | if (passed) { |
| | | List<MesWmWarehouseLocationDO> locations = warehouseLocationService.getWarehouseLocationList(qualifiedWarehouse.getId()); |
| | | if (CollUtil.isNotEmpty(locations)) { |
| | | qualifiedLocationId = locations.get(0).getId(); |
| | | List<MesWmWarehouseAreaDO> areas = warehouseAreaService.getWarehouseAreaList(qualifiedLocationId); |
| | | if (CollUtil.isNotEmpty(areas)) qualifiedAreaId = areas.get(0).getId(); |
| | | } |
| | | } |
| | | // æè¢ç ç²åº¦æµè½¬åºåï¼éå®è¯¥è¢å¨æååºä¸çæ´»å¨æç»ï¼ç¦æ¢ææ¹æ¬¡ä»»ææ£å |
| | | MesWmMaterialStockBagDO stockBag = materialStockBagMapper.selectActiveByBagCode(bag.getCode()); |
| | | MesWmMaterialStockDO tempStock = resolveTempStock(bag, wmBatch, stockBag); |
| | |
| | | .setType(MesWmTransactionTypeEnum.OUT.getType()) |
| | | .setBizType(MesBizTypeConstants.QC_SEED_QUALITY) |
| | | .setBizId(quality.getId()) |
| | | .setBizCode(reqVO.getCode()) |
| | | .setBizCode(quality.getCode()) |
| | | .setBizLineId(quality.getId()) |
| | | .setItemId(batch.getItemId()) |
| | | .setQuantity(BigDecimal.ONE.negate()) |
| | |
| | | .setType(MesWmTransactionTypeEnum.IN.getType()) |
| | | .setBizType(MesBizTypeConstants.QC_SEED_QUALITY) |
| | | .setBizId(quality.getId()) |
| | | .setBizCode(reqVO.getCode()) |
| | | .setBizCode(quality.getCode()) |
| | | .setBizLineId(quality.getId()) |
| | | .setItemId(batch.getItemId()) |
| | | .setQuantity(BigDecimal.ONE) |
| | |
| | | // B2. 忥è¿ç§»è¢ç æç»å°åæ ¼åºåè½ç¹ |
| | | materialStockBagService.moveIn(qualInReq, qualifiedStock, qualInTxId); |
| | | bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.QUALIFIED.getStatus()); |
| | | log.info("[submit][è´¨æ£åæ ¼ï¼è¢ç {} ç±æååºè½¬åæ ¼åº {}]", bag.getCode(), qualifiedWarehouse.getName()); |
| | | log.info("[finalizeStock][å®¡æ¹æ¾è¡ï¼è¢ç {} ç±æååºè½¬åæ ¼åº {}]", bag.getCode(), qualifiedWarehouse.getName()); |
| | | } else { |
| | | // C. è´¨æ£ä¸åæ ¼ â è¯¥è¢æ¥åºï¼æååºåºäºå¡å·²æ£åæååºåï¼æ è®°è¢ç æ¥åº |
| | | materialStockBagService.scrap(tempOutReq, tempOutTxId); |
| | | bagCodeMapper.updateQcStatus(bag.getId(), MesProBagCodeQcStatusEnum.UNQUALIFIED.getStatus()); |
| | | log.info("[submit][è´¨æ£ä¸åæ ¼ï¼è¢ç {} æ¥åº]", bag.getCode()); |
| | | log.info("[finalizeStock][å®¡æ¹æ¥åºï¼è¢ç {}]", bag.getCode()); |
| | | } |
| | | return quality.getId(); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void retest(Long id, cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityRetestReqVO req) { |
| | | MesQcSeedQualityDO quality = requireStatus(id, WAIT_RETEST); |
| | | if (!Objects.equals(quality.getCode(), req.getCode())) throw new IllegalArgumentException("夿£è¢ç ä¸åè´¨æ£è®°å½ä¸ä¸è´"); |
| | | boolean passed = validateIndicators(req.getIndicators()); |
| | | if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("夿£ä¸åæ ¼å¿
须填ååå "); |
| | | recordAction(id, "RETEST", passed ? 1 : 2, req.getReason(), req.getIndicators()); |
| | | qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).result(passed ? 1 : 2) |
| | | .reason(passed ? "" : req.getReason().trim()).retestResult(passed ? 1 : 2).retestedAt(LocalDateTime.now()) |
| | | .inspectorId(getLoginUserId()).inspectedAt(LocalDateTime.now()) |
| | | .workflowStatus(passed ? WAIT_PROCESS : WAIT_PROCESS_FAIL).build()); |
| | | // 页é¢ãå
¬å¼è¿½æº¯è¯»åä¸»ææ è¡¨ï¼åæ¥ä¸ºææ°ä¸æ¬¡æµå¼ï¼å次æµå¼å¨æä½çç表ä¿çã |
| | | Map<String, MesQcSeedQualityIndicatorDO> existing = new HashMap<>(); |
| | | for (MesQcSeedQualityIndicatorDO indicator : indicatorMapper.selectListByQualityId(id)) |
| | | existing.put(indicator.getIndicatorCode(), indicator); |
| | | for (MesQcSeedQualitySubmitReqVO.Indicator item : req.getIndicators()) { |
| | | MesQcSeedQualityIndicatorDO indicator = existing.get(item.getCode()); |
| | | if (indicator == null) throw new IllegalArgumentException("è´¨æ£ææ è®°å½ç¼ºå¤±"); |
| | | indicatorMapper.updateById(MesQcSeedQualityIndicatorDO.builder().id(indicator.getId()) |
| | | .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build()); |
| | | } |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void processApprove(Long id, boolean approved, String opinion) { |
| | | MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id); |
| | | if (quality == null || !Set.of(WAIT_PROCESS, WAIT_PROCESS_FAIL).contains(quality.getWorkflowStatus())) |
| | | throw new IllegalArgumentException("å½åç¶æä¸è½è¿è¡å å·¥é¨å®¡æ¹"); |
| | | if (StrUtil.isBlank(opinion)) throw new IllegalArgumentException("请填åå®¡æ¹æè§"); |
| | | int next = approved ? (quality.getWorkflowStatus() == WAIT_PROCESS ? WAIT_COMPANY : WAIT_COMPANY_FAIL) : WAIT_RETEST; |
| | | qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next) |
| | | .processApprovedAt(LocalDateTime.now()).build()); |
| | | recordAction(id, approved ? "PROCESS_APPROVE" : "PROCESS_REJECT", quality.getResult(), opinion, null); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void companyApprove(Long id, boolean approved, String opinion) { |
| | | MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id); |
| | | if (quality == null || !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus())) |
| | | throw new IllegalArgumentException("å½åç¶æä¸è½è¿è¡å
¬å¸è´¨æ£å®¡æ¹"); |
| | | if (StrUtil.isBlank(opinion)) throw new IllegalArgumentException("请填åå®¡æ¹æè§"); |
| | | if (approved) finalizeStock(quality, quality.getWorkflowStatus() == WAIT_COMPANY); |
| | | int next = approved ? (quality.getWorkflowStatus() == WAIT_COMPANY ? RELEASED : SCRAPPED) : WAIT_RETEST; |
| | | qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next) |
| | | .companyApprovedAt(LocalDateTime.now()).build()); |
| | | recordAction(id, approved ? "COMPANY_APPROVE" : "COMPANY_REJECT", quality.getResult(), opinion, null); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void processApproveBatch(List<Long> ids, boolean approved, String opinion) { |
| | | for (Long id : approvalIds(ids)) processApprove(id, approved, opinion); |
| | | } |
| | | |
| | | @Override @Transactional(rollbackFor = Exception.class) |
| | | public void companyApproveBatch(List<Long> ids, boolean approved, String opinion) { |
| | | for (Long id : approvalIds(ids)) companyApprove(id, approved, opinion); |
| | | } |
| | | |
| | | private List<Long> approvalIds(List<Long> ids) { |
| | | if (ids == null || ids.isEmpty() || ids.size() > 500 || ids.stream().anyMatch(Objects::isNull)) |
| | | throw new IllegalArgumentException("è¯·éæ© 1 è³ 500 æ¡è´¨æ£è®°å½"); |
| | | List<Long> sorted = ids.stream().distinct().sorted().toList(); |
| | | if (sorted.size() != ids.size()) throw new IllegalArgumentException("è´¨æ£è®°å½ä¸è½éå¤éæ©"); |
| | | return sorted; |
| | | } |
| | | |
| | | private MesQcSeedQualityDO requireStatus(Long id, int expected) { |
| | | MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id); |
| | | if (quality == null || !Objects.equals(quality.getWorkflowStatus(), expected)) |
| | | throw new IllegalArgumentException("è´¨æ£è®°å½ç¶æå·²ååï¼è¯·å·æ°åéè¯"); |
| | | return quality; |
| | | } |
| | | |
| | | private boolean validateIndicators(List<MesQcSeedQualitySubmitReqVO.Indicator> indicators) { |
| | | if (indicators == null || indicators.size() != REQUIRED.size()) throw new IllegalArgumentException("å¿
é¡»æäº¤åé¡¹ææ "); |
| | | Set<String> codes = new HashSet<>(); |
| | | boolean passed = true; |
| | | for (MesQcSeedQualitySubmitReqVO.Indicator item : indicators) { |
| | | if (item == null || !REQUIRED.contains(item.getCode()) || !codes.add(item.getCode()) |
| | | || item.getValue() == null || item.getMinValue() == null || item.getMaxValue() == null) |
| | | throw new IllegalArgumentException("ææ å¿
须为纯度ãå度ãåè½çãæ°´åä¸ä¸å¯éå¤"); |
| | | if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("ææ ä¸éä¸è½å¤§äºä¸é"); |
| | | if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false; |
| | | } |
| | | return passed; |
| | | } |
| | | |
| | | private void recordAction(Long id, String action, Integer result, String opinion, |
| | | List<MesQcSeedQualitySubmitReqVO.Indicator> indicators) { |
| | | MesQcSeedQualityActionDO event = new MesQcSeedQualityActionDO(); |
| | | event.setQualityId(id); |
| | | event.setAction(action); |
| | | event.setResult(result); |
| | | event.setOpinion(opinion); |
| | | event.setIndicatorsJson(indicators == null ? null : JSONUtil.toJsonStr(indicators)); |
| | | event.setOperatorId(getLoginUserId()); |
| | | event.setCreateTime(LocalDateTime.now()); |
| | | actionMapper.insert(event); |
| | | } |
| | | |
| | | /** |
| | |
| | | return List.of(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<MesQcSeedQualityActionDO> getActions(Long qualityId) { |
| | | return actionMapper.selectByQualityId(qualityId); |
| | | } |
| | | } |
| | |
| | | } |
| | | MesQcSeedQualityDO quality = list.get(0); |
| | | MesProTracePublicRespVO.QualityVO vo = new MesProTracePublicRespVO.QualityVO(); |
| | | vo.setResult(quality.getResult()).setResultName(qualityResultName(quality.getResult())) |
| | | Integer finalResult = Integer.valueOf(40).equals(quality.getWorkflowStatus()) ? 1 |
| | | : Integer.valueOf(70).equals(quality.getWorkflowStatus()) ? 2 : 0; |
| | | vo.setResult(finalResult).setResultName(finalResult == 0 ? "夿£æå®¡æ¹ä¸" : qualityResultName(finalResult)) |
| | | .setReason(quality.getReason()).setInspectorName(userName(quality.getInspectorId())) |
| | | .setInspectedAt(quality.getInspectedAt()); |
| | | List<MesQcSeedQualityIndicatorDO> indicators = qcSeedQualityService.getIndicatorsByQualityId(quality.getId()); |
| | |
| | | |
| | | import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.oqc.MesQcOqcDO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.qc.oqc.vo.MesQcOqcRetestReqVO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.oqc.MesQcOqcMapper; |
| | | import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants; |
| | | import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum; |
| | |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesLineService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.productsales.MesWmProductSalesService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService; |
| | | import cn.iocoder.yudao.module.mes.service.qc.ncr.MesQcNcrService; |
| | | import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi; |
| | | import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import jakarta.annotation.Resource; |
| | | import org.junit.jupiter.api.Test; |
| | |
| | | private AdminUserApi adminUserApi; |
| | | @MockitoBean |
| | | private MesQcIndicatorResultService indicatorResultService; |
| | | @MockitoBean |
| | | private MesQcNcrService ncrService; |
| | | @MockitoBean |
| | | private MdmItemApi mdmItemApi; |
| | | @MockitoBean |
| | | private CrmCustomerApi customerApi; |
| | | |
| | | @Test |
| | | public void testFinishOqc_writeBack_productSales() { |
| | |
| | | o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); |
| | | o.setSourceLineId(sourceLineId); |
| | | o.setCheckResult(checkResult); |
| | | o.setUnqualifiedQuantity(java.math.BigDecimal.ZERO); |
| | | o.setQualifiedQuantity(java.math.BigDecimal.TEN); |
| | | o.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | }); |
| | | oqcMapper.insert(oqc); |
| | | |
| | |
| | | // æè¨ï¼éªè¯ç¶ææ´æ°ä¸ºå·²å®æ |
| | | MesQcOqcDO updatedOqc = oqcMapper.selectById(oqc.getId()); |
| | | assertEquals(MesQcStatusEnum.FINISHED.getStatus(), updatedOqc.getStatus()); |
| | | // æè¨ï¼éªè¯ååéå®åºåºåè¡ |
| | | assertEquals(20, updatedOqc.getWorkflowStatus()); |
| | | // 忣宿䏿¾è¡ï¼éå®é¨åå
¬å¸è´¨æ£å审æ¹åæååã |
| | | verify(productSalesLineService, never()).updateProductSalesLineWhenOqcFinish(anyLong(), anyLong(), anyInt()); |
| | | oqcService.salesApprove(oqc.getId(), true, "éå®é¨åæ"); |
| | | oqcService.companyApprove(oqc.getId(), true, "å
¬å¸è´¨æ£åæ"); |
| | | verify(productSalesLineService).updateProductSalesLineWhenOqcFinish( |
| | | eq(sourceLineId), eq(oqc.getId()), eq(checkResult)); |
| | | } |
| | | |
| | | @Test |
| | | public void testFailedInitialRetestPassOnlyReleasesAfterBothApprovals() { |
| | | Long sourceLineId = randomLongId(); |
| | | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| | | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | | o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); |
| | | o.setSourceLineId(sourceLineId); |
| | | o.setOutQuantity(java.math.BigDecimal.valueOf(100)); |
| | | o.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | o.setQualifiedQuantity(java.math.BigDecimal.valueOf(9)); |
| | | o.setUnqualifiedQuantity(java.math.BigDecimal.ONE); |
| | | o.setCheckResult(2); |
| | | }); |
| | | oqcMapper.insert(oqc); |
| | | oqcService.finishOqc(oqc.getId()); |
| | | assertEquals(10, oqcMapper.selectById(oqc.getId()).getWorkflowStatus()); |
| | | verifyNoInteractions(ncrService); |
| | | verify(productSalesLineService, never()).updateProductSalesLineWhenOqcFinish(anyLong(), anyLong(), anyInt()); |
| | | |
| | | MesQcOqcRetestReqVO retest = new MesQcOqcRetestReqVO(); |
| | | retest.setId(oqc.getId()); |
| | | retest.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | retest.setQualifiedQuantity(java.math.BigDecimal.TEN); |
| | | retest.setUnqualifiedQuantity(java.math.BigDecimal.ZERO); |
| | | retest.setCheckResult(1); |
| | | oqcService.retestOqc(retest); |
| | | assertEquals(20, oqcMapper.selectById(oqc.getId()).getWorkflowStatus()); |
| | | oqcService.salesApprove(oqc.getId(), true, "éå®é¨éè¿"); |
| | | assertEquals(30, oqcMapper.selectById(oqc.getId()).getWorkflowStatus()); |
| | | verify(productSalesLineService, never()).updateProductSalesLineWhenOqcFinish(anyLong(), anyLong(), anyInt()); |
| | | oqcService.companyApprove(oqc.getId(), true, "å
¬å¸è´¨æ£éè¿"); |
| | | assertEquals(40, oqcMapper.selectById(oqc.getId()).getWorkflowStatus()); |
| | | verify(productSalesLineService).updateProductSalesLineWhenOqcFinish(sourceLineId, oqc.getId(), 1); |
| | | verifyNoInteractions(ncrService); |
| | | } |
| | | |
| | | @Test |
| | | public void testFailedRetestCreatesNcrOnlyAfterCompanyApproval() { |
| | | Long sourceLineId = randomLongId(); |
| | | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| | | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | | o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); |
| | | o.setSourceLineId(sourceLineId); |
| | | o.setOutQuantity(java.math.BigDecimal.valueOf(100)); |
| | | o.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | o.setQualifiedQuantity(java.math.BigDecimal.valueOf(9)); |
| | | o.setUnqualifiedQuantity(java.math.BigDecimal.ONE); |
| | | o.setCheckResult(2); |
| | | }); |
| | | oqcMapper.insert(oqc); |
| | | oqcService.finishOqc(oqc.getId()); |
| | | MesQcOqcRetestReqVO retest = new MesQcOqcRetestReqVO(); |
| | | retest.setId(oqc.getId()); |
| | | retest.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | retest.setQualifiedQuantity(java.math.BigDecimal.valueOf(8)); |
| | | retest.setUnqualifiedQuantity(java.math.BigDecimal.valueOf(2)); |
| | | retest.setCheckResult(2); |
| | | retest.setOpinion("夿£ä»ä¸åæ ¼"); |
| | | oqcService.retestOqc(retest); |
| | | assertEquals(50, oqcMapper.selectById(oqc.getId()).getWorkflowStatus()); |
| | | verifyNoInteractions(ncrService); |
| | | oqcService.salesApprove(oqc.getId(), true, "éå®é¨ç¡®è®¤ä¸åæ ¼"); |
| | | assertEquals(60, oqcMapper.selectById(oqc.getId()).getWorkflowStatus()); |
| | | verifyNoInteractions(ncrService); |
| | | oqcService.companyApprove(oqc.getId(), true, "å
¬å¸è´¨æ£ç¡®è®¤ä¸åæ ¼"); |
| | | assertEquals(70, oqcMapper.selectById(oqc.getId()).getWorkflowStatus()); |
| | | verify(ncrService).createNcr(any()); |
| | | verify(productSalesLineService).updateProductSalesLineWhenOqcFinish(sourceLineId, oqc.getId(), 2); |
| | | } |
| | | |
| | | @Test |
| | |
| | | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| | | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | | o.setCheckResult(1); |
| | | o.setUnqualifiedQuantity(java.math.BigDecimal.ZERO); |
| | | o.setQualifiedQuantity(java.math.BigDecimal.TEN); |
| | | o.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | o.setSourceDocType(null); |
| | | o.setSourceLineId(null); |
| | | }); |
| | |
| | | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| | | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | | o.setCheckResult(1); |
| | | o.setUnqualifiedQuantity(java.math.BigDecimal.ZERO); |
| | | o.setQualifiedQuantity(java.math.BigDecimal.TEN); |
| | | o.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | o.setSourceDocType(MesBizTypeConstants.WM_PRODUCT_SALES); |
| | | o.setSourceLineId(null); // èæ°æ® |
| | | }); |
| | | oqcMapper.insert(oqc); |
| | | |
| | | // è°ç¨ï¼å¹¶æè¨å¼å¸¸ |
| | | assertThrows(IllegalArgumentException.class, () -> oqcService.finishOqc(oqc.getId())); |
| | | oqcService.finishOqc(oqc.getId()); |
| | | oqcService.salesApprove(oqc.getId(), true, "éå®é¨åæ"); |
| | | assertThrows(IllegalArgumentException.class, () -> oqcService.companyApprove(oqc.getId(), true, "å
¬å¸è´¨æ£åæ")); |
| | | } |
| | | |
| | | @Test |
| | |
| | | MesQcOqcDO oqc = randomPojo(MesQcOqcDO.class, o -> { |
| | | o.setStatus(MesQcStatusEnum.DRAFT.getStatus()); |
| | | o.setCheckResult(1); |
| | | o.setUnqualifiedQuantity(java.math.BigDecimal.ZERO); |
| | | o.setQualifiedQuantity(java.math.BigDecimal.TEN); |
| | | o.setCheckQuantity(java.math.BigDecimal.TEN); |
| | | o.setSourceDocType(99999); // æ æ³è¯å«çç±»å |
| | | o.setSourceLineId(randomLongId()); |
| | | }); |
| | | oqcMapper.insert(oqc); |
| | | |
| | | // è°ç¨ï¼å¹¶æè¨å¼å¸¸ |
| | | assertThrows(IllegalArgumentException.class, () -> oqcService.finishOqc(oqc.getId())); |
| | | oqcService.finishOqc(oqc.getId()); |
| | | oqcService.salesApprove(oqc.getId(), true, "éå®é¨åæ"); |
| | | assertThrows(IllegalArgumentException.class, () -> oqcService.companyApprove(oqc.getId(), true, "å
¬å¸è´¨æ£åæ")); |
| | | } |
| | | |
| | | @Test |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.service.qc.seedquality; |
| | | |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality.MesQcSeedQualityActionMapper; |
| | | import cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality.MesQcSeedQualityMapper; |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService; |
| | | import org.junit.jupiter.api.BeforeEach; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.mockito.ArgumentCaptor; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class SeedQualityWorkflowTest { |
| | | private final MesQcSeedQualityServiceImpl service = new MesQcSeedQualityServiceImpl(); |
| | | private final MesQcSeedQualityMapper qualityMapper = mock(MesQcSeedQualityMapper.class); |
| | | private final MesQcSeedQualityActionMapper actionMapper = mock(MesQcSeedQualityActionMapper.class); |
| | | private final MesWmTransactionService transactionService = mock(MesWmTransactionService.class); |
| | | |
| | | @BeforeEach |
| | | void setUp() { |
| | | ReflectionTestUtils.setField(service, "qualityMapper", qualityMapper); |
| | | ReflectionTestUtils.setField(service, "actionMapper", actionMapper); |
| | | ReflectionTestUtils.setField(service, "wmTransactionService", transactionService); |
| | | } |
| | | |
| | | @Test |
| | | void failedRetestNeedsBothApprovalsBeforeDisposal() { |
| | | when(qualityMapper.selectByIdForUpdate(1L)).thenReturn( |
| | | MesQcSeedQualityDO.builder().id(1L).code("B1").result(2).workflowStatus(50).build(), |
| | | MesQcSeedQualityDO.builder().id(1L).code("B1").result(2).workflowStatus(60).build()); |
| | | service.processApprove(1L, true, "确认ä¸åæ ¼ï¼éå
¬å¸è´¨æ£å®¡æ¹"); |
| | | verifyNoInteractions(transactionService); |
| | | service.companyApprove(1L, false, "è¿å夿£"); |
| | | ArgumentCaptor<MesQcSeedQualityDO> update = ArgumentCaptor.forClass(MesQcSeedQualityDO.class); |
| | | verify(qualityMapper, times(2)).updateById(update.capture()); |
| | | assertEquals(60, update.getAllValues().get(0).getWorkflowStatus()); |
| | | assertEquals(10, update.getAllValues().get(1).getWorkflowStatus()); |
| | | verifyNoInteractions(transactionService); |
| | | } |
| | | |
| | | @Test |
| | | void skipsApprovalWhenStatusChanged() { |
| | | when(qualityMapper.selectByIdForUpdate(1L)).thenReturn( |
| | | MesQcSeedQualityDO.builder().id(1L).workflowStatus(40).build()); |
| | | assertThrows(IllegalArgumentException.class, () -> service.companyApprove(1L, true, "éå¤å®¡æ¹")); |
| | | verify(qualityMapper, never()).updateById(any(MesQcSeedQualityDO.class)); |
| | | verifyNoInteractions(transactionService, actionMapper); |
| | | } |
| | | } |
| | |
| | | "inspect_date" timestamp DEFAULT NULL, |
| | | "inspector_user_id" bigint DEFAULT NULL, |
| | | "status" tinyint NOT NULL DEFAULT 0, |
| | | "workflow_status" tinyint NOT NULL DEFAULT 0, |
| | | "remark" varchar(500) DEFAULT '', |
| | | "creator" varchar(64) DEFAULT '', |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | |
| | | PRIMARY KEY ("id") |
| | | ); |
| | | |
| | | CREATE TABLE IF NOT EXISTS "mes_qc_oqc_action" ( |
| | | "id" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY, |
| | | "oqc_id" bigint NOT NULL, |
| | | "action" varchar(32) NOT NULL, |
| | | "result" tinyint DEFAULT NULL, |
| | | "opinion" varchar(500) DEFAULT NULL, |
| | | "quantities_json" varchar(4000) DEFAULT NULL, |
| | | "operator_id" bigint DEFAULT NULL, |
| | | "create_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, |
| | | PRIMARY KEY ("id") |
| | | ); |
| | | |
| | | -- ---------------------------- |
| | | -- MES éè´§æ£éªåï¼RQCï¼ |
| | | -- ---------------------------- |