自动化立体仓库 - WMS系统
#
lty
4 天以前 e77ac6ecc487be1a298623db832b4b433ba10b45
#
4个文件已添加
5个文件已修改
591 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/mapper/OrderMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/OrderPakinMapper.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/OrderPakinService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/OrderPakinServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/kingdee/AutoTransferScheduler.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/task/kingdee/handler/AutoTransferHandler.java 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/OrderPakinMapper.xml 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/pakStore/ownerTransfer.js 241 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/pakStore/ownerTransfer.html 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/OrderMapper.java
@@ -20,6 +20,7 @@
    List<Order> selectComplete8();
    List<Order> selectComplete10();
    List<Order> selectComplete9();
    int addToLogTable(Order order);
    List<Order> selectorderNoL(@Param("orderNo")String orderNo);
src/main/java/com/zy/asrs/mapper/OrderPakinMapper.java
@@ -19,6 +19,7 @@
    List<OrderPakin> selectComplete8();
    List<OrderPakin> selectComplete9();
    List<OrderPakin> selectComplete10();
    List<OrderPakin> selectComplete66();
    int addToLogTable(OrderPakin order);
src/main/java/com/zy/asrs/service/OrderPakinService.java
@@ -29,6 +29,7 @@
    List<OrderPakin> selectComplete8();
    List<OrderPakin> selectComplete9();
    List<OrderPakin> selectComplete10();
    List<OrderPakin> selectComplete66();
    boolean addToLogTable(OrderPakin order);
src/main/java/com/zy/asrs/service/impl/OrderPakinServiceImpl.java
@@ -187,6 +187,10 @@
        return this.baseMapper.selectComplete10();
    }
    @Override
    public List<OrderPakin> selectComplete66() {
        return this.baseMapper.selectComplete66();
    }
    @Override
