自动化立体仓库 - WMS系统
chen.llin
2025-12-27 c32f684ccad74f2df04cac71f55ec4d4ef6d1712
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
package com.zy.asrs.service.impl;
 
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.core.common.SnowflakeIdWorker;
import com.core.exception.CoolException;
import com.zy.asrs.entity.*;
import com.zy.asrs.entity.param.OrderDomainParam;
import com.zy.asrs.service.*;
import com.zy.common.properties.CrossDockProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.util.Date;
import java.util.List;
 
/**
 * 越库服务实现类
 * 
 * 功能说明:
 * - 越库入库:货物不进入实际库位,直接通过虚拟库位完成入库和出库流程
 * - 不走外部设备:跳过WCS等外部系统调用
 * - 不走ERP上报:直接设置为已上报状态,跳过ERP同步
 * 
 * @author system
 */
@Slf4j
@Service("crossDockService")
public class CrossDockServiceImpl implements CrossDockService {
 
    @Autowired
    private OrderPakinService orderPakinService;
    @Autowired
    private OrderDetlPakinService orderDetlPakinService;
    @Autowired
    private OrderPakoutService orderPakoutService;
    @Autowired
    private OrderDetlPakoutService orderDetlPakoutService;
    @Autowired
    private DocTypeService docTypeService;
    @Autowired
    private LocDetlService locDetlService;
    @Autowired
    private MatService matService;
    @Autowired
    private ClientService clientService;
    @Autowired
    private SnowflakeIdWorker snowflakeIdWorker;
    @Autowired
    private CrossDockProperties crossDockProperties;
 
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String processCrossDockInbound(OrderPakin order, OrderDomainParam param, Long userId) {
        log.info("开始处理越库入库单,订单号:{}", order.getOrderNo());
        
        Date now = new Date();
        
        // 步骤1:设置入库单为已上报状态(跳过ERP上报流程)
        log.info("步骤1:设置入库单[{}]为已上报状态", order.getOrderNo());
        if (!orderPakinService.updateSettle(order.getId(), 6L, userId)) {
            throw new CoolException("设置入库单为已上报状态失败");
        }
        
        // 步骤2:更新明细完成数量并创建虚拟库位库存
        log.info("步骤2:更新明细完成数量并创建虚拟库位库存");
        List<OrderDetlPakin> orderDetls = orderDetlPakinService.selectList(
            new EntityWrapper<OrderDetlPakin>().eq("order_id", order.getId()));
        
        for (OrderDetlPakin orderDetl : orderDetls) {
            // 2.1 更新完成数量
            orderDetl.setQty(orderDetl.getAnfme());
            orderDetl.setUpdateBy(userId);
            orderDetl.setUpdateTime(now);
            if (!orderDetlPakinService.updateById(orderDetl)) {
                throw new CoolException("更新订单明细完成数量失败,物料:" + orderDetl.getMatnr());
            }
            
            // 2.2 创建或更新虚拟库位库存
            createOrUpdateVirtualLocationStock(order, orderDetl, userId, now);
        }
        
        // 步骤3:获取越库出库单类型
        Long outboundDocTypeId = crossDockProperties.getOutboundDocTypeId();
        log.info("步骤3:获取越库出库单类型,docId={}", outboundDocTypeId);
        DocType crossDockOutDocType = docTypeService.selectById(outboundDocTypeId);
        if (crossDockOutDocType == null) {
            throw new CoolException("越库出库单类型不存在,docId=" + outboundDocTypeId);
        }
        
        // 步骤4:创建越库出库单主档
        log.info("步骤4:创建越库出库单主档");
        String outOrderNo = "CK" + snowflakeIdWorker.nextId();
        OrderPakout outOrder = createCrossDockOutboundOrder(
            order, param, crossDockOutDocType, outOrderNo, userId, now);
        
        if (!orderPakoutService.insert(outOrder)) {
            throw new CoolException("创建越库出库单失败");
        }
        log.info("越库出库单创建成功,出库单号:{}", outOrderNo);
        
        // 步骤5:创建出库单明细并扣减虚拟库位库存
        log.info("步骤5:创建出库单明细并扣减虚拟库位库存");
        for (OrderDetlPakin orderDetl : orderDetls) {
            // 5.1 创建出库单明细
            createOutboundOrderDetail(outOrder, orderDetl, outOrderNo, userId, now);
            
            // 5.2 从虚拟库位扣减库存
            reduceVirtualLocationStock(orderDetl, userId);
        }
        
        log.info("越库入库单处理完成,入库单号:{},出库单号:{}", order.getOrderNo(), outOrderNo);
        return outOrderNo;
    }
 
