自动化立体仓库 - WMS系统
zwl
9 天以前 7ec0d6926482a21b38c246ad460ca25cc78d6ffc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package com.zy.asrs.task;
 
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.core.common.R;
import com.zy.api.controller.params.WorkTaskParams;
import com.zy.api.service.WcsApiService;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.service.WrkMastService;
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.handler.WorkMastHandler;
import com.zy.asrs.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
 
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Date;
import java.util.Map;
import java.util.List;
import java.util.Objects;
 
/**
 * Created by vincent on 2020/7/7
 */
@Component
public class WorkMastScheduler {
 
    private static final Logger log = LoggerFactory.getLogger(WorkMastScheduler.class);
 
    @Autowired
    private WcsApiService wcsApiService;
    @Autowired
    private WrkMastService wrkMastService;
    @Autowired
    private WorkMastHandler workMastHandler;
 
    @Scheduled(cron = "0/3 * * * * ? ")
    private void execute(){
        List<WrkMast> wrkMasts = wrkMastService.selectToBeCompleteData();
        if (wrkMasts.isEmpty()) {
            return;
        }
        for (WrkMast wrkMast : wrkMasts) {
            ReturnT<String> returnT = workMastHandler.start(wrkMast);
            if (!returnT.isSuccess()) {
                wrkMast.setUpdMk("X");
                wrkMast.setErrorMemo(returnT.getMsg());
                wrkMast.setErrorTime(new Date());
                if (!wrkMastService.updateById(wrkMast)) {
                    log.error("工作档[workNo={}]标记待处理失败", wrkMast.getWrkNo());
                }
            }
        }
    }
 
    /**
     * 任务自动下发。
     * <p>
     * 调度器只负责从工作档中挑出“当前允许下发”的任务,并将其转换成 WCS 接口需要的报文结构;
     * 出库任务按 userNo -> batchSeq 分层汇总后串行下发,确保同一 userNo 下前一个 batchSeq 完成后再发下一个。
     * <p>
     * 当前批量下发的归并维度是:
     * 1. WCS接口路径(入库/出库/移库不能混发);
     * 2. 出库任务按 work_mast.user_no -> work_mast.batch_seq 分层汇总,并按 batchSeq 自然升序下发。
     *
     * @author Ryan
     * @date 2026/1/10 14:42
     */
    @Scheduled(cron = "0/10 * * * * ? ")
    private void autoPubTasks() {
        // 仅处理待下发/已生成下发号的工作档。
        List<WrkMast> wrkMasts = wrkMastService.selectList(new EntityWrapper<WrkMast>().in("wrk_sts", Arrays.asList(1L, 11L))
                .orderBy("user_no", true)
                .orderBy("batch_seq", true)
                .orderBy("plt_type", true));
        if (wrkMasts.isEmpty()) {
            return;
        }
 
        List<WorkTaskParams> paramsList = new ArrayList<>();
        Map<String, LinkedHashMap<String, List<WorkTaskParams>>> outboundTasksByUserNo = new LinkedHashMap<>();
        for (WrkMast wrkMast : wrkMasts) {
            // 出库类任务(ioType > 100)默认需要 ERP 确认;未确认的任务在这里直接跳过。
            if (wrkMast.getIoType() > 100 && !"Y".equalsIgnoreCase(wrkMast.getPdcType())) {
                continue;
            }
 
            WorkTaskParams params = buildWorkTaskParams(wrkMast);
            if (isOutboundPublishTask(wrkMast)) {
                String userNo = normalizeGroupKey(wrkMast.getUserNo());
                String batchSeq = normalizeGroupKey(wrkMast.getBatchSeq());
                outboundTasksByUserNo
                        .computeIfAbsent(userNo, key -> new LinkedHashMap<>())
                        .computeIfAbsent(batchSeq, key -> new ArrayList<>())
                        .add(params);
            } else {
                paramsList.add(params);
            }
        }
 
        if (!paramsList.isEmpty()) {
            R r = wcsApiService.pubWrksToWcs(paramsList);
            if (r == null || !Objects.equals(r.get("code"), 200)) {
                log.warn("批量下发任务到WCS失败, result={}", r);
            }
        }
 
        if (outboundTasksByUserNo.isEmpty()) {
            return;
        }
 
        for (Map.Entry<String, LinkedHashMap<String, List<WorkTaskParams>>> userEntry : outboundTasksByUserNo.entrySet()) {
            String userNo = userEntry.getKey();
            List<String> batchSeqs = new ArrayList<>(userEntry.getValue().keySet());
            batchSeqs.sort(this::compareBatchSeqNatural);
 
            for (String batchSeq : batchSeqs) {
                String blockingBatchSeq = findFirstUnfinishedOutboundBatchSeq(userNo);
                if (!Objects.equals(batchSeq, blockingBatchSeq)) {
                    log.info("出库批次未完成,暂停后续下发, userNo={}, blockingBatchSeq={}, nextBatchSeq={}",
                            userNo, blockingBatchSeq, batchSeq);
                    break;
                }
 
                List<WorkTaskParams> batchParams = userEntry.getValue().get(batchSeq);
                if (batchParams == null || batchParams.isEmpty()) {
                    continue;
                }
 
                R r = wcsApiService.pubWrksToWcs(batchParams);
                if (r == null || !Objects.equals(r.get("code"), 200)) {
                    log.warn("批量下发出库任务到WCS失败, userNo={}, batchSeq={}, result={}", userNo, batchSeq, r);
                    break;
                }
            }
        }
    }
 