src/main/java/com/zy/asrs/task/kingdee/AutoTransferScheduler.java
New file
@@ -0,0 +1,53 @@
package com.zy.asrs.task.kingdee;
import com.core.common.Cools;
import com.zy.asrs.entity.Order;
import com.zy.asrs.entity.OrderPakin;
import com.zy.asrs.service.OrderDetlPakinService;
import com.zy.asrs.service.OrderPakinService;
import com.zy.asrs.service.OrderService;
import com.zy.asrs.task.core.ReturnT;
import com.zy.asrs.task.kingdee.handler.AutoTransferHandler;
import com.zy.asrs.task.kingdee.handler.SaveOrderSyncHandler;
import com.zy.common.entity.Parameter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class AutoTransferScheduler {
    @Autowired
    private SaveOrderSyncHandler saveOrderSyncHandler;
    @Autowired
    private OrderService orderService;
    @Autowired
    private OrderPakinService orderPakinService;
    @Autowired
    private OrderDetlPakinService orderDetlPakinService;
    @Value("${erp.switch.ReviewOrderSwitch}")
    private boolean ReviewOrderSwitch;
    @Autowired
    private AutoTransferHandler autoTransferHandler;
    @Scheduled(cron = "0/30 * * * * ? ")
    @Async("orderThreadPool")
    public void completeAndReport(){
        List<OrderPakin> orders = orderPakinService.selectComplete66();
        if(orders.size() > 0){
            for (OrderPakin order : orders) {
                    ReturnT<String> result = autoTransferHandler.start(order);//
                    if (!result.isSuccess()) {
                        log.error("单据[orderNo={}]转换失败", order.getOrderNo());
                    }
            }
        }
    }
}
src/main/java/com/zy/asrs/task/kingdee/handler/AutoTransferHandler.java
New file
@@ -0,0 +1,148 @@
package com.zy.asrs.task.kingdee.handler;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.zy.asrs.entity.*;
import com.zy.asrs.service.*;
import com.zy.asrs.service.impl.ErpSecretServiceImpl;
import com.zy.asrs.service.impl.OrderDetlPakinServiceImpl;
import com.zy.asrs.service.impl.OrderDetlServiceImpl;
import com.zy.asrs.service.impl.OrderPakinServiceImpl;
import com.zy.asrs.task.AbstractHandler;
import com.zy.asrs.task.core.ReturnT;
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.List;
@Slf4j
@Service
public class AutoTransferHandler extends AbstractHandler<String> {
    @Autowired
    private OrderService orderService;
    @Autowired
    private ApiLogService apiLogService;
    @Autowired
    private DocTypeService docTypeService;
    @Autowired
    private LoginAuthenticationHandler loginAuthenticationHandler;
    @Autowired
    private ErpSecretServiceImpl erpSecretService;
    @Autowired
    private LocDetlService locDetlService;
    @Autowired
    private MatService matService;
    @Autowired
    private OrderDetlPakinServiceImpl orderDetlPakinService;
    @Autowired
    private OrderDetlServiceImpl orderDetlService;
    @Autowired
    private OrderPakinServiceImpl orderPakinServiceImpl;
    @Transactional(rollbackFor = Exception.class)
    public ReturnT<String> start(OrderPakin order) {
        // 获取该订单下的所有明细
        List<OrderDetlPakin> orderDetlPakins = orderDetlPakinService.selectList(
                new EntityWrapper<OrderDetlPakin>().eq("order_no", order.getOrderNo()));
        // 遍历每个订单明细进行库存转换
        for (OrderDetlPakin orderDetlPakin : orderDetlPakins) {
            double orderQuantity = orderDetlPakin.getAnfme(); // 本明细需要转换的数量
            double remainingQuantity = orderQuantity;        // 剩余未满足的数量
            double convertedQuantity = 0.0;                  // 已成功转换的数量
            log.info("开始处理订单明细: 订单号 = {}, 物料号 = {}, 需要转换数量 = {}",
                    order.getOrderNo(), orderDetlPakin.getMatnr(), orderQuantity);
            // 查询符合条件的可用库存:同一物料 + box_type3 以 HDU25 开头
            List<LocDetl> locDetls = locDetlService.selectList(
                    new EntityWrapper<LocDetl>()
                            .eq("matnr", orderDetlPakin.getMatnr())
                            .like("box_type3", "HDU25%")
                            .orderBy("anfme") // 可根据业务调整排序(如先进先出)
            );
            if (locDetls.isEmpty()) {
                log.error("无符合条件的库存: 订单 {} 物料 {}", order.getOrderNo(), orderDetlPakin.getMatnr());
                throw new IllegalArgumentException("无符合条件的库存满足订单 " + order.getOrderNo() +
                        " 的物料 " + orderDetlPakin.getMatnr());
            }
            // 逐条消耗库存
            for (LocDetl locDetl : locDetls) {
                if (remainingQuantity <= 0) {
                    break; // 已完全满足需求
                }
                double stockQty = locDetl.getAnfme();
                if (stockQty <= 0) {
                    continue; // 跳过空库存
                }
                // 本次实际消耗的数量
                double consumeQty = Math.min(stockQty, remainingQuantity);
                // 创建一条新的“已分配给订单”的库存记录(完整复制原记录)
                LocDetl allocated = new LocDetl();
                BeanUtils.copyProperties(locDetl, allocated);
                // 设置分配数量 + 覆盖指定字段
                allocated.setAnfme(consumeQty);
                allocated.setStandby1(orderDetlPakin.getStandby1());
                allocated.setStandby2(orderDetlPakin.getStandby2());
                allocated.setStandby3(orderDetlPakin.getStandby3());
                allocated.setBoxType1(orderDetlPakin.getBoxType1());
                allocated.setBoxType2(orderDetlPakin.getBoxType2());
                allocated.setBoxType3(orderDetlPakin.getBoxType3());
                allocated.setOrderNo(orderDetlPakin.getOrderNo());
                // 插入已分配记录
                locDetlService.insert(allocated);
                log.info("已分配库存记录: 位置={}, 数量={}, 剩余需求={}",
                        locDetl.getLocNo(), consumeQty, remainingQuantity - consumeQty);
                // 处理原库存记录:扣减后数量为0则删除,否则更新剩余数量
                double remainingStock = stockQty - consumeQty;
                if (remainingStock <= 0) {
                    // 数量正好耗尽或已为0,直接删除原记录
                    locDetlService.delete(new EntityWrapper<LocDetl>().eq("loc_no",locDetl.getLocNo()).eq("matnr",locDetl.getMatnr()).eq("box_type3",locDetl.getBoxType3()));
                    log.info("原库存已耗尽,删除记录: 位置={}, 原数量={}", locDetl.getLocNo(), stockQty);
                } else {
                    // 仍有剩余,更新数量
                    locDetl.setAnfme(remainingStock);
                    locDetlService.update(locDetl,new EntityWrapper<LocDetl>().eq("loc_no",locDetl.getLocNo()).eq("matnr",locDetl.getMatnr()).eq("box_type3",locDetl.getBoxType3()));
                    log.info("原库存部分消耗,更新剩余数量: 位置={}, 剩余={}", locDetl.getLocNo(), remainingStock);
                }
                // 更新统计量
                convertedQuantity += consumeQty;
                remainingQuantity -= consumeQty;
            }
            // 检查是否完全满足该明细的需求
            if (remainingQuantity > 0) {
                log.error("库存不足: 订单 {} 物料 {} 需求 {},仅转换 {},缺少 {}",
                        order.getOrderNo(), orderDetlPakin.getMatnr(), orderQuantity, convertedQuantity, remainingQuantity);
                throw new IllegalArgumentException("库存不足以满足订单 " + order.getOrderNo() +
                        " 的物料 " + orderDetlPakin.getMatnr() + " 需求,缺少 " + remainingQuantity);
            }
            // 更新订单明细已转换数量
            orderDetlPakin.setQty(convertedQuantity);
            orderDetlPakinService.updateById(orderDetlPakin);
            log.info("订单明细转换完成: 订单号 = {}, 物料号 = {}, 转换数量 = {}",
                    order.getOrderNo(), orderDetlPakin.getMatnr(), convertedQuantity);
        }
        // 所有明细均处理完成,设置订单 settle = 67
        order.setSettle(67L);
        orderPakinServiceImpl.updateById(order);
        log.info("订单全部明细转换完成,设置 settle = 67,订单号 = {}", order.getOrderNo());
        return SUCCESS;
    }
}
src/main/resources/mapper/OrderPakinMapper.xml
@@ -79,7 +79,14 @@
          and status = 1
        order by create_time asc
    </select>
    <select id="selectComplete66" resultMap="BaseResultMap">
        select top 5 *
        from man_order_pakin
        where 1=1
          and settle = 66
          and status = 1
        order by create_time asc
    </select>
    <select id="selectComplete8" resultMap="BaseResultMap">
        select top 5 *
        from man_order_pakin