    /**
     * 创建或更新虚拟库位库存
     * 
     * @param order 入库单
     * @param orderDetl 订单明细
     * @param userId 操作人ID
     * @param now 当前时间
     */
    private void createOrUpdateVirtualLocationStock(OrderPakin order, OrderDetlPakin orderDetl, 
                                                    Long userId, Date now) {
        log.debug("处理虚拟库位库存,物料:{},批次:{},数量:{}", 
            orderDetl.getMatnr(), orderDetl.getBatch(), orderDetl.getAnfme());
        
        // 查询物料信息
        Mat mat = matService.selectByMatnr(orderDetl.getMatnr());
        if (mat == null) {
            throw new CoolException("物料不存在:" + orderDetl.getMatnr());
        }
        
        // 查询虚拟库位是否已有该物料库存
        String virtualLocNo = crossDockProperties.getVirtualLocationNo();
        LocDetl locDetl = locDetlService.selectItem(
            virtualLocNo, orderDetl.getMatnr(), orderDetl.getBatch(), orderDetl.getBrand(),
            orderDetl.getStandby1(), orderDetl.getStandby2(), orderDetl.getStandby3(),
            orderDetl.getBoxType1(), orderDetl.getBoxType2(), orderDetl.getBoxType3());
        
        if (locDetl != null) {
            // 如果已存在,增加数量
            log.debug("虚拟库位已存在该物料库存,增加数量:{}", orderDetl.getAnfme());
            if (!locDetlService.updateAnfme(
                locDetl.getAnfme() + orderDetl.getAnfme(),
                virtualLocNo, orderDetl.getMatnr(), orderDetl.getBatch(), orderDetl.getBrand(),
                orderDetl.getStandby1(), orderDetl.getStandby2(), orderDetl.getStandby3(),
                orderDetl.getBoxType1(), orderDetl.getBoxType2(), orderDetl.getBoxType3())) {
                throw new CoolException("更新虚拟库位库存失败,物料:" + orderDetl.getMatnr());
            }
        } else {
            // 创建新的库存明细
            log.debug("创建新的虚拟库位库存记录");
            locDetl = new LocDetl();
            BeanUtils.copyProperties(mat, locDetl);
            locDetl.setBatch(orderDetl.getBatch());
            locDetl.setBrand(orderDetl.getBrand());
            locDetl.setStandby1(orderDetl.getStandby1());
            locDetl.setStandby2(orderDetl.getStandby2());
            locDetl.setStandby3(orderDetl.getStandby3());
            locDetl.setBoxType1(orderDetl.getBoxType1());
            locDetl.setBoxType2(orderDetl.getBoxType2());
            locDetl.setBoxType3(orderDetl.getBoxType3());
            locDetl.setLocNo(virtualLocNo);
            locDetl.setZpallet("VIRTUAL-" + order.getOrderNo());
            locDetl.setAnfme(orderDetl.getAnfme());
            locDetl.setOrderNo(order.getOrderNo());
            locDetl.setModiUser(userId);
            locDetl.setModiTime(now);
            locDetl.setAppeUser(userId);
            locDetl.setAppeTime(now);
            if (!locDetlService.insert(locDetl)) {
                throw new CoolException("创建虚拟库位库存失败,物料:" + orderDetl.getMatnr());
            }
        }
    }
 
