chen.lin
19 小时以前 01175f85df1e8402ada5a30aacfeb18fa621e95e
委外加工出库单同步+PDA出库功能
2个文件已修改
9个文件已添加
1660 ■■■■■ 已修改文件
src/main/java/com/zy/asrs/controller/MaterialWwOutController.java 409 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/entity/MaterialWwOut.java 218 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/mapper/MaterialWwOutMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/MaterialWwOutService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/service/impl/MaterialWwOutServiceImpl.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/materialReceive.sql 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/materialWwOut.sql 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/materialWwOut_alter_decimal.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/materialReceive/materialReceive.html 33 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/materialWwOut/materialWwOut.html 380 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/webapp/views/pda/materialWwOut.html 511 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/zy/asrs/controller/MaterialWwOutController.java
New file
@@ -0,0 +1,409 @@
package com.zy.asrs.controller;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.core.common.Cools;
import com.core.common.R;
import com.core.annotations.ManagerAuth;
import com.zy.asrs.entity.MaterialWwOut;
import com.zy.asrs.service.MaterialWwOutService;
import com.zy.common.web.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.core.common.DateUtils;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class MaterialWwOutController extends BaseController {
    @Autowired
    private MaterialWwOutService materialWwOutService;
    @RequestMapping(value = "/materialWwOut/{id}/auth")
    @ManagerAuth
    public R get(@PathVariable("id") String id) {
        return R.ok(materialWwOutService.selectById(String.valueOf(id)));
    }
    @RequestMapping(value = "/materialWwOut/list/auth")
    @ManagerAuth
    public R list(@RequestParam(defaultValue = "1") Integer curr,
                  @RequestParam(defaultValue = "1000") Integer limit,
                  @RequestParam(required = false) String orderByField,
                  @RequestParam(required = false) String orderByType,
                  @RequestParam(required = false) String condition,
                  @RequestParam(required = false) Boolean pdaQuery,
                  @RequestParam Map<String, Object> param) {
        EntityWrapper<MaterialWwOut> wrapper = new EntityWrapper<>();
        excludeTrash(param);
        // 移除pdaQuery参数,因为它不是数据库字段,只是控制参数
        param.remove("pdaQuery");
        convert(param, wrapper);
        allLike(MaterialWwOut.class, param.keySet(), wrapper, condition);
        // 参考其他出库模块,PDA出库查询时过滤掉已全部出库的物料(只显示还有剩余数量的)
        // 通过 pdaQuery 参数判断是否是PDA出库查询
        if (pdaQuery != null && pdaQuery) {
            // PDA出库查询:只显示剩余数量大于0的记录
            wrapper.gt("remain_qty", 0);
        }
        if (!Cools.isEmpty(orderByField)) {
            wrapper.orderBy(humpToLine(orderByField), "asc".equals(orderByType));
        }
        return R.ok(materialWwOutService.selectPage(new Page<>(curr, limit), wrapper));
    }
    @RequestMapping(value = "/materialWwOut/batchSave/auth", method = RequestMethod.POST)
    @ManagerAuth
    public R batchSave(@RequestBody List<Map<String, Object>> dataList) {
        if (Cools.isEmpty(dataList)) {
            return R.error("数据不能为空");
        }
        int successCount = 0;
        int updateCount = 0;
        for (Map<String, Object> data : dataList) {
            try {
                // 检查是否已存在(根据recordId和invCode唯一标识,如果recordId为空则使用id)
                String recordId = data.get("recordId") != null ? String.valueOf(data.get("recordId")) : null;
                // 如果recordId为空,尝试使用id字段
                if (recordId == null || recordId.isEmpty() || "null".equals(recordId)) {
                    if (data.get("id") != null) {
                        recordId = String.valueOf(data.get("id"));
                    }
                }
                String invCode = data.get("invCode") != null ? String.valueOf(data.get("invCode")) : null;
                MaterialWwOut existRecord = null;
                if (recordId != null && !recordId.isEmpty() && !"null".equals(recordId) && invCode != null) {
                    existRecord = materialWwOutService.selectOne(new EntityWrapper<MaterialWwOut>()
                            .eq("record_id", recordId)
                            .eq("inv_code", invCode));
                }
                MaterialWwOut materialWwOut;
                if (existRecord != null) {
                    // 已存在,更新数据(不覆盖出库相关字段)
                    materialWwOut = existRecord;
                    updateCount++;
                } else {
                    // 不存在,创建新记录
                    materialWwOut = new MaterialWwOut();
                    materialWwOut.setCreateTime(new Date());
                    // 初始化出库相关字段
                    materialWwOut.setOutQty(BigDecimal.ZERO);
                    materialWwOut.setRemainQty(data.get("fqty") != null ?
                        new BigDecimal(String.valueOf(data.get("fqty"))) :
                        (data.get("qty") != null ? new BigDecimal(String.valueOf(data.get("qty"))) : BigDecimal.ZERO));
                    materialWwOut.setIsAllOut(0);
                    successCount++;
                }
                // 更新/设置字段 - 确保recordId有值
                if (recordId == null || recordId.isEmpty() || "null".equals(recordId)) {
                    // 如果recordId为空,尝试使用id字段
                    if (data.get("id") != null) {
                        recordId = String.valueOf(data.get("id"));
                    }
                }
                materialWwOut.setRecordId(recordId);
                if (data.get("dateStart") != null && !Cools.isEmpty(String.valueOf(data.get("dateStart")))) {
                    try {
                        String dateStr = String.valueOf(data.get("dateStart"));
                        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
                        materialWwOut.setDateStart(sdf.parse(dateStr));
                    } catch (Exception ex) {
                        // 忽略日期解析错误
                    }
                }
                if (data.get("dateEnd") != null && !Cools.isEmpty(String.valueOf(data.get("dateEnd")))) {
                    try {
                        String dateStr = String.valueOf(data.get("dateEnd"));
                        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
                        materialWwOut.setDateEnd(sdf.parse(dateStr));
                    } catch (Exception ex) {
                        // 忽略日期解析错误
                    }
                }
                if (data.get("venId") != null) {
                    try {
                        materialWwOut.setVenId(Integer.valueOf(String.valueOf(data.get("venId"))));
                    } catch (Exception e) {
                        // 忽略转换错误
                    }
                }
                materialWwOut.setVenName(data.get("venName") != null ? String.valueOf(data.get("venName")) : null);
                materialWwOut.setInvCode(invCode);
                materialWwOut.setInvName(data.get("invName") != null ? String.valueOf(data.get("invName")) : null);
                materialWwOut.setInvStd(data.get("invStd") != null ? String.valueOf(data.get("invStd")) : null);
                materialWwOut.setUnit(data.get("unit") != null ? String.valueOf(data.get("unit")) : null);
                // 数量字段处理(优先使用fqty,如果没有则使用qty)
                BigDecimal qty = null;
                if (data.get("fqty") != null) {
                    qty = new BigDecimal(String.valueOf(data.get("fqty")));
                    materialWwOut.setFqty(qty);
                } else if (data.get("qty") != null) {
                    qty = new BigDecimal(String.valueOf(data.get("qty")));
                    materialWwOut.setQty(qty);
                }
                if (data.get("qqty") != null) {
                    materialWwOut.setQqty(new BigDecimal(String.valueOf(data.get("qqty"))));
                }
                if (data.get("yqty") != null) {
                    materialWwOut.setYqty(new BigDecimal(String.valueOf(data.get("yqty"))));
                }
                if (data.get("wqty") != null) {
                    materialWwOut.setWqty(new BigDecimal(String.valueOf(data.get("wqty"))));
                }
                // 如果已存在,更新剩余数量(基于原始数量)
                if (existRecord == null && qty != null) {
                    materialWwOut.setRemainQty(qty);
                } else if (existRecord != null && qty != null) {
                    // 已存在时,如果数量有变化,重新计算剩余数量
                    BigDecimal originalQty = existRecord.getFqty() != null ? existRecord.getFqty() :
                                           (existRecord.getQty() != null ? existRecord.getQty() : BigDecimal.ZERO);
                    BigDecimal outQty = existRecord.getOutQty() != null ? existRecord.getOutQty() : BigDecimal.ZERO;
                    // 如果新数量与原始数量不同,说明数量更新了,需要重新计算剩余数量
                    if (qty.compareTo(originalQty) != 0) {
                        // 数量变化,剩余数量 = 新数量 - 已出库数量
                        materialWwOut.setRemainQty(qty.subtract(outQty));
                    } else {
                        // 数量没变化,保持原有剩余数量
                        materialWwOut.setRemainQty(existRecord.getRemainQty());
                    }
                }
                if (data.get("funitid") != null) {
                    try {
                        materialWwOut.setFunitid(Integer.valueOf(String.valueOf(data.get("funitid"))));
                    } catch (Exception e) {
                        // 忽略转换错误
                    }
                }
                if (data.get("fitemid") != null) {
                    try {
                        materialWwOut.setFitemid(Integer.valueOf(String.valueOf(data.get("fitemid"))));
                    } catch (Exception e) {
                        // 忽略转换错误
                    }
                }
                if (data.get("whId") != null) {
                    try {
                        materialWwOut.setWhId(Integer.valueOf(String.valueOf(data.get("whId"))));
                    } catch (Exception e) {
                        // 忽略转换错误
                    }
                }
                materialWwOut.setWhName(data.get("whName") != null ? String.valueOf(data.get("whName")) : null);
                if (data.get("izSync") != null) {
                    materialWwOut.setIzSync(String.valueOf(data.get("izSync")));
                }
                if (data.get("izPrint") != null) {
                    materialWwOut.setIzPrint(String.valueOf(data.get("izPrint")));
                }
                // 将其他字段存储到ext_data(JSON格式)
                Map<String, Object> extDataMap = new HashMap<>();
                // 已映射到数据库表字段的字段列表(这些字段不需要存储在ext_data中)
                java.util.Set<String> mappedFields = new java.util.HashSet<>();
                mappedFields.add("recordId");
                mappedFields.add("dateStart");
                mappedFields.add("dateEnd");
                mappedFields.add("venId");
                mappedFields.add("venName");
                mappedFields.add("invCode");
                mappedFields.add("invName");
                mappedFields.add("invStd");
                mappedFields.add("unit");
                mappedFields.add("funitid");
                mappedFields.add("fitemid");
                mappedFields.add("whId");
                mappedFields.add("whName");
                mappedFields.add("qty");
                mappedFields.add("fqty");
                mappedFields.add("qqty");
                mappedFields.add("yqty");
                mappedFields.add("wqty");
                mappedFields.add("izSync");
                mappedFields.add("izPrint");
                // 将所有未映射的字段存储到ext_data中
                for (Map.Entry<String, Object> entry : data.entrySet()) {
                    String key = entry.getKey();
                    if (!mappedFields.contains(key) && entry.getValue() != null) {
                        extDataMap.put(key, entry.getValue());
                    }
                }
                // 始终更新ext_data,即使为空也要存储(用于后续扩展)
                materialWwOut.setExtData(com.alibaba.fastjson.JSON.toJSONString(extDataMap));
                materialWwOut.setSyncTime(new Date());
                materialWwOut.setUpdateTime(new Date());
                // 保存或更新
                if (existRecord != null) {
                    materialWwOutService.updateById(materialWwOut);
                } else {
                    materialWwOutService.insert(materialWwOut);
                }
            } catch (Exception e) {
                // 记录失败的记录,继续处理下一条
                e.printStackTrace();
            }
        }
        return R.ok("成功保存 " + successCount + " 条新数据,更新 " + updateCount + " 条已存在数据");
    }
    @RequestMapping(value = "/materialWwOut/update/auth")
    @ManagerAuth
    public R update(MaterialWwOut materialWwOut) {
        if (Cools.isEmpty(materialWwOut) || null == materialWwOut.getId()) {
            return R.error();
        }
        materialWwOut.setUpdateTime(new Date());
        materialWwOutService.updateById(materialWwOut);
        return R.ok();
    }
    @RequestMapping(value = "/materialWwOut/updateOutQty/auth", method = RequestMethod.POST)
    @ManagerAuth
    public R updateOutQty(@RequestBody Map<String, Object> param) {
        Long id = Long.valueOf(String.valueOf(param.get("id")));
        BigDecimal addOutQty = new BigDecimal(String.valueOf(param.get("outQty"))); // 本次出库数量
        MaterialWwOut materialWwOut = materialWwOutService.selectById(id);
        if (materialWwOut == null) {
            return R.error("记录不存在");
        }
        // 累加出库数量
        BigDecimal currentOutQty = materialWwOut.getOutQty() != null ? materialWwOut.getOutQty() : BigDecimal.ZERO;
        BigDecimal newOutQty = currentOutQty.add(addOutQty);
        materialWwOut.setOutQty(newOutQty);
        // 计算剩余数量(优先使用fqty,如果没有则使用qty)
        BigDecimal qty = materialWwOut.getFqty() != null ? materialWwOut.getFqty() :
                         (materialWwOut.getQty() != null ? materialWwOut.getQty() : BigDecimal.ZERO);
        BigDecimal remainQty = qty.subtract(newOutQty);
        materialWwOut.setRemainQty(remainQty.compareTo(BigDecimal.ZERO) > 0 ? remainQty : BigDecimal.ZERO);
        // 判断是否全部出库完成
        materialWwOut.setIsAllOut(remainQty.compareTo(BigDecimal.ZERO) <= 0 ? 1 : 0);
        materialWwOut.setUpdateTime(new Date());
        materialWwOutService.updateById(materialWwOut);
        return R.ok();
    }
    @RequestMapping(value = "/materialWwOut/checkSyncStatus/auth")
    @ManagerAuth
    public R checkSyncStatus(@RequestParam String recordId, @RequestParam(required = false) String invCode) {
        EntityWrapper<MaterialWwOut> wrapper = new EntityWrapper<>();
        wrapper.eq("record_id", recordId);
        if (!Cools.isEmpty(invCode)) {
            wrapper.eq("inv_code", invCode);
        }
        List<MaterialWwOut> list = materialWwOutService.selectList(wrapper);
        Map<String, Object> result = new HashMap<>();
        if (Cools.isEmpty(list)) {
            result.put("synced", false);
            result.put("allOut", false);
            result.put("message", "该记录ID未同步到WMS");
            return R.ok(result);
        }
        // 检查是否全部出库完成
        boolean allOut = true;
        List<Map<String, Object>> notOutList = new ArrayList<>();
        for (MaterialWwOut mwo : list) {
            if (mwo.getIsAllOut() == null || mwo.getIsAllOut() != 1) {
                allOut = false;
                Map<String, Object> notOutItem = new HashMap<>();
                notOutItem.put("invCode", mwo.getInvCode());
                notOutItem.put("invName", mwo.getInvName());
                notOutItem.put("remainQty", mwo.getRemainQty());
                notOutList.add(notOutItem);
            }
        }
        result.put("synced", true);
        result.put("allOut", allOut);
        result.put("records", list);
        if (!allOut && !notOutList.isEmpty()) {
            result.put("notOutList", notOutList);
            result.put("message", "部分物料未全部出库完成");
        } else if (allOut) {
            result.put("message", "全部物料已出库完成");
        }
        return R.ok(result);
    }
    private void convert(Map<String, Object> map, EntityWrapper<MaterialWwOut> wrapper) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String fieldName = entry.getKey();
            String columnName = getColumnName(MaterialWwOut.class, fieldName);
            String val = String.valueOf(entry.getValue());
            if (val.contains(RANGE_TIME_LINK)) {
                String[] dates = val.split(RANGE_TIME_LINK);
                wrapper.ge(columnName, DateUtils.convert(dates[0]));
                wrapper.le(columnName, DateUtils.convert(dates[1]));
            } else {
                wrapper.like(columnName, val);
            }
        }
    }
    /**
     * 根据实体类字段名获取数据库列名
     * @param cls 实体类
     * @param fieldName Java字段名(驼峰命名)
     * @return 数据库列名(下划线命名)
     */
    private String getColumnName(Class<?> cls, String fieldName) {
        for (Field field : Cools.getAllFields(cls)) {
            if (field.getName().equals(fieldName)) {
                if (field.isAnnotationPresent(TableField.class)) {
                    TableField annotation = field.getAnnotation(TableField.class);
                    if (!annotation.exist()) {
                        continue;
                    }
                    String column = annotation.value();
                    if (!Cools.isEmpty(column)) {
                        return column;
                    }
                }
                // 如果没有@TableField注解,使用humpToLine转换
                return humpToLine(fieldName);
            }
        }
        // 如果找不到字段,使用humpToLine转换
        return humpToLine(fieldName);
    }
    @RequestMapping(value = "/materialWwOut/delete/auth")
    @ManagerAuth
    public R delete(@RequestParam String param) {
        com.alibaba.fastjson.JSONArray jsonArray = com.alibaba.fastjson.JSONArray.parseArray(param);
        List<MaterialWwOut> list = jsonArray.toJavaList(MaterialWwOut.class);
        if (Cools.isEmpty(list)) {
            return R.error("请选择要删除的数据");
        }
        for (MaterialWwOut entity : list) {
            materialWwOutService.deleteById(entity.getId());
        }
        return R.ok();
    }
}
src/main/java/com/zy/asrs/entity/MaterialWwOut.java
New file
@@ -0,0 +1,218 @@
package com.zy.asrs.entity;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
 * 委外加工出库单实体类
 */
