自动化立体仓库 - WMS系统
lty
7 小时以前 daac5618fc2322620a82d25603c5b4d15465c5ef
#检验流程
2个文件已添加
17个文件已修改
1268 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/OrderController.java 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/OrderDetlController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/OutController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/LocDetlMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/OrderDetlMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/LocDetlService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/OrderDetlService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/LocDetlServiceImpl.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/OrderDetlServiceImpl.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/common/web/WcsController.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/LocDetlMapper.xml 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/OrderDetlMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/i18n/en.json 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/i18n/zh-cn.json 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/locDetl/locDetl.js 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/order/inspect.js 412 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/static/js/order/order.js 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/order/inspect.html 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/order/order.html 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/OrderController.java
@@ -98,6 +98,184 @@
        return r;
    }
    @RequestMapping(value = "/order/inspect/list/auth")
    @ManagerAuth
    public R inspectList(@RequestParam(required = false) String orderNo){
        // 1. 查找包含“检验”或“Inspection”的 DocType IDs
        List<DocType> docTypes = docTypeService.selectList(new EntityWrapper<DocType>()
                .like("doc_name", "检验")
                .or()
                .like("doc_name", "Inspection"));
        if (Cools.isEmpty(docTypes)) {
            R r = R.ok();
            r.put("data", new ArrayList<>());
            return r;
        }
        List<Long> docTypeIds = new ArrayList<>();
        for (DocType dt : docTypes) {
            docTypeIds.add(dt.getDocId());
        }
        // 2. 查找对应类型的订单
        EntityWrapper<Order> wrapper = new EntityWrapper<>();
        if (!Cools.isEmpty(orderNo)) {
            wrapper.like("order_no", orderNo);
        }
        wrapper.in("doc_type", docTypeIds);
        wrapper.le("settle", 2); // 待处理或处理中
        wrapper.eq("status", 1);
        wrapper.orderBy("create_time", false);
        List<Order> orders = orderService.selectList(wrapper);
        R r = R.ok();
        r.put("data", orders);
        return r;
    }
    @RequestMapping(value = "/order/inspection/matchingLocations/auth")
    @ManagerAuth
    public R queryMatchingLocations(
            @RequestParam(required = false) String poNumber,
            @RequestParam(required = false) String style,
            @RequestParam(required = false) String color,
            @RequestParam(required = false) Integer cartonNumberFrom,
            @RequestParam(required = false) Integer cartonNumberTo) {
        if(Cools.isEmpty(poNumber)){
            return R.error("response.poNumber_no_exists");
        }
        EntityWrapper<LocDetl> wrapper = new EntityWrapper<>();
        if (!Cools.isEmpty(poNumber)) {
            wrapper.eq("sku", poNumber);
        }
        if (!Cools.isEmpty(style)) {
            wrapper.eq("specs", style);
        }
        if (!Cools.isEmpty(color)) {
            wrapper.eq("color", color);
        }
        wrapper.eq("item_num",null);
        List<LocDetl> all = locDetlService.selectList(wrapper);
        List<LocDetl> list = new ArrayList<>();
        if (cartonNumberFrom == null && cartonNumberTo == null) {
            list = all;
        } else {
            for (LocDetl item : all) {
                String batch = item.getBatch();
                if (Cools.isEmpty(batch)) {
                    continue;
                }
                String numStr = batch.replaceAll("[^0-9]", "");
                if (Cools.isEmpty(numStr)) {
                    continue;
                }
                Integer num;
                try {
                    num = Integer.parseInt(numStr);
                } catch (NumberFormatException e) {
                    continue;
                }
                if (cartonNumberFrom != null && num < cartonNumberFrom) {
                    continue;
                }
                if (cartonNumberTo != null && num > cartonNumberTo) {
                    continue;
                }
                list.add(item);
            }
        }
        Map<String, Object> data = new HashMap<>();
        data.put("records", list);
        data.put("total", list.size());
        return R.ok(data);
    }
    @RequestMapping(value = "/order/inspection/create/auth")
    @ManagerAuth(memo = "response.create_inspection_order")
    @Transactional
    public R createInspectionOrder(@RequestBody Map<String, Object> params) {
        String orderNo = (String) params.get("orderNo");
        String buyerShortCode = (String) params.get("buyerShortCode");
        List<Map<String, Object>> records = (List<Map<String, Object>>) params.get("records");
        if (Cools.isEmpty(orderNo) || Cools.isEmpty(records)) {
            return R.error("response.invalid_param");
        }
        Order orderCheck = orderService.selectByNo(orderNo);
        if (orderCheck != null) {
            return R.error("response.order_no_exists");
        }
        Date now = new Date();
        Long userId = getUserId();
        // 查找检验类型的单据
        DocType inspectionDocType = docTypeService.selectOne(new EntityWrapper<DocType>().like("doc_name", "检验").or().like("doc_name", "Inspection"));
        Long docTypeId = (inspectionDocType != null) ? inspectionDocType.getDocId() : null;
        // 使用与 formAdd 相同的完整构造函数确保所有必要字段都被初始化
        Order order = new Order(
                String.valueOf(snowflakeIdWorker.nextId()),    // 编号[非空]
                orderNo,    // 订单编号
                DateUtils.convert(now),    // 单据日期
                docTypeId,    // 单据类型
                null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null,
                1L,    // 订单状态 (待处理)
                1,    // 状态 (正常)
                userId, now, userId, now,
                "Inspection Order for Buyer: " + buyerShortCode    // 备注
        );
        if (!orderService.insert(order)) {
            throw new CoolException("response.save_order_master_failed");
        }
        // 保存所有检索到的记录作为订单明细
        for (Map<String, Object> record : records) {
            OrderDetl detl = new OrderDetl();
            detl.setOrderId(Long.valueOf(order.getId()));
            detl.setOrderNo(order.getOrderNo());
            detl.setMatnr((String) record.get("matnr"));
            detl.setMaktx((String) record.get("maktx"));
            detl.setSpecs((String) record.get("specs"));
            detl.setColor((String) record.get("color"));
            detl.setBatch((String) record.get("batch"));
            detl.setAnfme(Double.valueOf(String.valueOf(record.get("anfme"))));
            detl.setSku((String) record.get("sku"));
            detl.setOrigin((String) record.get("zpallet"));
            detl.setBarcode((String) record.get("barcode"));
            detl.setManu((String) record.get("manu"));
            detl.setManuDate((String) record.get("manuDate"));
            detl.setWorkQty(0.0);
            detl.setQty(0.0);
            detl.setStatus(1);
            detl.setCreateBy(userId);
            detl.setCreateTime(now);
            detl.setUpdateBy(userId);
            detl.setUpdateTime(now);
            if (!orderDetlService.insert(detl)) {
                throw new CoolException("response.save_order_detail_failed");
            }
            LocDetl locDetl = locDetlService.selectOne(new EntityWrapper<LocDetl>().eq("zpallet", detl.getOrigin()).eq("batch",detl.getBatch()));
            if (locDetl != null) {
                if (!locDetlService.updateInspectNumber(detl.getOrigin(),detl.getBatch(),detl.getOrderNo())) {
                    throw new CoolException("response.save_order_detail_failed");
                }
            }
        }
        return R.ok("response.order_add_success");
    }
    @RequestMapping(value = "/order/form/add/auth")
    @ManagerAuth(memo = "response.manual_add_order")
    @Transactional
