自动化立体仓库 - WMS系统
1.MQTT传过来的出库单据 中有destinationLocationIds字段该字段在出库完成上报时也需要返回该字段,将现有的出库完成上报destinationLocationIds字段从站点号改成出库单据中的destinationLocationIds
2.MQTT下发的订单没有进仓编号,如果库存中没有进仓编号则将采用订单号
4个文件已修改
233 ■■■■ 已修改文件
src/main/java/com/zy/asrs/service/impl/ExternalTaskFacadeServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/integration/iot/biz/impl/IotInstructionServiceImpl.java 109 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/asrs/service/impl/ExternalTaskFacadeServiceImplTest.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/test/java/com/zy/integration/iot/IotInstructionServiceImplPickTest.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/ExternalTaskFacadeServiceImpl.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.Cools;
import com.core.common.R;
import com.zy.asrs.entity.LocDetl;
import com.zy.asrs.entity.param.MesToCombParam;
import com.zy.asrs.entity.param.OpenOrderPakoutExecuteParam;
import com.zy.asrs.entity.param.OutTaskParam;
@@ -92,8 +93,8 @@
        if (autoConfirm && Cools.isEmpty(param.getStationId())) {
            return R.error("stationId不能为空");
        }
        int countLoc = locDetlService.selectCount(new EntityWrapper<com.zy.asrs.entity.LocDetl>().eq("zpallet", param.getPalletId()));
        if (countLoc == 0) {
        LocDetl locDetl = locDetlService.selectOne(new EntityWrapper<LocDetl>().eq("zpallet", param.getPalletId()));
        if (locDetl == null) {
            return R.error("库存中不存在该托盘:" + param.getPalletId());
        }
@@ -101,15 +102,7 @@
            // 设备直调通常是单托盘出库,没有 ERP 顺序号;0 表示无序,和 /outOrder 的校验语义一致。
            param.setSeq(0);
        }
        if (Cools.isEmpty(param.getBatchSeq())) {
            // batchSeq 是接口原始字段,明细里会保存;实际生成任务时低站点仍按 orderId 作为批次键。
            param.setBatchSeq(param.getOrderId());
        }
        if (autoConfirm && isHighStation(param.getStationId()) && Cools.isEmpty(param.getEntryWmsCode())) {
            // IoT 直调常见为单托盘任务,没有 ERP 进仓编号;用 orderId 作为批次键,
            // 这样既满足高站点订单明细校验,也能让执行后 WrkMast.batchSeq 保持可追溯。
            param.setEntryWmsCode(param.getOrderId());
        }
        fillOutboundEntryWmsCode(param, locDetl);
        // IoT/MQTT 默认只预创建订单。没有站点的明细会被后台生成任务逻辑跳过,
        // 直到人工在出库页面选择站点并执行。
