From cb249acbd7ed7f3bc2afa2bc9bee7d69ac8b5e30 Mon Sep 17 00:00:00 2001
From: chen.lin <1442464845@qq.com>
Date: 星期二, 10 三月 2026 14:22:43 +0800
Subject: [PATCH] 空板自动出库定时任务

---
 rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java |   11 ++++-
 version/db/material_auto_config.sql                                                          |    3 +
 rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java        |    3 +
 rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java         |   39 +++++--------------
 rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java |   61 +++++++++++++++++++++++++++++-
 5 files changed, 84 insertions(+), 33 deletions(-)

diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
index f1dd469..3b01c2f 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/api/service/impl/WcsServiceImpl.java
@@ -141,6 +141,11 @@
         // 楠岃瘉璁惧绔欑偣
         DeviceSite deviceSite = validateDeviceSite(param);
 
+        // 绌烘澘鍏ュ簱锛氫笌闈炵┖鏉垮悓涓�鍏ュ彛锛屼粎涓嶆牎楠岀粍鎵橈紝鍙仛鍒嗛厤搴撲綅銆佸缓浠诲姟銆佹洿鏂板簱浣�
+        if (param.getIoType() != null && param.getIoType().equals(TaskType.TASK_TYPE_EMPITY_IN.type)) {
+            return createInTaskForEmptyPallet(param.getBarcode(), param.getSourceStaNo(), param.getLocType1());
+        }
+
         // 鎻愬墠瀹氫箟 waitPakin / waitPakinItems锛屼緵鍚庣画鍏朵粬鍏ュ簱閫昏緫浣跨敤
         WaitPakin waitPakin = null;
         List<WaitPakinItem> waitPakinItems = Collections.emptyList();