    private WorkTaskParams buildWorkTaskParams(WrkMast wrkMast) {
        // WMS 库位编码转换成 WCS 可识别的库位编码。
        String wcsSourceLocNo = Cools.isEmpty(wrkMast.getSourceLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getSourceLocNo());
        String wcsLocNo = Cools.isEmpty(wrkMast.getLocNo()) ? "" : Utils.WMSLocToWCSLoc(wrkMast.getLocNo());
        WorkTaskParams params = new WorkTaskParams();
 
        // 101: 出库。有序任务才向 WCS 传 batch/batchSeq;seq=0 表示无序,不传这两个字段。
        if (wrkMast.getIoType() == 101) {
            params.setType("out")
                    .setTaskNo(wrkMast.getWrkNo() + "")
                    .setLocNo(wcsSourceLocNo)
                    .setStaNo(String.valueOf(wrkMast.getStaNo()))
                    .setTaskPri(wrkMast.getIoPri().intValue())
                    .setBarcode(wrkMast.getBarcode());
            if (wrkMast.getPltType() != null && wrkMast.getPltType() > 0) {
                params.setBatch(wrkMast.getUserNo())
                        .setBatchSeq(wrkMast.getPltType());
            }
        // 2: 入库。入库接口使用 sourceStaNo + 目标库位。
        } else if (wrkMast.getIoType() == 2 && !Cools.isEmpty(wrkMast.getSourceStaNo())) {
            params.setType("in")
                    .setTaskNo(wrkMast.getWrkNo() + "")
                    .setSourceStaNo(String.valueOf(wrkMast.getSourceStaNo()))
                    .setLocNo(wcsLocNo)
                    .setTaskPri(wrkMast.getIoPri().intValue())
                    .setBarcode(wrkMast.getBarcode());
        // 其余走移库接口,源库位和目标库位都需要带给 WCS。
        } else {
            params.setType("move")
                    .setTaskNo(wrkMast.getWrkNo() + "")
                    .setSourceLocNo(wcsSourceLocNo)
                    .setLocNo(wcsLocNo)
                    .setBarcode(wrkMast.getBarcode());
        }
        return params;
    }
 
    private boolean isOutboundPublishTask(WrkMast wrkMast) {
        return wrkMast != null && Objects.equals(wrkMast.getIoType(), 101);
    }
 
    private String findFirstUnfinishedOutboundBatchSeq(String userNo) {
        EntityWrapper<WrkMast> wrapper = new EntityWrapper<>();
        if (Cools.isEmpty(userNo)) {
            wrapper.isNull("user_no");
        } else {
            wrapper.eq("user_no", userNo);
        }
        wrapper.eq("io_type", 101);
        wrapper.lt("wrk_sts", 14);
        List<WrkMast> rows = wrkMastService.selectList(wrapper);
        if (rows == null || rows.isEmpty()) {
            return null;
        }
        String firstBatchSeq = null;
        for (WrkMast row : rows) {
            String batchSeq = normalizeGroupKey(row.getBatchSeq());
            if (firstBatchSeq == null || compareBatchSeqNatural(batchSeq, firstBatchSeq) < 0) {
                firstBatchSeq = batchSeq;
            }
        }
        return firstBatchSeq;
    }
 
    private int compareBatchSeqNatural(String left, String right) {
        String safeLeft = Cools.isEmpty(left) ? "" : left;
        String safeRight = Cools.isEmpty(right) ? "" : right;
        boolean leftNumeric = isDigits(safeLeft);
        boolean rightNumeric = isDigits(safeRight);
        if (leftNumeric && rightNumeric) {
            BigInteger leftValue = new BigInteger(safeLeft);
            BigInteger rightValue = new BigInteger(safeRight);
            int compare = leftValue.compareTo(rightValue);
            if (compare != 0) {
                return compare;
            }
        }
        return safeLeft.compareTo(safeRight);
    }
 
    private boolean isDigits(String value) {
        if (Cools.isEmpty(value)) {
            return false;
        }
        for (int i = 0; i < value.length(); i++) {
            if (!Character.isDigit(value.charAt(i))) {
                return false;
            }
        }
        return true;
    }
 
    private String normalizeGroupKey(String value) {
        return Cools.isEmpty(value) ? "" : value;
    }
 
}