@@ -125,14 +118,17 @@
        return openService.pakoutOrderExecute(executeParam);
    }
    private boolean isHighStation(String stationId) {
        if (Cools.isEmpty(stationId)) {
            return false;
    private void fillOutboundEntryWmsCode(OutTaskParam param, LocDetl locDetl) {
        String entryWmsCode = param.getEntryWmsCode();
        if (Cools.isEmpty(entryWmsCode) && locDetl != null) {
            entryWmsCode = locDetl.getStandby1();
        }
        try {
            return Integer.valueOf(stationId) > 600;
        } catch (NumberFormatException ignored) {
            return false;
        if (Cools.isEmpty(entryWmsCode)) {
            entryWmsCode = param.getOrderId();
        }
        param.setEntryWmsCode(entryWmsCode);
        if (Cools.isEmpty(param.getBatchSeq())) {
            param.setBatchSeq(entryWmsCode);
        }
    }
}
src/main/java/com/zy/integration/iot/biz/impl/IotInstructionServiceImpl.java
@@ -14,7 +14,6 @@
import com.zy.asrs.service.WrkDetlService;
import com.zy.asrs.utils.LocAliasUtils;
import com.zy.integration.iot.biz.IotInstructionService;
import com.zy.iot.config.IotProperties;
import com.zy.iot.constant.IotConstants;
import com.zy.iot.entity.IotFeedbackMessage;
import com.zy.iot.entity.IotInstructionMessage;
@@ -32,7 +31,6 @@
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
@@ -54,8 +52,6 @@
    @Autowired
    private IotPublishRecordService iotPublishRecordService;
    @Autowired
    private IotProperties iotProperties;
    @Autowired
    private IotDbConfigService iotDbConfigService;
    @Autowired
@@ -135,7 +131,6 @@
        OutTaskParam param = new OutTaskParam();
        param.setPalletId(message.getContainerId());
        param.setOrderId(resolveReferenceId(message));
        param.setBatchSeq("amazon"+new Date().getTime());
        param.setSeq(0);
        R result = externalTaskFacadeService.createOutboundTask(param, false);
        if (result == null || !Objects.equals(result.get("code"), 200)) {
@@ -492,16 +487,106 @@
    }
    private IotPublishRecord buildPickOutboundRecord(WrkMast wrkMast, String containerId) {
        List<String> destinationLocationIds = resolvePickCompletionDestinationLocationIds(wrkMast, containerId);
        if (Cools.isEmpty(destinationLocationIds)) {
            log.warn("skip iot pick completion without original destinationLocationIds, wrkNo={}, containerId={}",
                    wrkMast.getWrkNo(), containerId);
            return null;
        }
        IotInstructionMessage payload = new IotInstructionMessage();
        payload.setInstructionId(IotInstructionIdGenerator.generate(String.valueOf(wrkMast.getWrkNo())));
        payload.setContainerId(containerId);
        payload.setSourceLocationId(wrkMast.getSourceLocNo());
        payload.setDestinationLocationIds(Collections.singletonList(resolveRemoteStationId(wrkMast.getStaNo())));
        payload.setDestinationLocationIds(destinationLocationIds);
        if (!Cools.isEmpty(wrkMast.getUserNo())) {
            payload.setReferenceId(wrkMast.getUserNo());
        }
        payload.setCreationTime(System.currentTimeMillis());
        return initOutboundRecord(wrkMast, IotConstants.MESSAGE_TYPE_PICK, iotDbConfigService.getEffectiveTopics().getIngressPick(), payload);
    }
    private List<String> resolvePickCompletionDestinationLocationIds(WrkMast wrkMast, String containerId) {
        IotPublishRecord inboundRecord = findProcessedInboundPickRecord(wrkMast, containerId);
        return inboundRecord == null
                ? Collections.<String>emptyList()
                : parseDestinationLocationIds(inboundRecord.getDestinationLocationIds());
    }
    private IotPublishRecord findProcessedInboundPickRecord(WrkMast wrkMast, String containerId) {
        IotPublishRecord record = findProcessedInboundRecordByWrkNo(wrkMast.getWrkNo(), IotConstants.MESSAGE_TYPE_PICK);
        if (record != null) {
            return record;
        }
        String orderNo = wrkMast.getUserNo();
        if (Cools.isEmpty(orderNo)) {
            orderNo = resolveOrderNo(loadWrkDetls(wrkMast));
        }
        return findProcessedInboundRecordByBusinessKey(IotConstants.MESSAGE_TYPE_PICK, containerId, orderNo);
    }
    private IotPublishRecord findProcessedInboundRecordByWrkNo(Integer wrkNo, String messageType) {
        if (wrkNo == null || Cools.isEmpty(messageType)) {
            return null;
        }
        List<IotPublishRecord> records = iotPublishRecordService.selectList(new EntityWrapper<IotPublishRecord>()
                .eq("direction", IotConstants.DIRECTION_INBOUND)
                .eq("message_type", messageType)
                .eq("process_status", IotConstants.PROCESS_STATUS_PROCESSED)
                .eq("wrk_no", wrkNo)
                .orderBy("message_creation_time", false)
                .orderBy("create_time", false)
                .orderBy("id", false));
        return Cools.isEmpty(records) ? null : records.get(0);
    }
    private IotPublishRecord findProcessedInboundRecordByBusinessKey(String messageType, String containerId, String bizNo) {
        if (Cools.isEmpty(messageType) || Cools.isEmpty(containerId) || Cools.isEmpty(bizNo)) {
            return null;
        }
        IotPublishRecord record = findProcessedInboundRecord(messageType, containerId, "order_no", bizNo);
        if (record != null) {
            return record;
        }
        record = findProcessedInboundRecord(messageType, containerId, "reference_id", bizNo);
        if (record != null) {
            return record;
        }
        return findProcessedInboundRecord(messageType, containerId, "instruction_id", bizNo);
    }
    private IotPublishRecord findProcessedInboundRecord(String messageType, String containerId, String column, String value) {
        List<IotPublishRecord> records = iotPublishRecordService.selectList(new EntityWrapper<IotPublishRecord>()
                .eq("direction", IotConstants.DIRECTION_INBOUND)
                .eq("message_type", messageType)
                .eq("process_status", IotConstants.PROCESS_STATUS_PROCESSED)
                .eq("container_id", containerId)
                .eq(column, value)
                .orderBy("message_creation_time", false)
                .orderBy("create_time", false)
                .orderBy("id", false));
        return Cools.isEmpty(records) ? null : records.get(0);
    }
    private List<String> parseDestinationLocationIds(String destinationLocationIds) {
        if (Cools.isEmpty(destinationLocationIds)) {
            return Collections.emptyList();
        }
        try {
            List<String> parsed = JSON.parseArray(destinationLocationIds, String.class);
            if (Cools.isEmpty(parsed)) {
                return Collections.emptyList();
            }
            List<String> result = new ArrayList<String>();
            for (String destinationLocationId : parsed) {
                if (!Cools.isEmpty(destinationLocationId)) {
                    result.add(destinationLocationId);
                }
            }
            return result;
        } catch (Exception e) {
            log.warn("parse iot destinationLocationIds failed, value={}", destinationLocationIds, e);
            return Collections.emptyList();
        }
    }
    private IotPublishRecord initOutboundRecord(WrkMast wrkMast, String messageType, String publishTopic, IotInstructionMessage payload) {
@@ -541,18 +626,6 @@
            }
        }
        return null;
    }
    private String resolveRemoteStationId(Integer staNo) {
        if (staNo == null) {
            return null;
        }
        for (Map.Entry<String, Integer> entry : iotProperties.getPickStationMappings().entrySet()) {
            if (Objects.equals(entry.getValue(), staNo)) {
                return entry.getKey();
            }
        }
        return String.valueOf(staNo);
    }
    private String resolveAmazonLocationId(String locNo) {
src/test/java/com/zy/asrs/service/impl/ExternalTaskFacadeServiceImplTest.java
@@ -1,6 +1,7 @@
package com.zy.asrs.service.impl;
import com.core.common.R;
import com.zy.asrs.entity.LocDetl;
import com.zy.asrs.entity.param.OutTaskParam;
import com.zy.asrs.service.LocDetlService;
import com.zy.asrs.service.OpenService;
@@ -19,6 +20,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,12 +50,28 @@
    @Test
    void mqttPickPreCreateUsesAppendModeAndKeepsOrderNotExecutable() {
        OutTaskParam param = outTask("PKR1007723398", "GSL010863");
        when(locDetlService.selectCount(any())).thenReturn(1);
        when(locDetlService.selectOne(any())).thenReturn(locDetl(null));
        when(openService.outOrderCreatePakoutOrder(eq(Collections.singletonList(param)), eq(false), eq(true))).thenReturn(R.ok());
        R result = service.createOutboundTask(param, false);
        assertEquals(200, ((Number) result.get("code")).intValue());
        assertEquals("PKR1007723398", param.getEntryWmsCode());
        assertEquals("PKR1007723398", param.getBatchSeq());
        verify(openService).outOrderCreatePakoutOrder(eq(Collections.singletonList(param)), eq(false), eq(true));
    }
    @Test
    void mqttPickPreCreateUsesStockEntryWmsCodeWhenAvailable() {
        OutTaskParam param = outTask("PKR1007723398", "GSL010863");
        when(locDetlService.selectOne(any())).thenReturn(locDetl("ENTRY-001"));
        when(openService.outOrderCreatePakoutOrder(eq(Collections.singletonList(param)), eq(false), eq(true))).thenReturn(R.ok());
        R result = service.createOutboundTask(param, false);
        assertEquals(200, ((Number) result.get("code")).intValue());
        assertEquals("ENTRY-001", param.getEntryWmsCode());
        assertEquals("ENTRY-001", param.getBatchSeq());
        verify(openService).outOrderCreatePakoutOrder(eq(Collections.singletonList(param)), eq(false), eq(true));
    }
@@ -61,15 +79,28 @@
    void autoConfirmKeepsReplaceModeThenExecutesOrder() {
        OutTaskParam param = outTask("PKR1007723398", "GSL010863");
        param.setStationId("601");
        when(locDetlService.selectCount(any())).thenReturn(1);
        when(locDetlService.selectOne(any())).thenReturn(locDetl(null));
        when(openService.outOrderCreatePakoutOrder(eq(Collections.singletonList(param)), eq(true), eq(false))).thenReturn(R.ok());
        when(openService.pakoutOrderExecute(any())).thenReturn(R.ok());
        R result = service.createOutboundTask(param, true);
        assertEquals(200, ((Number) result.get("code")).intValue());
        assertEquals("PKR1007723398", param.getEntryWmsCode());
        assertEquals("PKR1007723398", param.getBatchSeq());
        verify(openService).outOrderCreatePakoutOrder(eq(Collections.singletonList(param)), eq(true), eq(false));
        verify(openService).pakoutOrderExecute(any());
    }
    @Test
    void returnsErrorWhenPalletNotInStock() {
        OutTaskParam param = outTask("PKR1007723398", "GSL010863");
        when(locDetlService.selectOne(any())).thenReturn(null);
        R result = service.createOutboundTask(param, false);
        assertEquals(500, ((Number) result.get("code")).intValue());
        verify(openService, never()).outOrderCreatePakoutOrder(any(), anyBoolean(), anyBoolean());
    }
    private static OutTaskParam outTask(String orderId, String palletId) {
@@ -78,4 +109,10 @@
        param.setPalletId(palletId);
        return param;
    }
    private static LocDetl locDetl(String standby1) {
        LocDetl locDetl = new LocDetl();
        locDetl.setStandby1(standby1);
        return locDetl;
    }
}
src/test/java/com/zy/integration/iot/IotInstructionServiceImplPickTest.java
@@ -2,9 +2,12 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.core.common.R;
import com.zy.asrs.entity.WrkMast;
import com.zy.asrs.entity.param.OutTaskParam;
import com.zy.asrs.service.ExternalTaskFacadeService;
import com.zy.asrs.service.WrkDetlService;
import com.zy.integration.iot.biz.impl.IotInstructionServiceImpl;
import com.zy.iot.constant.IotConstants;
import com.zy.iot.entity.IotInstructionMessage;
@@ -21,6 +24,7 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Arrays;
import java.util.Collections;
import static org.mockito.ArgumentMatchers.any;
@@ -37,6 +41,8 @@
    private IotDbConfigService iotDbConfigService;
    @Mock
    private ExternalTaskFacadeService externalTaskFacadeService;
    @Mock
    private WrkDetlService wrkDetlService;
    private IotInstructionServiceImpl service;