@@ -621,8 +626,8 @@
     }
 
     /**
-     * 绌烘澘鍏ュ簱锛歊CS 鐢宠鏃� full=true锛屾棤闇�缁勬墭锛屽垎閰嶅簱浣嶅苟鍒涘缓 TASK_TYPE_EMPITY_IN 浠诲姟銆�
-     * 闇�鍦ㄨ澶囩珯鐐逛腑閰嶇疆 type=10锛堢┖鏉垮叆搴擄級鐨勭珯鐐硅矾寰勩��
+     * 绌烘澘鍏ュ簱锛氫笌闈炵┖鏉垮悓涓�娴佺▼锛堟牎楠岀珯鐐广�佸垎閰嶅簱浣嶃�佸缓浠诲姟銆佹洿鏂板簱浣嶏級锛屼粎涓嶆牎楠岀粍鎵樸�佷笉鍐欎换鍔℃槑缁嗐�佷笉鏇存柊缁勬墭鐘舵�併��
+     * 鐢� createInTask 鍦� ioType=绌烘澘鏃惰皟鐢紱闇�鍦ㄨ澶囩珯鐐逛腑閰嶇疆 type=10锛堢┖鏉垮叆搴擄級鐨勭珯鐐硅矾寰勩��
      */
     private InTaskMsgDto createInTaskForEmptyPallet(String barcode, String staNo, Integer type) {
         TaskInParam param = new TaskInParam();
@@ -1560,36 +1565,14 @@
         log.info("========== 寮�濮嬬敵璇峰叆搴撲换鍔★紝鍒嗛厤搴撲綅 ==========");
         log.info("鏂欑鐮侊細{}锛屽叆搴撶珯鐐癸細{}锛屽叆搴撶被鍨嬶細{}锛岀┖鏉匡細{}", barcode, staNo, type, full);
 
-        // full=true 鏃惰蛋绌烘澘鍏ュ簱锛堟棤闇�缁勬墭锛夛紱鍚﹀垯璧版櫘閫氬叆搴擄紙闇�缁勬墭鎴栬嚜鍔ㄧ粍鎵橈級
-        if (Boolean.TRUE.equals(full)) {
-            InTaskMsgDto msgDto = createInTaskForEmptyPallet(barcode, staNo, type);
-            JSONObject result = new JSONObject();
-            result.put("locNo", msgDto.getLocNo());
-            result.put("batchNo", msgDto.getWorkNo());
-            result.put("taskNo", msgDto.getWorkNo());
-            return R.ok(result);
-        }
-
-        // 鏋勫缓 TaskInParam 鍙傛暟锛屼笌 /wcs/create/in/task 鎺ュ彛鍙傛暟涓�鑷�
+        // 缁熶竴璧� createInTask锛氱┖鏉�(full=true)浠呬笉鏍¢獙缁勬墭锛屼粛鏍¢獙绔欑偣銆佸垎閰嶅簱浣嶃�佸缓浠诲姟锛涢潪绌烘澘闇�缁勬墭
         TaskInParam param = new TaskInParam();
         param.setBarcode(barcode);
         param.setSourceStaNo(staNo);
-        param.setIoType(TaskType.TASK_TYPE_IN.type); // 鍏ュ簱绫诲瀷
-        param.setLocType1(type); // 搴撲綅绫诲瀷锛堥珮浣庢娴嬩俊鍙凤級
-        param.setUser(1L); // 榛樿鐢ㄦ埛ID锛屽彲浠ユ牴鎹疄闄呴渶姹傝皟鏁�
+        param.setLocType1(type != null ? type : 1);
+        param.setUser(1L);
+        param.setIoType(Boolean.TRUE.equals(full) ? TaskType.TASK_TYPE_EMPITY_IN.type : TaskType.TASK_TYPE_IN.type);
 
-        // 璋冪敤 createInTask 鏂规硶锛屽垱寤哄畬鏁寸殑鍏ュ簱浠诲姟
-        // 璇ユ柟娉曚細鎵ц浠ヤ笅娴佺▼锛�
-        // 1. 楠岃瘉璁惧绔欑偣
-        // 2. 楠岃瘉缁勬嫋鐘舵��
-        // 3. 妫�鏌ユ槸鍚︽湁鍖归厤鐨勫叆搴撲换鍔★紙鎷f枡/鐩樼偣鍏ュ簱浼氬尮閰嶇姸鎬�199骞舵洿鏂颁负2锛�
-        // 4. 鐢熸垚浠诲姟缂栫爜锛堝鏋滈渶瑕佸垱寤烘柊浠诲姟锛�
-        // 5. 鑾峰彇搴撲綅鍙�
-        // 6. 鍒涘缓骞朵繚瀛樹换鍔★紙濡傛灉闇�瑕佸垱寤烘柊浠诲姟锛�
-        // 7. 鏇存柊搴撲綅鐘舵��
-        // 8. 鑾峰彇骞堕獙璇佺粍鎷栨槑缁�
-        // 9. 鍒涘缓骞朵繚瀛樹换鍔℃槑缁�
-        // 10. 鏇存柊缁勬墭鐘舵��
         InTaskMsgDto msgDto = createInTask(param);
 
         // 鏌ヨ浠诲姟褰撳墠鐘舵��
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java
index 8ed7634..48ae660 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/schedules/MaterialAutoSchedules.java
@@ -3,11 +3,14 @@
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.vincent.rsf.framework.exception.CoolException;
+import com.vincent.rsf.server.manager.controller.params.LocToTaskParams;
 import com.vincent.rsf.server.manager.controller.params.OutStockToTaskParams;
 import com.vincent.rsf.server.manager.controller.params.PakinItem;
 import com.vincent.rsf.server.manager.controller.params.WaitPakinParam;
 import com.vincent.rsf.server.manager.entity.*;
+import com.vincent.rsf.server.common.constant.Constants;
 import com.vincent.rsf.server.manager.enums.AsnExceStatus;
+import com.vincent.rsf.server.manager.enums.LocStsType;
 import com.vincent.rsf.server.manager.enums.OrderType;
 import com.vincent.rsf.server.manager.enums.OrderWorkType;
 import com.vincent.rsf.server.manager.enums.TaskStsType;
@@ -31,10 +34,11 @@
 import java.util.stream.Collectors;
 
 /**
- * 鎸囧畾鐗╂枡鑷姩鍖栧畾鏃朵换鍔★細鍙厤缃墿鏂欑紪鐮佸悗锛�
+ * 鎸囧畾鐗╂枡/绌烘澘鑷姩鍖栧畾鏃朵换鍔★細鍙厤缃悗
  * 1锛夋湁搴撳瓨鏃惰嚜鍔ㄧ敓鎴愬叏鐗堝嚭搴撳崟锛�
  * 2锛夎鐗╂枡鍑哄簱鍗曡嚜鍔ㄤ笅鍙戜换鍔★紱
- * 3锛塕CS 鍏ュ簱閫氱煡鏃讹紙鍙�夛級鑷姩缁勬墭锛屾暟閲忓彲閰嶇疆銆�
+ * 3锛塕CS 鍏ュ簱閫氱煡鏃讹紙鍙�夛級鑷姩缁勬墭锛屾暟閲忓彲閰嶇疆锛�
+ * 4锛夌┖鏉�(D)搴撲綅瀹氭椂鑷姩鐢熸垚绌烘澘鍑哄簱浠诲姟骞朵笅鍙� RCS锛圓UTO_EMPTY_OUT_ENABLED锛夈��
  */
 @Slf4j
 @Component
@@ -229,6 +233,59 @@
     }
 
     /**
+     * 瀹氭椂浠诲姟锛氱┖鏉垮簱瀛樿嚜鍔ㄥ嚭搴擄紙姣� 2 鍒嗛挓锛�
+     * 閰嶇疆锛欰UTO_EMPTY_OUT_ENABLED=true 鏃讹紝鎵弿绌烘澘(D)搴撲綅锛岀敓鎴愮┖鏉垮嚭搴撲换鍔″苟涓嬪彂 RCS锛屾祦绋嬩笌 AUTO_FULL_OUT 瀵瑰簲瀹氭椂浠诲姟涓�鑷淬��
+     */
+    @Scheduled(cron = "0 0/2 * * * ?")
+    @Transactional(rollbackFor = Exception.class)
+    public void autoEmptyOutTask() {
+        Config enabledConfig = configService.getOne(new LambdaQueryWrapper<Config>().eq(Config::getFlag, GlobalConfigCode.AUTO_EMPTY_OUT_ENABLED));
+        if (enabledConfig == null || !Boolean.parseBoolean(enabledConfig.getVal())) {
+            return;
+        }
+        List<Loc> emptyLocs = locService.list(new LambdaQueryWrapper<Loc>()
+                .eq(Loc::getUseStatus, LocStsType.LOC_STS_TYPE_D.type));
+        if (emptyLocs.isEmpty()) {
+            return;
+        }
+        List<Task> existingEmptyOut = taskService.list(new LambdaQueryWrapper<Task>()
+                .eq(Task::getTaskType, TaskType.TASK_TYPE_EMPITY_OUT.type)
+                .lt(Task::getTaskStatus, TaskStsType.COMPLETE_OUT.id));
+        Set<String> locCodesInProgress = existingEmptyOut.stream()
+                .map(Task::getOrgLoc).filter(Objects::nonNull).collect(Collectors.toSet());
+        List<Task> created = new ArrayList<>();
+        for (Loc loc : emptyLocs) {
+            if (locCodesInProgress.contains(loc.getCode())) {
+                continue;
+            }
+            try {
+                LocToTaskParams params = new LocToTaskParams();
+                params.setType(Constants.TASK_TYPE_OUT_STOCK_EMPTY)
+                        .setOrgLoc(loc.getCode())
+                        .setSiteNo(DEFAULT_SITE_NO);
+                Task task = locItemService.generateTaskEmpty(params, SYSTEM_USER_ID);
+                created.add(task);
+                locCodesInProgress.add(loc.getCode());
+            } catch (Exception e) {
+                log.warn("[鑷姩绌烘澘鍑哄簱] 搴撲綅 {} 鐢熸垚浠诲姟澶辫触: {}", loc.getCode(), e.getMessage());
+            }
+        }
+        if (!created.isEmpty()) {
+            List<Task> toPublish = created.stream()
+                    .filter(t -> TaskStsType.GENERATE_OUT.id.equals(t.getTaskStatus()))
+                    .collect(Collectors.toList());
+            if (!toPublish.isEmpty()) {
+                try {
+                    taskService.pubTaskToWcs(toPublish);
+                    log.info("[鑷姩绌烘澘鍑哄簱] 宸茬敓鎴愬苟涓嬪彂 {} 涓┖鏉垮嚭搴撲换鍔�", toPublish.size());
+                } catch (Exception e) {
+                    log.error("[鑷姩绌烘澘鍑哄簱] 涓嬪彂 RCS 澶辫触", e);
+                }
+            }
+        }
+    }
+
+    /**
      * 瀹氭椂浠诲姟锛氶厤缃墿鏂欏嚭搴撲换鍔″湪 RCS 鍥炶皟涓� 199锛堝緟纭锛夊悗鑷姩鎷h揣瀹屾垚锛屾棤闇� PDA 蹇�熸嫞璐х‘璁ゅ嵆鍙洿鏂板簱瀛樸��
      * 閰嶇疆锛欰UTO_FULL_OUT_MATNR_CODE锛堢墿鏂欑紪鐮侊紝閰嶇疆浜嗗垯瀵硅鐗╂枡鐢熸晥锛�
      */
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java
index 0b19e52..1932160 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/manager/service/impl/LocItemServiceImpl.java
@@ -26,11 +26,15 @@
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.util.*;
 import java.util.stream.Collectors;
 
 @Service("locItemService")
 public class LocItemServiceImpl extends ServiceImpl<LocItemMapper, LocItem> implements LocItemService {
+
+    private static final BigDecimal FULL_OUT_QTY_TOLERANCE = new BigDecimal("0.000001");
 
     Logger logger = LoggerFactory.getLogger(LocItemServiceImpl.class);
 
@@ -167,12 +171,15 @@
             Double orgQty = locItems.stream().mapToDouble(LocItem::getAnfme).sum();
             List<LocItem> locItemList = listMap.get(key);
             Double outQty = locItemList.stream().mapToDouble(LocItem::getOutQty).sum();
+            BigDecimal orgQtyBd = BigDecimal.valueOf(orgQty).setScale(6, RoundingMode.HALF_UP);
+            BigDecimal outQtyBd = BigDecimal.valueOf(outQty).setScale(6, RoundingMode.HALF_UP);
 
             if (map.getType().equals(Constants.TASK_TYPE_OUT_STOCK)
                     || map.getType().equals(Constants.TASK_TYPE_ORDER_OUT_STOCK)
                     || map.getType().equals(Constants.TASK_TYPE_WAVE_OUT_STOCK)) {
-                if (orgQty.compareTo(outQty) > 0) {
-                    //鎷f枡鍑哄簱 -- 鐩樼偣鍑哄簱
+                // 鍑哄簱閲忚揪鍒板簱浣嶅簱瀛橈紙鍚宸級瑙嗕负鍏ㄧ増鍑哄簱锛岄伩鍏嶆诞鐐硅宸鑷磋鍒や负鎷f枡/閮ㄥ垎鍑哄簱
+                if (orgQtyBd.subtract(outQtyBd).compareTo(FULL_OUT_QTY_TOLERANCE) > 0) {
+                    // 鎷f枡鍑哄簱锛堥儴鍒嗗嚭搴擄級
                     DeviceSite deviceSite = deviceSiteService.getOne(new LambdaQueryWrapper<DeviceSite>()
                             .eq(DeviceSite::getSite, siteNo)
                             .eq(!Objects.isNull(loc.getChannel()),DeviceSite::getChannel, loc.getChannel())
diff --git a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java
index 1dd2027..efed840 100644
--- a/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java
+++ b/rsf-server/src/main/java/com/vincent/rsf/server/system/constant/GlobalConfigCode.java
@@ -53,4 +53,7 @@
     /** 鑷姩缁勬墭鏁伴噺锛堜笌 AUTO_PAKIN_ON_ASN_ENABLED 閰嶅悎锛屾瘡鏉℃槑缁嗙粍鎵樻暟閲忥級 */
     public final static String AUTO_PAKIN_QTY = "AUTO_PAKIN_QTY";
 
+    /** 鏄惁鍚敤锛氬畾鏃惰嚜鍔ㄧ敓鎴愮┖鏉垮嚭搴撲换鍔″苟涓嬪彂 RCS锛堟瘡 2 鍒嗛挓鎵弿 D 绌烘澘搴撲綅锛� */
+    public final static String AUTO_EMPTY_OUT_ENABLED = "AUTO_EMPTY_OUT_ENABLED";
+
 }
diff --git a/version/db/material_auto_config.sql b/version/db/material_auto_config.sql
index 63153fa..6f164a2 100644
--- a/version/db/material_auto_config.sql
+++ b/version/db/material_auto_config.sql
@@ -6,5 +6,6 @@
 (UPPER(UUID()), '鍚敤锛氭湁搴撳瓨鏃惰嚜鍔ㄧ敓鎴愬叏鐗堝嚭搴撳崟', 'AUTO_FULL_OUT_ENABLED', 1, 'false', 'true/false', 1, 0, 1, NULL, NOW(), NULL, NOW(), '闇�閰嶇疆 AUTO_FULL_OUT_MATNR_CODE'),
 (UPPER(UUID()), '鍚敤锛氳鐗╂枡鍑哄簱鍗曡嚜鍔ㄤ笅鍙戜换鍔�', 'AUTO_FULL_OUT_DISPATCH_ENABLED', 1, 'false', 'true/false', 1, 0, 1, NULL, NOW(), NULL, NOW(), '闇�閰嶇疆 AUTO_FULL_OUT_MATNR_CODE'),
 (UPPER(UUID()), '鍚敤锛歊CS鍏ュ簱閫氱煡鏃惰嚜鍔ㄧ粍鎵�', 'AUTO_PAKIN_ON_ASN_ENABLED', 1, 'false', 'true/false', 1, 0, 1, NULL, NOW(), NULL, NOW(), '闇�閰嶇疆 AUTO_FULL_OUT_MATNR_CODE銆丄UTO_PAKIN_QTY'),
-(UPPER(UUID()), '鑷姩缁勬墭鏁伴噺', 'AUTO_PAKIN_QTY', 2, '1', '姣忔潯鍏ュ簱鏄庣粏鑷姩缁勬墭鏁伴噺', 1, 0, 1, NULL, NOW(), NULL, NOW(), '涓� AUTO_PAKIN_ON_ASN_ENABLED 閰嶅悎')
+(UPPER(UUID()), '鑷姩缁勬墭鏁伴噺', 'AUTO_PAKIN_QTY', 2, '1', '姣忔潯鍏ュ簱鏄庣粏鑷姩缁勬墭鏁伴噺', 1, 0, 1, NULL, NOW(), NULL, NOW(), '涓� AUTO_PAKIN_ON_ASN_ENABLED 閰嶅悎'),
+(UPPER(UUID()), '鍚敤锛氬畾鏃惰嚜鍔ㄧ敓鎴愮┖鏉垮嚭搴撲换鍔″苟涓嬪彂RCS', 'AUTO_EMPTY_OUT_ENABLED', 1, 'false', 'true/false锛屾瘡2鍒嗛挓鎵弿绌烘澘(D)搴撲綅骞剁敓鎴愬嚭搴撲换鍔°�佸懠鍙玆CS', 1, 0, 1, NULL, NOW(), NULL, NOW(), '闇�鍦ㄨ澶囩珯鐐归厤缃� type=110 绌烘澘鍑哄簱鐨勭珯鐐癸紙濡�1001锛�')
 ;

--
Gitblit v1.9.1