@@ -374,6 +552,17 @@
    @ManagerAuth(memo = "response.manual_delete_order")
    @Transactional
    public R delete(@RequestParam Long orderId){
        Order order = orderService.selectById(orderId);
        if (order != null) {
            DocType docType = docTypeService.selectById(order.getDocType());
            String docName = docType != null ? String.valueOf(docType.getDocName()) : "";
            if (!Cools.isEmpty(docName)) {
                String dn = docName.toLowerCase();
                if (dn.contains("inspection") || dn.contains("检验")) {
                    locDetlService.clearInspectNumberByOrderNo(order.getOrderNo());
                }
            }
        }
        orderService.remove(orderId);
//        Order order = orderService.selectById(orderId);
//        if (order != null) {
@@ -425,4 +614,25 @@
        return R.ok();
    }
    @RequestMapping(value = "/order/checkInspection/auth")
    @ManagerAuth
    @Transactional
    public R checkInspection(@RequestParam String orderNo, Integer inspectNumber) {
        if (orderNo == null || orderNo.isEmpty()) {
            return R.error("response.order_no_is_empty");
        }
        // 1. 更新库存明细中的检验状态
        locDetlService.updateInspectionStatus(orderNo, inspectNumber);
        // 2. 更新单据主档状态为已完成 (settle = 4)
        Order order = orderService.selectByNo(orderNo);
        if (order != null) {
            order.setSettle(4L);
            orderService.updateById(order);
        }
        return R.ok("response.operation_success");
    }
}
src/main/java/com/zy/asrs/controller/OrderDetlController.java
@@ -58,6 +58,14 @@
        return R.ok(orderDetlService.getPakoutPage(toPage(curr, limit, param, OrderDetl.class)));
    }
    @RequestMapping(value = "/orderDetl/pakout/inspectionList/auth")
    @ManagerAuth
    public R getPakoutInspectionPage(@RequestParam(defaultValue = "1")Integer curr,
                        @RequestParam(defaultValue = "10")Integer limit,
                        @RequestParam Map<String, Object> param){
        return R.ok(orderDetlService.getPakoutInspectionPage(toPage(curr, limit, param, OrderDetl.class)));
    }
    private <T> void convert(Map<String, Object> map, EntityWrapper<T> wrapper){
        for (Map.Entry<String, Object> entry : map.entrySet()){
            String val = String.valueOf(entry.getValue());
src/main/java/com/zy/asrs/controller/OutController.java
@@ -74,7 +74,7 @@
                    List<Integer> staNos = staDescService.queryOutStaNosByLocNo(locDetl.getLocNo(), issued >= locDetl.getAnfme() ? 101 : 103);
                    locDto.setStaNos(staNos);
                    locDtos.add(locDto);
                    exist.add(locDetl.getLocNo());
                    exist.add(locDetl.getLocNo() + "|" + Optional.ofNullable(locDetl.getBatch()).orElse(""));
                    // 剩余待出数量递减
                    issued = issued - locDetl.getAnfme();
                } else {
src/main/java/com/zy/asrs/mapper/LocDetlMapper.java
@@ -23,9 +23,11 @@
    LocDetl selectItem(@Param("locNo")String locNo, @Param("matnr")String matnr, @Param("batch")String batch);
    LocDetl selectItemCarton(@Param("locNo")String locNo, @Param("matnr")String matnr, @Param("batch")String batch,@Param("barcode")String barcode);
    boolean updateInspectNumber(@Param("barcode")String barcode,@Param("batch")String batch,@Param("orderNo")String orderNo);
    @Update("update asr_loc_detl set item_num = NULL where item_num = #{orderNo}")
    boolean clearInspectNumberByOrderNo(@Param("orderNo")String orderNo);
    List<LocDetl> selectItemByLocNo(String locNo);
    boolean updateInspectionStatus(@Param("orderNo")String orderNo,@Param("inspectNumber")Integer inspectNumber);
    int deleteItem(@Param("locNo")String locNo, @Param("matnr")String matnr, @Param("batch")String batch);
    int updateAnfme(@Param("anfme")Double anfme, @Param("locNo")String locNo, @Param("matnr")String matnr, @Param("batch")String batch);
src/main/java/com/zy/asrs/mapper/OrderDetlMapper.java
@@ -21,6 +21,8 @@
    List<OrderDetl> selectWorkingDetls(Long orderId);
    List<OrderDetl> getPakoutPage(Map<String, Object> map);
    List<OrderDetl> getPakoutInspectionPage(Map<String, Object> map);
    Integer getPakoutInspectionPageCount(Map<String, Object> map);
    Integer getPakoutPageCount(Map<String, Object> map);
src/main/java/com/zy/asrs/service/LocDetlService.java
@@ -61,6 +61,7 @@
    List<StockVo> queryStockTotal();
    boolean updateInspectionStatus(String orderNo, Integer inspectNumber);
    Integer sum();
@@ -78,4 +79,6 @@
    List<String> getSameDetlList2(String orderNo,Integer areaId);
    boolean updateInspectNumber(String barcode,String batch,String orderNo);
    boolean clearInspectNumberByOrderNo(String orderNo);
}
src/main/java/com/zy/asrs/service/OrderDetlService.java
@@ -11,6 +11,7 @@
    List<OrderDetl> selectByOrderId(Long orderId);
    Page<OrderDetl> getPakoutPage(Page<OrderDetl> page);
    Page<OrderDetl> getPakoutInspectionPage(Page<OrderDetl> page);
    OrderDetl selectItem(Long orderId, String matnr, String batch);
    OrderDetl selectItem(Long orderId, String matnr, String batch,String cartonLabel);
src/main/java/com/zy/asrs/service/impl/LocDetlServiceImpl.java
@@ -127,6 +127,21 @@
    }
    @Override
    public boolean updateInspectionStatus(String orderNo,Integer inspectNumber) {
        return this.baseMapper.updateInspectionStatus(orderNo,inspectNumber);
    }
    @Override
    public boolean updateInspectNumber(String barcode, String  batch,String orderNo) {
        return this.baseMapper.updateInspectNumber(barcode, batch,orderNo);
    }
    @Override
    public boolean clearInspectNumberByOrderNo(String orderNo) {
        return this.baseMapper.clearInspectNumberByOrderNo(orderNo);
    }
    @Override
    public LocDetl selectItemCarton(String locNo, String matnr, String batch,String barcode) {
        return this.baseMapper.selectItemCarton(locNo, matnr, batch,barcode);
    }
src/main/java/com/zy/asrs/service/impl/OrderDetlServiceImpl.java
@@ -27,6 +27,13 @@
    }
    @Override
    public Page<OrderDetl> getPakoutInspectionPage(Page<OrderDetl> page) {
        page.setRecords(baseMapper.getPakoutInspectionPage(page.getCondition()));
        page.setTotal(baseMapper.getPakoutInspectionPageCount(page.getCondition()));
        return page;
    }
    @Override
    public OrderDetl selectItem(Long orderId, String matnr, String batch) {
        return this.baseMapper.selectItem(orderId, matnr, batch);
    }
src/main/java/com/zy/common/web/WcsController.java
@@ -400,7 +400,8 @@
        WrkDetl wrkDetl = wrkDetlList.get(0);
        findLocNoAttributeVo.setMatnr(wrkDetl.getMatnr());
        findLocNoAttributeVo.setBatch(wrkDetl.getBatch());
//        StartupDto dto = commonService.getLocNo(1, devpNo, matnrs.get(0), batchs.get(0), null, locTypeDto);
        findLocNoAttributeVo.setAreaId(7);