@TableName("erp_material_ww_out")
@Data
public class MaterialWwOut implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ID
     */
    @ApiModelProperty(value = "ID")
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * 记录ID(ERP中的记录ID)
     */
    @ApiModelProperty(value = "记录ID")
    @TableField("record_id")
    private String recordId;
    /**
     * 开工日期
     */
    @ApiModelProperty(value = "开工日期")
    @TableField("date_start")
    private Date dateStart;
    /**
     * 完成日期
     */
    @ApiModelProperty(value = "完成日期")
    @TableField("date_end")
    private Date dateEnd;
    /**
     * 供应商ID
     */
    @ApiModelProperty(value = "供应商ID")
    @TableField("ven_id")
    private Integer venId;
    /**
     * 供应商名称
     */
    @ApiModelProperty(value = "供应商名称")
    @TableField("ven_name")
    private String venName;
    /**
     * 物料编码
     */
    @ApiModelProperty(value = "物料编码")
    @TableField("inv_code")
    private String invCode;
    /**
     * 物料名称
     */
    @ApiModelProperty(value = "物料名称")
    @TableField("inv_name")
    private String invName;
    /**
     * 规格型号
     */
    @ApiModelProperty(value = "规格型号")
    @TableField("inv_std")
    private String invStd;
    /**
     * 单位
     */
    @ApiModelProperty(value = "单位")
    @TableField("unit")
    private String unit;
    /**
     * 单位ID
     */
    @ApiModelProperty(value = "单位ID")
    @TableField("funitid")
    private Integer funitid;
    /**
     * 物料ID
     */
    @ApiModelProperty(value = "物料ID")
    @TableField("fitemid")
    private Integer fitemid;
    /**
     * 仓库ID
     */
    @ApiModelProperty(value = "仓库ID")
    @TableField("wh_id")
    private Integer whId;
    /**
     * 仓库名称
     */
    @ApiModelProperty(value = "仓库名称")
    @TableField("wh_name")
    private String whName;
    /**
     * 数量
     */
    @ApiModelProperty(value = "数量")
    @TableField("qty")
    private BigDecimal qty;
    /**
     * 应发数量
     */
    @ApiModelProperty(value = "应发数量")
    @TableField("fqty")
    private BigDecimal fqty;
    /**
     * 期初数量
     */
    @ApiModelProperty(value = "期初数量")
    @TableField("qqty")
    private BigDecimal qqty;
    /**
     * 已发数量
     */
    @ApiModelProperty(value = "已发数量")
    @TableField("yqty")
    private BigDecimal yqty;
    /**
     * 未发数量
     */
    @ApiModelProperty(value = "未发数量")
    @TableField("wqty")
    private BigDecimal wqty;
    /**
     * 出库数量
     */
    @ApiModelProperty(value = "出库数量")
    @TableField("out_qty")
    private BigDecimal outQty;
    /**
     * 剩余数量
     */
    @ApiModelProperty(value = "剩余数量")
    @TableField("remain_qty")
    private BigDecimal remainQty;
    /**
     * 是否全部出库(0:否,1:是)
     */
    @ApiModelProperty(value = "是否全部出库")
    @TableField("is_all_out")
    private Integer isAllOut;
    /**
     * 是否同步
     */
    @ApiModelProperty(value = "是否同步")
    @TableField("iz_sync")
    private String izSync;
    /**
     * 是否打印
     */
    @ApiModelProperty(value = "是否打印")
    @TableField("iz_print")
    private String izPrint;
    /**
     * 同步时间
     */
    @ApiModelProperty(value = "同步时间")
    @TableField("sync_time")
    private Date syncTime;
    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    @TableField("create_time")
    private Date createTime;
    /**
     * 更新时间
     */
    @ApiModelProperty(value = "更新时间")
    @TableField("update_time")
    private Date updateTime;
    /**
     * 扩展数据(JSON格式)
     */
    @ApiModelProperty(value = "扩展数据")
    @TableField("ext_data")
    private String extData;
}
src/main/java/com/zy/asrs/mapper/MaterialWwOutMapper.java
New file
@@ -0,0 +1,12 @@
package com.zy.asrs.mapper;
import com.zy.asrs.entity.MaterialWwOut;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface MaterialWwOutMapper extends BaseMapper<MaterialWwOut> {
}
src/main/java/com/zy/asrs/service/MaterialWwOutService.java
New file
@@ -0,0 +1,8 @@
package com.zy.asrs.service;
import com.zy.asrs.entity.MaterialWwOut;
import com.baomidou.mybatisplus.service.IService;
public interface MaterialWwOutService extends IService<MaterialWwOut> {
}
src/main/java/com/zy/asrs/service/impl/MaterialWwOutServiceImpl.java
New file
@@ -0,0 +1,12 @@
package com.zy.asrs.service.impl;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.zy.asrs.entity.MaterialWwOut;
import com.zy.asrs.mapper.MaterialWwOutMapper;
import com.zy.asrs.service.MaterialWwOutService;
import org.springframework.stereotype.Service;
@Service("materialWwOutService")
public class MaterialWwOutServiceImpl extends ServiceImpl<MaterialWwOutMapper, MaterialWwOut> implements MaterialWwOutService {
}
src/main/java/materialReceive.sql
@@ -7,9 +7,9 @@
    [inv_code] [nvarchar](100) NULL,
    [inv_name] [nvarchar](200) NULL,
    [inv_std] [nvarchar](200) NULL,
    [qty] [decimal](18, 2) NULL,
    [fqty] [decimal](18, 2) NULL,
    [fauxqty] [decimal](18, 2) NULL,
    [qty] [decimal](18, 4) NULL,
    [fqty] [decimal](18, 4) NULL,
    [fauxqty] [decimal](18, 4) NULL,
    [unit] [nvarchar](50) NULL,
    [funitid] [int] NULL,
    [dep_name] [nvarchar](100) NULL,
