feat(aftersales): 增加售后退货流程管理功能
- 新增确认外部退款/换货完成接口和取消退货申请接口
- 扩展退货单数据对象增加外部退款流水号字段
- 实现退货申请状态流转控制和业务逻辑验证
- 添加MES系统退货单取消同步处理机制
- 优化工单与退货关联状态管理
- 增强退货流程权限控制和数据校验规则
- 添加API访问日志敏感参数过滤功能
- 更新CRM回款模块审批流程集成BPM工作流
- 修正数据库查询条件和本地环境配置参数
- 添加相关错误码定义和文档资源文件更新
| | |
| | | *.iml |
| | | *.ipr |
| | | *.sql |
| | | !sql/mysql/xgdl_receivable_workflow_seed.sql |
| | | !sql/mysql/xgdl_receivable_legacy_repair_20261024.sql |
| | | !sql/mysql/xgdl_aftersales_return_link_repair_20261024.sql |
| | | !sql/mysql/xgdl_aftersales_refund_confirm_20261024.sql |
| | | !sql/mysql/hrm_attendance_location_20261024.sql |
| | | *.md |
| | | !docs/xgdl-business-flow-alignment-20261024.md |
| | | *.xlsx |
| | | *.docx |
| | | *.txt |
| | |
| | | .claude/worktrees/ |
| | | .claude/scheduled_tasks.json |
| | | |
| | | /.export_tmp |
| | | /.export_tmp |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | # æ°é«çµåå
±æ§ä¸å¡æµç¨å¯¹ç
§ä¸æ¬è½®ä¿®å¤ |
| | | |
| | | 便®ç¨æ·æä¾çä¸å¡éæ±æªå¾å `mom-pro2-after-test/docs/project-business` æµç¨å¾æ ¸å¯¹å½åå®ç°ãæµç¨å¾åªä½ä¸ºéæ±èµæï¼ä»¥ä»£ç å `mom-xgdl` å®é
æ°æ®ä¸ºåãAI ç¸å
³è½åä¸çº³å
¥æ¬è½®ã |
| | | |
| | | | éæ±å | å
±æ§æµç¨å¤ç | æ¬è½®ç»è®º | |
| | | | --- | --- | --- | |
| | | | 产å设计ãå·¥èºè®¾è®¡ | äº§åæ¡£æ¡ãå·¥èºè·¯çº¿å±äºç°æåºç¡è½å | çµå产åç¼ç ãçµåç级ãä¾çµ/åçµè¿ç»´å·¥èºå±äºå®å¶éæ±ï¼æ¬è½®æªå¼å
¥éç¨æ¨¡å | |
| | | | è¥é管ç | ååãéå®è®¢åãå¼ç¥¨ã忬¾ | åä¸è½®ä¿®å¤å款æäº¤ BPM ä¸ç¶æååï¼å¹¶è¡¥å¿
è¦å®¡æ¹é
ç½®ï¼æ¬è½®ä¸é夿¹å¨ | |
| | | | å®åæå¡ | å·¥å â éè´§ç³è¯· â MES éå®éè´§ â RQC â å
¥åº â 鿬¾/æ¢è´§ç¡®è®¤ â å·¥åå
³é | ä¿®å¤ MES 忮头ä¸è¡å
³è丢失ãè´¨æ£/å
¥åºåå缺å£ï¼è¡¥ç¶ææ ¡éªãåæ¶åéååè¯ç¡®è®¤å
¥å£ | |
| | | | 计åæç¨ãçäº§ç®¡æ§ | MPSãå·¥åãä»»å¡ãæ¥å·¥ | åä¸è½®ä¿®å¤æ¥å·¥åçä»»å¡ç¶æä¸æå/æ¢å¤ï¼è¡ä¸è°åº¦ç®æ³æªçº³å
¥å
±æ§æ¹å¨ | |
| | | | è´¨é管ç | RQC è´¨æ£ç»æååå®å | MES è´¨æ£å®æäºä»¶ç°å¨æ¨è¿å®åç¶æï¼è´¨æ£åä»ç±è´¨æ£äººåæç°ææ¨¡æ¿æµç¨å建å宿ï¼ä¸èªå¨ä¼ªé æ£éªæ°æ® | |
| | | | ä»å¨ç©æµ | éå®éè´§ä¸æ¶äº§çå
¥åºäºå¡ | å
¥åºå®æäºä»¶ç°å¨æ¨è¿å®åå°å¾
鿬¾ï¼åå²å
³èä»
å¨ ID ä¸åå·åéä¸è´æ¶åå¡« | |
| | | | 设å¤ãéè´ã人åèµæºãå³çæ¯æ | ç°æéç¨æ¨¡å | æ¬è½®æªæ¹å
¶ä¸å¡è§åï¼æªå¾ä¸ççµç½èµäº§ãæ¢ä¿®ãè´è·é¢æµçåºæ¯éå¦è¡å®ä¹å段åéªæ¶æ å | |
| | | |
| | | ## å·²è½å°çå®åç¶æé¾ |
| | | |
| | | 1. å®åå·¥åå¤äºâå¤çä¸âï¼æéå®è®¢åã客æ·åææç©ææç»æ¶å建éè´§ç³è¯·ï¼é¡µé¢å¯å¡«åæ¬æ¬¡ç³è¯·æ°éå¹¶æ ¡éªè®¢åæç»ï¼åä¸å·¥ååªå
许ä¸ä¸ªæªåæ¶ç³è¯·ã |
| | | 2. å®¡æ ¸æ®ééè´§/æ¢è´§æ¶ï¼MES 忮头åæç»å¨åä¸äºå¡å建ï¼ä»
鿬¾ç³è¯·ä¸äº§çå®ç©å
¥åºåã |
| | | 3. MES éå®éè´§æäº¤åæç°æ RQC æ¨¡æ¿æ§è¡æ£éªï¼å
¨é¨æ£å®ååâæ£éªå®æâï¼å®æä¸æ¶ååâå·²å
¥åºâã |
| | | 4. è´¢å¡å¨å¤é¨å®æéæ¬¾ææ¢è´§åï¼è¾å
¥å®é
éé¢åå¯è¿½æº¯åè¯å·ç¡®è®¤ï¼å®åç³è¯·å为â鿬¾/æ¢è´§å®æâï¼å·¥åå为âå·²å®ç»âã |
| | | 5. å·¥åå
³é忥å
³éç³è¯·ãå
¥åºååæ¶éè´§ä¼åæ¥åæ¶ MES åæ®ãæ¢å¤å·¥åâå¤çä¸âï¼å
è®¸éæ°ç³è¯·ã |
| | | |
| | | MES æäº¤ãæ£éªæ¨è¿ãåæ¶ä¸ä¸æ¶éç¨å½åç¶ææ¡ä»¶æ´æ°ï¼å¹¶åéå¤ä¸æ¶åªæä¸ä¸ªè¯·æ±å¯ååºåæµæ°´ã |
| | | |
| | | ## é
ç½®ä¸éªæ¶è¾¹ç |
| | | |
| | | - `mom-xgdl` å·²éªè¯åå¨å½ååå²éè´§ç©æç RQC 模æ¿å
³èï¼æ²¡æä¸ºå
¶ä»ç©æèé è´¨æ£æ åãæ°ç©æé¡»å
é
ç½®éç¨çéè´§æ£éªæ¨¡æ¿ã |
| | | - å·²æ ¸å¯¹æ¬å°åºæ 8 个ä»åºã13 个åºåºã13 个åºä½ã4 å¥å¯ç¨è´¨æ£æ¨¡æ¿å 47 æ¡ç©æå
³èã6 æ¡å·¥èºè·¯çº¿å 39 æ¡ BPM æµç¨å®ä¹ï¼åºç¡æ°æ®å¹¶éæ´ä½ç¼ºå¤±ï¼å
·ä½ç©æä¸å
·ä½åæ®ä»ééåæ ¡éªã |
| | | - 鿬¾/æ¢è´§ç¡®è®¤éç¨äººå·¥åè¯ç»è®°ï¼å½åä»£ç æ²¡æå¯ç´æ¥å
³èå®åç³è¯·ç ERP 鿬¾åèªå¨åè°ã确认æé®åªè¡¨ç¤ºå¤é¨ä¸å¡å·²å®æï¼ä¸æ§è¡è½¬è´¦ã |
| | | - æ¬è½® SQL ä½äº `sql/mysql/xgdl_aftersales_return_link_repair_20261024.sql` å `sql/mysql/xgdl_aftersales_refund_confirm_20261024.sql`ãå¨å
¶ä»ç¯å¢é¨ç½²ä»£ç åé¡»å
æ§è¡åæ®µä¸æéèæ¬ã |
| | | - Maven ç¼è¯éè¿ï¼å端å
¨éç±»åæ£æ¥ä»åå¨ä¸æ¬æ¬¡å®åæä»¶æ å
³çæ¢æé误ãå°æªå®ææµè§å¨ä¸çæ´é¾è·¯ä¸å¡éªæ¶ã |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- èå¤å®ä½å段è¿ç§»ãæ§è§åä¿æ location_required=0ï¼å
é
ç½®å°ç¹åå¯ç¨èå´éå¶ã |
| | | DELIMITER $$ |
| | | DROP PROCEDURE IF EXISTS xgdl_hrm_attendance_add_column_20261024$$ |
| | | CREATE PROCEDURE xgdl_hrm_attendance_add_column_20261024( |
| | | IN p_table_name VARCHAR(64), IN p_column_name VARCHAR(64), IN p_ddl_sql VARCHAR(1000)) |
| | | BEGIN |
| | | IF NOT EXISTS (SELECT 1 FROM information_schema.COLUMNS |
| | | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = p_table_name AND COLUMN_NAME = p_column_name) THEN |
| | | SET @hrm_location_ddl = p_ddl_sql; |
| | | PREPARE hrm_location_stmt FROM @hrm_location_ddl; |
| | | EXECUTE hrm_location_stmt; |
| | | DEALLOCATE PREPARE hrm_location_stmt; |
| | | END IF; |
| | | END$$ |
| | | DELIMITER ; |
| | | |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_rule','location_name', |
| | | 'ALTER TABLE hrm_attendance_rule ADD COLUMN location_name varchar(100) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_rule','location_address', |
| | | 'ALTER TABLE hrm_attendance_rule ADD COLUMN location_address varchar(255) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_rule','longitude', |
| | | 'ALTER TABLE hrm_attendance_rule ADD COLUMN longitude decimal(10,7) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_rule','latitude', |
| | | 'ALTER TABLE hrm_attendance_rule ADD COLUMN latitude decimal(10,7) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_rule','radius_meters', |
| | | 'ALTER TABLE hrm_attendance_rule ADD COLUMN radius_meters int DEFAULT 200'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_rule','location_required', |
| | | 'ALTER TABLE hrm_attendance_rule ADD COLUMN location_required bit(1) NOT NULL DEFAULT b''0'''); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_rule','coordinate_system', |
| | | 'ALTER TABLE hrm_attendance_rule ADD COLUMN coordinate_system varchar(20) DEFAULT ''WGS84'''); |
| | | |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_in_longitude', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_in_longitude decimal(10,7) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_in_latitude', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_in_latitude decimal(10,7) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_in_accuracy', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_in_accuracy decimal(8,2) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_in_distance', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_in_distance decimal(10,2) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_in_address', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_in_address varchar(255) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_out_longitude', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_out_longitude decimal(10,7) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_out_latitude', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_out_latitude decimal(10,7) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_out_accuracy', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_out_accuracy decimal(8,2) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_out_distance', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_out_distance decimal(10,2) NULL'); |
| | | CALL xgdl_hrm_attendance_add_column_20261024('hrm_attendance_record','clock_out_address', |
| | | 'ALTER TABLE hrm_attendance_record ADD COLUMN clock_out_address varchar(255) NULL'); |
| | | |
| | | DROP PROCEDURE xgdl_hrm_attendance_add_column_20261024; |
| | |
| | | `late_allow_minutes` tinyint DEFAULT 0 COMMENT 'è¿å°å
许åéæ°', |
| | | `early_allow_minutes` tinyint DEFAULT 0 COMMENT 'æ©éå
许åéæ°', |
| | | `overtime_threshold` decimal(4,2) DEFAULT 1 COMMENT 'å çèµ·ç®æ¶é¿ï¼å°æ¶ï¼', |
| | | `location_name` varchar(100) DEFAULT NULL COMMENT 'æå¡å°ç¹åç§°', |
| | | `location_address` varchar(255) DEFAULT NULL COMMENT 'æå¡å°ç¹è¯¦ç»å°å', |
| | | `longitude` decimal(10,7) DEFAULT NULL COMMENT 'æå¡å°ç¹ç»åº¦ï¼WGS84ï¼', |
| | | `latitude` decimal(10,7) DEFAULT NULL COMMENT 'æå¡å°ç¹çº¬åº¦ï¼WGS84ï¼', |
| | | `radius_meters` int DEFAULT 200 COMMENT 'å
许æå¡åå¾ï¼ç±³ï¼', |
| | | `location_required` bit(1) NOT NULL DEFAULT b'0' COMMENT 'æ¯å¦å¯ç¨ä½ç½®éå¶', |
| | | `coordinate_system` varchar(20) DEFAULT 'WGS84' COMMENT 'åæ ç³»', |
| | | `status` tinyint NOT NULL DEFAULT 0 COMMENT 'ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨', |
| | | `remark` varchar(500) DEFAULT NULL COMMENT '夿³¨', |
| | | `creator` varchar(64) DEFAULT '' COMMENT 'å建è
', |
| | |
| | | `work_hours` decimal(4,2) DEFAULT NULL COMMENT '工使¶é¿ï¼å°æ¶ï¼', |
| | | `overtime_hours` decimal(4,2) DEFAULT 0 COMMENT 'å çæ¶é¿ï¼å°æ¶ï¼', |
| | | `location` varchar(200) DEFAULT NULL COMMENT 'æå¡å°ç¹', |
| | | `clock_in_longitude` decimal(10,7) DEFAULT NULL COMMENT 'ä¸çæå¡ç»åº¦ï¼WGS84ï¼', |
| | | `clock_in_latitude` decimal(10,7) DEFAULT NULL COMMENT 'ä¸çæå¡çº¬åº¦ï¼WGS84ï¼', |
| | | `clock_in_accuracy` decimal(8,2) DEFAULT NULL COMMENT 'ä¸çå®ä½ç²¾åº¦ï¼ç±³ï¼', |
| | | `clock_in_distance` decimal(10,2) DEFAULT NULL COMMENT 'ä¸çè·è§åå°ç¹è·ç¦»ï¼ç±³ï¼', |
| | | `clock_in_address` varchar(255) DEFAULT NULL COMMENT 'ä¸çè§åå°ç¹å°åå¿«ç
§', |
| | | `clock_out_longitude` decimal(10,7) DEFAULT NULL COMMENT 'ä¸çæå¡ç»åº¦ï¼WGS84ï¼', |
| | | `clock_out_latitude` decimal(10,7) DEFAULT NULL COMMENT 'ä¸çæå¡çº¬åº¦ï¼WGS84ï¼', |
| | | `clock_out_accuracy` decimal(8,2) DEFAULT NULL COMMENT 'ä¸çå®ä½ç²¾åº¦ï¼ç±³ï¼', |
| | | `clock_out_distance` decimal(10,2) DEFAULT NULL COMMENT 'ä¸çè·è§åå°ç¹è·ç¦»ï¼ç±³ï¼', |
| | | `clock_out_address` varchar(255) DEFAULT NULL COMMENT 'ä¸çè§åå°ç¹å°åå¿«ç
§', |
| | | `remark` varchar(500) DEFAULT NULL COMMENT '夿³¨', |
| | | `data_source` tinyint DEFAULT 1 COMMENT 'æ°æ®æ¥æºï¼1-æå¡ï¼2-导å
¥', |
| | | `creator` varchar(64) DEFAULT '' COMMENT 'å建è
', |
| | |
| | | -- ============================================= |
| | | -- æ§è¡å®ææç¤º |
| | | -- ============================================= |
| | | SELECT 'HRM 人åèµæºæ¨¡å SQL åå§å宿ï¼' AS result; |
| | | SELECT 'HRM 人åèµæºæ¨¡å SQL åå§å宿ï¼' AS result; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- å®å鿬¾/æ¢è´§äººå·¥ç¡®è®¤éè¦å¯è¿½æº¯åè¯åç¬ç«æéã |
| | | SET @has_refund_voucher := (SELECT COUNT(*) FROM information_schema.COLUMNS |
| | | WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'after_sale_return' AND COLUMN_NAME = 'refund_voucher_no'); |
| | | SET @ddl := IF(@has_refund_voucher = 0, |
| | | 'ALTER TABLE after_sale_return ADD COLUMN refund_voucher_no varchar(100) NULL COMMENT ''Refund or exchange voucher number'' AFTER actual_refund_price', |
| | | 'SELECT 1'); |
| | | PREPARE stmt FROM @ddl; |
| | | EXECUTE stmt; |
| | | DEALLOCATE PREPARE stmt; |
| | | |
| | | INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, |
| | | component_name, status, visible, keep_alive, always_show, creator, updater) |
| | | SELECT CONVERT(0xE7A1AEE8AEA4E98080E6ACBE2FE68DA2E8B4A7 USING utf8mb4), 'aftersales:return:refund', 3, 3, 602589, '', '#', NULL, |
| | | NULL, 0, b'1', b'0', b'0', 'system', 'system' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = 602589 AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'aftersales:return:refund' AND deleted = b'0'); |
| | | |
| | | UPDATE system_menu |
| | | SET name = CONVERT(0xE7A1AEE8AEA4E98080E6ACBE2FE68DA2E8B4A7 USING utf8mb4) |
| | | WHERE permission = 'aftersales:return:refund' AND 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, updater) |
| | | SELECT CONVERT(0xE58F96E6B688E98080E8B4A7 USING utf8mb4), 'aftersales:return:cancel', 3, 4, 602589, '', '#', NULL, |
| | | NULL, 0, b'1', b'0', b'0', 'system', 'system' |
| | | WHERE EXISTS (SELECT 1 FROM system_menu WHERE id = 602589 AND deleted = b'0') |
| | | AND NOT EXISTS (SELECT 1 FROM system_menu WHERE permission = 'aftersales:return:cancel' AND deleted = b'0'); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- ä»
ä¿®å¤å·²ç±å®åç³è¯·è®°å½äº MES åæ® IDãåå·ä¸åæ¹ä¸è´çåå²å
³èã |
| | | -- ä¸ä¿®æ¹ä¸å¡ç¶æï¼é夿§è¡ä¸ä¼äº§çæ°ååã |
| | | UPDATE mes_wm_return_sales AS mes |
| | | JOIN after_sale_return AS after_sale |
| | | ON after_sale.mes_return_id = mes.id |
| | | AND after_sale.mes_return_code = mes.code |
| | | SET mes.after_sale_return_id = after_sale.id |
| | | WHERE mes.after_sale_return_id IS NULL |
| | | AND mes.deleted = 0 |
| | | AND after_sale.deleted = 0; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- mom-xgdlï¼ä¿®å¤ç¨æ·æç¡®æ¹åçåæ¡åå²å款 HK20261016001ï¼ID 5ï¼ã |
| | | -- æ§çå建å³è¿å
¥å®¡æ¹ä¸ï¼ä½æ¤åæ å¼ç¥¨ãæ æµç¨å®ä¾ãæ BPM åå²ã |
| | | -- å·²äº 2026-10-24 æ§è¡ï¼å½±å 1 è¡ï¼é夿§è¡ä¸ä¼å次修æ¹ã |
| | | START TRANSACTION; |
| | | UPDATE crm_receivable r |
| | | SET r.audit_status = 0, r.update_time = NOW() |
| | | WHERE r.id = 5 AND r.no = 'HK20261016001' AND r.audit_status = 10 |
| | | AND r.invoice_id IS NULL |
| | | AND (r.process_instance_id IS NULL OR r.process_instance_id = '') |
| | | AND r.deleted = b'0' |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM ACT_HI_PROCINST h |
| | | WHERE h.BUSINESS_KEY_ = CAST(r.id AS CHAR) |
| | | AND h.PROC_DEF_ID_ LIKE 'crm-receivable-audit:%' |
| | | ); |
| | | COMMIT; |
| | | |
| | | SELECT id, no, audit_status, invoice_id, process_instance_id |
| | | FROM crm_receivable WHERE id = 5; |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | -- mom-xgdlï¼å款 BPM å¿
è¦é
ç½®ãä»
å¨ç°æå¼ç¥¨å®¡æ¹é
ç½®å¯ç¨æ¶å¤ç¨åä¸è´¢å¡å®¡æ¹äººã |
| | | -- å¹çæ§è¡ï¼ä¸ä¼æå
¥ä¸å¡å款ã伪é 审æ¹ç»æææ¹å¨å®å¶æµç¨å®ä¹ã |
| | | START TRANSACTION; |
| | | |
| | | INSERT INTO system_approval_config |
| | | (biz_type, biz_name, approval_enabled, remark, creator, create_time, updater, update_time, deleted) |
| | | SELECT 'crm_receivable_approve', '忬¾å®¡æ¹', i.approval_enabled, |
| | | '忬¾å®¡æ¹å¤ç¨å¼ç¥¨è´¢å¡å®¡æ¹äºº', '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_approval_config i |
| | | WHERE i.biz_type = 'crm_invoice_approve' AND i.deleted = b'0' |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM system_approval_config c |
| | | WHERE c.biz_type = 'crm_receivable_approve' AND c.deleted = b'0' |
| | | ) |
| | | LIMIT 1; |
| | | |
| | | INSERT INTO system_approval_config_user |
| | | (config_id, user_id, sort, creator, create_time, updater, update_time, deleted) |
| | | SELECT r.id, iu.user_id, iu.sort, '1', NOW(), '1', NOW(), b'0' |
| | | FROM system_approval_config r |
| | | JOIN system_approval_config i ON i.biz_type = 'crm_invoice_approve' AND i.deleted = b'0' |
| | | JOIN system_approval_config_user iu ON iu.config_id = i.id AND iu.deleted = b'0' |
| | | WHERE r.biz_type = 'crm_receivable_approve' AND r.deleted = b'0' |
| | | AND r.remark = '忬¾å®¡æ¹å¤ç¨å¼ç¥¨è´¢å¡å®¡æ¹äºº' |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM system_approval_config_user existing |
| | | WHERE existing.config_id = r.id AND existing.deleted = b'0' |
| | | ) |
| | | AND NOT EXISTS ( |
| | | SELECT 1 FROM system_approval_config_user ru |
| | | WHERE ru.config_id = r.id AND ru.user_id = iu.user_id AND ru.deleted = b'0' |
| | | ); |
| | | |
| | | COMMIT; |
| | | |
| | | -- åªè¯»æ ¸éªï¼å款æµç¨å®ä¹å审æ¹é
ç½®ååºåå¨ä¸å¯ç¨ã |
| | | SELECT KEY_, SUSPENSION_STATE_ FROM ACT_RE_PROCDEF WHERE KEY_ = 'crm-receivable-audit'; |
| | | SELECT c.biz_type, c.approval_enabled, COUNT(u.id) AS approver_count |
| | | FROM system_approval_config c |
| | | LEFT JOIN system_approval_config_user u ON u.config_id = c.id AND u.deleted = b'0' |
| | | WHERE c.biz_type = 'crm_receivable_approve' AND c.deleted = b'0' |
| | | GROUP BY c.id, c.biz_type, c.approval_enabled; |
| | |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; |
| | | import cn.iocoder.yudao.framework.common.util.spring.SpringUtils; |
| | | import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog; |
| | | import jakarta.servlet.http.HttpServletRequest; |
| | | import jakarta.servlet.http.HttpServletResponse; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | |
| | | // æå° request æ¥å¿ |
| | | if (!SpringUtils.isProd()) { |
| | | Map<String, String> queryString = ServletUtils.getParamMap(request); |
| | | String requestBody = ServletUtils.getBody(request); |
| | | if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) { |
| | | log.info("[preHandle][å¼å§è¯·æ± URL({}) æ åæ°]", request.getRequestURI()); |
| | | ApiAccessLog accessLog = handlerMethod != null ? handlerMethod.getMethodAnnotation(ApiAccessLog.class) : null; |
| | | if (accessLog != null && !accessLog.requestEnable()) { |
| | | log.info("[preHandle][å¼å§è¯·æ± URL({}) ææè¯·æ±åæ°å·²éè]", request.getRequestURI()); |
| | | } else { |
| | | log.info("[preHandle][å¼å§è¯·æ± URL({}) åæ°({})]", request.getRequestURI(), |
| | | StrUtil.blankToDefault(requestBody, queryString.toString())); |
| | | Map<String, String> queryString = ServletUtils.getParamMap(request); |
| | | String requestBody = ServletUtils.getBody(request); |
| | | if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) { |
| | | log.info("[preHandle][å¼å§è¯·æ± URL({}) æ åæ°]", request.getRequestURI()); |
| | | } else { |
| | | log.info("[preHandle][å¼å§è¯·æ± URL({}) åæ°({})]", request.getRequestURI(), |
| | | StrUtil.blankToDefault(requestBody, queryString.toString())); |
| | | } |
| | | } |
| | | // è®¡æ¶ |
| | | StopWatch stopWatch = new StopWatch(); |
| | |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnRespVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleReturnSaveReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo.AfterSaleRefundConfirmReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnItemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; |
| | |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/complete-refund") |
| | | @Operation(summary = "确认å¤é¨é款/æ¢è´§å®æ") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:return:refund')") |
| | | public CommonResult<Boolean> completeRefund(@Valid @RequestBody AfterSaleRefundConfirmReqVO reqVO) { |
| | | returnService.confirmRefundCompleted(reqVO.getId(), reqVO.getActualRefundPrice(), reqVO.getVoucherNo()); |
| | | return success(true); |
| | | } |
| | | |
| | | @PutMapping("/cancel") |
| | | @Operation(summary = "åæ¶å°æªå
¥åºæé款çéè´§ç³è¯·") |
| | | @PreAuthorize("@ss.hasPermission('aftersales:return:cancel')") |
| | | public CommonResult<Boolean> cancelReturn(@RequestParam("id") Long id) { |
| | | returnService.cancelReturn(id); |
| | | return success(true); |
| | | } |
| | | |
| | | @GetMapping("/get") |
| | | @Operation(summary = "è·å¾éè´§ç³è¯·") |
| | | @Parameter(name = "id", description = "éè´§ç³è¯·ç¼å·", required = true, example = "1") |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.controller.admin.returnobj.vo; |
| | | |
| | | import jakarta.validation.constraints.DecimalMin; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import jakarta.validation.constraints.Size; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** å¤é¨é款/æ¢è´§å®æç¡®è®¤ï¼å¿
é¡»æä¾å¯è¿½æº¯çä¸å¡åè¯ã */ |
| | | @Data |
| | | public class AfterSaleRefundConfirmReqVO { |
| | | @NotNull(message = "éè´§ç³è¯· ID ä¸è½ä¸ºç©º") |
| | | private Long id; |
| | | @NotNull(message = "å®é
鿬¾éé¢ä¸è½ä¸ºç©ºï¼æ¢è´§å¯å¡« 0") |
| | | @DecimalMin(value = "0", message = "å®é
鿬¾éé¢ä¸è½ä¸ºè´æ°") |
| | | private BigDecimal actualRefundPrice; |
| | | @NotBlank(message = "鿬¾æµæ°´å·ææ¢è´§åå·ä¸è½ä¸ºç©º") |
| | | @Size(max = 100, message = "åè¯å·ä¸è½è¶
è¿ 100 个å符") |
| | | private String voucherNo; |
| | | } |
| | |
| | | |
| | | @Schema(description = "å®ééé¢", example = "950.00") |
| | | private BigDecimal actualRefundPrice; |
| | | private String refundVoucherNo; |
| | | |
| | | @Schema(description = "ç¶æ", example = "10") |
| | | private Integer status; |
| | |
| | | * å®ééé¢ |
| | | */ |
| | | private BigDecimal actualRefundPrice; |
| | | /** å¤é¨éæ¬¾æµæ°´å·ææ¢è´§åå·ï¼ä¾äººå·¥å®æç¡®è®¤æ¶è¿½æº¯ã */ |
| | | private String refundVoucherNo; |
| | | /** |
| | | * éè´§ç¶æ |
| | | * |
| | |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleReturnStatusEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum; |
| | | import cn.iocoder.yudao.module.aftersales.util.AfterSalePermissionUtils; |
| | | import org.apache.ibatis.annotations.Mapper; |
| | |
| | | return selectOne(AfterSaleReturnDO::getTicketId, ticketId); |
| | | } |
| | | |
| | | default boolean existsActiveByTicketId(Long ticketId) { |
| | | return selectCount(new LambdaQueryWrapperX<AfterSaleReturnDO>() |
| | | .eq(AfterSaleReturnDO::getTicketId, ticketId) |
| | | .ne(AfterSaleReturnDO::getStatus, AfterSaleReturnStatusEnum.CANCELED.getStatus())) > 0; |
| | | } |
| | | |
| | | default PageResult<AfterSaleReturnDO> selectPage(PageParam pageParam, Long userId, Integer sceneType, |
| | | String no, Long ticketId, |
| | | Long customerId, Integer status) { |
| | |
| | | ErrorCode RETURN_ITEM_EMPTY = new ErrorCode(1_022_002_003, "éè´§æç»è¡ä¸è½ä¸ºç©º"); |
| | | ErrorCode RETURN_DELETE_FAIL_NOT_DRAFT = new ErrorCode(1_022_002_004, "éè´§ç³è¯·ä¸æ¯èç¨¿ç¶æï¼æ æ³å é¤"); |
| | | ErrorCode RETURN_TICKET_SALE_ORDER_REQUIRED = new ErrorCode(1_022_002_005, "å建éè´§ç³è¯·å¤±è´¥ï¼åå ï¼å®åå·¥åæªå
³èéå®è®¢å"); |
| | | ErrorCode RETURN_TICKET_INVALID = new ErrorCode(1_022_002_006, "éè´§ç³è¯·ä¸å·¥åçå®¢æ·æç¶æä¸ä¸è´ï¼æè¯¥å·¥åå·²æéè´§ç³è¯·"); |
| | | ErrorCode RETURN_ITEM_INVALID = new ErrorCode(1_022_002_007, "éè´§æç»ä¸å·¥åç©æä¸ä¸è´ï¼æéè´§æ°éãé颿 æ"); |
| | | ErrorCode RETURN_STATUS_ERROR = new ErrorCode(1_022_002_008, "éè´§ç³è¯·å½åç¶æä¸å
è®¸æ¤æä½"); |
| | | ErrorCode RETURN_TICKET_NOT_FINISHED = new ErrorCode(1_022_002_009, "éè´§å°æªå®æå
¥åºå鿬¾/æ¢è´§ï¼å·¥åä¸è½å®ç»"); |
| | | ErrorCode RETURN_REFUND_VOUCHER_REQUIRED = new ErrorCode(1_022_002_010, "请填å鿬¾æµæ°´å·ææ¢è´§åå·"); |
| | | ErrorCode RETURN_REFUND_EXCEEDS_TOTAL = new ErrorCode(1_022_002_011, "å®é
鿬¾éé¢ä¸è½è¶
è¿åºéæ»éé¢"); |
| | | |
| | | // ========== ç»´ä¿®è®°å½ 1-022-003-000 ========== |
| | | ErrorCode REPAIR_NOT_EXISTS = new ErrorCode(1_022_003_000, "维修记å½ä¸åå¨"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.aftersales.service.returnobj; |
| | | |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnMapper; |
| | | import cn.iocoder.yudao.module.mes.api.event.MesReturnSalesProgressEvent; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.event.EventListener; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** å° MES è´¨æ£ãå
¥åºç»æåæ¥å°å®åç³è¯·ã忥çå¬ä¿è¯ä»»ä¸æ¥å¤±è´¥æ¶æ´ä½åæ»ã */ |
| | | @Component |
| | | public class AfterSaleMesReturnProgressListener { |
| | | |
| | | @Resource |
| | | private AfterSaleReturnMapper returnMapper; |
| | | @Resource |
| | | private AfterSaleReturnService returnService; |
| | | |
| | | @EventListener |
| | | public void onProgress(MesReturnSalesProgressEvent event) { |
| | | AfterSaleReturnDO returnDO = returnMapper.selectByMesReturnId(event.mesReturnId()); |
| | | if (returnDO == null) { |
| | | return; |
| | | } |
| | | if (event.stage() == MesReturnSalesProgressEvent.Stage.QUALITY_FINISHED) { |
| | | returnService.onQualityFinished(returnDO.getId(), event.qualityResult(), null); |
| | | } else if (event.stage() == MesReturnSalesProgressEvent.Stage.INBOUND_FINISHED) { |
| | | returnService.onInboundFinished(returnDO.getId()); |
| | | } else if (event.stage() == MesReturnSalesProgressEvent.Stage.CANCELED) { |
| | | returnService.onMesCanceled(returnDO.getId()); |
| | | } |
| | | } |
| | | } |
| | |
| | | */ |
| | | void approveReturn(Long id); |
| | | |
| | | /** åæ¶å°æªå
¥åºæé款çç³è¯·ã */ |
| | | void cancelReturn(Long id); |
| | | |
| | | /** MES ä¾§åæ¶æ¶åæ¥åéå®åå·¥åã */ |
| | | void onMesCanceled(Long returnId); |
| | | |
| | | /** |
| | | * è·å¾éè´§ç³è¯· |
| | | * |
| | |
| | | */ |
| | | void onRefundCompleted(Long returnId, BigDecimal actualRefundPrice); |
| | | |
| | | /** 确认å¤é¨éæ¬¾ææ¢è´§å·²å®æï¼åè¯å·å¿
å¡«ã */ |
| | | void confirmRefundCompleted(Long returnId, BigDecimal actualRefundPrice, String voucherNo); |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnItemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnItemMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketItemMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.redis.no.AfterSaleNoRedisDAO; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleBizTypeEnum; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleReturnStatusEnum; |
| | |
| | | import java.time.LocalDateTime; |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.Objects; |
| | | import java.util.HashSet; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.aftersales.enums.ErrorCodeConstants.*; |
| | |
| | | private AfterSaleReturnItemMapper returnItemMapper; |
| | | @Resource |
| | | private AfterSaleTicketMapper ticketMapper; |
| | | @Resource |
| | | private AfterSaleTicketItemMapper ticketItemMapper; |
| | | |
| | | @Resource |
| | | private AfterSaleNoRedisDAO noRedisDAO; |
| | |
| | | } |
| | | if (ticket.getSaleOrderId() == null) { |
| | | throw exception(RETURN_TICKET_SALE_ORDER_REQUIRED); |
| | | } |
| | | if (!AfterSaleTicketStatusEnum.PROCESSING.getStatus().equals(ticket.getStatus()) |
| | | || !Objects.equals(ticket.getCustomerId(), createReqVO.getCustomerId()) |
| | | || ticket.getReturnId() != null || returnMapper.existsActiveByTicketId(ticket.getId())) { |
| | | throw exception(RETURN_TICKET_INVALID); |
| | | } |
| | | if (CollUtil.isEmpty(createReqVO.getItems())) { |
| | | throw exception(RETURN_ITEM_EMPTY); |
| | | } |
| | | if (createReqVO.getReturnType() == null || !List.of(1, 2, 3).contains(createReqVO.getReturnType())) { |
| | | throw exception(RETURN_ITEM_INVALID); |
| | | } |
| | | Map<Long, AfterSaleTicketItemDO> ticketItems = ticketItemMapper.selectListByTicketId(ticket.getId()) |
| | | .stream().collect(Collectors.toMap(AfterSaleTicketItemDO::getId, Function.identity())); |
| | | Set<Long> usedItems = new HashSet<>(); |
| | | for (AfterSaleReturnSaveReqVO.Item item : createReqVO.getItems()) { |
| | | AfterSaleTicketItemDO ticketItem = ticketItems.get(item.getTicketItemId()); |
| | | if (ticketItem == null || !usedItems.add(item.getTicketItemId()) |
| | | || item.getItemId() == null || !item.getItemId().equals(ticketItem.getItemId()) |
| | | || (!Integer.valueOf(3).equals(createReqVO.getReturnType()) |
| | | && !Boolean.TRUE.equals(ticketItem.getNeedReturn())) |
| | | || (ticketItem.getBatchId() != null && !Objects.equals(item.getBatchId(), ticketItem.getBatchId())) |
| | | || item.getReturnQuantity() == null || item.getReturnQuantity().signum() <= 0 |
| | | || ticketItem.getSaleQuantity() == null |
| | | || item.getReturnQuantity().compareTo(ticketItem.getSaleQuantity()) > 0 |
| | | || (ticketItem.getReturnQuantity() != null && ticketItem.getReturnQuantity().signum() > 0 |
| | | && item.getReturnQuantity().compareTo(ticketItem.getReturnQuantity()) > 0) |
| | | || item.getUnitPrice() == null || item.getUnitPrice().signum() < 0 |
| | | || item.getReturnPrice() == null || item.getReturnPrice().signum() < 0) { |
| | | throw exception(RETURN_ITEM_INVALID); |
| | | } |
| | | } |
| | | // éå®è®¢å以工å为åï¼ä¿è¯éè´§åä¸å·¥åå
³èåä¸è®¢å |
| | | createReqVO.setSaleOrderId(ticket.getSaleOrderId()); |
| | |
| | | permissionService.createPermission(permBO); |
| | | |
| | | // 7. æ´æ°å·¥åç¶æä¸ºå¾
éè´§ |
| | | ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId()) |
| | | .setReturnId(returnDO.getId()) |
| | | .setStatus(AfterSaleTicketStatusEnum.WAITING_RETURN.getStatus())); |
| | | int ticketUpdated = ticketMapper.update(null, new LambdaUpdateWrapper<AfterSaleTicketDO>() |
| | | .eq(AfterSaleTicketDO::getId, ticket.getId()) |
| | | .eq(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.PROCESSING.getStatus()) |
| | | .isNull(AfterSaleTicketDO::getReturnId) |
| | | .set(AfterSaleTicketDO::getReturnId, returnDO.getId()) |
| | | .set(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.WAITING_RETURN.getStatus())); |
| | | if (ticketUpdated != 1) { |
| | | throw exception(RETURN_TICKET_INVALID); |
| | | } |
| | | |
| | | return returnDO.getId(); |
| | | } |
| | |
| | | // 2. è·åå
³èå·¥åï¼ç¨äºè·åéå®è®¢åå·çåä½ä¿¡æ¯ï¼ |
| | | AfterSaleTicketDO ticket = ticketMapper.selectById(returnDO.getTicketId()); |
| | | |
| | | // 3. è°ç¨ MES å建éå®éè´§å |
| | | // 3. ä»
鿬¾ä¸äº§çå®ç©å
¥åºï¼å
¶ä½ç±»åå°æç»å®æ´å¸¦å
¥ MES |
| | | if (Integer.valueOf(3).equals(returnDO.getReturnType())) { |
| | | returnMapper.updateById(new AfterSaleReturnDO().setId(id) |
| | | .setStatus(AfterSaleReturnStatusEnum.APPROVED.getStatus()) |
| | | .setAuditTime(LocalDateTime.now())); |
| | | if (ticketMapper.update(null, new LambdaUpdateWrapper<AfterSaleTicketDO>() |
| | | .eq(AfterSaleTicketDO::getId, returnDO.getTicketId()) |
| | | .eq(AfterSaleTicketDO::getReturnId, id) |
| | | .eq(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.WAITING_RETURN.getStatus()) |
| | | .set(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.WAITING_REFUND.getStatus())) != 1) { |
| | | throw exception(RETURN_TICKET_INVALID); |
| | | } |
| | | return; |
| | | } |
| | | MesWmReturnSalesCreateReqDTO mesReqDTO = new MesWmReturnSalesCreateReqDTO(); |
| | | mesReqDTO.setCode(returnDO.getNo()); |
| | | mesReqDTO.setName("å®åéè´§-" + returnDO.getNo()); |
| | |
| | | mesReqDTO.setReturnReason(returnDO.getReturnReason()); |
| | | mesReqDTO.setRemark("å®åå·¥åéè´§ - å·¥åå·: " + (ticket != null ? ticket.getNo() : "")); |
| | | mesReqDTO.setAfterSaleReturnId(returnDO.getId()); |
| | | mesReqDTO.setItems(returnItemMapper.selectListByReturnId(id).stream().map(item -> { |
| | | MesWmReturnSalesCreateReqDTO.Item line = new MesWmReturnSalesCreateReqDTO.Item(); |
| | | line.setItemId(item.getItemId()); |
| | | line.setQuantity(item.getReturnQuantity()); |
| | | line.setBatchId(item.getBatchId()); |
| | | line.setBatchCode(item.getBatchCode()); |
| | | line.setRemark(item.getRemark()); |
| | | return line; |
| | | }).toList()); |
| | | Long mesReturnId = mesWmReturnSalesApi.createReturnSales(mesReqDTO); |
| | | |
| | | // 4. æ´æ°éè´§ç³è¯·ç¶æ |
| | |
| | | .setMesReturnId(mesReturnId) |
| | | .setMesReturnCode(returnDO.getNo()) |
| | | .setAuditTime(LocalDateTime.now())); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void cancelReturn(Long id) { |
| | | AfterSaleReturnDO returnDO = validateReturnExists(id); |
| | | if (!List.of(AfterSaleReturnStatusEnum.DRAFT.getStatus(), AfterSaleReturnStatusEnum.APPROVED.getStatus(), |
| | | AfterSaleReturnStatusEnum.INSPECTING.getStatus(), AfterSaleReturnStatusEnum.INSPECTED.getStatus()) |
| | | .contains(returnDO.getStatus())) { |
| | | throw exception(RETURN_STATUS_ERROR); |
| | | } |
| | | if (returnDO.getMesReturnId() != null) { |
| | | mesWmReturnSalesApi.cancelReturnSales(returnDO.getMesReturnId()); |
| | | } else { |
| | | onMesCanceled(id); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void onMesCanceled(Long returnId) { |
| | | AfterSaleReturnDO returnDO = validateReturnExists(returnId); |
| | | if (AfterSaleReturnStatusEnum.CANCELED.getStatus().equals(returnDO.getStatus())) { |
| | | return; |
| | | } |
| | | if (!List.of(AfterSaleReturnStatusEnum.DRAFT.getStatus(), AfterSaleReturnStatusEnum.APPROVED.getStatus(), |
| | | AfterSaleReturnStatusEnum.INSPECTING.getStatus(), AfterSaleReturnStatusEnum.INSPECTED.getStatus()) |
| | | .contains(returnDO.getStatus())) { |
| | | throw exception(RETURN_STATUS_ERROR); |
| | | } |
| | | returnMapper.updateById(new AfterSaleReturnDO().setId(returnId) |
| | | .setStatus(AfterSaleReturnStatusEnum.CANCELED.getStatus())); |
| | | if (ticketMapper.update(null, new LambdaUpdateWrapper<AfterSaleTicketDO>() |
| | | .eq(AfterSaleTicketDO::getId, returnDO.getTicketId()) |
| | | .eq(AfterSaleTicketDO::getReturnId, returnId) |
| | | .set(AfterSaleTicketDO::getReturnId, null) |
| | | .set(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.PROCESSING.getStatus())) != 1) { |
| | | throw exception(RETURN_TICKET_INVALID); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void onQualityFinished(Long returnId, String qualityResult, String reportUrl) { |
| | | AfterSaleReturnDO returnDO = validateReturnExists(returnId); |
| | | if (List.of(AfterSaleReturnStatusEnum.INSPECTED.getStatus(), AfterSaleReturnStatusEnum.INBOUND.getStatus(), |
| | | AfterSaleReturnStatusEnum.REFUNDED.getStatus(), AfterSaleReturnStatusEnum.CLOSED.getStatus()) |
| | | .contains(returnDO.getStatus())) { |
| | | return; |
| | | } |
| | | if (!AfterSaleReturnStatusEnum.APPROVED.getStatus().equals(returnDO.getStatus()) |
| | | && !AfterSaleReturnStatusEnum.INSPECTING.getStatus().equals(returnDO.getStatus())) { |
| | | throw exception(RETURN_STATUS_ERROR); |
| | | } |
| | | returnMapper.updateById(new AfterSaleReturnDO().setId(returnId) |
| | | .setQualityResult(qualityResult) |
| | | .setQualityReportUrl(reportUrl) |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void onInboundFinished(Long returnId) { |
| | | AfterSaleReturnDO returnDO = validateReturnExists(returnId); |
| | | if (List.of(AfterSaleReturnStatusEnum.INBOUND.getStatus(), AfterSaleReturnStatusEnum.REFUNDED.getStatus(), |
| | | AfterSaleReturnStatusEnum.CLOSED.getStatus()).contains(returnDO.getStatus())) { |
| | | return; |
| | | } |
| | | if (!AfterSaleReturnStatusEnum.INSPECTED.getStatus().equals(returnDO.getStatus())) { |
| | | throw exception(RETURN_STATUS_ERROR); |
| | | } |
| | | returnMapper.updateById(new AfterSaleReturnDO().setId(returnId) |
| | | .setStatus(AfterSaleReturnStatusEnum.INBOUND.getStatus()) |
| | | .setReturnInboundTime(LocalDateTime.now())); |
| | | if (ticketMapper.update(null, new LambdaUpdateWrapper<AfterSaleTicketDO>() |
| | | .eq(AfterSaleTicketDO::getId, returnDO.getTicketId()) |
| | | .eq(AfterSaleTicketDO::getReturnId, returnId) |
| | | .eq(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.WAITING_RETURN.getStatus()) |
| | | .set(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.WAITING_REFUND.getStatus())) != 1) { |
| | | throw exception(RETURN_TICKET_INVALID); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void onRefundCompleted(Long returnId, BigDecimal actualRefundPrice) { |
| | | completeRefund(returnId, actualRefundPrice, null, false); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void confirmRefundCompleted(Long returnId, BigDecimal actualRefundPrice, String voucherNo) { |
| | | if (StrUtil.isBlank(voucherNo)) { |
| | | throw exception(RETURN_REFUND_VOUCHER_REQUIRED); |
| | | } |
| | | completeRefund(returnId, actualRefundPrice, voucherNo.trim(), true); |
| | | } |
| | | |
| | | private void completeRefund(Long returnId, BigDecimal actualRefundPrice, String voucherNo, boolean manual) { |
| | | AfterSaleReturnDO returnDO = validateReturnExists(returnId); |
| | | if (List.of(AfterSaleReturnStatusEnum.REFUNDED.getStatus(), AfterSaleReturnStatusEnum.CLOSED.getStatus()) |
| | | .contains(returnDO.getStatus())) { |
| | | return; |
| | | } |
| | | boolean refundOnly = Integer.valueOf(3).equals(returnDO.getReturnType()); |
| | | if (!(refundOnly && AfterSaleReturnStatusEnum.APPROVED.getStatus().equals(returnDO.getStatus())) |
| | | && !AfterSaleReturnStatusEnum.INBOUND.getStatus().equals(returnDO.getStatus())) { |
| | | throw exception(RETURN_STATUS_ERROR); |
| | | } |
| | | if (actualRefundPrice == null || actualRefundPrice.signum() < 0) { |
| | | throw exception(RETURN_ITEM_INVALID); |
| | | } |
| | | if (manual && returnDO.getTotalReturnPrice() != null |
| | | && actualRefundPrice.compareTo(returnDO.getTotalReturnPrice()) > 0) { |
| | | throw exception(RETURN_REFUND_EXCEEDS_TOTAL); |
| | | } |
| | | if (manual && !Integer.valueOf(2).equals(returnDO.getReturnType()) |
| | | && returnDO.getTotalReturnPrice() != null && returnDO.getTotalReturnPrice().signum() > 0 |
| | | && actualRefundPrice.signum() == 0) { |
| | | throw exception(RETURN_ITEM_INVALID); |
| | | } |
| | | returnMapper.updateById(new AfterSaleReturnDO().setId(returnId) |
| | | .setActualRefundPrice(actualRefundPrice) |
| | | .setRefundVoucherNo(voucherNo) |
| | | .setStatus(AfterSaleReturnStatusEnum.REFUNDED.getStatus()) |
| | | .setRefundTime(LocalDateTime.now())); |
| | | |
| | | // æ´æ°å
³èå·¥åç¶æä¸ºå¾
鿬¾/æ¢è´§å®æ |
| | | AfterSaleTicketDO ticket = ticketMapper.selectById(returnDO.getTicketId()); |
| | | if (ticket != null) { |
| | | ticketMapper.updateById(new AfterSaleTicketDO().setId(ticket.getId()) |
| | | .setStatus(AfterSaleTicketStatusEnum.WAITING_REFUND.getStatus())); |
| | | // 鿬¾ææ¢è´§å®æåå·¥åæè½å®ç» |
| | | if (ticketMapper.update(null, new LambdaUpdateWrapper<AfterSaleTicketDO>() |
| | | .eq(AfterSaleTicketDO::getId, returnDO.getTicketId()) |
| | | .eq(AfterSaleTicketDO::getReturnId, returnId) |
| | | .eq(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.WAITING_REFUND.getStatus()) |
| | | .set(AfterSaleTicketDO::getStatus, AfterSaleTicketStatusEnum.COMPLETED.getStatus()) |
| | | .set(AfterSaleTicketDO::getResolvedTime, LocalDateTime.now())) != 1) { |
| | | throw exception(RETURN_TICKET_INVALID); |
| | | } |
| | | } |
| | | |
| | |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketPageReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.controller.admin.ticket.vo.ticket.AfterSaleTicketSaveReqVO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.returnobj.AfterSaleReturnDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.returnobj.AfterSaleReturnMapper; |
| | | import cn.iocoder.yudao.module.aftersales.enums.AfterSaleReturnStatusEnum; |
| | | import cn.iocoder.yudao.module.aftersales.dal.dataobject.ticket.AfterSaleTicketItemDO; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketItemMapper; |
| | | import cn.iocoder.yudao.module.aftersales.dal.mysql.ticket.AfterSaleTicketMapper; |
| | |
| | | private AfterSaleTicketMapper ticketMapper; |
| | | @Resource |
| | | private AfterSaleTicketItemMapper ticketItemMapper; |
| | | @Resource |
| | | private AfterSaleReturnMapper returnMapper; |
| | | |
| | | @Resource |
| | | private AfterSaleNoRedisDAO noRedisDAO; |
| | |
| | | AfterSaleTicketDO ticket = validateTicketExists(id); |
| | | validateTicketStatus(ticket, AfterSaleTicketStatusEnum.PROCESSING, |
| | | AfterSaleTicketStatusEnum.WAITING_RETURN, AfterSaleTicketStatusEnum.WAITING_REFUND); |
| | | if (ticket.getReturnId() != null) { |
| | | AfterSaleReturnDO returnDO = returnMapper.selectById(ticket.getReturnId()); |
| | | if (returnDO == null || !AfterSaleReturnStatusEnum.REFUNDED.getStatus().equals(returnDO.getStatus())) { |
| | | throw exception(RETURN_TICKET_NOT_FINISHED); |
| | | } |
| | | } |
| | | ticketMapper.updateById(new AfterSaleTicketDO().setId(id) |
| | | .setStatus(AfterSaleTicketStatusEnum.COMPLETED.getStatus()) |
| | | .setResolvedTime(LocalDateTime.now())); |
| | |
| | | public void closeTicket(Long id) { |
| | | AfterSaleTicketDO ticket = validateTicketExists(id); |
| | | validateTicketStatus(ticket, AfterSaleTicketStatusEnum.COMPLETED); |
| | | if (ticket.getReturnId() != null) { |
| | | AfterSaleReturnDO returnDO = returnMapper.selectById(ticket.getReturnId()); |
| | | if (returnDO == null || !AfterSaleReturnStatusEnum.REFUNDED.getStatus().equals(returnDO.getStatus())) { |
| | | throw exception(RETURN_TICKET_NOT_FINISHED); |
| | | } |
| | | returnMapper.updateById(new AfterSaleReturnDO().setId(returnDO.getId()) |
| | | .setStatus(AfterSaleReturnStatusEnum.CLOSED.getStatus())); |
| | | } |
| | | ticketMapper.updateById(new AfterSaleTicketDO().setId(id) |
| | | .setStatus(AfterSaleTicketStatusEnum.CLOSED.getStatus()) |
| | | .setClosedTime(LocalDateTime.now())); |
| | |
| | | }); |
| | | } |
| | | |
| | | @PutMapping("/update-status") |
| | | @Operation(summary = "æ´æ°å款çç¶æï¼å®¡æ ¸/åå®¡æ ¸ï¼") |
| | | @PutMapping("/submit") |
| | | @Operation(summary = "æäº¤å款审æ¹ï¼è稿/驳åâ审æ¹ä¸ï¼") |
| | | @PreAuthorize("@ss.hasPermission('crm:receivable:update')") |
| | | public CommonResult<Boolean> updateReceivableStatus(@RequestParam("id") Long id, |
| | | @RequestParam("status") Integer status) { |
| | | receivableService.updateReceivableStatus(id, status); |
| | | public CommonResult<Boolean> submitReceivable(@RequestParam("id") Long id) { |
| | | receivableService.submitReceivable(id); |
| | | return success(true); |
| | | } |
| | | |
| | |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| | | import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; |
| | | import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| | | import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; |
| | | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; |
| | |
| | | CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), |
| | | CrmReceivableDO::getId, userId, CrmSceneTypeEnum.OWNER.getType()); |
| | | // æªå®¡æ ¸ |
| | | query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus()); |
| | | query.eq(CrmReceivableDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus()); |
| | | return selectCount(query); |
| | | } |
| | | |
| | |
| | | ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "忬¾ä¸åå¨"); |
| | | ErrorCode RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_004_001, "æ´æ°å款失败ï¼åå ï¼ç¦æ¢ç¼è¾"); |
| | | ErrorCode RECEIVABLE_DELETE_FAIL = new ErrorCode(1_020_004_002, "å é¤å款失败ï¼åå ï¼ è¢«åæ¬¾è®¡åæä½¿ç¨ï¼ä¸å
许å é¤"); |
| | | ErrorCode RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_004_003, "忬¾æäº¤å®¡æ ¸å¤±è´¥ï¼åå ï¼å款没å¤å¨æªæäº¤ç¶æ"); |
| | | ErrorCode RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_004_003, "忬¾æäº¤å®¡æ ¸å¤±è´¥ï¼åå ï¼å款须å¤äºè稿æå®¡æ ¸ä¸éè¿ç¶æ"); |
| | | ErrorCode RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_004_004, "æ´æ°åæ¬¾å®¡æ ¸ç¶æå¤±è´¥ï¼åå ï¼åæ¬¾ä¸æ¯å®¡æ ¸ä¸ç¶æ"); |
| | | ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "çæå款åºåå·éå¤ï¼è¯·éè¯"); |
| | | ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "åå»ºåæ¬¾å¤±è´¥ï¼åå ï¼åå䏿¯å®¡æ ¸éè¿ç¶æ"); |
| | |
| | | */ |
| | | void updateReceivable(@Valid CrmReceivableSaveReqVO updateReqVO); |
| | | |
| | | /** |
| | | * æ´æ°å款çç¶æï¼å®¡æ ¸/åå®¡æ ¸ï¼ |
| | | * |
| | | * @param id ç¼å· |
| | | * @param status ç¶æ |
| | | */ |
| | | void updateReceivableStatus(Long id, Integer status); |
| | | /** æäº¤å款审æ¹ã */ |
| | | void submitReceivable(Long id); |
| | | |
| | | /** BPM 审æ¹ç»ææ¶åå忬¾ç¶æã */ |
| | | void updateReceivableAuditStatus(Long id, Integer bpmStatus); |
| | | |
| | | /** |
| | | * å é¤å款 |
| | |
| | | import cn.iocoder.yudao.module.crm.service.invoice.CrmInvoiceService; |
| | | import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; |
| | | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; |
| | | import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; |
| | | import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; |
| | | import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum; |
| | | import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi; |
| | | import cn.iocoder.yudao.module.system.api.approval.ApprovalConfigApi; |
| | | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| | | import cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum; |
| | | import com.mzt.logapi.context.LogRecordContext; |
| | | import com.mzt.logapi.service.impl.DiffParseFunction; |
| | | import com.mzt.logapi.starter.annotation.LogRecord; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | |
| | | 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.crm.enums.ErrorCodeConstants.*; |
| | | import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; |
| | | |
| | |
| | | */ |
| | | @Service |
| | | @Validated |
| | | @Slf4j |
| | | public class CrmReceivableServiceImpl implements CrmReceivableService { |
| | | |
| | | private static final String RECEIVABLE_PROCESS_KEY = "crm-receivable-audit"; |
| | | private static final String RECEIVABLE_APPROVE_BIZ_TYPE = "crm_receivable_approve"; |
| | | |
| | | @Resource |
| | | private CrmReceivableMapper receivableMapper; |
| | |
| | | private AdminUserApi adminUserApi; |
| | | @Resource |
| | | private StorageAttachmentApi storageAttachmentApi; |
| | | @Resource |
| | | private BpmProcessInstanceApi bpmProcessInstanceApi; |
| | | @Resource |
| | | private ApprovalConfigApi approvalConfigApi; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | throw exception(RECEIVABLE_NO_EXISTS); |
| | | } |
| | | |
| | | // 2.1 æå
¥å款ï¼å建åå³ä¸ºæªå®¡æ ¸ç¶æï¼ |
| | | // 2.1 æå
¥èç¨¿ï¼æäº¤æ¶åå建 BPM æµç¨ |
| | | CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class) |
| | | .setNo(no).setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()); |
| | | .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); |
| | | receivableMapper.insert(receivable); |
| | | // 2.2 |
| | | |
| | |
| | | // 1.5 æ ¡éªå¯å款éé¢ä¸è¶
è¿è¯¥å¼ç¥¨å©ä½å¯å款éé¢ |
| | | validateReceivablePriceExceedsInvoiceRemaining(updateReqVO); |
| | | |
| | | // 1.6 åªææªå®¡æ ¸ï¼å¯ä»¥ç¼è¾ï¼ |
| | | if (ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | // 1.6 åªæè稿æé©³ååå¯ä»¥ç¼è¾ |
| | | if (ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus()) |
| | | && ObjectUtil.notEqual(oldReceivable.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) { |
| | | throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED); |
| | | } |
| | | |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateReceivableStatus(Long id, Integer status) { |
| | | boolean approve = CrmAuditStatusEnum.APPROVE.getStatus().equals(status); |
| | | // 1.1 æ ¡éªåå¨ |
| | | @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) |
| | | public void submitReceivable(Long id) { |
| | | CrmReceivableDO receivable = validateReceivableExists(id); |
| | | // 1.2 æ ¡éªç¶æ |
| | | if (receivable.getAuditStatus().equals(status)) { |
| | | throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL); |
| | | if (ObjectUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus()) |
| | | && ObjectUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.REJECT.getStatus())) { |
| | | throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT); |
| | | } |
| | | |
| | | // 2. æ´æ°ç¶æ |
| | | // è稿æå¯è½å·²äº§çå
¶ä»åæ¬¾ï¼æäº¤æ¶éæ°æ ¡éªåååå¼ç¥¨é¢åº¦ã |
| | | CrmReceivableSaveReqVO req = new CrmReceivableSaveReqVO().setId(id) |
| | | .setContractId(receivable.getContractId()).setInvoiceId(receivable.getInvoiceId()) |
| | | .setCustomerId(receivable.getCustomerId()).setPrice(receivable.getPrice()); |
| | | validateInvoiceForReceivable(req); |
| | | validateReceivablePriceExceedsLimit(req); |
| | | validateReceivablePriceExceedsInvoiceRemaining(req); |
| | | approvalConfigApi.validateApprovalEnabledAndGetApprovers(RECEIVABLE_APPROVE_BIZ_TYPE); |
| | | int updateCount = receivableMapper.updateByIdAndStatus(id, receivable.getAuditStatus(), |
| | | new CrmReceivableDO().setAuditStatus(status)); |
| | | new CrmReceivableDO().setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); |
| | | if (updateCount == 0) { |
| | | throw exception(approve ? RECEIVABLE_APPROVE_FAIL : RECEIVABLE_PROCESS_FAIL); |
| | | throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT); |
| | | } |
| | | String processInstanceId = bpmProcessInstanceApi.createProcessInstance(getLoginUserId(), |
| | | new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(RECEIVABLE_PROCESS_KEY) |
| | | .setBusinessKey(String.valueOf(id))); |
| | | receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId)); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateReceivableAuditStatus(Long id, Integer bpmStatus) { |
| | | Integer auditStatus; |
| | | if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmStatus)) { |
| | | auditStatus = CrmAuditStatusEnum.APPROVE.getStatus(); |
| | | } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmStatus)) { |
| | | auditStatus = CrmAuditStatusEnum.REJECT.getStatus(); |
| | | } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmStatus)) { |
| | | auditStatus = CrmAuditStatusEnum.CANCEL.getStatus(); |
| | | } else { |
| | | return; |
| | | } |
| | | if (receivableMapper.updateByIdAndStatus(id, CrmAuditStatusEnum.PROCESS.getStatus(), |
| | | new CrmReceivableDO().setAuditStatus(auditStatus)) != 1) { |
| | | log.warn("[updateReceivableAuditStatus] 忬¾({}) å·²ä¸å¤äºå®¡æ¹ä¸ï¼å¿½ç¥ BPM ç¶æ({})", id, bpmStatus); |
| | | } |
| | | } |
| | | |
| | |
| | | if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) { |
| | | throw exception(RECEIVABLE_DELETE_FAIL); |
| | | } |
| | | // 1.3 审æ¹éè¿æ¶ï¼ä¸å
许å é¤ |
| | | if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) { |
| | | // 审æ¹ä¸ãå®¡æ ¸éè¿ä¸å¯å é¤ |
| | | if (ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus()) |
| | | || ObjUtil.equal(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { |
| | | throw exception(RECEIVABLE_DELETE_FAIL_IS_APPROVE); |
| | | } |
| | | |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.crm.service.receivable.listener; |
| | | |
| | | import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent; |
| | | import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener; |
| | | import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; |
| | | import jakarta.annotation.Resource; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** 忬¾å®¡æ¹ç»æååã */ |
| | | @Component |
| | | @Slf4j |
| | | public class CrmReceivableStatusListener extends BpmProcessInstanceStatusEventListener { |
| | | |
| | | @Resource |
| | | private CrmReceivableService receivableService; |
| | | |
| | | @Override |
| | | protected String getProcessDefinitionKey() { |
| | | return "crm-receivable-audit"; |
| | | } |
| | | |
| | | @Override |
| | | protected void onEvent(BpmProcessInstanceStatusEvent event) { |
| | | try { |
| | | receivableService.updateReceivableAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus()); |
| | | } catch (NumberFormatException e) { |
| | | log.warn("[onEvent] æ æç忬¾ä¸å¡ç¼å·: {}", event.getBusinessKey()); |
| | | } |
| | | } |
| | | } |
| | |
| | | ErrorCode ATTENDANCE_RULE_EXISTS = new ErrorCode(1_030_004_024, "å·²åå¨èå¤è§åã{}ãï¼ä¸æªå¼å¯è¦çï¼"); |
| | | ErrorCode ATTENDANCE_RULE_DEPT_NOT_EXISTS = new ErrorCode(1_030_004_025, "é¨é¨ã{}ãä¸åå¨"); |
| | | ErrorCode ATTENDANCE_EXCEPTION_TIME_REQUIRED = new ErrorCode(1_030_004_026, "è¡¥å¡ç³è¯·å¿
须填åè¡¥å¡æ¶é´"); |
| | | ErrorCode ATTENDANCE_LOCATION_RULE_INVALID = new ErrorCode(1_030_004_050, "èå¤è§åå°ç¹é
ç½®ä¸å®æ´ï¼è¯·è系管çå"); |
| | | ErrorCode ATTENDANCE_LOCATION_REQUIRED = new ErrorCode(1_030_004_051, "请å¼å¯å®ä½å¹¶æä¾ç»çº¬åº¦ã精度ååæ ç³»åæå¡"); |
| | | ErrorCode ATTENDANCE_LOCATION_COORDINATE_INVALID = new ErrorCode(1_030_004_052, "æå¡ç»çº¬åº¦è¶
åºææèå´"); |
| | | ErrorCode ATTENDANCE_LOCATION_ACCURACY_INVALID = new ErrorCode(1_030_004_053, "å®ä½ç²¾åº¦é¡»å¤§äº 0 ä¸ä¸è¶
è¿ 100 ç±³ï¼è¯·å°å¼éä½ç½®éè¯"); |
| | | ErrorCode ATTENDANCE_LOCATION_SYSTEM_INVALID = new ErrorCode(1_030_004_054, "æå¡åæ ç³»å¿
须为 WGS84"); |
| | | ErrorCode ATTENDANCE_LOCATION_OUT_OF_RANGE = new ErrorCode(1_030_004_055, "å½åä½ç½®è·â{}â{}ç±³ï¼è¶
è¿å
许èå´{}ç±³"); |
| | | |
| | | // ========== 绩æèæ ¸ 1-030-008-000 ========== |
| | | ErrorCode PERFORMANCE_APPRAISE_NOT_EXISTS = new ErrorCode(1_030_008_000, "绩æèæ ¸æ¹æ¡ä¸åå¨"); |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceMyStatusRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportExcelVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO; |
| | |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; |
| | | import cn.iocoder.yudao.module.hrm.enums.HrmAttendanceClockTypeEnum; |
| | | import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceRecordService; |
| | | import cn.iocoder.yudao.module.hrm.service.attendance.HrmAttendanceClockResult; |
| | | import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService; |
| | | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| | | import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; |
| | |
| | | |
| | | @PostMapping("/clock-in") |
| | | @Operation(summary = "ä¸çæå¡") |
| | | public CommonResult<HrmAttendanceRecordRespVO> clockIn(@RequestParam("location") String location) { |
| | | @ApiAccessLog(requestEnable = false) |
| | | public CommonResult<HrmAttendanceClockRespVO> clockIn( |
| | | @Valid @RequestBody(required = false) HrmAttendanceClockReqVO reqVO, |
| | | @RequestParam(value = "location", required = false) String legacyLocation) { |
| | | Long loginUserId = getLoginUserId(); |
| | | HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId); |
| | | if (employee == null) { |
| | | throw exception(EMPLOYEE_NOT_EXISTS, |
| | | "å½åç»å½è´¦å·æªå
³èå工档æ¡ï¼æ æ³æå¡ã请è系管çåå¨ãå工管çãä¸å°è¯¥ç»å½è´¦å·ç»å®å°åå·¥ã"); |
| | | } |
| | | HrmAttendanceRecordDO record = attendanceRecordService.clockIn(employee.getId(), LocalDateTime.now(), location); |
| | | return success(buildClockRespVO(record, employee)); |
| | | HrmAttendanceClockResult result = attendanceRecordService.clockIn(employee.getId(), LocalDateTime.now(), reqVO, legacyLocation); |
| | | return success(buildClockResultVO(result, employee)); |
| | | } |
| | | |
| | | @PostMapping("/clock-out") |
| | | @Operation(summary = "ä¸çæå¡") |
| | | public CommonResult<HrmAttendanceRecordRespVO> clockOut(@RequestParam("location") String location) { |
| | | @ApiAccessLog(requestEnable = false) |
| | | public CommonResult<HrmAttendanceClockRespVO> clockOut( |
| | | @Valid @RequestBody(required = false) HrmAttendanceClockReqVO reqVO, |
| | | @RequestParam(value = "location", required = false) String legacyLocation) { |
| | | Long loginUserId = getLoginUserId(); |
| | | HrmEmployeeDO employee = employeeService.getEmployeeByUserId(loginUserId); |
| | | if (employee == null) { |
| | | throw exception(EMPLOYEE_NOT_EXISTS, |
| | | "å½åç»å½è´¦å·æªå
³èå工档æ¡ï¼æ æ³æå¡ã请è系管çåå¨ãå工管çãä¸å°è¯¥ç»å½è´¦å·ç»å®å°åå·¥ã"); |
| | | } |
| | | HrmAttendanceRecordDO record = attendanceRecordService.clockOut(employee.getId(), LocalDateTime.now(), location); |
| | | return success(buildClockRespVO(record, employee)); |
| | | HrmAttendanceClockResult result = attendanceRecordService.clockOut(employee.getId(), LocalDateTime.now(), reqVO, legacyLocation); |
| | | return success(buildClockResultVO(result, employee)); |
| | | } |
| | | |
| | | @GetMapping("/my-status") |
| | |
| | | if (dept != null) { |
| | | respVO.setDeptName(dept.getName()); |
| | | } |
| | | } |
| | | return respVO; |
| | | } |
| | | |
| | | private HrmAttendanceClockRespVO buildClockResultVO(HrmAttendanceClockResult result, HrmEmployeeDO employee) { |
| | | HrmAttendanceClockRespVO respVO = new HrmAttendanceClockRespVO(); |
| | | respVO.setRecord(buildClockRespVO(result.record(), employee)); |
| | | respVO.setLocationName(result.rule().getLocationName()); |
| | | if (Boolean.TRUE.equals(result.rule().getLocationRequired())) { |
| | | respVO.setDistanceMeters(result.distanceMeters()); |
| | | respVO.setRadiusMeters(result.rule().getRadiusMeters()); |
| | | respVO.setWithinRange(true); |
| | | } |
| | | return respVO; |
| | | } |
| | |
| | | ExcelUtils.write(response, "èå¤è®°å½.xls", "æ°æ®", HrmAttendanceRecordRespVO.class, list); |
| | | } |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** App/Web æå¡å®ä½åå§æ°æ®ï¼æ¯å¦å¿
å¡«ç±å½åçæè§åå³å®ã */ |
| | | @Data |
| | | public class HrmAttendanceClockReqVO { |
| | | @Schema(description = "设å¤ç»åº¦ï¼WGS84ï¼") |
| | | private BigDecimal longitude; |
| | | @Schema(description = "设å¤çº¬åº¦ï¼WGS84ï¼") |
| | | private BigDecimal latitude; |
| | | @Schema(description = "å®ä½ç²¾åº¦ï¼ç±³") |
| | | private BigDecimal accuracy; |
| | | @Schema(description = "åæ ç³»ï¼ä»
æ¯æ WGS84") |
| | | private String coordinateSystem; |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | @Data |
| | | public class HrmAttendanceClockRespVO { |
| | | @Schema(description = "èå¤è®°å½") |
| | | private HrmAttendanceRecordRespVO record; |
| | | @Schema(description = "è§åå°ç¹åç§°") |
| | | private String locationName; |
| | | @Schema(description = "è·è§åå°ç¹è·ç¦»ï¼ç±³ï¼æªå¯ç¨ä½ç½®éå¶æ¶ä¸ºç©º") |
| | | private BigDecimal distanceMeters; |
| | | @Schema(description = "å
许åå¾ï¼ç±³ï¼æªå¯ç¨ä½ç½®éå¶æ¶ä¸ºç©º") |
| | | private Integer radiusMeters; |
| | | @Schema(description = "æ¯å¦å¨å
许èå´ï¼æªå¯ç¨ä½ç½®éå¶æ¶ä¸ºç©º") |
| | | private Boolean withinRange; |
| | | } |
| | |
| | | @ExcelProperty("æå¡å°ç¹") |
| | | private String location; |
| | | |
| | | @Schema(description = "ä¸çæå¡ç»åº¦ï¼WGS84ï¼") |
| | | private BigDecimal clockInLongitude; |
| | | @Schema(description = "ä¸çæå¡çº¬åº¦ï¼WGS84ï¼") |
| | | private BigDecimal clockInLatitude; |
| | | @Schema(description = "ä¸çå®ä½ç²¾åº¦ï¼ç±³") |
| | | private BigDecimal clockInAccuracy; |
| | | @Schema(description = "ä¸çè·è§åå°ç¹è·ç¦»ï¼ç±³") |
| | | private BigDecimal clockInDistance; |
| | | @Schema(description = "ä¸çè§åå°ç¹å°åå¿«ç
§") |
| | | private String clockInAddress; |
| | | @Schema(description = "ä¸çæå¡ç»åº¦ï¼WGS84ï¼") |
| | | private BigDecimal clockOutLongitude; |
| | | @Schema(description = "ä¸çæå¡çº¬åº¦ï¼WGS84ï¼") |
| | | private BigDecimal clockOutLatitude; |
| | | @Schema(description = "ä¸çå®ä½ç²¾åº¦ï¼ç±³") |
| | | private BigDecimal clockOutAccuracy; |
| | | @Schema(description = "ä¸çè·è§åå°ç¹è·ç¦»ï¼ç±³") |
| | | private BigDecimal clockOutDistance; |
| | | @Schema(description = "ä¸çè§åå°ç¹å°åå¿«ç
§") |
| | | private String clockOutAddress; |
| | | |
| | | @Schema(description = "夿³¨") |
| | | @ExcelProperty("夿³¨") |
| | | private String remark; |
| | |
| | | @ExcelProperty("å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | | } |
| | |
| | | @ExcelProperty("å çèµ·ç®æ¶é¿") |
| | | private BigDecimal overtimeThreshold; |
| | | |
| | | @Schema(description = "æå¡å°ç¹åç§°") |
| | | private String locationName; |
| | | @Schema(description = "æå¡å°ç¹å°å") |
| | | private String locationAddress; |
| | | @Schema(description = "å°ç¹ç»åº¦ï¼WGS84ï¼") |
| | | private BigDecimal longitude; |
| | | @Schema(description = "å°ç¹çº¬åº¦ï¼WGS84ï¼") |
| | | private BigDecimal latitude; |
| | | @Schema(description = "å
许æå¡åå¾ï¼åä½ç±³") |
| | | private Integer radiusMeters; |
| | | @Schema(description = "æ¯å¦å¯ç¨ä½ç½®éå¶") |
| | | private Boolean locationRequired; |
| | | @Schema(description = "åæ ç³»ï¼ä»
æ¯æ WGS84") |
| | | private String coordinateSystem; |
| | | |
| | | @Schema(description = "ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨") |
| | | @ExcelProperty(value = "ç¶æ", converter = DictConvert.class) |
| | | @DictFormat("hrm_rule_status") |
| | |
| | | @ExcelProperty("å建æ¶é´") |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | | } |
| | |
| | | @Schema(description = "å çèµ·ç®æ¶é¿ï¼å°æ¶ï¼", example = "1") |
| | | private BigDecimal overtimeThreshold; |
| | | |
| | | @Schema(description = "æå¡å°ç¹åç§°") |
| | | private String locationName; |
| | | @Schema(description = "æå¡å°ç¹å°å") |
| | | private String locationAddress; |
| | | @Schema(description = "å°ç¹ç»åº¦ï¼WGS84ï¼") |
| | | private BigDecimal longitude; |
| | | @Schema(description = "å°ç¹çº¬åº¦ï¼WGS84ï¼") |
| | | private BigDecimal latitude; |
| | | @Schema(description = "å
许æå¡åå¾ï¼åä½ç±³") |
| | | private Integer radiusMeters; |
| | | @Schema(description = "æ¯å¦å¯ç¨ä½ç½®éå¶") |
| | | private Boolean locationRequired; |
| | | @Schema(description = "åæ ç³»ï¼ä»
æ¯æ WGS84") |
| | | private String coordinateSystem; |
| | | |
| | | @Schema(description = "ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨", example = "0") |
| | | private Integer status; |
| | | |
| | | @Schema(description = "夿³¨", example = "éç¨äºç åé¨é¨") |
| | | private String remark; |
| | | |
| | | } |
| | | } |
| | |
| | | */ |
| | | private String location; |
| | | |
| | | private BigDecimal clockInLongitude; |
| | | private BigDecimal clockInLatitude; |
| | | private BigDecimal clockInAccuracy; |
| | | private BigDecimal clockInDistance; |
| | | private String clockInAddress; |
| | | private BigDecimal clockOutLongitude; |
| | | private BigDecimal clockOutLatitude; |
| | | private BigDecimal clockOutAccuracy; |
| | | private BigDecimal clockOutDistance; |
| | | private String clockOutAddress; |
| | | |
| | | /** |
| | | * 夿³¨ |
| | | */ |
| | |
| | | */ |
| | | private Integer dataSource; |
| | | |
| | | } |
| | | } |
| | |
| | | */ |
| | | private BigDecimal overtimeThreshold; |
| | | |
| | | /** èå¤å°ç¹ä¸å¯æå¡èå´ï¼æ§è§åé»è®¤ä¸å¯ç¨ä½ç½®éå¶ã */ |
| | | private String locationName; |
| | | private String locationAddress; |
| | | private BigDecimal longitude; |
| | | private BigDecimal latitude; |
| | | private Integer radiusMeters; |
| | | private Boolean locationRequired; |
| | | private String coordinateSystem; |
| | | |
| | | /** |
| | | * ç¶æï¼0-å¯ç¨ï¼1-ç¦ç¨ |
| | | */ |
| | |
| | | */ |
| | | private String remark; |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.attendance; |
| | | |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** æå¡ä¿åç»æåæ¬æ¬¡æ ¡éªä½¿ç¨çè§åå¿«ç
§ã */ |
| | | public record HrmAttendanceClockResult(HrmAttendanceRecordDO record, HrmAttendanceRuleDO rule, |
| | | BigDecimal distanceMeters) { |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceMyStatusRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO; |
| | | import jakarta.validation.Valid; |
| | |
| | | * @param location æå¡å°ç¹ |
| | | * @return èå¤è®°å½ |
| | | */ |
| | | HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location); |
| | | HrmAttendanceClockResult clockIn(Long userId, LocalDateTime clockTime, |
| | | HrmAttendanceClockReqVO reqVO, String legacyLocation); |
| | | |
| | | /** |
| | | * æå¡ï¼ä¸çæå¡ï¼ |
| | |
| | | * @param location æå¡å°ç¹ |
| | | * @return èå¤è®°å½ |
| | | */ |
| | | HrmAttendanceRecordDO clockOut(Long userId, LocalDateTime clockTime, String location); |
| | | HrmAttendanceClockResult clockOut(Long userId, LocalDateTime clockTime, |
| | | HrmAttendanceClockReqVO reqVO, String legacyLocation); |
| | | |
| | | /** |
| | | * è¡¥å¡å®¡æ¹éè¿åæ´æ°èå¤è®°å½ |
| | |
| | | */ |
| | | void recordOvertime(HrmAttendanceExceptionDO exception); |
| | | |
| | | } |
| | | } |
| | |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRuleRespVO; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceHolidayDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO; |
| | |
| | | import java.time.LocalDateTime; |
| | | import java.time.LocalTime; |
| | | import java.util.List; |
| | | import cn.hutool.core.util.StrUtil; |
| | | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*; |
| | |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location) { |
| | | public HrmAttendanceClockResult clockIn(Long userId, LocalDateTime clockTime, |
| | | HrmAttendanceClockReqVO reqVO, String legacyLocation) { |
| | | LocalDate clockDate = clockTime.toLocalDate(); |
| | | // 1. æ ¡éªæ¯å¦å·¥ä½æ¥ |
| | | if (!attendanceHolidayService.isWorkDay(clockDate)) { |
| | |
| | | if (record != null && record.getClockInTime() != null) { |
| | | throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED); |
| | | } |
| | | // 4. 夿æå¡ç±»å |
| | | // 4. å¨ä»»ä½ååºæä½åæ ¡éªä½ç½®ï¼ä¸è½ç¨æ§ç location å符串ç»è¿èå´éå¶ã |
| | | ClockLocation clockLocation = validateClockLocation(rule, reqVO); |
| | | // 5. 夿æå¡ç±»å |
| | | LocalTime workStartTime = rule.getWorkStartTime(); |
| | | int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0; |
| | | LocalTime lateThreshold = workStartTime.plusMinutes(lateAllowMinutes); |
| | |
| | | Integer clockInType = clockTimeOnly.isBefore(lateThreshold) || clockTimeOnly.equals(lateThreshold) |
| | | ? HrmAttendanceClockTypeEnum.NORMAL.getType() |
| | | : HrmAttendanceClockTypeEnum.LATE.getType(); |
| | | // 5. åå»ºææ´æ°èå¤è®°å½ |
| | | // 6. åå»ºææ´æ°èå¤è®°å½ |
| | | if (record == null) { |
| | | record = HrmAttendanceRecordDO.builder() |
| | | .userId(userId) |
| | |
| | | .date(clockDate) |
| | | .clockInTime(clockTime) |
| | | .clockInType(clockInType) |
| | | .location(location) |
| | | .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType()) |
| | | .build(); |
| | | applyClockLocation(record, rule, clockLocation, legacyLocation, true); |
| | | attendanceRecordMapper.insert(record); |
| | | } else { |
| | | record.setClockInTime(clockTime); |
| | | record.setClockInType(clockInType); |
| | | if (location != null) { |
| | | record.setLocation(location); |
| | | } |
| | | applyClockLocation(record, rule, clockLocation, legacyLocation, true); |
| | | attendanceRecordMapper.updateById(record); |
| | | } |
| | | return record; |
| | | return new HrmAttendanceClockResult(record, rule, |
| | | clockLocation != null ? clockLocation.distanceMeters() : null); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public HrmAttendanceRecordDO clockOut(Long userId, LocalDateTime clockTime, String location) { |
| | | public HrmAttendanceClockResult clockOut(Long userId, LocalDateTime clockTime, |
| | | HrmAttendanceClockReqVO reqVO, String legacyLocation) { |
| | | LocalDate clockDate = clockTime.toLocalDate(); |
| | | // 1. æ ¡éªæ¯å¦å·¥ä½æ¥ |
| | | if (!attendanceHolidayService.isWorkDay(clockDate)) { |
| | |
| | | } |
| | | // 3. æ¥è¯¢å½æ¥èå¤è®°å½ |
| | | HrmAttendanceRecordDO record = attendanceRecordMapper.selectByUserIdAndDate(userId, clockDate); |
| | | if (record != null && record.getClockOutTime() != null) { |
| | | throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED); |
| | | } |
| | | ClockLocation clockLocation = validateClockLocation(rule, reqVO); |
| | | // 4. æ ä¸ç塿¶ä¹å
宿ä½ç½®æ ¡éªï¼åå建缺å¡è®°å½ã |
| | | if (record == null) { |
| | | // å¦ææ²¡æä¸çæå¡è®°å½ï¼åå»ºä¸æ¡ç¼ºå¡çè®°å½ |
| | | record = HrmAttendanceRecordDO.builder() |
| | | .userId(userId) |
| | | .deptId(employee.getDeptId()) |
| | |
| | | .clockInType(HrmAttendanceClockTypeEnum.MISSING.getType()) |
| | | .dataSource(HrmAttendanceDataSourceEnum.CLOCK.getType()) |
| | | .build(); |
| | | attendanceRecordMapper.insert(record); |
| | | } |
| | | if (record.getClockOutTime() != null) { |
| | | throw exception(ATTENDANCE_CLOCK_ALREADY_CLOCKED); |
| | | } |
| | | // 4. 夿æå¡ç±»å |
| | | // 5. 夿æå¡ç±»å |
| | | LocalTime workEndTime = rule.getWorkEndTime(); |
| | | int earlyAllowMinutes = rule.getEarlyAllowMinutes() != null ? rule.getEarlyAllowMinutes() : 0; |
| | | LocalTime earlyThreshold = workEndTime.minusMinutes(earlyAllowMinutes); |
| | |
| | | Integer clockOutType = clockTimeOnly.isAfter(earlyThreshold) || clockTimeOnly.equals(earlyThreshold) |
| | | ? HrmAttendanceClockTypeEnum.NORMAL.getType() |
| | | : HrmAttendanceClockTypeEnum.EARLY.getType(); |
| | | // 5. æ´æ°èå¤è®°å½ |
| | | // 6. æ´æ°èå¤è®°å½ |
| | | record.setClockOutTime(clockTime); |
| | | record.setClockOutType(clockOutType); |
| | | if (location != null) { |
| | | record.setLocation(location); |
| | | } |
| | | applyClockLocation(record, rule, clockLocation, legacyLocation, false); |
| | | // 6. 计ç®å·¥ä½æ¶é¿ |
| | | if (record.getClockInTime() != null) { |
| | | Duration duration = Duration.between(record.getClockInTime(), clockTime); |
| | |
| | | } |
| | | // 7. 计ç®å çæ¶é¿ï¼è¶
è¿è§åä¸çæ¶é´çé¨å |
| | | record.setOvertimeHours(computeOvertimeHours(rule.getWorkEndTime(), clockTimeOnly)); |
| | | attendanceRecordMapper.updateById(record); |
| | | return record; |
| | | if (record.getId() == null) { |
| | | attendanceRecordMapper.insert(record); |
| | | } else { |
| | | attendanceRecordMapper.updateById(record); |
| | | } |
| | | return new HrmAttendanceClockResult(record, rule, |
| | | clockLocation != null ? clockLocation.distanceMeters() : null); |
| | | } |
| | | |
| | | record ClockLocation(BigDecimal longitude, BigDecimal latitude, BigDecimal accuracy, |
| | | BigDecimal distanceMeters) { } |
| | | |
| | | ClockLocation validateClockLocation(HrmAttendanceRuleDO rule, HrmAttendanceClockReqVO reqVO) { |
| | | boolean required = Boolean.TRUE.equals(rule.getLocationRequired()); |
| | | if (required && (StrUtil.isBlank(rule.getLocationName()) || rule.getLongitude() == null |
| | | || rule.getLatitude() == null || rule.getRadiusMeters() == null |
| | | || rule.getRadiusMeters() < 50 || rule.getRadiusMeters() > 5000 |
| | | || !"WGS84".equalsIgnoreCase(rule.getCoordinateSystem()))) { |
| | | throw exception(ATTENDANCE_LOCATION_RULE_INVALID); |
| | | } |
| | | if (reqVO == null || reqVO.getLongitude() == null || reqVO.getLatitude() == null |
| | | || reqVO.getAccuracy() == null || StrUtil.isBlank(reqVO.getCoordinateSystem())) { |
| | | if (required) throw exception(ATTENDANCE_LOCATION_REQUIRED); |
| | | return null; |
| | | } |
| | | boolean coordinatesValid = reqVO.getLongitude().compareTo(BigDecimal.valueOf(-180)) >= 0 |
| | | && reqVO.getLongitude().compareTo(BigDecimal.valueOf(180)) <= 0 |
| | | && reqVO.getLatitude().compareTo(BigDecimal.valueOf(-90)) >= 0 |
| | | && reqVO.getLatitude().compareTo(BigDecimal.valueOf(90)) <= 0; |
| | | if (!coordinatesValid) { |
| | | if (required) throw exception(ATTENDANCE_LOCATION_COORDINATE_INVALID); |
| | | return null; |
| | | } |
| | | if (reqVO.getAccuracy().signum() <= 0 || reqVO.getAccuracy().compareTo(BigDecimal.valueOf(100)) > 0) { |
| | | if (required) throw exception(ATTENDANCE_LOCATION_ACCURACY_INVALID); |
| | | return null; |
| | | } |
| | | if (!"WGS84".equalsIgnoreCase(reqVO.getCoordinateSystem().trim())) { |
| | | if (required) throw exception(ATTENDANCE_LOCATION_SYSTEM_INVALID); |
| | | return null; |
| | | } |
| | | if (!required) { |
| | | return new ClockLocation(reqVO.getLongitude(), reqVO.getLatitude(), reqVO.getAccuracy(), null); |
| | | } |
| | | double rawDistance = haversineMeters(rule.getLongitude().doubleValue(), rule.getLatitude().doubleValue(), |
| | | reqVO.getLongitude().doubleValue(), reqVO.getLatitude().doubleValue()); |
| | | BigDecimal roundedDistance = BigDecimal.valueOf(rawDistance).setScale(2, RoundingMode.HALF_UP); |
| | | if (rawDistance > rule.getRadiusMeters()) { |
| | | throw exception(ATTENDANCE_LOCATION_OUT_OF_RANGE, rule.getLocationName(), |
| | | roundedDistance.toPlainString(), rule.getRadiusMeters()); |
| | | } |
| | | return new ClockLocation(reqVO.getLongitude(), reqVO.getLatitude(), reqVO.getAccuracy(), roundedDistance); |
| | | } |
| | | |
| | | static double haversineMeters(double lon1, double lat1, double lon2, double lat2) { |
| | | double latDelta = Math.toRadians(lat2 - lat1); |
| | | double lonDelta = Math.toRadians(lon2 - lon1); |
| | | double a = Math.pow(Math.sin(latDelta / 2), 2) |
| | | + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) |
| | | * Math.pow(Math.sin(lonDelta / 2), 2); |
| | | double bounded = Math.max(0D, Math.min(1D, a)); |
| | | return 6_371_000D * 2D * Math.atan2(Math.sqrt(bounded), Math.sqrt(1D - bounded)); |
| | | } |
| | | |
| | | private void applyClockLocation(HrmAttendanceRecordDO record, HrmAttendanceRuleDO rule, |
| | | ClockLocation location, String legacyLocation, boolean clockIn) { |
| | | if (StrUtil.isNotBlank(rule.getLocationName())) { |
| | | record.setLocation(rule.getLocationName()); |
| | | } else if (!Boolean.TRUE.equals(rule.getLocationRequired()) && StrUtil.isNotBlank(legacyLocation)) { |
| | | record.setLocation(legacyLocation); |
| | | } |
| | | if (location == null) return; |
| | | if (clockIn) { |
| | | record.setClockInLongitude(location.longitude()); |
| | | record.setClockInLatitude(location.latitude()); |
| | | record.setClockInAccuracy(location.accuracy()); |
| | | record.setClockInDistance(location.distanceMeters()); |
| | | record.setClockInAddress(rule.getLocationAddress()); |
| | | } else { |
| | | record.setClockOutLongitude(location.longitude()); |
| | | record.setClockOutLatitude(location.latitude()); |
| | | record.setClockOutAccuracy(location.accuracy()); |
| | | record.setClockOutDistance(location.distanceMeters()); |
| | | record.setClockOutAddress(rule.getLocationAddress()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | attendanceRecordMapper.updateById(record); |
| | | } |
| | | |
| | | } |
| | | } |
| | |
| | | |
| | | @Override |
| | | public Long createAttendanceRule(HrmAttendanceRuleSaveReqVO createReqVO) { |
| | | validateLocation(createReqVO); |
| | | // æ ¡éªè§ååç§°æ¯å¦éå¤ |
| | | validateAttendanceRuleNameUnique(null, createReqVO.getName()); |
| | | // æ ¡éªé¨é¨æ¯å¦å·²é
ç½®è§åï¼å¦ææå®äºé¨é¨ï¼ |
| | |
| | | |
| | | @Override |
| | | public void updateAttendanceRule(HrmAttendanceRuleSaveReqVO updateReqVO) { |
| | | validateAttendanceRuleExists(updateReqVO.getId()); |
| | | HrmAttendanceRuleDO currentRule = attendanceRuleMapper.selectById(updateReqVO.getId()); |
| | | if (currentRule == null) { |
| | | throw exception(ATTENDANCE_RULE_NOT_EXISTS); |
| | | } |
| | | // å
¼å®¹å°æªä¼ å°ç¹åæ®µçæ§ç®¡ç端ï¼é¿å
ç¼è¾çæ¬¡æ¶æå¤å
³éå·²é
ç½®çä½ç½®éå¶ã |
| | | if (updateReqVO.getLocationRequired() == null) { |
| | | updateReqVO.setLocationRequired(currentRule.getLocationRequired()); |
| | | if (updateReqVO.getLocationName() == null) updateReqVO.setLocationName(currentRule.getLocationName()); |
| | | if (updateReqVO.getLocationAddress() == null) updateReqVO.setLocationAddress(currentRule.getLocationAddress()); |
| | | if (updateReqVO.getLongitude() == null) updateReqVO.setLongitude(currentRule.getLongitude()); |
| | | if (updateReqVO.getLatitude() == null) updateReqVO.setLatitude(currentRule.getLatitude()); |
| | | if (updateReqVO.getRadiusMeters() == null) updateReqVO.setRadiusMeters(currentRule.getRadiusMeters()); |
| | | if (updateReqVO.getCoordinateSystem() == null) updateReqVO.setCoordinateSystem(currentRule.getCoordinateSystem()); |
| | | } |
| | | validateLocation(updateReqVO); |
| | | // æ ¡éªè§ååç§°æ¯å¦éå¤ |
| | | validateAttendanceRuleNameUnique(updateReqVO.getId(), updateReqVO.getName()); |
| | | // æ ¡éªé¨é¨æ¯å¦å·²é
ç½®è§åï¼å¦ææå®äºé¨é¨ï¼ |
| | |
| | | private void validateAttendanceRuleExists(Long id) { |
| | | if (attendanceRuleMapper.selectById(id) == null) { |
| | | throw exception(ATTENDANCE_RULE_NOT_EXISTS); |
| | | } |
| | | } |
| | | |
| | | private void validateLocation(HrmAttendanceRuleSaveReqVO reqVO) { |
| | | if (reqVO.getLocationRequired() == null) { |
| | | reqVO.setLocationRequired(false); |
| | | } |
| | | if (reqVO.getRadiusMeters() == null) { |
| | | reqVO.setRadiusMeters(200); |
| | | } |
| | | if (StrUtil.isBlank(reqVO.getCoordinateSystem())) { |
| | | reqVO.setCoordinateSystem("WGS84"); |
| | | } else { |
| | | reqVO.setCoordinateSystem(reqVO.getCoordinateSystem().trim().toUpperCase(java.util.Locale.ROOT)); |
| | | } |
| | | if (!"WGS84".equals(reqVO.getCoordinateSystem())) { |
| | | throw exception(ATTENDANCE_LOCATION_SYSTEM_INVALID); |
| | | } |
| | | if (reqVO.getLongitude() != null && (reqVO.getLongitude().compareTo(BigDecimal.valueOf(-180)) < 0 |
| | | || reqVO.getLongitude().compareTo(BigDecimal.valueOf(180)) > 0)) { |
| | | throw exception(ATTENDANCE_LOCATION_COORDINATE_INVALID); |
| | | } |
| | | if (reqVO.getLatitude() != null && (reqVO.getLatitude().compareTo(BigDecimal.valueOf(-90)) < 0 |
| | | || reqVO.getLatitude().compareTo(BigDecimal.valueOf(90)) > 0)) { |
| | | throw exception(ATTENDANCE_LOCATION_COORDINATE_INVALID); |
| | | } |
| | | if (reqVO.getRadiusMeters() < 50 || reqVO.getRadiusMeters() > 5000) { |
| | | throw exception(ATTENDANCE_LOCATION_RULE_INVALID); |
| | | } |
| | | if (Boolean.TRUE.equals(reqVO.getLocationRequired()) |
| | | && (StrUtil.isBlank(reqVO.getLocationName()) || reqVO.getLongitude() == null |
| | | || reqVO.getLatitude() == null || StrUtil.isBlank(reqVO.getCoordinateSystem()))) { |
| | | throw exception(ATTENDANCE_LOCATION_RULE_INVALID); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.hrm.service.attendance; |
| | | |
| | | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| | | import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceClockReqVO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRuleDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; |
| | | import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceRecordMapper; |
| | | import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService; |
| | | import org.junit.jupiter.api.Test; |
| | | import org.springframework.test.util.ReflectionTestUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | |
| | | import static org.junit.jupiter.api.Assertions.*; |
| | | import static org.mockito.ArgumentMatchers.any; |
| | | import static org.mockito.Mockito.*; |
| | | |
| | | class HrmAttendanceClockLocationTest { |
| | | |
| | | private final HrmAttendanceRecordServiceImpl service = new HrmAttendanceRecordServiceImpl(); |
| | | |
| | | private HrmAttendanceRuleDO requiredRule() { |
| | | return HrmAttendanceRuleDO.builder().locationRequired(true).locationName("å
¬å¸åå°") |
| | | .longitude(BigDecimal.ZERO).latitude(BigDecimal.ZERO) |
| | | .radiusMeters(100).coordinateSystem("WGS84").build(); |
| | | } |
| | | |
| | | private HrmAttendanceClockReqVO position(String latitude, String accuracy) { |
| | | HrmAttendanceClockReqVO reqVO = new HrmAttendanceClockReqVO(); |
| | | reqVO.setLongitude(BigDecimal.ZERO); |
| | | reqVO.setLatitude(new BigDecimal(latitude)); |
| | | reqVO.setAccuracy(new BigDecimal(accuracy)); |
| | | reqVO.setCoordinateSystem("WGS84"); |
| | | return reqVO; |
| | | } |
| | | |
| | | @Test |
| | | void oldRuleWithoutLocationStillAcceptsClock() { |
| | | HrmAttendanceRuleDO rule = HrmAttendanceRuleDO.builder().locationRequired(false).build(); |
| | | assertNull(service.validateClockLocation(rule, null)); |
| | | assertNull(service.validateClockLocation(rule, position("0.0001", "20")).distanceMeters()); |
| | | } |
| | | |
| | | @Test |
| | | void requiredLocationValidatesCompletenessCoordinatesAndAccuracy() { |
| | | HrmAttendanceRuleDO rule = requiredRule(); |
| | | assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, null)); |
| | | assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, position("91", "20"))); |
| | | assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, position("0", "100.01"))); |
| | | assertNotNull(service.validateClockLocation(rule, position("0", "100"))); |
| | | } |
| | | |
| | | @Test |
| | | void comparesUnroundedDistanceWithRadius() { |
| | | HrmAttendanceRuleDO rule = requiredRule(); |
| | | assertTrue(HrmAttendanceRecordServiceImpl.haversineMeters(0, 0, 0, 0.00089) < 100); |
| | | assertNotNull(service.validateClockLocation(rule, position("0.00089", "20"))); |
| | | assertThrows(ServiceException.class, () -> service.validateClockLocation(rule, position("0.00091", "20"))); |
| | | } |
| | | |
| | | @Test |
| | | void invalidClockOutLocationDoesNotCreateMissingClockInRecord() { |
| | | HrmAttendanceHolidayService holidayService = mock(HrmAttendanceHolidayService.class); |
| | | HrmEmployeeService employeeService = mock(HrmEmployeeService.class); |
| | | HrmAttendanceRuleService ruleService = mock(HrmAttendanceRuleService.class); |
| | | HrmAttendanceRecordMapper mapper = mock(HrmAttendanceRecordMapper.class); |
| | | ReflectionTestUtils.setField(service, "attendanceHolidayService", holidayService); |
| | | ReflectionTestUtils.setField(service, "employeeService", employeeService); |
| | | ReflectionTestUtils.setField(service, "attendanceRuleService", ruleService); |
| | | ReflectionTestUtils.setField(service, "attendanceRecordMapper", mapper); |
| | | LocalDateTime clockTime = LocalDateTime.of(2026, 10, 22, 18, 56); |
| | | when(holidayService.isWorkDay(any(LocalDate.class))).thenReturn(true); |
| | | HrmEmployeeDO employee = HrmEmployeeDO.builder().id(1L).deptId(2L).build(); |
| | | when(employeeService.getEmployee(1L)).thenReturn(employee); |
| | | when(ruleService.getAttendanceRuleByDeptId(2L)).thenReturn(requiredRule()); |
| | | assertThrows(ServiceException.class, |
| | | () -> service.clockOut(1L, clockTime, position("0.00091", "20"), null)); |
| | | verify(mapper, never()).insert(any(HrmAttendanceRecordDO.class)); |
| | | verify(mapper, never()).updateById(any(HrmAttendanceRecordDO.class)); |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.module.mes.api.event; |
| | | |
| | | /** MES éå®éè´§çä¸å¡è¿åº¦ï¼çå¬å¨ä¸ MES æä½å¤äºåä¸äºå¡ã */ |
| | | public record MesReturnSalesProgressEvent(Long mesReturnId, Stage stage, String qualityResult) { |
| | | |
| | | public enum Stage { QUALITY_FINISHED, INBOUND_FINISHED, CANCELED } |
| | | |
| | | } |
| | |
| | | */ |
| | | Long createReturnSales(MesWmReturnSalesCreateReqDTO reqDTO); |
| | | |
| | | /** åæ¶å°æªå®æå
¥åºçéå®éè´§åã */ |
| | | void cancelReturnSales(Long id); |
| | | |
| | | } |
| | |
| | | import lombok.Data; |
| | | |
| | | import java.time.LocalDate; |
| | | import java.math.BigDecimal; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * MES éå®éè´§åå建 Request DTO |
| | |
| | | @Schema(description = "å®åéè´§ç³è¯·ç¼å·ï¼å
³è after_sale_return.idï¼", example = "1") |
| | | private Long afterSaleReturnId; |
| | | |
| | | /** å®¡æ ¸éè¿çå®åéè´§æç»ï¼å建 MES åæ®æ¶ä¸å¹¶å¸¦å
¥ã */ |
| | | private List<Item> items; |
| | | |
| | | @Data |
| | | public static class Item { |
| | | private Long itemId; |
| | | private BigDecimal quantity; |
| | | private Long batchId; |
| | | private String batchCode; |
| | | private String remark; |
| | | } |
| | | |
| | | } |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.module.mes.api.returnsales.dto.MesWmReturnSalesCreateReqDTO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.line.MesWmReturnSalesLineSaveReqVO; |
| | | import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesService; |
| | | import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesLineService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * MES éå®éè´§å API å®ç°ç±» |
| | |
| | | |
| | | @Resource |
| | | private MesWmReturnSalesService returnSalesService; |
| | | @Resource |
| | | private MesWmReturnSalesLineService returnSalesLineService; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long createReturnSales(MesWmReturnSalesCreateReqDTO reqDTO) { |
| | | MesWmReturnSalesSaveReqVO createReqVO = BeanUtils.toBean(reqDTO, MesWmReturnSalesSaveReqVO.class); |
| | | return returnSalesService.createReturnSales(createReqVO); |
| | | createReqVO.setAfterSaleReturnId(reqDTO.getAfterSaleReturnId()); |
| | | Long returnId = returnSalesService.createReturnSales(createReqVO); |
| | | List<MesWmReturnSalesCreateReqDTO.Item> items = reqDTO.getItems(); |
| | | if (items != null) { |
| | | for (MesWmReturnSalesCreateReqDTO.Item item : items) { |
| | | MesWmReturnSalesLineSaveReqVO line = new MesWmReturnSalesLineSaveReqVO() |
| | | .setReturnId(returnId).setItemId(item.getItemId()).setQuantity(item.getQuantity()) |
| | | .setBatchId(item.getBatchId()).setBatchCode(item.getBatchCode()) |
| | | .setRqcCheckFlag(true).setRemark(item.getRemark()); |
| | | returnSalesLineService.createReturnSalesLine(line); |
| | | } |
| | | } |
| | | return returnId; |
| | | } |
| | | |
| | | @Override |
| | | public void cancelReturnSales(Long id) { |
| | | returnSalesService.cancelReturnSales(id); |
| | | } |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import com.fasterxml.jackson.annotation.JsonIgnore; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.Data; |
| | |
| | | @Schema(description = "ç¼å·", example = "1024") |
| | | private Long id; |
| | | |
| | | /** ä»
å
é¨å®å API åå
¥ï¼å端ä¸è½æå®æ¥æºç³è¯·ã */ |
| | | @JsonIgnore |
| | | private Long afterSaleReturnId; |
| | | |
| | | @Schema(description = "éè´§åç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "RS202603010001") |
| | | @NotBlank(message = "éè´§åç¼å·ä¸è½ä¸ºç©º") |
| | | private String code; |
| | |
| | | */ |
| | | @TableId |
| | | private Long id; |
| | | /** å
³èçå®åéè´§ç³è¯· IDï¼æå·¥éå®éè´§å为空ã */ |
| | | private Long afterSaleReturnId; |
| | | /** |
| | | * éè´§åç¼å· |
| | | */ |
| | |
| | | MPJLambdaWrapperX<MesProTaskDO> query = new MPJLambdaWrapperX<>(); |
| | | query.selectAll(MesProTaskDO.class); |
| | | // å¾
æ¥å·¥å°è´¦ï¼è¿è¡ä¸çä»»å¡ï¼æé¤å·²å®æ/已忶ï¼ï¼ä¸ä»æå¾
æ¥å·¥é |
| | | query.in(MesProTaskDO::getStatus, MesProTaskStatusEnum.PREPARE.getStatus(),MesProTaskStatusEnum.PAUSED.getStatus()); |
| | | query.in(MesProTaskDO::getStatus, MesProTaskStatusEnum.PREPARE.getStatus(), |
| | | MesProTaskStatusEnum.PRODUCING.getStatus(), MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus(), |
| | | MesProTaskStatusEnum.PAUSED.getStatus()); |
| | | query.apply("(quantity - IFNULL(produced_quantity, 0)) > 0"); |
| | | query.likeIfPresent(MesProTaskDO::getCode, reqVO.getCode()) |
| | | .likeIfPresent(MesProTaskDO::getName, reqVO.getName()) |
| | |
| | | .eq(MesProTaskDO::getId, id) |
| | | .setSql("produced_quantity = IFNULL(produced_quantity, 0) + " + incrProducedQuantity) |
| | | .setSql("qualify_quantity = IFNULL(qualify_quantity, 0) + " + incrQualifyQuantity) |
| | | .setSql("unqualify_quantity = IFNULL(unqualify_quantity, 0) + " + incrUnqualifyQuantity)); |
| | | .setSql("unqualify_quantity = IFNULL(unqualify_quantity, 0) + " + incrUnqualifyQuantity) |
| | | .setSql("status = CASE WHEN IFNULL(produced_quantity, 0) > 0 THEN " |
| | | + MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus() + " ELSE " |
| | | + MesProTaskStatusEnum.PRODUCING.getStatus() + " END") |
| | | .setSql("first_feedback_time = IFNULL(first_feedback_time, NOW())") |
| | | .setSql("last_feedback_time = NOW()")); |
| | | } |
| | | |
| | | } |
| | |
| | | ErrorCode WM_RETURN_SALES_NO_LINE = new ErrorCode(1_040_713_005, "éå®éè´§åè³å°éè¦ä¸æ¡è¡æ°æ®"); |
| | | ErrorCode WM_RETURN_SALES_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_713_006, "éå®éè´§åè¡æ°éä¸æç»æ°éä¸ä¸è´"); |
| | | ErrorCode WM_RETURN_SALES_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_713_007, "éå®éè´§å已宿æå·²åæ¶ï¼æ æ³åæ¶"); |
| | | ErrorCode WM_RETURN_SALES_AFTERSALE_LINKED = new ErrorCode(1_040_713_008, "该éå®éè´§åå
³èå®åç³è¯·ï¼ä¸è½ç´æ¥ä¿®æ¹å头æå é¤ï¼éè¦åæ¶æ¶è¯·èµ°åæ¶æµç¨"); |
| | | ErrorCode WM_RETURN_SALES_RQC_SOURCE_INVALID = new ErrorCode(1_040_713_009, "éè´§æ£éªåæ¥æºè¡ä¸éå®éè´§åä¸ä¸è´ï¼æè¯¥è¡å·²å®ææ£éª"); |
| | | ErrorCode WM_RETURN_SALES_LINE_NOT_EXISTS = new ErrorCode(1_040_713_100, "éå®éè´§åè¡ä¸åå¨"); |
| | | ErrorCode WM_RETURN_SALES_DETAIL_NOT_EXISTS = new ErrorCode(1_040_713_200, "éå®éè´§åæç»ä¸åå¨"); |
| | | ErrorCode WM_RETURN_SALES_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_713_201, "䏿¶æç»æ»æ°éä¸è½è¶
è¿éè´§è¡æ°é"); |
| | |
| | | // 1. æ ¡éªä»»å¡åå¨ |
| | | MesProTaskDO task = validateTaskExists(pauseReqVO.getId()); |
| | | // 2. æ ¡éªç¶ææ¯å¦å
许åå·¥ï¼å¦å¿
é¡»æ¯ çäº§ä¸ æ é¨åå®æï¼ |
| | | if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PREPARE.getStatus())) { |
| | | if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PRODUCING.getStatus(), |
| | | MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus())) { |
| | | throw exception(PRO_TASK_CANNOT_PAUSE); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | // 3. æ¢å¤ä»»å¡ç¶æ |
| | | Integer resumeStatus = MesProTaskStatusEnum.PREPARE.getStatus(); |
| | | Integer resumeStatus = task.getProducedQuantity() != null |
| | | && task.getProducedQuantity().signum() > 0 |
| | | ? MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus() |
| | | : MesProTaskStatusEnum.PRODUCING.getStatus(); |
| | | taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(resumeStatus)); |
| | | |
| | | // 4. æ´æ°ææ°ç䏿¡åå·¥è®°å½ |
| | |
| | | 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; |
| | | import cn.iocoder.yudao.module.mes.api.event.MesReturnSalesProgressEvent; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO; |
| | | import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO; |
| | |
| | | import cn.iocoder.yudao.module.mes.service.wm.batch.MesWmBatchService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.context.annotation.Lazy; |
| | | import org.springframework.context.ApplicationEventPublisher; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | |
| | | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.MD_ITEM_BATCH_REQUIRED; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_SALES_LINE_NOT_EXISTS; |
| | | import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_RETURN_SALES_RQC_SOURCE_INVALID; |
| | | |
| | | /** |
| | | * MES éå®éè´§åè¡ Service å®ç°ç±» |
| | |
| | | @Resource |
| | | @Lazy |
| | | private MesWmReturnSalesDetailService returnSalesDetailService; |
| | | @Resource |
| | | private ApplicationEventPublisher eventPublisher; |
| | | |
| | | @Override |
| | | public Long createReturnSalesLine(MesWmReturnSalesLineSaveReqVO createReqVO) { |
| | |
| | | public void updateReturnSalesLineWhenRqcFinish(Long sourceLineId, Long sourceDocId, Integer checkResult, |
| | | BigDecimal qualifiedQuantity, BigDecimal unqualifiedQuantity) { |
| | | MesWmReturnSalesLineDO sourceLine = validateReturnSalesLineExists(sourceLineId); |
| | | if (sourceDocId != null) { |
| | | MesWmReturnSalesDO sourceDoc = returnSalesService.validateReturnSalesExists(sourceDocId); |
| | | if (!sourceDocId.equals(sourceLine.getReturnId()) |
| | | || !MesWmReturnSalesStatusEnum.CONFIRMED.getStatus().equals(sourceDoc.getStatus()) |
| | | || !MesWmQualityStatusEnum.PENDING.getStatus().equals(sourceLine.getQualityStatus())) { |
| | | throw exception(WM_RETURN_SALES_RQC_SOURCE_INVALID); |
| | | } |
| | | } |
| | | boolean hasUnqualified = unqualifiedQuantity != null && unqualifiedQuantity.compareTo(BigDecimal.ZERO) > 0; |
| | | boolean hasQualified = qualifiedQuantity != null && qualifiedQuantity.compareTo(BigDecimal.ZERO) > 0; |
| | | |
| | |
| | | boolean allInspected = !CollUtil.contains(allLines, |
| | | line -> Objects.equals(line.getQualityStatus(), MesWmQualityStatusEnum.PENDING.getStatus())); |
| | | if (allInspected) { |
| | | returnSalesService.updateReturnSalesStatus(sourceDocId, MesWmReturnSalesStatusEnum.APPROVING.getStatus()); |
| | | MesWmReturnSalesDO returnSales = returnSalesService.validateReturnSalesExists(sourceDocId); |
| | | if (MesWmReturnSalesStatusEnum.CONFIRMED.getStatus().equals(returnSales.getStatus())) { |
| | | returnSalesService.updateReturnSalesStatus(sourceDocId, MesWmReturnSalesStatusEnum.APPROVING.getStatus()); |
| | | if (returnSales.getAfterSaleReturnId() != null) { |
| | | boolean failed = CollUtil.contains(allLines, |
| | | line -> MesWmQualityStatusEnum.FAIL.getStatus().equals(line.getQualityStatus())); |
| | | eventPublisher.publishEvent(new MesReturnSalesProgressEvent(sourceDocId, |
| | | MesReturnSalesProgressEvent.Stage.QUALITY_FINISHED, failed ? "å«ä¸åæ ¼å" : "åæ ¼")); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.mes.service.wm.returnsales; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; |
| | | |
| | | import cn.hutool.core.util.ObjUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | |
| | | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| | | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesImportExcelVO; |
| | | import cn.iocoder.yudao.module.mes.api.event.MesReturnSalesProgressEvent; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesImportRespVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesPageReqVO; |
| | | import cn.iocoder.yudao.module.mes.controller.admin.wm.returnsales.vo.MesWmReturnSalesSaveReqVO; |
| | |
| | | import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.context.ApplicationEventPublisher; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.validation.annotation.Validated; |
| | | |
| | |
| | | private MesMdmItemService mdmItemService; |
| | | @Resource |
| | | private MesWmTransactionService wmTransactionService; |
| | | @Resource |
| | | private ApplicationEventPublisher eventPublisher; |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | |
| | | // 2. æå
¥ä¸»è¡¨ |
| | | MesWmReturnSalesDO returnSales = BeanUtils.toBean(createReqVO, MesWmReturnSalesDO.class); |
| | | returnSales.setAfterSaleReturnId(createReqVO.getAfterSaleReturnId()); |
| | | returnSales.setStatus(MesWmReturnSalesStatusEnum.PREPARE.getStatus()); |
| | | returnSalesMapper.insert(returnSales); |
| | | return returnSales.getId(); |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateReturnSales(MesWmReturnSalesSaveReqVO updateReqVO) { |
| | | // 1.1 æ ¡éªåå¨ + åå¤ä¸ç¶æ |
| | | validateReturnSalesExistsAndPrepare(updateReqVO.getId()); |
| | | MesWmReturnSalesDO existing = validateReturnSalesExistsAndPrepare(updateReqVO.getId()); |
| | | if (existing.getAfterSaleReturnId() != null) { |
| | | throw exception(WM_RETURN_SALES_AFTERSALE_LINKED); |
| | | } |
| | | // 1.2 æ ¡éªå
³èæ°æ® |
| | | validateSaveData(updateReqVO); |
| | | |
| | | // 2. æ´æ°ä¸»è¡¨ |
| | | MesWmReturnSalesDO updateObj = BeanUtils.toBean(updateReqVO, MesWmReturnSalesDO.class); |
| | | updateObj.setAfterSaleReturnId(null); // æ¥æºç³è¯·ä¸å¯éè¿ç¼è¾æ¥å£æ¹ç» |
| | | returnSalesMapper.updateById(updateObj); |
| | | } |
| | | |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteReturnSales(Long id) { |
| | | // 1. æ ¡éªåå¨ + åå¤ä¸ç¶æ |
| | | validateReturnSalesExistsAndPrepare(id); |
| | | MesWmReturnSalesDO returnSales = validateReturnSalesExistsAndPrepare(id); |
| | | if (returnSales.getAfterSaleReturnId() != null) { |
| | | throw exception(WM_RETURN_SALES_AFTERSALE_LINKED); |
| | | } |
| | | |
| | | // 2.1 级èå é¤æç» |
| | | returnSalesDetailService.deleteReturnSalesDetailByReturnId(id); |
| | |
| | | Integer targetStatus = hasPendingQc ? MesWmReturnSalesStatusEnum.CONFIRMED.getStatus() |
| | | : MesWmReturnSalesStatusEnum.APPROVING.getStatus(); |
| | | // 2.2 æäº¤ï¼è稿 -> å¾
æ£éª/å¾
æ§è¡ï¼ |
| | | returnSalesMapper.updateById(new MesWmReturnSalesDO() |
| | | .setId(id).setStatus(targetStatus)); |
| | | if (updateStatusIfCurrent(id, MesWmReturnSalesStatusEnum.PREPARE.getStatus(), targetStatus) != 1) { |
| | | throw exception(WM_RETURN_SALES_STATUS_NOT_PREPARE); |
| | | } |
| | | if (!hasPendingQc && validateReturnSalesExists(id).getAfterSaleReturnId() != null) { |
| | | eventPublisher.publishEvent(new MesReturnSalesProgressEvent(id, |
| | | MesReturnSalesProgressEvent.Stage.QUALITY_FINISHED, "å
æ£")); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | throw exception(WM_RETURN_SALES_NO_LINE); |
| | | } |
| | | // 2. æ§è¡éè´§ï¼å¾
æ§è¡ -> å¾
䏿¶ï¼ |
| | | returnSalesMapper.updateById(new MesWmReturnSalesDO() |
| | | .setId(id).setStatus(MesWmReturnSalesStatusEnum.APPROVED.getStatus())); |
| | | if (updateStatusIfCurrent(id, MesWmReturnSalesStatusEnum.APPROVING.getStatus(), |
| | | MesWmReturnSalesStatusEnum.APPROVED.getStatus()) != 1) { |
| | | throw exception(WM_RETURN_SALES_STATUS_NOT_APPROVING); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | } |
| | | } |
| | | |
| | | // 2. éåæææç»ï¼å建åºåäºå¡ï¼å¢å åºå + è®°å½æµæ°´ï¼ |
| | | // æ¢å ç¶æååè®°åºåæµæ°´ï¼é¿å
两个并å䏿¶è¯·æ±éå¤å
¥åºï¼äºå¡å¤±è´¥ä¼åæ»ç¶æã |
| | | if (updateStatusIfCurrent(id, MesWmReturnSalesStatusEnum.APPROVED.getStatus(), |
| | | MesWmReturnSalesStatusEnum.FINISHED.getStatus()) != 1) { |
| | | throw exception(WM_RETURN_SALES_STATUS_NOT_APPROVED); |
| | | } |
| | | createTransactionList(returnSales); |
| | | |
| | | // 3. æ´æ°éè´§åç¶æ |
| | | returnSalesMapper.updateById(new MesWmReturnSalesDO() |
| | | .setId(id).setStatus(MesWmReturnSalesStatusEnum.FINISHED.getStatus())); |
| | | if (returnSales.getAfterSaleReturnId() != null) { |
| | | eventPublisher.publishEvent(new MesReturnSalesProgressEvent(id, |
| | | MesReturnSalesProgressEvent.Stage.INBOUND_FINISHED, null)); |
| | | } |
| | | } |
| | | |
| | | private void createTransactionList(MesWmReturnSalesDO returnSales) { |
| | |
| | | } |
| | | |
| | | // åæ¶ |
| | | returnSalesMapper.updateById(new MesWmReturnSalesDO() |
| | | .setId(id).setStatus(MesWmReturnSalesStatusEnum.CANCELED.getStatus())); |
| | | if (updateStatusIfCurrent(id, returnSales.getStatus(), MesWmReturnSalesStatusEnum.CANCELED.getStatus()) != 1) { |
| | | throw exception(WM_RETURN_SALES_CANCEL_NOT_ALLOWED); |
| | | } |
| | | if (returnSales.getAfterSaleReturnId() != null) { |
| | | eventPublisher.publishEvent(new MesReturnSalesProgressEvent(id, |
| | | MesReturnSalesProgressEvent.Stage.CANCELED, null)); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | |
| | | @Override |
| | | public void updateReturnSalesStatus(Long id, Integer status) { |
| | | validateReturnSalesExists(id); |
| | | returnSalesMapper.updateById(new MesWmReturnSalesDO().setId(id).setStatus(status)); |
| | | if (updateStatusIfCurrent(id, MesWmReturnSalesStatusEnum.CONFIRMED.getStatus(), status) != 1) { |
| | | throw exception(WM_RETURN_SALES_RQC_SOURCE_INVALID); |
| | | } |
| | | } |
| | | |
| | | private int updateStatusIfCurrent(Long id, Integer currentStatus, Integer targetStatus) { |
| | | return returnSalesMapper.update(null, new LambdaUpdateWrapper<MesWmReturnSalesDO>() |
| | | .eq(MesWmReturnSalesDO::getId, id) |
| | | .eq(MesWmReturnSalesDO::getStatus, currentStatus) |
| | | .set(MesWmReturnSalesDO::getStatus, targetStatus)); |
| | | } |
| | | |
| | | /** |
| | |
| | | primary: master |
| | | datasource: |
| | | master: |
| | | url: jdbc:mysql://192.168.197.128:3306/mom-xgdl?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿æ¥çç¤ºä¾ |
| | | url: jdbc:mysql://localhost:3306/mom-xgdl?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿æ¥çç¤ºä¾ |
| | | username: root |
| | | password: sugaozhixing.cn |
| | | password: 123456 |
| | | |
| | | # Redis é
ç½®ãRedisson é»è®¤çé
置足å¤ä½¿ç¨ï¼ä¸è¬ä¸éè¦è¿è¡è°ä¼ |
| | | data: |
| | | redis: |
| | | host: 192.168.197.128 # å°å |
| | | host: localhost # å°å |
| | | port: 6379 # ç«¯å£ |
| | | database: 0 # æ°æ®åºç´¢å¼ |
| | | password: sugaozhixing.cn |
| | | # password: sugaozhixing.cn |
| | | |
| | | --- #################### 宿¶ä»»å¡ç¸å
³é
ç½® #################### |
| | | |