//        StartupDto dto = commonServic9e.getLocNo(1, devpNo, matnrs.get(0), batchs.get(0), null, locTypeDto);
        StartupDto dto = commonService.getLocNoNew(1, devpNo, findLocNoAttributeVo, locTypeDto,barcode);
        if (dto == null) {
            throw new CoolException("response.no_empty_location_found");
src/main/resources/mapper/LocDetlMapper.xml
@@ -254,6 +254,10 @@
            <foreach item="item" collection="locNos" index="index"  separator="," open="(" close=")">
                #{item}
            </foreach>
            and a.loc_no + '|' + isnull(a.batch,'') not in
            <foreach item="item" collection="locNos" index="index"  separator="," open="(" close=")">
                #{item}
            </foreach>
        </if>
        order by
@@ -445,4 +449,13 @@
          and lm.loc_sts = 'F'
        order by ld.appe_time asc
    </select>
    <update id="updateInspectNumber">
        UPDATE asr_loc_detl SET item_num = #{orderNo}
        WHERE batch = #{batch} and zpallet = #{barcode}
    </update>
    <update id="updateInspectionStatus">
        UPDATE asr_loc_detl SET inspect = #{inspectNumber}
        WHERE item_num = #{orderNo}
        </update>
</mapper>
src/main/resources/mapper/OrderDetlMapper.xml
@@ -134,6 +134,36 @@
        <include refid="pakOutPageCondition"></include>
    </select>
    <select id="getPakoutInspectionPage" resultMap="BaseResultMap">
        select * from
        (
        select
        ROW_NUMBER() over (order by mo.create_time desc) as row,
        mod.*
        from man_order_detl mod
        inner join man_order mo on mod.order_id = mo.id
        inner join man_doc_type mdt on mo.doc_type = mdt.doc_id
        where 1=1
        and mo.settle &lt;= 2
        and mo.status = 1
        and mdt.doc_id = 9
        <include refid="pakOutPageCondition"></include>
        ) t where t.row between ((#{pageNumber}-1)*#{pageSize}+1) and (#{pageNumber}*#{pageSize})
    </select>
    <select id="getPakoutInspectionPageCount" parameterType="java.util.Map" resultType="java.lang.Integer">
        select
        count(1)
        from man_order_detl mod
        inner join man_order mo on mod.order_id = mo.id
        inner join man_doc_type mdt on mo.doc_type = mdt.doc_id
        where 1=1
        and mo.settle &lt;= 2
        and mo.status = 1
        and mdt.doc_id = 9
        <include refid="pakOutPageCondition"></include>
    </select>
    <update id="increase">
        update man_order_detl
        set qty = qty + #{qty}
src/main/webapp/static/i18n/en.json
@@ -1,4 +1,4 @@
{
{
    "_comment_common":  "Common Table Columns",
    "_comment_home":  "Home Tab Bar",
    "access_address":  "Access Address",
@@ -74,6 +74,8 @@
    "configuration":  "Configuration",
    "confirm":  "OK",
    "confirm_adjust_location_detail":  "Are you sure to adjust details for location?",
    "confirm_inspect_fail": "Confirm inspection fail?",
    "confirm_inspect_pass": "Confirm inspection pass?",
    "confirm_cancel_erp_order":  "Current task linked to ERP sales order. Cancellation will regenerate outbound task. Continue?",
    "confirm_cancel_work_order":  "Confirm cancel this work order?",
    "confirm_complete_work_order":  "Confirm complete this work order?",
@@ -106,6 +108,26 @@
    "crane_no":  "Crane No",
    "crane_start_time":  "Crane Start Time",
    "create_detail":  "Creation Detail",
    "create_inspection_order":  "Create Inspection Order",
    "buyer_short_code":  "Buyer Short Code",
    "buyer_short_code_placeholder":  "Enter Buyer Short Code",
    "po_number":  "PO Number",
    "po_number_placeholder":  "Enter PO Number",
    "style":  "Style",
    "style_placeholder":  "Enter Style",
    "color_placeholder":  "Enter Color",
    "carton_from":  "Carton From",
    "carton_from_placeholder":  "From",
    "to":  "To",
    "carton_to_placeholder":  "To",
    "po_summary":  "PO Summary",
    "buyer_po":  "Buyer PO",
    "total_styles":  "Total Styles",
    "total_colors":  "Total Colors",
    "total_cartons":  "Total Cartons",
    "total_quantity":  "Total Quantity",
    "matching_locations":  "Matching Locations",
    "qty":  "Qty",
    "create_time":  "Create Time",
    "creator":  "Creator",
    "creator_detail":  "Creator Details",
@@ -222,9 +244,14 @@
    "input_qty_cannot_less_than_working":  "Input quantity cannot be less than working quantity",
    "inspection_reqd":  "Inspection Reqd",
    "inventory_quantity":  "Inventory Qty",
    "inspection_status": "Inspection Status",
    "inspect_status_0": "Pending",
    "inspect_status_1": "Pass",
    "inspect_status_2": "Fail",
    "io_status":  "Transaction Status",
    "io_type_1":  "1.Inbound",
    "io_type_10":  "10.Empty Pallet Inbound",
    "inspection_orders":  "Inspection Orders",
    "io_type_101":  "101.Outbound",
    "io_type_103":  "103.Picking Outbound",
    "io_type_104":  "104.Merge Outbound",
@@ -372,7 +399,7 @@
    "manual_cancel":  "Manual Cancel",
    "manual_complete":  "Manual Complete",
    "manufacturer":  "Manufacturer",
    "mat_code":  "Item Code",
    "mat_code":  "Item Material",
    "mat_code_label":  "Item No.:",
    "mat_multi_select":  "Material - Multi Select",
    "mat_name":  "Item Name",
@@ -405,6 +432,7 @@
    "no_intersection_outbound_station":  "No intersection of outbound stations, cannot batch modify",
    "no_task":  "No Task",
    "normal":  "Normal",
    "locNo":  "location",
    "not_worked":  "Not Worked",
    "one_click_outbound":  "One-click Outbound",
    "operation":  "Operation",
@@ -534,6 +562,7 @@
    "requesting":  "Requesting...",
    "reset":  "Reset",
    "reset_pwd":  "Reset Pwd",
    "order_details":  "Order Details",
    "reset_pwd_success":  "Reset password successful",
    "response.account_disabled":  "Account Disabled",
    "response.account_not_exist":  "Account Not Exist",
@@ -621,6 +650,7 @@
    "response.fetch_outbound_station_failed":  "Failed to fetch outbound station",
    "response.fifo_handling":  "FIFO Handling",
    "response.friday":  "Friday",
    "response.order_no_is_empty":  "order no is empty",
    "response.front_loc_has_goods_forbid_out":  "Front Loc Has Goods Forbid Out",
    "response.front_loc_has_in_task_forbid_out":  "Front Loc Has In Task Forbid Out",
    "response.full_pallet_inbound":  "Full Pallet Inbound",
@@ -1025,7 +1055,7 @@
    "select_template":  "Select Template",
    "select_type":  "Select Type",
    "selection_mode_tip":  "Selection mode enabled, please drag to select on the location map",
    "serial_code":  "Serial Code",
    "serial_code":  "CTN",
    "serial_number":  "No.",
    "settle_1":  "Pending",
    "settle_2":  "Processing",
@@ -1041,8 +1071,8 @@
    "source_location":  "Source Location",
    "source_location_no":  "source location",
    "source_station":  "Source Station",
    "spec":  "Spec",
    "spec_label":  "Spec:",
    "spec":  "Style",
    "spec_label":  "Style label:",
    "standard_crane_whs":  "Standard Crane Whs",
    "start_crane":  "Start Crane",
    "start_end_bay":  "Start/End Bay",
@@ -1262,6 +1292,7 @@
    "导出":  "Export",
    "登录":  "Login",
    "订单出库":  "Order Outbound",
    "检验出库":  "Inspection Outbound",
    "订单系统":  "Order System",
    "订单状态":  "Order Status",
    "堆垛机导出":  "Export Stacker",
src/main/webapp/static/i18n/zh-cn.json
@@ -1,4 +1,4 @@
{
{
    "_comment_common":  "Common Table Columns",
    "_comment_home":  "主页标签栏",
    "_comment_loc_map":  "=== 库位热点图 ===",
@@ -65,6 +65,8 @@
    "complete":  "完成",
    "confirm":  "确定",
    "confirm_adjust_location_detail":  "确定调整库位的明细吗?",
    "confirm_inspect_fail": "确认检验不通过?",
    "confirm_inspect_pass": "确认检验通过?",
    "confirm_cancel_erp_order":  "当前任务关联ERP销售单,取消将重新生成出库作业,是否继续?",
    "confirm_cancel_work_order":  "确认取消该笔工作档?",
    "confirm_complete_work_order":  "确认完成该笔工作档?",
@@ -97,6 +99,26 @@
    "crane_no":  "堆垛机号",
    "crane_start_time":  "堆垛机启动时间",
    "create_detail":  "创建详情",
    "create_inspection_order":  "创建检验单",
    "buyer_short_code":  "买家标签",
    "buyer_short_code_placeholder":  "输入买家标签",
    "po_number":  "PO号",
    "po_number_placeholder":  "输入PO号",
    "style":  "款式",
    "style_placeholder":  "输入款式",
    "color_placeholder":  "输入颜色",
    "carton_from":  "箱号从",
    "carton_from_placeholder":  "起始箱号",
    "to":  "至",
    "carton_to_placeholder":  "结束箱号",
    "po_summary":  "PO 汇总",
    "buyer_po":  "买方PO",
    "total_styles":  "总款式数",
    "total_colors":  "总颜色数",
    "total_cartons":  "总箱数",
    "total_quantity":  "总数量",
    "matching_locations":  "匹配库位",
    "qty":  "数量",
    "create_time":  "创建时间",
    "creator":  "创建者",
    "creator_detail":  "创建者详情",
@@ -199,6 +221,10 @@
    "inventory_quantity":  "库存数量",
    "io_status":  "入出状态",
    "io_type":  "任务类型",
    "inspection_status": "检验状态",
    "inspect_status_0": "待检验",
    "inspect_status_1": "检验通过",
    "inspect_status_2": "检验失败",
    "io_type_1":  "1.入库",
    "io_type_10":  "10.空板入库",
    "io_type_101":  "101.出库",
@@ -227,6 +253,8 @@
    "leader_label":  "上级",
    "level":  "层",
    "license_validity_prefix":  "临时许可证有效期:",
    "inspection_orders": "检验单据",
    "item": "品项",
    "license_validity_suffix":  "天",
    "light_location":  "轻库位",
    "line_item":  "行项目",
@@ -304,6 +332,7 @@
    "order_intro":  "入库通知单:由ERP提供单据编号、类型、单据时间及物料明细,生成入库作业单,为维护系统高可用,用户可自行添加入库通知单数据,完成独立的入库作业。",
    "order_intro_warning":  "手动添加时,请检查单据编号是否在ERP系统中已存在,避免发生数据错误问题。",
    "order_no":  "单据编号",
    "order_details":  "单据明细",
    "order_status":  "订单状态",
    "origin":  "产地",
    "original_item_no":  "原品号",
@@ -593,6 +622,7 @@
    "response.manual_process_work":  "手动处理工作档",
    "response.manual_work_handling":  "手动处理工作档",
    "response.mat_add":  "物料新增",
    "response.order_no_is_empty":  "单据编号为空",
    "response.mat_code_print":  "物料条码打印",
    "response.mat_delete":  "物料删除",
    "response.mat_detail":  "物料详情",
@@ -925,7 +955,7 @@
    "select_template":  "选择模板",
    "select_type":  "选择类型",
    "selection_mode_tip":  "已开启框选模式,请在库位图上拖拽选择",
    "serial_code":  "序列码",
    "serial_code":  "箱号",
    "serial_number":  "序号",
    "settle_1":  "待处理",
    "settle_2":  "作业中",
@@ -1074,6 +1104,7 @@
    "导出":  "导出",
    "登录":  "登录",
    "订单出库":  "订单出库",
    "检验出库":  "检验出库",
    "订单系统":  "订单系统",
    "订单状态":  "订单状态",
    "堆垛机导出":  "堆垛机导出",
src/main/webapp/static/js/locDetl/locDetl.js
@@ -33,7 +33,15 @@
        ,{field: 'deadTime', align: 'center',title: typeof I18n !== 'undefined' ? I18n.t('shelf_life') : 'Shelf Life', hide: true}
        ,{field: 'deadWarn', align: 'center',title: typeof I18n !== 'undefined' ? I18n.t('warning_days') : 'Warning Days', hide: true}
        ,{field: 'source$', align: 'center',title: typeof I18n !== 'undefined' ? I18n.t('make_buy') : 'Make/Buy', hide: true}
        ,{field: 'check$', align: 'center',title: typeof I18n !== 'undefined' ? I18n.t('inspection_reqd') : 'Inspection Reqd', hide: true}
        ,{field: 'inspect', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('inspection_status') : '检验状态', templet: function(d) {
            if (d.inspect === 1) {
                return '<span class="layui-badge layui-bg-green">' + (typeof I18n !== 'undefined' ? I18n.t('inspect_status_1') : '检验通过') + '</span>';
            } else if (d.inspect === 2) {
                return '<span class="layui-badge layui-bg-red">' + (typeof I18n !== 'undefined' ? I18n.t('inspect_status_2') : '检验失败') + '</span>';
            } else {
                return '<span class="layui-badge layui-bg-gray">' + (typeof I18n !== 'undefined' ? I18n.t('inspect_status_0') : '待检验') + '</span>';
            }
        }}
        ,{field: 'danger$', align: 'center',title: typeof I18n !== 'undefined' ? I18n.t('hazardous') : 'Hazardous', hide: true}
    ];
@@ -53,7 +61,7 @@
    var form = layui.form;
    // 数据渲染
    tableIns = table.render({
    var tableIns = table.render({
        elem: '#locDetl',
        headers: {token: localStorage.getItem('token')},
        url: baseUrl+'/locDetl/list/auth',
@@ -104,29 +112,14 @@
    // 监听排序事件
    table.on('sort(locDetl)', function (obj) {
        var searchData = {};
        $.each($('#search-box [name]').serializeArray(), function() {
            searchData[this.name] = this.value;
        });
        searchData['orderByField'] = obj.field;
        searchData['orderByType'] = obj.type;
        tableIns.reload({
            where: searchData,
            initSort: obj,
            where: {
                orderByField: obj.field,
                orderByType: obj.type
            },
            page: {
                curr: 1
            },
            done: function (res, curr, count) {
                if (res.code === 403) {
                    top.location.href = baseUrl+"/";
                }
                if (typeof I18n !== 'undefined') {
                    I18n.updatePage($('.layui-table-tool'));
                    setTimeout(function() {
                        I18n.updateLayuiPagination();
                    }, 50);
                }
                pageCurr=curr;
                limit();
            }
        });
    });
src/main/webapp/static/js/order/inspect.js
New file
@@ -0,0 +1,412 @@
var insTb, insTb2;
layui.config({
    base: baseUrl + "/static/layui/lay/modules/"
}).extend({
    notice: 'notice/notice',
    tableMerge: 'tableMerge'
}).use(['table', 'laydate', 'form', 'util', 'admin', 'notice', 'xmSelect', 'tableMerge', 'tableX'], function(){
    var table = layui.table;
    var $ = layui.jquery;
    var layer = layui.layer;
    var form = layui.form;
    var admin = layui.admin;
    var notice = layui.notice;
    var tableMerge = layui.tableMerge;
    var tableX = layui.tableX;
    /****************************************** 左侧检验订单表 *************************************************/
    function getLeftCols() {
        return [[
            {field: 'orderTime', title: typeof I18n !== 'undefined' ? I18n.t('date') : '日期', width: 100},
            {field: 'orderNo', title: typeof I18n !== 'undefined' ? I18n.t('order_no') : '单据编号', align: 'center'}
        ]];
    }
    var initLeftTable = function() {
        insTb = table.render({
            elem: '#originTable',
            url: baseUrl + '/order/inspect/list/auth',
            headers: {token: localStorage.getItem('token')},
            height: 'full-180',
            page: false,
            request: { pageName: 'curr', pageSize: 'limit' },
            parseData: function (res) {
                return { 'code': res.code, 'msg': res.msg, 'data': res.data };
            },
            response: { statusCode: 200 },
            text: { none: typeof I18n !== 'undefined' ? I18n.t('no_data') : '暂无数据' },
            cols: getLeftCols(),
            done: function (res, curr, count) {
                if (typeof I18n !== 'undefined' && I18n.isReady()) {
                    I18n.updatePage();
                    if (count === 0) { $('.layui-table-empty').text(I18n.t('no_data')); }
                }
                // 默认选中第一行
                $('#originTable+.layui-table-view .layui-table-body tbody>tr:first').trigger('click');
                // 绑定右键一键出库
                tableX.bindCtxMenu('originTable', function (d) {
                    return [{
                        icon: 'layui-icon layui-icon-ok',
                        name: typeof I18n !== 'undefined' ? I18n.t('one_click_outbound') : '一键出库',
                        click: function (d) { autoOut(d.id); }
                    }]
                });
            }
        });
    };
    /****************************************** 右侧订单明细表 *************************************************/
    function getRightCols() {
        return [[
            {type: 'checkbox', merge: 'origin'},
            {field: 'origin', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('pallet_code') : '托盘码', merge: true, width: 140},
            {field: 'matnr', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('mat_code') : '商品编码', width: 140},
            {field: 'maktx', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('mat_name') : '商品名称', width: 180},
            {field: 'batch', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('serial_code') : '批号'},
            {field: 'specs', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('spec') : '规格'},
            {field: 'color', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('color') : '颜色'},
            // {field: 'origin', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('locationNo') : '库位号'},
            {field: 'enableQty', align: 'center', title: typeof I18n !== 'undefined' ? I18n.t('outbound_pending_qty') : '待出库数量', style: 'font-weight: bold'},
            {fixed: 'right', title: typeof I18n !== 'undefined' ? I18n.t('operation') : '操作', align: 'center', toolbar: '#operate', width: 120, merge: 'origin'}
        ]];
    }
    var initRightTable = function() {
        insTb2 = table.render({
            elem: '#orderDetlTable',
            headers: {token: localStorage.getItem('token')},
            url: baseUrl + '/orderDetl/pakout/inspectionList/auth',
            page: true,
            limit: 15,
            height: 'full-180',
            toolbar: '#orderDetToolbar',
            text: { none: typeof I18n !== 'undefined' ? I18n.t('no_data') : '暂无数据' },
            where: { order_id: -1 },
            cols: getRightCols(),
            parseData: function (res) {
                return { 'code': res.code, 'msg': res.msg, 'count': res.data.total, 'data': res.data.records };
            },
            response: { statusCode: 200 },
            done: function(res, curr, count) {
                if (typeof I18n !== 'undefined' && I18n.isReady()) {
                    I18n.updatePage();
                    I18n.updateLayuiPagination();
                }
                tableMerge.render(this);
            }
        });
    };
    // 监听语言切换事件
    $(document).on('i18n:languageChanged', function() {
        if (typeof I18n !== 'undefined') {
            I18n.updatePage();
        }
        if (insTb) {
            insTb.reload({
                cols: getLeftCols(),
                text: { none: typeof I18n !== 'undefined' ? I18n.t('no_data') : '暂无数据' }
            });
        }
        if (insTb2) {
            insTb2.reload({
                cols: getRightCols(),
                text: { none: typeof I18n !== 'undefined' ? I18n.t('no_data') : '暂无数据' }
            });
        }
    });
    // 初始化加载
    if (typeof I18n !== 'undefined' && I18n.isReady()) {
        initLeftTable();
        initRightTable();
    } else {
        $(document).on('i18n:ready', function() {
            initLeftTable();
            initRightTable();
        });
    }
    // 监听左侧行单击
    var currentOrderId = -1;
    table.on('row(originTable)', function (obj) {
        currentOrderId = obj.data.id;
        obj.tr.addClass('layui-table-click').siblings().removeClass('layui-table-click');
        insTb2.reload({ where: { order_id: currentOrderId }, page: { curr: 1 } });
    });
    // 搜索与重置
    form.on('submit(originTableSearch)', function (data) {
        insTb.reload({ where: data.field });
        return false;
    });
    form.on('submit(sensorTbSearch)', function (data) {
        insTb2.reload({ where: data.field, page: { curr: 1 } });
        return false;
    });
    // 表格操作
    table.on('toolbar(orderDetlTable)', function (obj) {
        var checkStatus = table.checkStatus(obj.config.id).data;
        if (obj.event === 'pakoutPreview') {
            if (checkStatus.length === 0) {
                layer.msg(typeof I18n !== 'undefined' ? I18n.t('select_at_least_one_outbound_detail') : '请至少选择一条出库明细', {icon: 2});
                return;
            }
            pakoutPreview(checkStatus.map(d => d.id));
        } else if (obj.event === 'oneClickOut') {
            if (currentOrderId === -1) {
                layer.msg(typeof I18n !== 'undefined' ? I18n.t('please_select_order') : '请先选择订单', {icon: 2});
                return;
            }
            autoOut(currentOrderId);
        } else if (obj.event === 'inspectPass') {
            if (currentOrderId === -1) {
                layer.msg(typeof I18n !== 'undefined' ? I18n.t('please_select_order') : '请先选择订单', {icon: 2});
                return;
            }
            inspectPass(currentOrderId);
        } else if (obj.event === 'inspectFail') {
            if (currentOrderId === -1) {
                layer.msg(typeof I18n !== 'undefined' ? I18n.t('please_select_order') : '请先选择订单', {icon: 2});
                return;
            }
            inspectFail(currentOrderId);
        }
    });
    table.on('tool(orderDetlTable)', function (obj) {
        if (obj.event === 'pakoutPreview') {
            // 获取当前 origin 分组的所有明细 ID
            var origin = obj.data.origin;
            var tableData = table.cache['orderDetlTable'];
            var groupIds = [];
            if (origin) {
                for (var i = 0; i < tableData.length; i++) {
                    if (tableData[i].origin === origin && tableData[i].enableQty > 0) {
                        groupIds.push(tableData[i].id);
                    }
                }
            } else {
                groupIds = [obj.data.id];
            }
            if (groupIds.length === 0) {
                layer.msg(typeof I18n !== 'undefined' ? I18n.t('no_available_qty') : '没有可出库数量', {icon: 2});
                return;
            }
            pakoutPreview(groupIds);
        }
    });
    // 出库预览与确认逻辑
    function pakoutPreview(ids) {
        let loadIndex = layer.load(2);
        $.ajax({
            url: baseUrl + "/out/pakout/preview/auth",
            headers: {'token': localStorage.getItem('token')},
            contentType: 'application/json;charset=UTF-8',
            data: JSON.stringify(ids),
            method: 'POST',
            success: function (res) {
                layer.close(loadIndex);
                if (res.code === 200) {
                    var tableCache;
                    layer.open({
                        type: 1, title: false, closeBtn: false, offset: '50px', area: ['1000px', '600px'], shade: 0.5,
                        btn: [I18n.t('immediate_outbound'), I18n.t('process_later')], btnAlign: 'c',
                        content: $('#pakoutPreviewBox').html(),
                        success: function(layero, index) {
                            I18n.updatePage($(layero));
                            var stoPreTabIdx = table.render({
                                elem: '#stoPreTab', data: res.data, height: 420, page: false, limit: 1000,
                                cols: [[
                                    {field: 'orderNo', title: I18n.t('order_no'), align: 'center'},
                                    {field: 'batch', title: I18n.t('serial_code'), align: 'center'},
                                    {field: 'anfme', title: I18n.t('quantity'), align: 'center', width: 90},
                                    {field: 'locNo', title: I18n.t('location'), align: 'center', width: 120, templet: '#locNoTpl', merge: true},
                                    {field: 'staNos', align: 'center', title: I18n.t('station_outbound'), templet: '#tbBasicTbStaNos', merge: 'locNo'},
                                    {type: 'checkbox', merge: 'locNo'}
                                ]],
                                done: function() {
                                    tableCache = table.cache.stoPreTab;
                                    I18n.updatePage($('.layui-table-view[lay-id="stoPreTab"]'));
                                    form.render('select');
                                    tableMerge.render(this);
                                }
                            });
                            form.on('select(tbBasicTbStaNos)', function (obj) {
                                let idx = obj.othis.parents('tr').attr("data-index");
                                let currentLocNo = tableCache[idx]['locNo'];
                                let selectedStaNo = Number(obj.elem.value);
                                // 同一库位的所有记录同步选择相同的站点
                                for (let i = 0; i < tableCache.length; i++) {
                                    if (tableCache[i]['locNo'] === currentLocNo) {
                                        tableCache[i]['staNo'] = selectedStaNo;
                                        // 同时更新界面上该库位对应的所有下拉框(如果有多个的话)
                                        let $selects = $(layero).find('tr[data-index="' + i + '"] select.order-sta-select');
                                        if ($selects.length > 0) {
                                            $selects.val(obj.elem.value);
                                        }
                                    }
                                }
                                form.render('select');
                            });
                            form.on('submit(batchModifySta)', function (data) {
                                let checkStatus = table.checkStatus('stoPreTab').data;
                                if (checkStatus.length === 0) {
                                    layer.msg(typeof I18n !== 'undefined' ? I18n.t('select_at_least_one_outbound_detail') : '请至少选择一条记录', {icon: 2});
                                    return false;
                                }
                                // 获取第一个选中的有效站点作为基准
                                let baseStaNo = null;
                                for (let i = 0; i < checkStatus.length; i++) {
                                    if (checkStatus[i].staNo) {
                                        baseStaNo = checkStatus[i].staNo;
                                        break;
                                    }
                                }
                                if (!baseStaNo) {
                                    layer.msg('请先为其中一个选中项选择站点', {icon: 2});
                                    return false;
                                }
                                // 应用到所有选中的行,并同步同库位的行
                                for (let i = 0; i < checkStatus.length; i++) {
                                    let targetLocNo = checkStatus[i].locNo;
                                    for (let j = 0; j < tableCache.length; j++) {
                                        if (tableCache[j].locNo === targetLocNo) {
                                            tableCache[j].staNo = baseStaNo;
                                            $(layero).find('tr[data-index="' + j + '"] select.order-sta-select').val(baseStaNo);
                                        }
                                    }
                                }
                                form.render('select');
                                return false;
                            });
                        },
                        yes: function(index) { pakout(tableCache, index); },
                        btn2: function(index) { layer.close(index); }
                    });
                } else {
                    layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 2});
                }
            }
        });
    }
    function pakout(tableCache, layerIndex) {
        notice.msg(typeof I18n !== 'undefined' ? I18n.t('generating_outbound_task') : '正在生成出库任务...', {icon: 4});
        $.ajax({
            url: baseUrl + "/out/pakout/auth",
            headers: {'token': localStorage.getItem('token')},
            contentType: 'application/json;charset=UTF-8',
            data: JSON.stringify(tableCache),
            method: 'POST',
            success: function (res) {
                notice.destroy();
                if (res.code === 200) {
                    layer.close(layerIndex);
                    layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 1});
                    insTb.reload();
                    insTb2.reload({ page: { curr: 1 } });
                } else {
                    layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 2});
                }
            }
        });
    }
    function autoOut(orderId) {
        let loadIndex = layer.msg(typeof I18n !== 'undefined' ? I18n.t('requesting') : '请求中...', {icon: 16, shade: 0.01, time: false});
        $.ajax({
            url: baseUrl + "/out/pakout/orderDetlIds/auth",
            headers: {'token': localStorage.getItem('token')},
            data: { orderId : orderId },
            method: 'POST',
            success: function (res) {
                layer.close(loadIndex);
                if (res.code === 200) { pakoutPreview(res.data); }
                else { layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 2}); }
            }
        });
    }
    function inspectPass(orderId) {
        var data = table.cache['originTable'];
        var orderNo = "";
        for (var i = 0; i < data.length; i++) {
            if (data[i].id == orderId) {
                orderNo = data[i].orderNo;
                break;
            }
        }
        layer.confirm(typeof I18n !== 'undefined' ? I18n.t('confirm_inspect_pass') : '确认检验通过?', {icon: 3}, function (index) {
            layer.close(index);
            let loadIndex = layer.msg(typeof I18n !== 'undefined' ? I18n.t('processing') : '处理中...', {icon: 16, shade: 0.01, time: false});
            $.ajax({
                url: baseUrl + "/order/checkInspection/auth",
                headers: {'token': localStorage.getItem('token')},
                data: { orderNo: orderNo, inspectNumber: 1 },
                method: 'POST',
                success: function (res) {
                    layer.close(loadIndex);
                    if (res.code === 200) {
                        layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 1});
                        insTb.reload();
                        insTb2.reload({ where: { order_id: -1 }, page: { curr: 1 } });
                        currentOrderId = -1;
                    } else {
                        layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 2});
                    }
                }
            });
        });
    }
    function inspectFail(orderId) {
        var data = table.cache['originTable'];
        var orderNo = "";
        for (var i = 0; i < data.length; i++) {
            if (data[i].id == orderId) {
                orderNo = data[i].orderNo;
                break;
            }
        }
        layer.confirm(typeof I18n !== 'undefined' ? I18n.t('confirm_inspect_fail') : '确认检验不通过?', {icon: 3}, function (index) {
            layer.close(index);
            let loadIndex = layer.msg(typeof I18n !== 'undefined' ? I18n.t('processing') : '处理中...', {icon: 16, shade: 0.01, time: false});
            $.ajax({
                url: baseUrl + "/order/checkInspection/auth",
                headers: {'token': localStorage.getItem('token')},
                data: { orderNo: orderNo, inspectNumber: 2 },
                method: 'POST',
                success: function (res) {
                    layer.close(loadIndex);
                    if (res.code === 200) {
                        layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 1});
                        insTb.reload();
                        insTb2.reload({ where: { order_id: -1 }, page: { curr: 1 } });
                        currentOrderId = -1;
                    } else {
                        layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 2});
                    }
                }
            });
        });
    }
    // 暴露函数
    window.autoOut = autoOut;
    window.inspectPass = inspectPass;
    window.inspectFail = inspectFail;
});
src/main/webapp/static/js/order/order.js
@@ -1,7 +1,16 @@
var insTbCount = 0;
var insTb;
    var insLookTb;
    layui.config({
var insLookTb;
function generateInspectionOrderNo(buyerCode) {
    var now = new Date();
    var year = String(now.getFullYear()).slice(-2);
    var code = (buyerCode || '').toUpperCase();
    var seq = String(now.getTime() % 100000).padStart(5, '0');
    return 'INS' + year + code + '/' + seq;
}
layui.config({
    base: baseUrl + "/static/layui/lay/modules/"
}).use(['layer', 'form', 'table', 'util', 'admin', 'xmSelect', 'laydate'], function () {
    var $ = layui.jquery;
@@ -187,6 +196,10 @@
        showEditModel();
    });
    $("#createInspectionBtn").click(function () {
        openInspectionDialog('');
    });
    // 工具条点击事件
    table.on('tool(order)', function (obj) {
        var data = obj.data;
@@ -250,6 +263,151 @@
        }
    });
    function openInspectionDialog(buyerCode) {
        admin.open({
            type: 1,
            title: I18n.t('create_inspection_order'),
            content: $('#inspectionDialog').html(),
            area: '1200px',
            end: function () {
                $(document).off('i18n:languageChanged.inspection');
            },
            success: function (layero, dIndex) {
                I18n.updatePage($(layero));
                $(layero).children('.layui-layer-content').css('overflow', 'visible');
                var $container = $(layero);
                function getMatchingLocationCols() {
                    return [[
                        {type: 'numbers'},
                        {field: 'locNo', title: (typeof I18n !== 'undefined' ? I18n.t('location_no') : 'Location No')},
                        {field: 'specs', title: (typeof I18n !== 'undefined' ? I18n.t('style') : 'Style')},
                        {field: 'color', title: (typeof I18n !== 'undefined' ? I18n.t('color') : 'Color')},
                        {field: 'anfme', title: (typeof I18n !== 'undefined' ? I18n.t('qty') : 'Qty')}
                    ]];
                }
                var inspectionTable = table.render({
                    elem: '#matchingLocationsTable',
                    data: [],
                    page: true,
                    cellMinWidth: 80,
                    cols: getMatchingLocationCols(),
                    text: {none: (typeof I18n !== 'undefined' ? I18n.t('no_data') : 'No Data')},
                    done: function () {
                        I18n.updateLayuiPagination();
                        $(layero).find('.layui-table-view').css('margin', '0');
                    },
                    size: ''
                });
                $(document).on('i18n:languageChanged.inspection', function () {
                    if (typeof I18n !== 'undefined') {
                        I18n.updatePage($container);
                    }
                    inspectionTable.reload({
                        cols: getMatchingLocationCols(),
                        text: {none: (typeof I18n !== 'undefined' ? I18n.t('no_data') : 'No Data')}
                    });
                });
                function updateSummary(records) {
                    if (!records || !records.length) {
                        $container.find('#poSummaryBuyerPo').text('');
                        $container.find('#poSummaryTotalStyles').text(0);
                        $container.find('#poSummaryTotalColors').text(0);
                        $container.find('#poSummaryTotalCartons').text(0);
                        $container.find('#poSummaryTotalQty').text(0);
                        return;
                    }
                    var buyerPo = records[0].sku || '';
                    var styleSet = {};
                    var colorSet = {};
                    var cartonSet = {};
                    var totalQty = 0;
                    for (var i = 0; i < records.length; i++) {
                        var r = records[i];
                        if (r.specs) styleSet[r.specs] = true;
                        if (r.color) colorSet[r.color] = true;
                        if (r.batch) cartonSet[r.batch] = true;
                        if (r.anfme) totalQty += Number(r.anfme) || 0;
                    }
                    $container.find('#poSummaryBuyerPo').text(buyerPo);
                    $container.find('#poSummaryTotalStyles').text(Object.keys(styleSet).length);
                    $container.find('#poSummaryTotalColors').text(Object.keys(colorSet).length);
                    $container.find('#poSummaryTotalCartons').text(Object.keys(cartonSet).length);
                    $container.find('#poSummaryTotalQty').text(totalQty);
                }
                var lastSearchRecords = [];
                form.on('submit(inspectionSearch)', function (data) {
                    var searchParam = data.field || {};
                    $.ajax({
                        url: baseUrl + "/order/inspection/matchingLocations/auth",
                        headers: {'token': localStorage.getItem('token')},
                        data: searchParam,
                        method: 'GET',
                        success: function (res) {
                            if (res.code === 200) {
                                var records = res.data && res.data.records ? res.data.records : (res.data || []);
                                lastSearchRecords = records;
                                inspectionTable.reload({data: records, page: {curr: 1}});
                                updateSummary(records);
                            } else if (res.code === 403) {
                                top.location.href = baseUrl+"/";
                            } else {
                                layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 2});
                            }
                        }
                    });
                    return false;
                });
                $container.find('#inspectionCreateBtn').click(function () {
                    var buyerCodeInput = ($container.find('input[name="buyerShortCode"]').val() || buyerCode || '').trim();
                    if (!buyerCodeInput) {
                        var msg = 'Buyer short code不能为空';
                        if (typeof I18n !== 'undefined') {
                            msg = I18n.t('buyer_short_code') + I18n.t('form_required');
                        }
                        layer.msg(msg, {icon: 2});
                        return;
                    }
                    if (!lastSearchRecords || lastSearchRecords.length === 0) {
                        layer.msg('请先检索出匹配库位数据', {icon: 2});
                        return;
                    }
                    var orderNo = generateInspectionOrderNo(buyerCodeInput);
                    layer.load(2);
                    $.ajax({
                        url: baseUrl + "/order/inspection/create/auth",
                        headers: {'token': localStorage.getItem('token')},
                        contentType: 'application/json;charset=UTF-8',
                        data: JSON.stringify({
                            orderNo: orderNo,
                            buyerShortCode: buyerCodeInput,
                            records: lastSearchRecords
                        }),
                        method: 'POST',
                        success: function (res) {
                            layer.closeAll('loading');
                            if (res.code === 200) {
                                layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 1});
                                layer.close(dIndex);
                                insTb.reload({page: {curr: 1}});
                            } else {
                                layer.msg(typeof I18n !== 'undefined' ? I18n.t(res.msg) : res.msg, {icon: 2});
                            }
                        },
                        error: function() {
                            layer.closeAll('loading');
                            layer.msg(typeof I18n !== 'undefined' ? I18n.t('load_failed') : 'Load failed', {icon: 2});
                        }
                    });
                });
            }
        });
    }
    // 显示表单弹窗
    function showEditModel(expTpe) {
        admin.open({
src/main/webapp/views/order/inspect.html
New file
@@ -0,0 +1,183 @@
<!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/originTable.css" media="all">
    <style>
        body { color: #595959; background-color: #f5f7f9; }
        .admin-form { padding: 25px 30px 0 0 !important; margin: 0 !important; }
        /* 权限控制 */
        #btn-pakoutPreview { display: none; }
        /* 优化搜索栏对齐 */
        .toolbar .layui-form-item {
            margin-bottom: 0;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
        }
        .toolbar .layui-inline {
            margin-right: 15px;
            margin-bottom: 10px;
            display: flex;
            align-items: center;
        }
        .toolbar .layui-form-label {
            width: auto !important;
            padding: 0 10px 0 0 !important;
            line-height: 38px;
            height: 38px;
        }
        .toolbar .layui-input-inline {
            width: 180px !important;
            margin-right: 0 !important;
        }
        /* 左侧搜索栏特殊处理 */
        #left-table .toolbar .layui-input-inline {
            width: 140px !important;
        }
    </style>
</head>
<body>
<!-- 正文开始 -->
<div class="layui-fluid" style="padding-bottom: 0;">
    </style>
</head>
<body>
<!-- 正文开始 -->
<div class="layui-fluid" style="padding-bottom: 0;">
    <div class="layui-row layui-col-space15">
        <!-- 左侧:检验单据 -->
        <div class="layui-col-md3" id="left-table">
            <div class="layui-card">
                <div class="layui-card-header" data-i18n="inspection_orders">检验单据</div>
                <div class="layui-card-body" style="padding: 10px;">
                    <form class="layui-form toolbar">
                        <div class="layui-form-item">
                            <div class="layui-inline">
                                <div class="layui-input-inline">
                                    <input name="orderNo" class="layui-input" placeholder="" data-i18n="order_no" autocomplete="off"/>
                                </div>
                                <button class="layui-btn icon-btn" lay-filter="originTableSearch" lay-submit>
                                    <i class="layui-icon">&#xe615;</i>
                                </button>
                            </div>
                        </div>
                    </form>
                    <table id="originTable" lay-filter="originTable"></table>
                </div>
            </div>
        </div>
        <!-- 右侧:订单明细 -->
        <div class="layui-col-md9">
            <div class="layui-card">
                <div class="layui-card-header" data-i18n="order_details">订单明细</div>
                <div class="layui-card-body" style="padding: 10px;">
                    <form class="layui-form toolbar">
                        <div class="layui-form-item">
                            <div class="layui-inline">
                                <label class="layui-form-label" data-i18n="mat_code">商品编码</label>
                                <div class="layui-input-inline">
                                    <input name="matnr" class="layui-input" placeholder="" data-i18n="mat_code"/>
                                </div>
                            </div>
                            <div class="layui-inline">
                                <label class="layui-form-label" data-i18n="serial_code">批号</label>
                                <div class="layui-input-inline">
                                    <input name="batch" class="layui-input" placeholder="" data-i18n="serial_code"/>
                                </div>
                            </div>
                            <div class="layui-inline">
                                <button class="layui-btn icon-btn" lay-filter="sensorTbSearch" lay-submit>
                                    <i class="layui-icon">&#xe615;</i><span data-i18n="search">搜索</span>
                                </button>
                            </div>
                        </div>
                    </form>
                    <table id="orderDetlTable" lay-filter="orderDetlTable"></table>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- 头工具栏 -->
<script type="text/html" id="orderDetToolbar">
    <button class="layui-btn layui-btn-sm layui-btn-danger" id="btn-pakoutPreview" lay-event="pakoutPreview">
        <i class="layui-icon">&#xe609;</i> <span data-i18n="batch_outbound">批量出库</span>
    </button>
    <button class="layui-btn layui-btn-sm layui-btn-warm" id="btn-oneClickOut" lay-event="oneClickOut">
        <i class="layui-icon">&#xe628;</i> <span data-i18n="one_click_outbound">一键出库</span>
    </button>
    <button class="layui-btn layui-btn-sm layui-btn-normal" id="btn-inspectPass" lay-event="inspectPass">
        <i class="layui-icon">&#xe605;</i> <span data-i18n="inspect_pass">检验通过</span>
    </button>
    <button class="layui-btn layui-btn-sm layui-btn-primary layui-border-red" id="btn-inspectFail" lay-event="inspectFail">
        <i class="layui-icon">&#x1006;</i> <span data-i18n="inspect_fail">检验失败</span>
    </button>
</script>
<!-- 行工具栏 -->
<script type="text/html" id="operate">
    {{# if (d.enableQty > 0) { }}
    <a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="pakoutPreview">
        <i class="layui-icon">&#xe609;</i> <span data-i18n="outbound">出库</span>
    </a>
    {{# } }}
</script>
<!-- 出库预览弹窗 -->
<script type="text/html" id="pakoutPreviewBox" style="display: none">
    <div style="padding: 20px; background-color: #393D49; color: #fff;">
        <span style="font-size: 18px; font-weight: bold" data-i18n="outbound_preview">出库预览</span>
    </div>
    <div class="layui-card">
        <div class="layui-card-body">
            <table id="stoPreTab" lay-filter="stoPreTab"></table>
        </div>
        <div style="text-align: right; padding: 10px;">
             <button class="layui-btn layui-btn-primary layui-border-black layui-btn-sm" lay-filter="batchModifySta" lay-submit data-i18n="batch_modify">批量修改站点</button>
        </div>
    </div>
</script>
<!-- 库位显示模板 -->
<script type="text/html" id="locNoTpl">
    {{# if( d.lack === false){ }}
    <span class="layui-badge layui-badge-green">{{d.locNo}}</span>
    {{# } else { }}
    <span class="layui-badge layui-badge-red" data-i18n="stock_shortage">缺货</span>
    {{# } }}
</script>
<!-- 站点选择模板 -->
<script type="text/html" id="tbBasicTbStaNos">
    <div class="ew-select-fixed">
        <select class="order-sta-select" lay-filter="tbBasicTbStaNos">
            <option value="" data-i18n="please_select">请选择</option>
            {{# if(d.staNos != null) { }}
                {{# for(let i=0; i<d.staNos.length; i++) { }}
                <option value="{{d.staNos[i]}}">{{d.staNos[i]}}</option>
                {{# } }}
            {{# } }}
        </select>
    </div>
</script>
<script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="../../static/js/handlebars/handlebars-v4.5.3.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 src="../../static/js/i18n/i18n-helper.js"></script>
<script type="text/javascript" src="../../static/js/order/inspect.js" charset="utf-8"></script>
</body>
</html>
src/main/webapp/views/order/order.html
@@ -79,6 +79,9 @@
                        <button class="layui-btn icon-btn" lay-filter="tbSearch" lay-submit>
                            <i class="layui-icon">&#xe615;</i><span data-i18n="search">搜索</span>
                        </button>
                        <button id="createInspectionBtn" class="layui-btn icon-btn btn-add" type="button">
                            <i class="layui-icon">&#xe654;</i><span data-i18n="create_inspection_order">创建检验单</span>
                        </button>
                        <button id="orderAddBtn" class="layui-btn icon-btn btn-add"><i class="layui-icon">&#xe654;</i><span data-i18n="add">添加</span>
                        </button>
                    </div>
@@ -96,9 +99,11 @@
</div>
<!-- 表格操作列 -->
<script type="text/html" id="operate">
    {{# if (d.settle == 0 || d.settle == 1) { }}
    {{# if ((d.settle == 0 || d.settle == 1) && d.docType !=9) { }}
        <a class="layui-btn layui-btn-primary layui-btn-xs btn-edit" lay-event="edit">{{ I18n.t('modify') }}</a>
        <a class="layui-btn layui-btn-danger layui-btn-xs btn-delete" lay-event="del">{{ I18n.t('delete') }}</a>
    {{# } }}
    {{# if (d.settle == 0 || d.settle == 1 ) { }}
    <a class="layui-btn layui-btn-danger layui-btn-xs btn-delete" lay-event="del">{{ I18n.t('delete') }}</a>
    {{# } }}
    {{# if (d.settle == 2) { }}
    <a class="layui-btn layui-btn-primary layui-border-blue layui-btn-xs btn-complete" lay-event="complete">{{ I18n.t('complete') }}</a>
@@ -201,6 +206,95 @@
        </div>
    </form>
</script>
<!-- 检验单创建弹窗 -->
<script type="text/html" id="inspectionDialog">
    <div class="layui-form model-form" style="padding: 15px;">
        <div class="layui-form-item">
            <div class="layui-inline">
                <label class="layui-form-label" style="width: 110px;" data-i18n="po_number">PO Number</label>
                <div class="layui-input-inline" style="width: 200px;">
                    <input name="poNumber" class="layui-input" type="text" placeholder="PO Number" data-i18n-placeholder="po_number_placeholder">
                </div>
            </div>
            <div class="layui-inline">
                <label class="layui-form-label" style="width: 80px;" data-i18n="style">Style</label>
                <div class="layui-input-inline" style="width: 160px;">
                    <input name="style" class="layui-input" type="text" placeholder="Style" data-i18n-placeholder="style_placeholder">
                </div>
            </div>
            <div class="layui-inline">
                <label class="layui-form-label" style="width: 80px;" data-i18n="color">Color</label>
                <div class="layui-input-inline" style="width: 160px;">
                    <input name="color" class="layui-input" type="text" placeholder="Color" data-i18n-placeholder="color_placeholder">
                </div>
            </div>
            <div class="layui-inline">
                <label class="layui-form-label" style="width: 120px;" data-i18n="carton_from">Carton From</label>
                <div class="layui-input-inline" style="width: 120px;">
                    <input name="cartonNumberFrom" class="layui-input" type="number" placeholder="From" data-i18n-placeholder="carton_from_placeholder">
                </div>
            </div>
            <div class="layui-inline">
                <label class="layui-form-label" style="width: 1px;" data-i18n="to">To</label>
                <div class="layui-input-inline" style="width: 120px;">
                    <input name="cartonNumberTo" class="layui-input" type="number" placeholder="To" data-i18n-placeholder="carton_to_placeholder">
                </div>
            </div>
            <div class="layui-inline">
                <button class="layui-btn" lay-filter="inspectionSearch" lay-submit><span data-i18n="search">搜索</span></button>
            </div>
        </div>
        <div class="layui-form-item">
            <fieldset class="layui-elem-field">
                <legend data-i18n="po_summary" style="font-size: 18px; font-weight: inherit;">Po Summary</legend>
                <div class="layui-field-box">
                    <div class="layui-row layui-col-space10">
                        <div class="layui-col-md2">
                            <span data-i18n="buyer_po">Buyer PO</span>:
                            <span id="poSummaryBuyerPo"></span>
                        </div>
                        <div class="layui-col-md2">
                            <span data-i18n="total_styles">Total Styles</span>:
                            <span id="poSummaryTotalStyles"></span>
                        </div>
                        <div class="layui-col-md2">
                            <span data-i18n="total_colors">Total Colors</span>:
                            <span id="poSummaryTotalColors"></span>
                        </div>
                        <div class="layui-col-md2">
                            <span data-i18n="total_cartons">Total Cartons</span>:
                            <span id="poSummaryTotalCartons"></span>
                        </div>
                        <div class="layui-col-md2">
                            <span data-i18n="total_quantity">Total Quantity</span>:
                            <span id="poSummaryTotalQty"></span>
                        </div>
                    </div>
                </div>
            </fieldset>
        </div>
        <div class="layui-form-item">
            <fieldset class="layui-elem-field">
                <legend data-i18n="matching_locations" style="font-size: 18px; font-weight: inherit;">Matching Locations</legend>
                <div class="layui-field-box">
                    <table id="matchingLocationsTable" lay-filter="matchingLocationsTable"></table>
                </div>
            </fieldset>
        </div>
        <div class="layui-form-item">
            <div class="layui-inline">
                <label class="layui-form-label" style="width: 140px;" data-i18n="buyer_short_code">Buyer short code</label>
                <div class="layui-input-inline" style="width: 180px;">
                    <input name="buyerShortCode" class="layui-input" type="text" placeholder="Buyer short code" data-i18n-placeholder="buyer_short_code_placeholder">
                </div>
            </div>
        </div>
        <div class="layui-form-item text-right">
            <button class="layui-btn layui-btn-primary" type="button" ew-event="closeDialog" data-i18n="cancel">取消</button>
            <button class="layui-btn" type="button" id="inspectionCreateBtn"><span data-i18n="create_inspection_order">创建检验单</span></button>
        </div>
    </div>
</script>
<!-- 订单任务追溯 -->
<script id="wrkTraceDialog" type="text/html" style="position: relative">
    <div style="position: absolute; top: 0; left: 0;">