@@ -23,8 +23,8 @@
    [finterid] [int] NULL,
    [fitemid] [int] NULL,
    -- 出库相关字段
    [out_qty] [decimal](18, 2) NULL DEFAULT 0,
    [remain_qty] [decimal](18, 2) NULL DEFAULT 0,
    [out_qty] [decimal](18, 4) NULL DEFAULT 0,
    [remain_qty] [decimal](18, 4) NULL DEFAULT 0,
    [is_all_out] [int] NULL DEFAULT 0,
    -- 同步相关字段
    [sync_time] [datetime] NULL,
src/main/java/materialWwOut.sql
New file
@@ -0,0 +1,42 @@
-- 委外加工出库单表(扩展字段以支持完整数据)
CREATE TABLE [dbo].[erp_material_ww_out](
    [id] [bigint] IDENTITY(1,1) NOT NULL,
    -- 基础字段
    [record_id] [nvarchar](100) NULL,
    [date_start] [datetime] NULL,
    [date_end] [datetime] NULL,
    [ven_id] [int] NULL,
    [ven_name] [nvarchar](200) NULL,
    [inv_code] [nvarchar](100) NULL,
    [inv_name] [nvarchar](200) NULL,
    [inv_std] [nvarchar](200) NULL,
    [unit] [nvarchar](50) NULL,
    [funitid] [int] NULL,
    [fitemid] [int] NULL,
    [wh_id] [int] NULL,
    [wh_name] [nvarchar](100) NULL,
    [qty] [decimal](18, 4) NULL,
    [fqty] [decimal](18, 4) NULL,
    [qqty] [decimal](18, 4) NULL,
    [yqty] [decimal](18, 4) NULL,
    [wqty] [decimal](18, 4) NULL,
    -- 出库相关字段
    [out_qty] [decimal](18, 4) NULL DEFAULT 0,
    [remain_qty] [decimal](18, 4) NULL DEFAULT 0,
    [is_all_out] [int] NULL DEFAULT 0,
    -- 同步相关字段
    [iz_sync] [nvarchar](10) NULL,
    [iz_print] [nvarchar](10) NULL,
    [sync_time] [datetime] NULL,
    [create_time] [datetime] NULL DEFAULT GETDATE(),
    [update_time] [datetime] NULL DEFAULT GETDATE(),
    -- 扩展字段(用于存储更多ERP字段,JSON格式)
    [ext_data] [nvarchar](max) NULL,
    CONSTRAINT [PK_erp_material_ww_out] PRIMARY KEY CLUSTERED ([id] ASC)
)
-- 创建索引
CREATE INDEX [IX_erp_material_ww_out_record_id] ON [dbo].[erp_material_ww_out] ([record_id])
CREATE INDEX [IX_erp_material_ww_out_inv_code] ON [dbo].[erp_material_ww_out] ([inv_code])
CREATE INDEX [IX_erp_material_ww_out_ven_id] ON [dbo].[erp_material_ww_out] ([ven_id])
CREATE INDEX [IX_erp_material_ww_out_sync] ON [dbo].[erp_material_ww_out] ([record_id], [inv_code])
src/main/java/materialWwOut_alter_decimal.sql
New file
@@ -0,0 +1,25 @@
-- 修改委外加工出库单表的数量字段精度,支持4位小数
-- 如果表已存在,执行此脚本修改字段精度
-- 修改数量字段精度为4位小数
ALTER TABLE [dbo].[erp_material_ww_out]
    ALTER COLUMN [qty] [decimal](18, 4) NULL;
