Commit 7555bfca by zhangtw

导入校验

parent 278338ea
......@@ -144,6 +144,7 @@
<el-table-column label="SAP物料号" align="center" prop="sapNo" width="150"/>
<!-- <el-table-column label="物料编码" align="center" prop="materialCode" width="120"/> -->
<el-table-column label="物料名称" align="center" prop="materialName" width="150"/>
<el-table-column label="物料英文名称" align="center" prop="materialEname" width="150"/>
<el-table-column label="TS Code" align="center" prop="tsCode" width="150"/>
<el-table-column label="物料分类" align="center" prop="categoryCode" >
<template slot-scope="scope">
......@@ -298,6 +299,9 @@
<el-form-item label="物料名称" prop="materialName">
<el-input v-model="form.materialName" placeholder="请输入物料名称" />
</el-form-item>
<el-form-item label="物料英文名称" prop="materialEname">
<el-input v-model="form.materialEname" placeholder="请输入物料英文名称" />
</el-form-item>
<el-form-item label="物料分类" prop="categoryCode">
<el-select v-model="form.categoryCode" placeholder="请选择物料分类" clearable>
<el-option
......@@ -444,6 +448,7 @@ export default {
id: null,
materialCode: null,
materialName: null,
materialEname: null,
sapNo: null,
tsCode: null,
categoryCode: null,
......
......@@ -251,7 +251,7 @@ export default {
.filter(item => item.isUsed !== 0 && item.isUsed !== '0')
.map(item => ({
...item,
displayCategory: this.categoryMap[item.categoryCode] || `${item.categoryCode}(未匹配分类)`
displayCategory: this.categoryMap[item.categoryCode] || `未匹配分类`
}));
this.total = response.total;
}).finally(() => {
......
......@@ -138,7 +138,15 @@
<el-table-column label="物料名称" align="center" prop="materialName" width="150" />
<el-table-column label="SAP物料号" align="center" prop="sapNo" width="120" />
<el-table-column label="TS Code" align="center" prop="tsCode" width="120" />
<el-table-column label="危险类别" align="center" prop="hazard" width="120" />
<el-table-column label="危险类别" align="center" prop="hazardId" width="120" >
<template slot-scope="scope">
<el-tag
:type="getDictListClass('danger_type',scope.row.hazardId)"
size="small">
{{ getDictLabel('danger_type',scope.row.hazardId) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="规格型号" align="center" prop="specification" width="120" />
<el-table-column label="计量单位" align="center" prop="materialUnit" width="120" />
<el-table-column label="单位重量" align="center" prop="unitWeight" width="120" >
......@@ -232,7 +240,15 @@
<el-table-column label="物料名称" align="center" prop="materialName" width="150" />
<el-table-column label="SAP物料号" align="center" prop="sapNo" width="120" />
<el-table-column label="TS Code" align="center" prop="tsCode" width="120" />
<el-table-column label="危险类别" align="center" prop="hazard" width="120" />
<el-table-column label="危险类别" align="center" prop="hazardId" width="120" >
<template slot-scope="scope">
<el-tag
:type="getDictListClass('danger_type',scope.row.hazardId)"
size="small">
{{ getDictLabel('danger_type',scope.row.hazardId) }}
</el-tag>
</template>
</el-table-column>
<!-- <el-table-column label="规格型号" align="center" prop="specification" width="120" /> -->
<!-- <el-table-column label="入库单号" align="center" prop="orderId" width="150" /> -->
<el-table-column label="批次" align="center" prop="batchId" width="120" />
......@@ -265,6 +281,7 @@ import ImportExcel from "@/components/ImportExcel"
export default {
name: "InventoryDetail",
dicts: ['danger_type'],
components: {
RightToolbar,
PageTitle,
......@@ -333,6 +350,19 @@ export default {
this.getList()
},
methods: {
//从表格中的值当作键获取字典lebel
getDictLabel(dictType, value){
if(!value || !this.dict?.type?.[dictType]) return '-'
const dictItem = this.dict.type[dictType].find(item => item.value === value)
return dictItem?.label || '-'
},
//从表格中的值当作键获取字典listClass
getDictListClass(dictType, value){
if(!value || !this.dict?.type?.[dictType]) return '-'
const dictItem = this.dict.type[dictType].find(item => item.value === value)
return dictItem?.label || '-'
},
/** 查询库存明细列表 */
getList() {
this.loading = true
......
package com.ruoyi.web.controller.inventory;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.uuid.UUID;
import javax.servlet.http.HttpServletResponse;
......@@ -10,6 +22,9 @@ import com.ruoyi.inventory.domain.vo.inboundVO.InboundFinishTemplateVO;
import com.ruoyi.inventory.domain.vo.InboundMaterialTotalVO;
import com.ruoyi.inventory.domain.vo.inboundVO.InboundTRDCTemplateVO;
import com.ruoyi.inventory.domain.vo.inboundVO.InboundTemplateVO;
import com.ruoyi.inventory.service.impl.InboundOrdersServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
......@@ -36,7 +51,7 @@ public class InboundOrdersController extends BaseController
{
@Autowired
private IInboundOrdersService inboundOrdersService;
private static final Logger log = LoggerFactory.getLogger(InboundOrdersServiceImpl.class);
/**
* 查询入库单主列表
*/
......@@ -137,83 +152,117 @@ public class InboundOrdersController extends BaseController
@Log(title = "入库信息导入", businessType = BusinessType.IMPORT)
@PostMapping("/import")
public AjaxResult importTemplate(@RequestParam("file") MultipartFile file,
// 接收 true/false
@RequestParam("updateSupport") Integer updateSupport,
@RequestParam(value = "orderType", required = false) Integer orderType) throws Exception
{
@RequestParam(value = "orderType", required = false) Integer orderType) throws Exception {
// 防护1:校验文件非空
if (file == null || file.isEmpty()) {
return error("导入文件不能为空!");
}
// 防护2:校验文件格式
String fileName = file.getOriginalFilename();
if (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls")) {
if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
return error("仅支持Excel格式文件(.xlsx/.xls)!");
}
// // 第二步:校验Excel列名是否匹配模板(核心!拦截非模板数据)
// List<String> templateColumns = Arrays.asList(
// "入库日期",
// "SAP No",
// "物料名称",
// "TS Code",
// "货主",
// "批号",
// "计划数量",
// "单号",
// "系统编号",
// "件重",
// "约数",
// "实际件数",
// "实发数量",
// "仓库",
// "库位",
// "标签颜色",
// "凭证号",
// "单价",
// "备注",
// "订单类型",
// "收货人",
// "物料备注"
// );
// List<String> excelColumns = ExcelUtil.getExcelHeader(file.getInputStream()); // 自定义方法读取表头
// if (CollectionUtils.isEmpty(excelColumns) || !excelColumns.containsAll(templateColumns)) {
// return AjaxResult.error("导入文件不是标准模板!请下载官方模板后填写(缺失列:"
// + getMissingColumns(templateColumns, excelColumns) + ")");
// }
String message = null;
String operName = getUsername();
switch(orderType){
case 1:
ExcelUtil<InboundTemplateVO> util = new ExcelUtil<InboundTemplateVO>(InboundTemplateVO.class);
List<InboundTemplateVO> inboundOrders = util.importExcel(file.getInputStream());
// 防护3:拦截空列表,避免 Service 层处理空数据
if (CollectionUtils.isEmpty(inboundOrders)) {
return error("Excel中未解析到有效数据,请检查模板是否正确!");
}
message = inboundOrdersService.importInboundOrders(inboundOrders, updateSupport, operName, orderType);
break;
case 2:
ExcelUtil<InboundFinishTemplateVO> util1 = new ExcelUtil<InboundFinishTemplateVO>(InboundFinishTemplateVO.class);
List<InboundFinishTemplateVO> inboundOrders1 = util1.importExcel(file.getInputStream());
// 防护3:拦截空列表,避免 Service 层处理空数据
if (CollectionUtils.isEmpty(inboundOrders1)) {
return error("Excel中未解析到有效数据,请检查模板是否正确!");
}
message = inboundOrdersService.importInboundOrders(inboundOrders1, updateSupport, operName, orderType);
break;
case 3:
ExcelUtil<InboundTRDCTemplateVO> util2 = new ExcelUtil<InboundTRDCTemplateVO>(InboundTRDCTemplateVO.class);
List<InboundTRDCTemplateVO> inboundOrders2 = util2.importExcel(file.getInputStream());
// 防护3:拦截空列表,避免 Service 层处理空数据
if (CollectionUtils.isEmpty(inboundOrders2)) {
return error("Excel中未解析到有效数据,请检查模板是否正确!");
}
message = inboundOrdersService.importInboundOrders(inboundOrders2, updateSupport, operName, orderType);
break;
// 防护3:校验orderType非空且合法
if (orderType == null || !Arrays.asList(1, 2, 3).contains(orderType)) {
return error("导入类型不能为空,仅支持1/2/3!");
}
// 2. 解析Excel表头(适配EasyExcel 2.x,无interrupt、无readRowNumber)
List<String> headerList = new ArrayList<>();
// 标记:是否已解析表头(避免重复处理)
AtomicBoolean headerParsed = new AtomicBoolean(false);
try {
EasyExcel.read(file.getInputStream(), new AnalysisEventListener<Object>() {
// 解析表头(2.x 中invokeHeadMap只会执行一次)
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
// 仅首次执行时解析表头
if (!headerParsed.get()) {
for (String header : headMap.values()) {
headerList.add(header.trim()); // 去空格存入
}
headerParsed.set(true); // 标记表头已解析完成
log.info("Excel表头解析完成,表头列表:{}", headerList);
}
}
// 解析数据行(表头解析完成后,直接返回不处理)
@Override
public void invoke(Object data, AnalysisContext context) {
// 逻辑终止:表头解析完后,数据行直接跳过
return;
}
// 解析完成(空实现即可)
@Override
public void doAfterAllAnalysed(AnalysisContext context) {}
})
.sheet() // 读取第一个sheet
.headRowNumber(1) // 指定表头在第1行(2.x 核心配置)
.doRead(); // 执行解析
} catch (IOException e) {
log.error("解析Excel表头失败", e);
return AjaxResult.error("解析Excel文件失败:" + e.getMessage());
}
// 防护4:表头解析为空的情况
if (CollectionUtils.isEmpty(headerList)) {
return error("未解析到Excel表头,请检查模板是否有表头行!");
}
// 3. 通用导入逻辑(抽取重复代码,避免冗余)
String message = handleImport(getVOClassByOrderType(orderType), file, headerList, updateSupport, getUsername(), orderType);
return success(message);
}
// 辅助方法:获取缺失的列名
/**
* 根据orderType获取对应的VO类
*/
private Class<?> getVOClassByOrderType(Integer orderType) {
switch (orderType) {
case 1: return InboundTemplateVO.class;
case 2: return InboundFinishTemplateVO.class;
case 3: return InboundTRDCTemplateVO.class;
default: throw new ServiceException("不支持的导入类型,请联系管理员" + orderType);
}
}
/**
* 通用导入逻辑(泛型适配不同VO)
*/
private <T> String handleImport(Class<T> clazz, MultipartFile file, List<String> headerList,
Integer updateSupport, String operName, Integer orderType) throws Exception {
// 反射读取VO中@Excel注解的必填表头
List<String> requiredExcelHeads = new ArrayList<>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Excel.class)) {
requiredExcelHeads.add(field.getAnnotation(Excel.class).name().trim());
}
}
// 校验表头是否包含所有必填项
if (!headerList.containsAll(requiredExcelHeads)) {
List<String> missingHeads = requiredExcelHeads.stream()
.filter(head -> !headerList.contains(head))
.collect(Collectors.toList());
return "导入数据字段与目标模板不一致,请检查!缺失字段:" + String.join("、", missingHeads);
}
// 解析Excel数据(若依ExcelUtil适配2.x,无需修改)
ExcelUtil<T> util = new ExcelUtil<>(clazz);
List<T> dataList = util.importExcel(file.getInputStream());
if (CollectionUtils.isEmpty(dataList)) {
return "Excel中未解析到有效数据,请检查模板是否正确!";
}
// 调用Service导入(需确保Service支持泛型列表,或强转Object)
return inboundOrdersService.importInboundOrders(dataList, updateSupport, operName, orderType);
}
// 辅助方法:获取缺失的列名(备用)
private String getMissingColumns(List<String> template, List<String> excel) {
return template.stream()
.filter(col -> !excel.contains(col))
......
......@@ -6,6 +6,7 @@ import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import com.ruoyi.common.config.WarehouseConfig;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.inventory.domain.*;
import com.ruoyi.inventory.domain.vo.InboundMaterialTotalVO;
......@@ -408,10 +409,11 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
}
String warehouseId = warehouseNameIdMap.get(warehouseName);
if (StringUtils.isBlank(warehouseId)) {
log.info("仓库【" + warehouseName + "】不存在,可能为成品入库");
log.info("仓库【" + warehouseName + "】不存在,可能暂无仓库或为成品入库,已使用默认仓库");
itemDO.setWarehouseId(WarehouseConfig.DEFAULT_WAREHOUSE_ID);
}else{
itemDO.setWarehouseId(warehouseId);
}
itemDO.setWarehouseId(warehouseId);
// 2. 库位名称转ID
String locationName = "";
try {
......@@ -426,10 +428,10 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
String locationId = storageLocationNameIdMap.get(locationName);
if (StringUtils.isBlank(locationId)) {
log.info("库位【" + locationName + "】不存在,可能为成品入库");
log.info("库位【" + locationName + "】不存在,可能暂无库位或为成品入库");
}else{
itemDO.setLocationId(locationId);
}
itemDO.setLocationId(locationId);
itemDOList.add(itemDO);
} catch (Exception e) {
// 单个明细失败:仅统计,不影响整单
......
......@@ -231,8 +231,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</insert>
<!-- 统计入库次数-->
<select id="countInboundOrders" resultType="int" parameterType="String">
select count(id)
......@@ -247,29 +245,36 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result column="total_money" property="totalMoney" jdbcType="DECIMAL"/>
</resultMap>
<select id="countInboundMaterialQuantity" resultMap="InboundMaterialTotalResultMap" parameterType="String">
select m.material_name,
COALESCE(SUM(ioi.actual_quantity), 0) as total_quantity
from materials as m
left join inbound_order_items as ioi
on m.id = ioi.material_id
left join inbound_orders as io
on io.id = ioi.inbound_order_id
and io.order_status = 2
where m.is_used = 1 and m.is_active = 1
group by m.id
order by total_quantity
SELECT t.material_name, t.total_quantity
FROM (
SELECT m.material_name,
COALESCE(SUM(ioi.actual_quantity), 0) as total_quantity
FROM materials as m
LEFT JOIN inbound_order_items as ioi ON m.id = ioi.material_id
LEFT JOIN inbound_orders as io ON io.id = ioi.inbound_order_id AND io.order_status = 2
WHERE m.is_used = 1 AND m.is_active = 1
GROUP BY m.id, m.material_name
ORDER BY total_quantity DESC
LIMIT 10
) t
ORDER BY t.total_quantity ASC;
</select>
<select id="countInboundMaterialMoney" resultMap="InboundMaterialTotalResultMap" parameterType="String">
select m.material_name,
COALESCE(sum(ioi.actual_quantity * ioi.unit_price), 0) as total_money
from materials as m
left join inbound_order_items as ioi
on m.id = ioi.material_id
left join inbound_orders as io
on io.id = ioi.inbound_order_id
and io.order_status = 2
where m.is_used = 1 and m.is_active = 1
group by m.id
order by total_money
select t.material_name,t.total_money
from(
select m.material_name,
COALESCE(sum(ioi.actual_quantity * ioi.unit_price), 0) as total_money
from materials as m
left join inbound_order_items as ioi
on m.id = ioi.material_id
left join inbound_orders as io
on io.id = ioi.inbound_order_id
and io.order_status = 2
where m.is_used = 1 and m.is_active = 1
group by m.id
order by total_money desc
limit 10
) t
order by total_money asc
</select>
</mapper>
\ No newline at end of file
......@@ -10,6 +10,7 @@
<result property="orderId" column="order_id" />
<result property="materialId" column="material_id" />
<result property="materialName" column="material_name"/>
<result property="hazardId" column="hazard_id" />
<result property="batchId" column="batch_id" />
<result property="warehousesCode" column="warehouses_code" />
<result property="warehousesName" column="warehouses_name"/>
......@@ -401,6 +402,7 @@
i.order_id,
i.material_id,
m.material_name,
m.hazard_id,
i.batch_id,
i.location_id,
sl.location_name,
......
......@@ -8,6 +8,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="id" column="id" />
<result property="materialCode" column="material_code" />
<result property="materialName" column="material_name" />
<result property="materialEname" column="material_ename" />
<result property="sapNo" column="sap_no" />
<result property="tsCode" column="ts_code" />
<result property="categoryCode" column="category_code" />
......@@ -36,7 +37,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectMaterialsVo">
select id, material_code, material_name, sap_no, ts_code, category_code, hazard_id, specification, material_unit, unit_weight, package_weight, total_weight, volume, shelf_life_days, storage_temperature, special_requirements, is_batch_managed, is_serial_managed, min_stock_level, max_stock_level, is_used, is_active, risk_level, sort_no, create_time, create_user_code, update_time, update_user_code from materials
select id, material_code, material_name,material_ename, sap_no, ts_code, category_code, hazard_id, specification, material_unit, unit_weight, package_weight, total_weight, volume, shelf_life_days, storage_temperature, special_requirements, is_batch_managed, is_serial_managed, min_stock_level, max_stock_level, is_used, is_active, risk_level, sort_no, create_time, create_user_code, update_time, update_user_code from materials
</sql>
<select id="selectMaterialsList" parameterType="Materials" resultMap="MaterialsResult">
......@@ -101,6 +102,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="id != null">id,</if>
<if test="materialCode != null">material_code,</if>
<if test="materialName != null">material_name,</if>
<if test="materialEname != null">material_ename,</if>
<if test="sapNo != null">sap_no,</if>
<if test="tsCode != null">ts_code,</if>
<if test="categoryCode != null">category_code,</if>
......@@ -131,6 +133,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="id != null">#{id},</if>
<if test="materialCode != null">#{materialCode},</if>
<if test="materialName != null">#{materialName},</if>
<if test="materialEname != null">#{materialEname},</if>
<if test="sapNo != null">#{sapNo},</if>
<if test="tsCode != null">#{tsCode},</if>
<if test="categoryCode != null">#{categoryCode},</if>
......@@ -164,6 +167,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<trim prefix="SET" suffixOverrides=",">
<if test="materialCode != null">material_code = #{materialCode},</if>
<if test="materialName != null">material_name = #{materialName},</if>
<if test="materialEname != null">material_ename = #{materialEname},</if>
<if test="sapNo != null">sap_no = #{sapNo},</if>
<if test="tsCode != null">ts_code = #{tsCode},</if>
<if test="categoryCode != null">category_code = #{categoryCode},</if>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论