src/main/webapp/static/js/pakStore/ownerTransfer.js
New file
@@ -0,0 +1,241 @@
var initCountVal = 0;
var initAnfmeVal = "-";
var matCodeData = [];
var currLocNo;
var matCodeLayerIdx;
function getCol() {
    var cols = [
        {fixed: 'left', field: 'count', title: '实际数量', align: 'center', edit:'text', width: 120,  style:'color: blue;font-weight: bold'}
        ,{field: 'anfme', align: 'center',title: '数量'}
        ,{field: 'batch', align: 'center',title: '批号(编辑)', edit: true, style: 'font-weight:bold'}
    ];
    arrRemove(detlCols, "field", "anfme");
    arrRemove(detlCols, "field", "batch");
    cols.push.apply(cols, detlCols);
    cols.push({fixed: 'right', title:'操作', align: 'center', toolbar: '#operate', width:80})
    return cols;
}
layui.config({
    base: baseUrl + "/static/layui/lay/modules/"
}).use(['table','laydate', 'form', 'admin'], function() {
    var table = layui.table;
    var $ = layui.jquery;
    var layer = layui.layer;
    var layDate = layui.laydate;
    var form = layui.form;
    var admin = layui.admin;
    tableIns = table.render({
        elem: '#chooseData',
        data: [],
        even: true,
        limit: 500,
        cellMinWidth: 50,
        toolbar: '#toolbar',
        cols: [getCol()],
        done: function (res, curr, count) {
            limit();
        }
    });
    // 页面修改
    table.on('edit(chooseData)', function (obj) {
        let index = obj.tr.attr("data-index");
        let data = matCodeData[index];
        let modify = true;
        if (obj.field === 'count'){
            let vle = Number(obj.value);
            if (isNaN(vle)) {
                layer.msg("请输入数字", {icon: 2});
                modify = false;
            } else {
                if (vle <= 0) {
                    layer.msg("数量必须大于零", {icon: 2});
                    modify = false;
                }
            }
        }
        if (modify) {
            data[obj.field] = obj.value;
        }
        tableIns.reload({data: matCodeData});
    });
    // 监听头工具栏事件
    table.on('toolbar(chooseData)', function (obj) {
        switch(obj.event) {
            case 'adjust':
                if (isEmpty(currLocNo)) {
                    layer.msg("请先检索库位", {icon: 2})
                    inputTip($("#searchLocNo"));
                    return;
                }
                if (matCodeData.length === 0) {
                    layer.msg("请先添加明细", {icon: 2});
                    return;
                }
                for (var i=0;i<matCodeData.length;i++){
                    if (isNaN(matCodeData[i].count)) {
                        layer.msg("请输入数字", {icon: 2});
                        return;
                    }
                    if (matCodeData[i].count < 0){
                        layer.msg("数量不能小于零", {icon: 2});
                        return;
                    }
                }
                layer.confirm('确定调整'+currLocNo+'库位的明细吗?', {shadeClose: true}, function(){
                    $.ajax({
                        url: baseUrl+"/locDdetl/adjust/start",
                        headers: {'token': localStorage.getItem('token')},
                        data: JSON.stringify({
                            locNo: currLocNo,
                            list: matCodeData
                        }),
                        contentType:'application/json;charset=UTF-8',
                        method: 'POST',
                        async: false,
                        success: function (res) {
                            if (res.code === 200){
                                layer.msg(currLocNo + res.msg, {icon: 1});
                                init(currLocNo)
                            } else if (res.code === 403){
                                top.location.href = baseUrl+"/";
                            }else {
                                layer.msg(res.msg, {icon: 2})
                            }
                        }
                    })
                });
                break;
        }
    });
    // 监听行工具事件
    table.on('tool(chooseData)', function(obj){
        var data = obj.data;
        switch (obj.event) {
            case 'remove':
                let index = obj.tr.attr("data-index");
                matCodeData.splice(index, 1);
                tableIns.reload({data: matCodeData});
                break;
        }
    });
    // 检索事件
    form.on('submit(search)', function (data) {
        let locNo = data.field.loc_no;
        if (locNo === "") {
            inputTip($("#searchLocNo"));
            layer.msg("请输入库位号");
            return;
        }
        init(locNo);
    });
    // 重置事件
    form.on('submit(reset)', function (data) {
        reset();
    });
    function init(locNo) {
        http.post(baseUrl + "/locDetl/list/auth", {locNo: locNo,limit: 1000}, function (res) {
            matCodeData = [];
            matCodeData = res.data.records;
            for (var i = 0; i<matCodeData.length; i++) {
                matCodeData[i]["count"] = matCodeData[i]["anfme"];
            }
            locTips(true, locNo);
            tableReload();
        })
    }
    function reset() {
        clearFormVal($('#search-box'));
        matCodeData = [];
        tableReload();
        locTips(false);
    }
    // 重载表格
    function tableReload() {
        tableIns.reload({data: matCodeData});
    }
    // 库位提示框
    function locTips(retrieve, locNo) {
        if (retrieve) {
            http.post(baseUrl+"/locMast/"+locNo+"/auth", null, function (res) {
                let data = res.data;
                if (data != null) {
                    $(".retrieve").show();
                    $("#locMsg").html(locNo + "&nbsp;,库位状态:" + data.locSts$);
                    $('.not-retrieve').hide();
                    currLocNo = locNo;
                } else {
                    layer.msg("请输入有效库位号", {icon: 2});
                    $('.not-retrieve').show();
                    $("#locMsg").html("");
                    $(".retrieve").hide();
                    currLocNo = null;
                    inputTip($("#searchLocNo"));
                }
            })
        } else {
            $('.not-retrieve').show();
            $("#locMsg").html("");
            $(".retrieve").hide();
            currLocNo = null;
        }
    }
    $(document).on('click','#mat-query', function () {
        if (isEmpty(currLocNo)) {
            layer.msg("请先检索库位")
            inputTip($("#searchLocNo"));
            return;
        }
        let loadIndex = layer.msg('请求中...', {icon: 16, shade: 0.01, time: false});
        matCodeLayerIdx = admin.open({
            type: 2,
            title: false,
            closeBtn: false,
            maxmin: false,
            area: ['90%', '85%'],
            shadeClose: true,
            content: 'matQuery.html',
            success: function(layero, index){
                layer.close(loadIndex);
            }
        });
    })
})
// 搜索框空值提示
function inputTip(el) {
    el.css("border-color", "red");
    setTimeout(function () {
        el.css("border-color", "#b8b8b8");
    }, 1000);
}
// 添加表格数据
function addTableData(data) {
    for (let i=0;i<data.length;i++){
        for (let j=0;j<matCodeData.length;j++){
            if (data[i].matnr === matCodeData[j].matnr && data[i].batch === matCodeData[j].batch) {
                data.splice(i, 1);
                break;
            } else {
                data[i]['anfme'] = initAnfmeVal;
                data[i]['count'] = initCountVal;
            }
        }
    }
    matCodeData.push.apply(matCodeData, data);
    tableIns.reload({data: matCodeData});
    layer.close(matCodeLayerIdx);
}
src/main/webapp/views/pakStore/ownerTransfer.html
New file
@@ -0,0 +1,133 @@
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title></title>
  <meta name="renderer" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <link rel="stylesheet" href="../../static/layui/css/layui.css" media="all">
  <link rel="stylesheet" href="../../static/css/admin.css?v=318" media="all">
  <link rel="stylesheet" href="../../static/css/cool.css" media="all">
  <link rel="stylesheet" href="../../static/css/common.css" media="all">
  <style>
    html {
      height: 100%;
      padding: 10px;
      background-color: #f1f1f1;
      box-sizing: border-box;
    }
    body {
      background-color: #fff;
      border-radius: 5px;
      box-shadow: 0 0 3px rgba(0,0,0,.3);
    }
    /* search */
    .layui-card-header {
      border-bottom: none;
    }
    #search-box {
      padding: 30px 0 10px 0;
    }
    #search-box .layui-inline:first-child {
      margin-left: 30px;
    }
    #search-box .layui-inline {
      margin-right: 5px;
    }
    #data-search-btn {
      margin-left: 10px;
      display: inline-block;
    }
    #data-search-btn.layui-btn-container .layui-btn {
      margin-right: 20px;
    }
    /* add */
    .function-area {
      padding: 15px 0 20px 40px;
    }
    .function-btn {
      font-size: 16px;
      padding: 1px 1px 1px 1px;
      width: 120px;
      height: 40px;
      border-color: #2b425b;
      border-radius: 4px;
      border-width: 1px;
      background: none;
      border-style: solid;
      transition: 0.4s;
      cursor: pointer;
    }
    .function-btn:hover {
      background-color: #2b425b;
      color: #fff;
    }
    #mat-query {
      display: none;
    }
    #btn-adjust {
      display: none;
    }
  </style>