ALTER TABLE [dbo].[erp_material_ww_out]
    ALTER COLUMN [fqty] [decimal](18, 4) NULL;
ALTER TABLE [dbo].[erp_material_ww_out]
    ALTER COLUMN [qqty] [decimal](18, 4) NULL;
ALTER TABLE [dbo].[erp_material_ww_out]
    ALTER COLUMN [yqty] [decimal](18, 4) NULL;
ALTER TABLE [dbo].[erp_material_ww_out]
    ALTER COLUMN [wqty] [decimal](18, 4) NULL;
-- 修改出库相关字段精度为4位小数
ALTER TABLE [dbo].[erp_material_ww_out]
    ALTER COLUMN [out_qty] [decimal](18, 4) NULL;
ALTER TABLE [dbo].[erp_material_ww_out]
    ALTER COLUMN [remain_qty] [decimal](18, 4) NULL;
src/main/webapp/views/materialReceive/materialReceive.html
@@ -59,30 +59,17 @@
                <el-table-column type="index" width="50"></el-table-column>
                <el-table-column prop="soCode" label="生产单号" width="120"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="fbillno" label="任务单号" width="120"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="invCode" label="物料编码" width="120"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="invName" label="物料名称" width="200"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="invStd" label="规格型号" width="150"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="fbillno" label="任务单号" width="120"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="qty" label="总数量" width="100">
                <el-table-column prop="qty" label="通知出库量" width="100">
                    <template #default="scope">
                        {{ scope.row.fqty || scope.row.qty || 0 }}
                    </template>
                </el-table-column>
                <el-table-column prop="unit" label="单位" width="80"></el-table-column>
                <el-table-column prop="depName" label="部门名称" width="120"></el-table-column>
                <el-table-column prop="depCode" label="部门编码" width="120"></el-table-column>
                <el-table-column prop="whName" label="仓库名称" width="120"></el-table-column>
                <el-table-column prop="fplancommitdate$" label="计划开工日期" width="120"></el-table-column>
                <el-table-column prop="fplanfinishdate$" label="计划完工日期" width="120"></el-table-column>
                <el-table-column prop="outQty" label="已出库数量" width="110">
                    <template #default="scope">
                        <span :style="{ color: scope.row.outQty > 0 ? 'green' : 'gray' }">
                            {{ scope.row.outQty || 0 }}
                        </span>
                    </template>
                </el-table-column>
                <el-table-column prop="remainQty" label="剩余数量" width="110">