@@ -46,6 +52,7 @@
        ReflectionTestUtils.setField(service, "iotPublishRecordService", iotPublishRecordService);
        ReflectionTestUtils.setField(service, "iotDbConfigService", iotDbConfigService);
        ReflectionTestUtils.setField(service, "externalTaskFacadeService", externalTaskFacadeService);
        ReflectionTestUtils.setField(service, "wrkDetlService", wrkDetlService);
    }
    @Test
@@ -80,6 +87,8 @@
        OutTaskParam outboundParam = outboundParamCaptor.getValue();
        Assertions.assertEquals("PALLET-A001", outboundParam.getPalletId());
        Assertions.assertEquals("OUT-ORDER-001", outboundParam.getOrderId());
        Assertions.assertNull(outboundParam.getBatchSeq());
        Assertions.assertNull(outboundParam.getEntryWmsCode());
        Assertions.assertNull(outboundParam.getStationId());
        ArgumentCaptor<IotPublishRecord> updateCaptor = ArgumentCaptor.forClass(IotPublishRecord.class);
@@ -102,4 +111,46 @@
        Assertions.assertEquals(IotConstants.FEEDBACK_SUCCESS, feedbackPayload.getString("status"));
        Assertions.assertNull(feedbackPayload.getString("errorCode"));
    }
    @Test
    public void shouldReportOriginalDestinationLocationIdsForPickCompletion() {
        WrkMast wrkMast = new WrkMast();
        wrkMast.setWrkNo(3001);
        wrkMast.setIoType(101);
        wrkMast.setBarcode("PALLET-A001");
        wrkMast.setUserNo("OUT-ORDER-001");
        wrkMast.setSourceLocNo("SRC-01");
        wrkMast.setStaNo(66);
        IotPublishRecord inboundRecord = new IotPublishRecord();
        inboundRecord.setDestinationLocationIds(JSON.toJSONString(Arrays.asList("amazon-out-01", "amazon-out-02")));
        IotTopicConfig topics = new IotTopicConfig();
        topics.setIngressPick("ingressPick");
        when(iotDbConfigService.isMqttEnabled()).thenReturn(true);
        when(iotDbConfigService.getEffectiveTopics()).thenReturn(topics);
        when(wrkDetlService.selectList(any(Wrapper.class))).thenReturn(Collections.emptyList());
        when(iotPublishRecordService.selectCount(any(Wrapper.class))).thenReturn(0, 1, 0);
        when(iotPublishRecordService.selectList(any(Wrapper.class)))
                .thenReturn(Collections.emptyList(), Collections.singletonList(inboundRecord));
        service.queueWorkCompletion(wrkMast);
        ArgumentCaptor<IotPublishRecord> recordCaptor = ArgumentCaptor.forClass(IotPublishRecord.class);
        verify(iotPublishRecordService).insert(recordCaptor.capture());
        IotPublishRecord record = recordCaptor.getValue();
        Assertions.assertEquals(IotConstants.DIRECTION_OUTBOUND, record.getDirection());
        Assertions.assertEquals(IotConstants.MESSAGE_TYPE_PICK, record.getMessageType());
        Assertions.assertEquals("ingressPick", record.getPublishTopic());
        Assertions.assertEquals("[\"amazon-out-01\",\"amazon-out-02\"]", record.getDestinationLocationIds());
        JSONObject payload = JSON.parseObject(record.getPublishPayload());
        Assertions.assertEquals("PALLET-A001", payload.getString("containerId"));
        Assertions.assertEquals("SRC-01", payload.getString("sourceLocationId"));
        Assertions.assertEquals("OUT-ORDER-001", payload.getString("referenceId"));
        Assertions.assertEquals("amazon-out-01", payload.getJSONArray("destinationLocationIds").getString(0));
        Assertions.assertEquals("amazon-out-02", payload.getJSONArray("destinationLocationIds").getString(1));
    }
}