</head>
<body style="padding-bottom: 30px">
<!-- 搜索栏 -->
<div id="search-box" class="layui-form layui-card-header">
  <div class="layui-inline">
    <div class="layui-input-inline">
      <input id="searchLocNo" class="layui-input" type="text" name="loc_no" placeholder="库位号" autocomplete="off"  style="height: 45px;border-color: #b8b8b8">
    </div>
  </div>
  <!-- 待添加 -->
  <div id="data-search-btn" class="layui-btn-container layui-form-item" style="display: inline-block">
    <button id="search" class="layui-btn layui-btn-primary layui-btn-radius" lay-submit lay-filter="search">检索</button>
    <button id="reset" class="layui-btn layui-btn-primary layui-btn-radius" lay-submit lay-filter="reset">重置</button>
  </div>
  <!-- 库位提示 -->
  <div style="display: inline-block; font-size: 20px;font-weight: 300">
    <div class="not-retrieve" style="color: #ff0000;font-family: '黑体';">
      请先检索库位
    </div>
    <div class="retrieve" style="display: none;color: #0097ff;font-family: '黑体';">
      当前检索库位:&nbsp; <span id="locMsg" style=""></span>
    </div>
  </div>
</div>
<hr>
<!-- 功能区 -->
<div class="function-area">
  <button id="mat-query" class="function-btn">新增库存</button>
</div>
<!-- 头部 -->
<script type="text/html" id="toolbar">
  <button class="layui-btn layui-btn-lg" id="btn-adjust" lay-event="adjust" style="">调整库存</button>
</script>
<!-- 行 -->
<script type="text/html" id="operate">
  <a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="remove">移除</a>
</script>
<!-- 表格 -->
<table class="layui-table" id="chooseData" lay-filter="chooseData"></table>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/layui/layui.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/cool.js" charset="utf-8"></script>
<script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.js"></script>
<script type="text/javascript" src="../../static/js/pakStore/ownerTransfer.js" charset="utf-8"></script>
</body>
</html>