@@ -92,6 +79,20 @@
                        </span>
                    </template>
                </el-table-column>
                <el-table-column prop="outQty" label="已出库数量" width="110">
                    <template #default="scope">
                        <span :style="{ color: scope.row.outQty > 0 ? 'green' : 'gray' }">
                            {{ scope.row.outQty || 0 }}
                        </span>
                    </template>
                </el-table-column>
                <el-table-column prop="unit" label="单位" width="80"></el-table-column>
                <el-table-column prop="depName" label="部门名称" width="120"></el-table-column>
                <el-table-column prop="depCode" label="部门编码" width="120"></el-table-column>
                <el-table-column prop="whName" label="仓库名称" width="120"></el-table-column>
                <el-table-column prop="fplancommitdate$" label="计划开工日期" width="120"></el-table-column>
                <el-table-column prop="fplanfinishdate$" label="计划完工日期" width="120"></el-table-column>
                <el-table-column prop="isAllOut" label="出库状态" width="100">
                    <template #default="scope">
                        <el-tag v-if="scope.row.isAllOut == 1" type="success">已完成</el-tag>
src/main/webapp/views/materialWwOut/materialWwOut.html
New file
@@ -0,0 +1,380 @@
<!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/evn/index.css">
</head>
<body>
    <div id="app">
        <el-card>
            <!-- 搜索表单 -->
            <el-form :inline="true">
                <el-form-item label="供应商">
                    <el-input v-model="tableSearchParam.venName" placeholder="请输入供应商名称"></el-input>
                </el-form-item>
                <el-form-item label="物料编码">
                    <el-input v-model="tableSearchParam.invCode" placeholder="请输入物料编码"></el-input>
                </el-form-item>
                <el-form-item label="物料名称">
                    <el-input v-model="tableSearchParam.invName" placeholder="请输入物料名称"></el-input>
                </el-form-item>
                <el-form-item label="">
                    <el-button type="primary" @click="search" circle>
                        <el-icon>
                            <Search />
                        </el-icon>
                    </el-button>
                    <el-button @click="reset" circle style="margin-left: 10px">
                        <el-icon>
                            <Refresh />
                        </el-icon>
                    </el-button>
                </el-form-item>
            </el-form>
            <!-- 工具栏 -->
            <el-form :inline="true" style="display: none;">
                <el-form-item label="">
                    <el-button type="danger" @click="selectDelete">删除</el-button>
                    <el-button type="success" @click="exportData">导出</el-button>
                </el-form-item>
            </el-form>
            <!-- 数据表格 -->
            <el-table :data="tableData" border style="width: 100%" @selection-change="handleSelectionChange"
                max-height="550">
                <el-table-column type="selection"></el-table-column>
                <el-table-column type="index" width="50"></el-table-column>
                <el-table-column prop="venName" label="供应商" width="150"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="invCode" label="物料编码" width="120"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="invName" label="物料名称" width="200"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="invStd" label="规格型号" width="150"
                    :show-overflow-tooltip="true"></el-table-column>
                <el-table-column prop="unit" label="单位" width="80"></el-table-column>
                <el-table-column prop="whName" label="仓库名称" width="120"></el-table-column>
                <el-table-column prop="dateStart$" label="开工日期" width="120"></el-table-column>
                <el-table-column prop="dateEnd$" label="完成日期" width="120"></el-table-column>
                <el-table-column prop="fqty" label="应发数量" width="100">
                    <template #default="scope">
                        {{ scope.row.fqty || scope.row.qty || 0 }}
                    </template>
                </el-table-column>
                <el-table-column prop="qqty" label="期初数量" width="100">
                    <template #default="scope">
                        {{ scope.row.qqty || 0 }}
                    </template>
                </el-table-column>
                <el-table-column prop="yqty" label="已发数量" width="100">
                    <template #default="scope">
                        {{ scope.row.yqty || 0 }}
                    </template>
                </el-table-column>
                <el-table-column prop="wqty" label="未发数量" width="100">
                    <template #default="scope">
                        {{ scope.row.wqty || 0 }}
                    </template>
                </el-table-column>
                <el-table-column prop="outQty" label="已出库数量" width="110">
                    <template #default="scope">
                        <span :style="{ color: scope.row.outQty > 0 ? 'green' : 'gray' }">
                            {{ scope.row.outQty || 0 }}
                        </span>
                    </template>
                </el-table-column>
                <el-table-column prop="remainQty" label="剩余数量" width="110">
                    <template #default="scope">
                        <span :style="{ color: scope.row.remainQty > 0 ? 'orange' : 'green' }">
                            {{ scope.row.remainQty || 0 }}
                        </span>
                    </template>
                </el-table-column>
                <el-table-column prop="isAllOut" label="出库状态" width="100">
                    <template #default="scope">
                        <el-tag v-if="scope.row.isAllOut == 1" type="success">已完成</el-tag>
                        <el-tag v-else type="warning">未完成</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="izSync" label="是否同步" width="100">
                    <template #default="scope">
                        <el-tag v-if="scope.row.izSync == '是'" type="success">已同步</el-tag>
                        <el-tag v-else type="info">未同步</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="izPrint" label="是否打印" width="100">
                    <template #default="scope">
                        <el-tag v-if="scope.row.izPrint == '是'" type="success">已打印</el-tag>
                        <el-tag v-else type="info">未打印</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="syncTime$" label="同步时间" width="160"></el-table-column>
                <el-table-column prop="createTime$" label="创建时间" width="160"></el-table-column>
                <el-table-column prop="updateTime$" label="更新时间" width="160"></el-table-column>
            </el-table>
            <!-- 分页 -->
            <div style="margin-top: 10px">
                <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
                    :current-page="currentPage" :page-sizes="pageSizes" :page-size="pageSize"
                    layout="total, sizes, prev, pager, next, jumper" :total="pageTotal">
                </el-pagination>
            </div>
        </el-card>
    </div>
    <script type="text/javascript" src="../../static/js/jquery/jquery-3.3.1.min.js"></script>
    <script type="text/javascript" src="../../static/js/common.js" charset="utf-8"></script>
    <script src="../../static/evn/vue.global.js"></script>
    <script src="../../static/evn/element-plus.js"></script>
    <script src="../../static/evn/icons-vue.js"></script>
    <script type="module">
        // 导入中文语言包
        import zhCn from '../../static/evn/zh-cn.js';
        const { createApp, ref } = Vue;
        const app = createApp({
            setup() {
                const currentPage = ref(1)
                const pageSizes = ref([15, 20, 30, 50, 100, 200])
                const pageSize = ref(15)
                const pageTotal = ref(0)
                const tableSearchParam = ref({
                    venName: null,
                    invCode: null,
                    invName: null
                })
                const tableData = ref([])
                const selectList = ref([])
                function handleSelectionChange(selection) {
                    selectList.value = selection
                }
                function handleSizeChange(val) {
                    pageSize.value = val
                    search()
                }
                function handleCurrentChange(val) {
                    currentPage.value = val
                    search()
                }
                function search() {
                    const loading = ElementPlus.ElLoading.service({
                        lock: true,
                        text: 'Loading',
                        background: 'rgba(0, 0, 0, 0.7)',
                    })
                    let data = JSON.parse(JSON.stringify(tableSearchParam.value))
                    data.curr = currentPage.value
                    data.limit = pageSize.value
                    // 移除空值
                    Object.keys(data).forEach(key => {
                        if (data[key] === null || data[key] === '' || data[key] === undefined) {
                            delete data[key]
                        }
                    })
                    $.ajax({
                        url: baseUrl + "/materialWwOut/list/auth",
                        headers: { 'token': localStorage.getItem('token') },
                        data: data,
                        dataType: 'json',
                        method: 'GET',
                        success: function (res) {
                            if (res.code == 200) {
                                tableData.value = res.data.records || []
                                pageTotal.value = res.data.total || 0
                            } else if (res.code === 403) {
                                top.location.href = baseUrl + "/";
                            } else {
                                ElementPlus.ElMessage({ message: res.msg, type: 'error' });
                            }
                        },
                        complete: function () {
                            loading.close()
                        }
                    });
                }
                function selectDelete() {
                    if (selectList.value.length === 0) {
                        ElementPlus.ElMessage({
                            message: "请先选择要删除的数据",
                            type: 'warning'
                        });
                        return;
                    }
                    ElementPlus.ElMessageBox.confirm('确定要删除选中的数据吗?', '提示', {
                        confirmButtonText: '确定',
                        cancelButtonText: '取消',
                        type: 'warning'
                    }).then(() => {
                        const loading = ElementPlus.ElLoading.service({
                            lock: true,
                            text: '删除中...',
                            background: 'rgba(0, 0, 0, 0.7)',
                        })
                        $.ajax({
                            url: baseUrl + "/materialWwOut/delete/auth",
                            headers: { 'token': localStorage.getItem('token') },
                            data: {
                                param: JSON.stringify(selectList.value)
                            },
                            dataType: 'json',
                            method: 'POST',
                            success: function (res) {
                                if (res.code == 200) {
                                    ElementPlus.ElMessage({
                                        message: "删除成功",
                                        type: 'success'
                                    });
                                    search()
                                } else if (res.code === 403) {
                                    top.location.href = baseUrl + "/";
                                } else {
                                    ElementPlus.ElMessage({ message: res.msg, type: 'error' });
                                }
                            },
                            complete: function () {
                                loading.close()
                            }
                        });
                    }).catch(() => {
                        // 取消
                    });
                }
                function reset() {
                    tableSearchParam.value = {
                        venName: null,
                        invCode: null,
                        invName: null
                    }
                    currentPage.value = 1
                    search()
                }
                function exportData() {
                    if (selectList.value.length === 0) {
                        ElementPlus.ElMessage({
                            message: "请先选择要导出的数据",
                            type: 'warning'
                        });
                        return;
                    }
                    // 构建导出数据
                    const exportData = selectList.value.map(item => ({
                        venName: item.venName || '',
                        invCode: item.invCode || '',
                        invName: item.invName || '',
                        invStd: item.invStd || '',
                        unit: item.unit || '',
                        whName: item.whName || '',
                        dateStart: item.dateStart$ || '',
                        dateEnd: item.dateEnd$ || '',
                        fqty: item.fqty || item.qty || 0,
                        qqty: item.qqty || 0,
                        yqty: item.yqty || 0,
                        wqty: item.wqty || 0,
                        outQty: item.outQty || 0,
                        remainQty: item.remainQty || 0,
                        isAllOut: item.isAllOut == 1 ? '已完成' : '未完成',
                        izSync: item.izSync || '否',
                        izPrint: item.izPrint || '否',
                        syncTime: item.syncTime$ || '',
                        createTime: item.createTime$ || '',
                        updateTime: item.updateTime$ || ''
                    }));
                    // 转换为CSV格式
                    const headers = ['供应商', '物料编码', '物料名称', '规格型号', '单位', '仓库名称', '开工日期', '完成日期', '应发数量', '期初数量', '已发数量', '未发数量', '已出库数量', '剩余数量', '出库状态', '是否同步', '是否打印', '同步时间', '创建时间', '更新时间'];
                    const csvContent = [
                        headers.join(','),
                        ...exportData.map(row => [
                            row.venName,
                            row.invCode,
                            row.invName,
                            row.invStd,
                            row.unit,
                            row.whName,
                            row.dateStart,
                            row.dateEnd,
                            row.fqty,
                            row.qqty,
                            row.yqty,
                            row.wqty,
                            row.outQty,
                            row.remainQty,
                            row.isAllOut,
                            row.izSync,
                            row.izPrint,
                            row.syncTime,
                            row.createTime,
                            row.updateTime
                        ].map(cell => `"${cell}"`).join(','))
                    ].join('\n');
                    // 下载文件
                    const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' });
                    const link = document.createElement('a');
                    const url = URL.createObjectURL(blob);
                    link.setAttribute('href', url);
                    link.setAttribute('download', '委外加工出库单数据_' + new Date().getTime() + '.csv');
                    link.style.visibility = 'hidden';
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                    ElementPlus.ElMessage({
                        message: "导出成功",
                        type: 'success'
                    });
                }
                // 初始化加载
                search()
                return {
                    currentPage,
                    pageSizes,
                    pageSize,
                    pageTotal,
                    tableSearchParam,
                    tableData,
                    selectList,
                    handleSelectionChange,
                    handleSizeChange,
                    handleCurrentChange,
                    search,
                    reset,
                    selectDelete,
                    exportData
                }
            }
        })
        // 配置Element Plus中文语言包
        app.use(ElementPlus, {
            locale: zhCn
        })
        // 注册所有图标组件
        for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
            app.component(key, component)
        }
        app.mount('#app')
    </script>
