自动化立体仓库 - WMS系统
44c6df2e7b14b2126b0157b07803ac83047d7829..c32f684ccad74f2df04cac71f55ec4d4ef6d1712
2025-12-27 chen.llin
越库功能单独封装,并且融入入库单新增
c32f68 对比 | 目录
2025-12-27 chen.llin
越库配置yml文件
8db245 对比 | 目录
2025-12-27 chen.llin
越库相关sql
a25ca8 对比 | 目录
4个文件已添加
3个文件已修改
401 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/OrderPakinController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/CrossDockService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/CrossDockServiceImpl.java 301 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/properties/CrossDockProperties.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-prod.yml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sql/crossDock.sql 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/OrderPakinController.java
@@ -12,6 +12,7 @@
import com.zy.asrs.entity.result.WrkTraceVo;
import com.zy.asrs.service.*;
import com.zy.common.model.DetlDto;
import com.zy.common.properties.CrossDockProperties;
import com.zy.common.web.BaseController;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -44,6 +45,10 @@
    private WrkMastLogService wrkMastLogService;
    @Autowired
    private ClientService clientService;
    @Autowired
    private CrossDockService crossDockService;
    @Autowired
    private CrossDockProperties crossDockProperties;
    @RequestMapping(value = "/order/list/pda/page/auth")
    @ManagerAuth
@@ -206,6 +211,13 @@
                }
            }
        }
        // 越库功能:如果是越库入库单,调用越库服务处理
        if (param.getDocType() != null && param.getDocType().equals(crossDockProperties.getInboundDocTypeId())) {
            String outOrderNo = crossDockService.processCrossDockInbound(order, param, getUserId());
            return R.ok("越库入库单创建成功,已自动生成越库出库单:" + outOrderNo);
        }
        return R.ok("订单添加成功");
    }
src/main/java/com/zy/asrs/service/CrossDockService.java
New file
@@ -0,0 +1,32 @@
package com.zy.asrs.service;
import com.zy.asrs.entity.OrderPakin;
import com.zy.asrs.entity.param.OrderDomainParam;
/**
 * 越库服务接口
 * 用于处理越库入库单的创建、库存管理和自动生成越库出库单
 *
 * @author system
 */
public interface CrossDockService {
    /**
     * 处理越库入库单
     *
     * 功能说明:
     * 1. 将入库单状态设置为已上报(跳过ERP上报流程)
     * 2. 更新明细完成数量
     * 3. 在虚拟库位创建库存明细记录
     * 4. 自动生成对应的越库出库单
     * 5. 从虚拟库位扣减库存
     *
     * @param order 入库单对象
     * @param param 订单参数(包含明细信息)
     * @param userId 操作人ID
     * @return 生成的出库单编号
     * @throws com.core.exception.CoolException 处理失败时抛出异常
     */
    String processCrossDockInbound(OrderPakin order, OrderDomainParam param, Long userId);
}
src/main/java/com/zy/asrs/service/impl/CrossDockServiceImpl.java
New file
@@ -0,0 +1,301 @@
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());
        }
    }
}
src/main/java/com/zy/common/properties/CrossDockProperties.java
New file
@@ -0,0 +1,32 @@
package com.zy.common.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * 越库配置属性
 *
 * @author system
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "cross-dock")
public class CrossDockProperties {
    /**
     * 越库入库单类型ID
     */
    private Long inboundDocTypeId = 35L;
    /**
     * 越库出库单类型ID
     */
    private Long outboundDocTypeId = 36L;
    /**
     * 虚拟库位编号
     */
    private String virtualLocationNo = "VIRTUAL";
}
src/main/resources/application-dev.yml
@@ -69,5 +69,13 @@
Agv:
  sendTask: false
# 越库配置
cross-dock:
  # 越库入库单类型ID
  inbound-doc-type-id: 35
  # 越库出库单类型ID
  outbound-doc-type-id: 36
  # 虚拟库位编号
  virtual-location-no: VIRTUAL
src/main/resources/application-prod.yml
@@ -66,3 +66,12 @@
  # 右深库位排号
  doubleLocsRight: 4,8,12,16,20,24,28,32
# 越库配置
cross-dock:
  # 越库入库单类型ID
  inbound-doc-type-id: 35
  # 越库出库单类型ID
  outbound-doc-type-id: 36
  # 虚拟库位编号
  virtual-location-no: VIRTUAL
src/main/resources/sql/crossDock.sql
New file
@@ -0,0 +1,7 @@
-- 越库单据类型
-- 越库入库单
INSERT INTO [dbo].[man_doc_type]([doc_id], [doc_name], [pakin], [pakout], [status], [create_by], [create_time], [update_by], [update_time], [memo]) VALUES (35, N'越库入库单', 1, 0, 1, NULL, GETDATE(), NULL, GETDATE(), N'越库入库,需要走账,记录出入库信息');
-- 越库出库单
INSERT INTO [dbo].[man_doc_type]([doc_id], [doc_name], [pakin], [pakout], [status], [create_by], [create_time], [update_by], [update_time], [memo]) VALUES (36, N'越库出库单', 0, 1, 1, NULL, GETDATE(), NULL, GETDATE(), N'越库出库,需要走账,记录出入库信息');