    /**
     * 创建越库出库单主档
     * 
     * @param inOrder 入库单
     * @param param 订单参数
     * @param docType 出库单类型
     * @param outOrderNo 出库单编号
     * @param userId 操作人ID
     * @param now 当前时间
     * @return 出库单对象
     */
    private OrderPakout createCrossDockOutboundOrder(OrderPakin inOrder, OrderDomainParam param,
                                                     DocType docType, String outOrderNo,
                                                     Long userId, Date now) {
        // 获取客户信息
        Client client = clientService.selectOne(
            new EntityWrapper<Client>().eq("name", param.getCstmrName()));
        if (client == null) {
            throw new CoolException("客户不存在:" + param.getCstmrName());
        }
        
        return new OrderPakout(
            String.valueOf(snowflakeIdWorker.nextId()),
            outOrderNo,
            param.getOrderTime(),
            docType.getDocId(),
            null, // itemId
            null, // itemName
            null, // allotItemId
            null, // defNumber
            null, // number
            client.getCode(), // cstmr
            client.getName(), // cstmrName
            null, // tel
            null, // operMemb
            null, // totalFee
            null, // discount
            null, // discountFee
            null, // otherFee
            null, // actFee
            null, // payType
            null, // salesman
            null, // accountDay
            null, // postFeeType
            null, // postFee
            null, // payTime
            null, // sendTime
            null, // shipName
            null, // shipCode
            6L, // settle - 直接设置为已上报状态,跳过ERP上报流程
            1, // status
            userId, // createBy
            now, // createTime
            userId, // updateBy
            now, // updateTime
            "越库出库单,关联入库单:" + inOrder.getOrderNo() // memo
        );
    }
 
    /**
     * 创建出库单明细
     * 
     * @param outOrder 出库单
     * @param inDetl 入库单明细
     * @param outOrderNo 出库单编号
     * @param userId 操作人ID
     * @param now 当前时间
     */
    private void createOutboundOrderDetail(OrderPakout outOrder, OrderDetlPakin inDetl,
                                          String outOrderNo, Long userId, Date now) {
        OrderDetlPakout outDetl = new OrderDetlPakout();
        BeanUtils.copyProperties(inDetl, outDetl);
        outDetl.setId(null); // 清除ID,让数据库自动生成
        outDetl.setOrderId(outOrder.getId());
        outDetl.setOrderNo(outOrderNo);
        outDetl.setQty(inDetl.getAnfme()); // 完成数量等于申请数量
        outDetl.setWorkQty(inDetl.getAnfme());
        outDetl.setCreateBy(userId);
        outDetl.setCreateTime(now);
        outDetl.setUpdateBy(userId);
        outDetl.setUpdateTime(now);
        outDetl.setStatus(1);
        if (!orderDetlPakoutService.insert(outDetl)) {
            throw new CoolException("创建出库单明细失败,物料:" + inDetl.getMatnr());
        }
    }
 
    /**
     * 从虚拟库位扣减库存
     * 
     * @param orderDetl 订单明细
     * @param userId 操作人ID
     */
    private void reduceVirtualLocationStock(OrderDetlPakin orderDetl, Long userId) {
        log.debug("从虚拟库位扣减库存,物料:{},批次:{},数量:{}", 
            orderDetl.getMatnr(), orderDetl.getBatch(), orderDetl.getAnfme());
        
        String virtualLocNo = crossDockProperties.getVirtualLocationNo();
        LocDetl virtualLocDetl = locDetlService.selectItem(
            virtualLocNo, orderDetl.getMatnr(), orderDetl.getBatch(), orderDetl.getBrand(),
            orderDetl.getStandby1(), orderDetl.getStandby2(), orderDetl.getStandby3(),
            orderDetl.getBoxType1(), orderDetl.getBoxType2(), orderDetl.getBoxType3());
        
        if (virtualLocDetl != null) {
            double newQty = virtualLocDetl.getAnfme() - orderDetl.getAnfme();
            // 使用 updateAnfme 方法,当数量<=0时会自动删除记录
            if (!locDetlService.updateAnfme(
                newQty,
                virtualLocNo, orderDetl.getMatnr(), orderDetl.getBatch(), orderDetl.getBrand(),
                orderDetl.getStandby1(), orderDetl.getStandby2(), orderDetl.getStandby3(),
                orderDetl.getBoxType1(), orderDetl.getBoxType2(), orderDetl.getBoxType3())) {
                throw new CoolException("扣减虚拟库位库存失败,物料:" + orderDetl.getMatnr());
            }
            log.debug("虚拟库位库存扣减完成,剩余数量:{}", newQty);
        } else {
            log.warn("虚拟库位未找到对应库存记录,物料:{},批次:{}", 
                orderDetl.getMatnr(), orderDetl.getBatch());
        }
    }
}