</body>
</html>
src/main/webapp/views/pda/materialWwOut.html
New file
@@ -0,0 +1,511 @@
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>委外加工出库</title>
    <link rel="stylesheet" href="../../static/layui/css/layui.css" media="all">
    <link rel="stylesheet" href="../../static/css/pda.css" media="all">
    <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>
</head>
<style>
    .form-box span {
        font-size: 16px;
        display: inline-block;
        text-align: right;
    }
    .form-box input {
        width: 165px;
        padding-left: 5px;
        height: 30px;
        border: 1px solid #777777;
        overflow:hidden;
        white-space:nowrap;
        text-overflow:ellipsis;
    }
    .number-tool {
        margin-left: 10px;
        padding: 1px 0 1px 5px;
        display: inline-block;
        width: 120px;
    }
    .number-tool:after {
        clear: both;
        content: "";
        display: table;
    }
    .number-tool button {
        background-color: #fff;
        margin-top: 3px;
        font-size: 16px;
        height: 25px;
        float: left;
        width: 25px;
        border: 1px solid #777777;
    }
    .number-tool input {
        text-align: center;
        height: 30px;
        float: left;
        margin: 0 5px;
        min-width: 50px;
        width: auto;
        padding: 0 5px;
        box-sizing: border-box;
    }
</style>
<body>
<header class="layui-form">
    <div class="layui-input-inline">
        <label class="layui-form-label">供应商</label>
        <input class="layui-input" id="venName" onkeyup="findCode(this, 'venName')" placeholder="扫码 / 输入"
               style="width: 175px"
               autocomplete="off">
    </div>
    <div class="layui-input-inline">
        <label class="layui-form-label">物料编码</label>
        <input class="layui-input" id="invCode" onkeyup="findCode(this, 'invCode')" placeholder="扫码 / 输入"
               style="width: 175px"
               autocomplete="off">
    </div>
</header>
<main>
    <table class="layui-table" id="materialWwOut" lay-filter="materialWwOut"></table>
</main>
<footer>
    <div class="layui-btn-container">
        <button type="button" id="reset-btn" class="layui-btn layui-btn-primary" onclick="reset()">重置</button>
        <button type="button" id="out-btn" class="layui-btn layui-btn-normal " onclick="materialWwOut()"
                style="margin-left: 20px">出库
        </button>
        <button type="button" id="retrun-btn" class="layui-btn layui-btn-primary " onclick="back()"
                style="margin-left: 20px">返回
        </button>
        <span id="tips"></span>
    </div>
</footer>
<!-- 修改数量弹窗 -->
<div id="modify" style="display: none; padding-top: 10px; text-align: center;">
    <div class="form-box">
        <div class="form-item">
            <table style="display: inline">
                <tr>
                    <td>
                        <span style="width: 35px; margin-right: 5px">编码</span>
                    </td>
                    <td style="text-align: left">
                        <input id="invCode2" type="text" disabled="disabled">
                    </td>
                </tr>
            </table>
        </div>
        <div class="form-item">
            <table style="display: inline">
                <tr>
                    <td style="vertical-align: top">
                        <span style="width: 35px; margin-right: 5px">名称</span>
                    </td>
                    <td style="text-align: left">
                    <textarea rows="3" style="resize: none; width: 165px" id="invName2" type="text" disabled="disabled"
                              readonly="readonly"></textarea>
                    </td>
                </tr>
            </table>
        </div>
    </div>
    <input id="index" type="text" disabled="disabled" style="display: none;">
    <div class="form-item" style="margin-top: 5px; margin-bottom: 8px;">
        <span style="vertical-align: middle">数量</span>
        <div class="number-tool" style="vertical-align: middle">
            <button onclick="reduce()">-</button>
            <input id="outQtyInput" type="number" onchange="fix(this)">
            <button onclick="add()">+</button>
        </div>
    </div>
    <button id="remove" onclick="remove()">移除</button>
    <button id="confirm" onclick="confirm()">保存</button>
</div>
</body>
<script>
    var countLayer;
    // 当前点击物料的最大剩余数量
    var maxRemainQty;
    // 表格数据
    var materialWwOutList = [];
    window.onload = function () {
        document.getElementById("venName").focus();
    }
    /**
     * 提示信息
     * @param msg 提示内容
     * @param warn true:红色字体
     */
    function tips(msg, warn) {
        layer.msg(msg, {icon: warn ? 2 : 1})
    }
    function back() {
        parent.backIndex();
    }
    var tableIns;
    layui.use(['table', 'form'], function () {
        var table = layui.table;
        var $ = layui.jquery;
        var layer = layui.layer;
        var form = layui.form;
        tableIns = table.render({
            id: 'materialWwOut',
            elem: '#materialWwOut',
            data: [],
            limit: 500,
            cellMinWidth: 50,
            cols: [[
                {field: 'invCode', align: 'center', title: '编码', event: 'detail', width: 80},
                {field: 'venName', align: 'center', title: '供应商', event: 'detail', width: 100},
                {field: 'invName', align: 'center', title: '名称', event: 'detail'},
                {field: 'whName', align: 'center', title: '库区', event: 'detail', width: 50},
                {
                    field: 'remainQty',
                    align: 'center',
                    title: '数量',
                    event: 'modify',
                    style: 'cursor: pointer;color: blue',
                    width: 80,
                    templet: function(d) {
                        // 显示出库数量(如果已修改)或剩余数量
                        // 优先显示修改后的出库数量,如果没有修改则显示剩余数量
                        var qty = (d.outQtyOut !== undefined && d.outQtyOut !== null) ? d.outQtyOut : (d.remainQty || 0);
                        // 格式化数字显示,保留适当的小数位数
                        if (typeof qty === 'number' || (typeof qty === 'string' && qty.indexOf('.') !== -1)) {
                            return parseFloat(qty).toString();
                        }
                        return qty;
                    }
                },
                {type: 'checkbox', fixed: 'right', width: 30},
            ]],
            done: function (res, curr, count) {
            }
        });
        // 监听行工具事件
        table.on('tool(materialWwOut)', function (obj) {
            var data = obj.data;
            switch (obj.event) {
                case 'modify':
                    countLayer = layer.open({
                        type: 1,
                        offset: '20px',
                        title: '修改数量',
                        shadeClose: true,
                        area: ['80%', '240px'],
                        content: $("#modify"),
                        success: function (layero, index) {
                            $('#invCode2').val(data.invCode);
                            $('#invName2').val(data.invName);
                            $('#index').val(data.id);
                            // 显示当前出库数量(如果已修改)或剩余数量
                            var currentOutQty = (data.outQtyOut !== undefined && data.outQtyOut !== null) ? data.outQtyOut : (data.remainQty || 0);
                            $('#outQtyInput').val(currentOutQty);
                            maxRemainQty = parseFloat(data.remainQty) || 0;
                            // 根据数字长度调整输入框宽度
                            adjustInputWidth($('#outQtyInput'));
                            // 监听输入变化,动态调整宽度
                            $('#outQtyInput').off('input').on('input', function() {
                                adjustInputWidth($(this));
                            });
                        }
                    });
                    break;
            }
        });
    });
    var venNameBar;
    var invCodeBar;
    /* 扫码、输入供应商和物料编码 */
    function findCode(el, type) {
        venNameBar = $("#venName").val();
        invCodeBar = $("#invCode").val();
        // 去除空格
        if (venNameBar) {
            venNameBar = venNameBar.trim();
        }
        if (invCodeBar) {
            invCodeBar = invCodeBar.trim();
        }
        switch (type) {
            case 'venName':
                break;
            case 'invCode':
                break;
            default:
                break;
        }
        // 构建查询参数
        var params = {};
        if (venNameBar) {
            params.venName = venNameBar;
        }
        if (invCodeBar) {
            params.invCode = invCodeBar;
        }
        // 如果所有查询条件都为空,不执行查询
        if (!venNameBar && !invCodeBar) {
            // 清空表格数据
            tableIns.reload({
                data: [],
            });
            materialWwOutList = [];
            return;
        }
        // PDA出库查询,添加pdaQuery参数,后端会过滤掉已全部出库的物料
        params.pdaQuery = true;
        $.ajax({
            url: baseUrl + "/materialWwOut/list/auth",
            headers: {'token': localStorage.getItem('token')},
            data: params,
            method: 'GET',
            async: false,
            success: function (res) {
                if (res && res.code === 200) {
                    var records = [];
                    if (res.data && res.data.records && res.data.records.length > 0) {
                        records = res.data.records;
                    } else if (res.data && Array.isArray(res.data)) {
                        records = res.data;
                    }
                    // 初始化出库数量字段(初始等于剩余数量,用户可以修改)
                    records.map(function (item) {
                        // 如果还没有设置出库数量,则初始化为剩余数量
                        if (item.outQtyOut === undefined || item.outQtyOut === null) {
                            item.outQtyOut = item.remainQty || 0;
                        }
                    });
                    tableIns.reload({
                        data: records,
                    });
                    materialWwOutList = records;
                } else if (res && res.code === 403) {
                    top.location.href = baseUrl + "/pda";
                } else if (res && res.msg) {
                    tips(res.msg, true)
                } else {
                    tips("查询失败,请稍后重试", true);
                }
            },
            error: function(xhr, status, error) {
                console.error("查询失败:", status, error);
                tips("查询失败,请稍后重试", true);
            }
        });
    }
    /* 修改数量 */
    var countDom = $('#outQtyInput');
    function add() {
        if (Number(countDom.val()) >= maxRemainQty) {
            return;
        }
        countDom.val(Number(countDom.val()) + 1);
        // 调整输入框宽度
        adjustInputWidth(countDom);
    }
    function reduce() {
        if (Number(countDom.val()) <= 0) {
            return;
        }
        countDom.val(Number(countDom.val()) - 1);
        // 调整输入框宽度
        adjustInputWidth(countDom);
    }
    function fix(e) {
        var val = Number(e.value) || 0;
        if (val > maxRemainQty) {
            countDom.val(maxRemainQty);
        }
        // 调整输入框宽度
        adjustInputWidth($(e));
    }
    /**
     * 根据数字长度动态调整输入框宽度
     * @param $input jQuery对象,输入框元素
     */
    function adjustInputWidth($input) {
        if (!$input || $input.length === 0) {
            return;
        }
        var value = $input.val() || '';
        var valueStr = value.toString();
        // 计算文本宽度(每个字符约8-10px,小数点和小数位需要额外空间)
        // 最小宽度50px,最大宽度150px
        var minWidth = 50;
        var maxWidth = 150;
        var charWidth = 10; // 每个字符大约10px
        var padding = 10; // 左右内边距
        // 计算需要的宽度:字符数 * 字符宽度 + 内边距
        var calculatedWidth = valueStr.length * charWidth + padding;
        // 确保宽度在最小和最大范围内
        var finalWidth = Math.max(minWidth, Math.min(maxWidth, calculatedWidth));
        $input.css('width', finalWidth + 'px');
    }
    function remove() {
        var invCode = $('#invCode2').val();
        var id = $('#index').val();
        for (var j = 0; j < materialWwOutList.length; j++) {
            if (invCode === materialWwOutList[j].invCode && id == materialWwOutList[j].id) {
                materialWwOutList.splice(j, 1);
            }
        }
        tableIns.reload({data: materialWwOutList});
        layer.close(countLayer);
        tips("移除成功");
    }
    // 修改数量
    function confirm() {
        var invCode = $('#invCode2').val();
        var outQty = parseFloat($('#outQtyInput').val()) || 0;
        var id = $('#index').val();
        if (outQty < 0) {
            tips("数量不能小于0", true);
            return;
        }
        if (outQty > maxRemainQty) {
            tips("数量不能超过剩余数量", true);
            return;
        }
        // 更新表格中的出库数量
        for (var j = 0; j < materialWwOutList.length; j++) {
            if (invCode === materialWwOutList[j].invCode && id == materialWwOutList[j].id) {
                materialWwOutList[j].outQtyOut = outQty;
            }
        }
        tableIns.reload({data: materialWwOutList});
        layer.close(countLayer);
        tips("修改成功");
    }
    /* 委外加工出库 */
    function materialWwOut() {
        var table = layui.table;
        var checkStatus = table.checkStatus('materialWwOut');
        var data = checkStatus.data;
        if (data.length == 0) {
            layer.msg("请选择物料!");
            return;
        }
        // 批量出库
        var outQtyList = [];
        for (var i = 0; i < data.length; i++) {
            var item = data[i];
            // 使用修改后的出库数量(outQtyOut),如果没有修改则使用剩余数量
            var outQty = 0;
            if (item.outQtyOut !== undefined && item.outQtyOut !== null) {
                outQty = parseFloat(item.outQtyOut) || 0;
            } else {
                outQty = parseFloat(item.remainQty) || 0;
            }
            if (outQty <= 0) {
                tips("物料 " + item.invCode + " 出库数量必须大于0", true);
                return;
            }
            var remainQty = parseFloat(item.remainQty) || 0;
            if (outQty > remainQty) {
                tips("物料 " + item.invCode + " 出库数量不能超过剩余数量", true);
                return;
            }
            outQtyList.push({
                id: item.id,
                outQty: outQty
            });
        }
        // 确认出库
        layer.confirm('确定要出库选中的物料吗?', {icon: 3, title: '提示'}, function(index){
            // 批量更新出库数量
            var successCount = 0;
            var failCount = 0;
            for (var i = 0; i < outQtyList.length; i++) {
                $.ajax({
                    url: baseUrl + "/materialWwOut/updateOutQty/auth",
                    headers: {'token': localStorage.getItem('token')},
                    data: JSON.stringify(outQtyList[i]),
                    contentType: 'application/json;charset=UTF-8',
                    method: 'POST',
                    async: false,
                    success: function (res) {
                        if (res && res.code === 200) {
                            successCount++;
                        } else {
                            failCount++;
                        }
                    },
                    error: function(xhr, status, error) {
                        console.error("出库失败:", status, error);
                        failCount++;
                    }
                });
            }
            if (successCount > 0) {
                tips("成功出库 " + successCount + " 条记录");
                // 清空输入框并刷新列表
                $("#venName").val("");
                $("#invCode").val("");
                materialWwOutList = [];
                tableIns.reload({data: materialWwOutList});
                document.getElementById("venName").focus();
            }
            if (failCount > 0) {
                tips("出库失败 " + failCount + " 条记录", true);
            }
            layer.close(index);
        });
    }
    function reset() {
        $("#venName").val(null);
        $("#invCode").val(null);
        materialWwOutList = [];
        tableIns.reload({data: materialWwOutList});
        layer.closeAll();
        document.getElementById("venName").focus();
    }
</script>
</html>