Commit bfec1bf4 by yubin

Merge remote-tracking branch 'origin/master'

parents d1b4d4ab 69a8a9fc
package com.scpyun.platform.jilinsscgsdp.bean.entity;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class InboundImp {
@ExcelProperty(value = "入库单号", index = 0)
private String inbound_no;
@ExcelProperty(value = "批次号", index = 1)
private String batch_no;
@ExcelProperty(value = "入库日期", index = 2)
private String inbound_date;
@ExcelProperty(value = "存放位置", index = 3)
private String storage_location;
@ExcelProperty(value = "备注", index = 4)
private String remark;
@ExcelProperty(value = "物料编号", index = 5)
private String material_code;
@ExcelProperty(value = "物料名称", index = 6)
private String material_name;
@ExcelProperty(value = "入库数量", index = 7)
private String inbound_quantity;
@ExcelProperty(value = "单价", index = 8)
private String unit_price;
@ExcelProperty(value = "生产日期", index = 9)
private String production_date;
@ExcelProperty(value = "有效期至", index = 10)
private String expiry_date;
}
......@@ -84,7 +84,6 @@ public class KeyDmBorrowServiceImpl {
}
map.put("expected_return_date", expected_return_date);
if (id == null || String.valueOf(id).trim().isEmpty()) {
map.put("id",UUID.randomUUID().toString());
map.put("application_no","borrow");
map.put("applicant_id",user.get("id"));
map.put("applicant_name", user.get("name"));
......
package com.scpyun.platform.jilinsscgsdp.service.impl;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.scpyun.base.bean.Page;
import com.scpyun.base.core.annotation.Api;
import com.scpyun.base.core.annotation.ApiOperation;
import com.scpyun.base.core.exception.CustomException;
import com.scpyun.base.db.service.CommonService;
import com.scpyun.platform.jilinsscgsdp.utils.InboundFileListener;
import com.scpyun.platform.jilinsscgsdp.utils.MaterialFileListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
......@@ -293,6 +305,39 @@ public class KeyDmInboundServiceImpl {
return ret;
}
@ApiOperation(value = "导入入库单", desc = "解析 Excel 导入入库信息")
public Map<String, Object> importInbound(Map<String, Object> map, MultipartFile file) {
Map<String, Object> result = new HashMap<>();
try {
InboundFileListener listener = new InboundFileListener(commonService, namespace, map);
EasyExcel.read(file.getInputStream(), com.scpyun.platform.jilinsscgsdp.bean.entity.InboundImp.class, listener).sheet().headRowNumber(1).doRead();
result = listener.getResult();
} catch (IOException e) {
throw new CustomException("导入失败");
}
return result;
}
@ApiOperation(value = "导入模板下载", desc = "下载入库导入模板")
public void templateDownload(Map<String, Object> map, HttpServletResponse response) {
response.setContentType("application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
try {
String fileName = URLEncoder.encode("入库导入模板", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
response.setHeader("filename", fileName + ".xlsx");
response.setHeader("Access-Control-Expose-Headers", "filename,Content-Disposition");
String tpl = "keyInboundImp.xlsx";
Resource resource = new ClassPathResource(tpl);
InputStream is = resource.getInputStream();
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(is).excelType(ExcelTypeEnum.XLSX).build();
excelWriter.finish();
} catch (Exception e) {
throw new CustomException("模板下载失败");
}
}
}
package com.scpyun.platform.jilinsscgsdp.utils;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.scpyun.base.core.utils.StringUtils;
import com.scpyun.base.core.utils.UUIDUtil;
import com.scpyun.base.db.service.CommonService;
import com.scpyun.platform.jilinsscgsdp.bean.entity.InboundImp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
public class InboundFileListener extends AnalysisEventListener<InboundImp> {
private static final Logger log = LoggerFactory.getLogger(InboundFileListener.class);
private static final int BATCH_COUNT = 200;
private static final int GROUP_BATCH_SIZE = 50; // 每批处理50个入库单
private List<InboundImp> cached = new ArrayList<>(BATCH_COUNT);
private AtomicInteger insertCount = new AtomicInteger(0);
private AtomicInteger errorCount = new AtomicInteger(0);
private AtomicInteger inboundCount = new AtomicInteger(0); // 入库单计数
private CommonService commonService;
private String namespace;
private Map<String,Object> param;
private List<String> errInfo = new ArrayList<>();
private Map<String,Object> materialMap;
// 用于存储按入库单号分组的明细数据
private Map<String, List<InboundImp>> inboundGroupMap = new HashMap<>();
private Map<String, Object> user = null;
private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
public InboundFileListener(CommonService commonService, String namespace, Map<String,Object> param) {
this.commonService = commonService;
this.namespace = namespace;
this.param = param;
user = (Map<String, Object>) param.get("_user");
// 初始化日期格式
dateFormat.setLenient(false);
// 初始化物料映射
initMaterialMap();
}
/**
* 初始化物料映射
*/
private void initMaterialMap() {
try {
List<Map<String,Object>> materialList = commonService.findList(namespace + "getMaterialMap", null);
materialMap = new HashMap<>();
for (Map<String, Object> material : materialList) {
String code = String.valueOf(material.get("material_code"));
if (StringUtils.isNotEmpty(code)) {
materialMap.put(code, material);
}
}
log.info("初始化物料映射完成,共 {} 条记录", materialMap.size());
} catch (Exception e) {
log.error("初始化物料映射异常", e);
materialMap = new HashMap<>();
}
}
@Override
public void invoke(InboundImp data, AnalysisContext context) {
try {
// 数据校验
if (isValidInbound(data)) {
cached.add(data);
// 按入库单号和批次号分组
String groupKey = getGroupKey(data);
// 初始化分组列表
if (!inboundGroupMap.containsKey(groupKey)) {
inboundGroupMap.put(groupKey, new ArrayList<>());
}
inboundGroupMap.get(groupKey).add(data);
} else {
errorCount.incrementAndGet();
errInfo.add("行数据校验失败: " + data.getInbound_no() + "-" + data.getMaterial_code());
}
if (cached.size() >= BATCH_COUNT) {
saveBatch();
cached.clear();
}
} catch (Exception e) {
errorCount.incrementAndGet();
errInfo.add("解析异常: " + data.getInbound_no() + "-" + data.getMaterial_code() + ":" + e.getMessage());
log.error("解析入库导入行异常", e);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (!cached.isEmpty()) {
saveBatch();
cached.clear();
}
// 处理所有分组的数据
processInboundGroupsTransactional();
}
/**
* 获取分组键(入库单号 + 批次号)
*/
private String getGroupKey(InboundImp data) {
String inboundNo = StringUtils.isNotEmpty(data.getInbound_no()) ? data.getInbound_no().trim() : "";
String batchNo = StringUtils.isNotEmpty(data.getBatch_no()) ? data.getBatch_no().trim() : "DEFAULT";
return inboundNo + "|" + batchNo;
}
/**
* 事务性批量处理入库单
*/
private void processInboundGroupsTransactional() {
if (inboundGroupMap.isEmpty()) {
return;
}
List<String> groupKeys = new ArrayList<>(inboundGroupMap.keySet());
// 按批次处理,避免内存溢出
for (int i = 0; i < groupKeys.size(); i += GROUP_BATCH_SIZE) {
int end = Math.min(i + GROUP_BATCH_SIZE, groupKeys.size());
List<String> batchGroupKeys = groupKeys.subList(i, end);
try {
// 开启事务处理一批入库单
processBatchTransactional(batchGroupKeys);
} catch (Exception e) {
log.error("批量处理入库单异常", e);
// 当前批次失败,尝试单个处理
for (String groupKey : batchGroupKeys) {
try {
processSingleInboundGroup(groupKey);
} catch (Exception ex) {
errorCount.incrementAndGet();
errInfo.add("入库单处理失败: " + groupKey + ":" + ex.getMessage());
}
}
}
}
}
/**
* 批量处理事务
*/
private void processBatchTransactional(List<String> groupKeys) {
List<Map<String, Object>> masterList = new ArrayList<>();
Map<String, String> inboundIdMap = new HashMap<>(); // groupKey -> inboundId
List<Map<String, Object>> detailList = new ArrayList<>();
// 第一步:创建入库单主表和明细数据
for (String groupKey : groupKeys) {
List<InboundImp> inboundList = inboundGroupMap.get(groupKey);
if (inboundList == null || inboundList.isEmpty()) {
continue;
}
InboundImp firstRecord = inboundList.get(0);
Map<String, Object> inboundMaster = createInboundMaster(firstRecord);
if (inboundMaster == null) {
throw new RuntimeException("创建入库单主表失败: " + groupKey);
}
String inboundId = (String) inboundMaster.get("id");
inboundIdMap.put(groupKey, inboundId);
masterList.add(inboundMaster);
// 创建明细数据
for (InboundImp detail : inboundList) {
Map<String, Object> inboundDetail = createInboundDetail(detail, inboundId);
if (inboundDetail != null) {
detailList.add(inboundDetail);
}
}
}
if (masterList.isEmpty()) {
return;
}
// 第二步:批量插入入库单主表
batchInsertInboundMasters(masterList);
inboundCount.addAndGet(masterList.size());
// 第三步:批量插入入库明细
if (!detailList.isEmpty()) {
batchInsertInboundDetails(detailList);
insertCount.addAndGet(detailList.size());
}
// 第四步:批量更新物料库存
batchUpdateMaterialStock(detailList);
}
/**
* 单个入库单组处理
*/
private void processSingleInboundGroup(String groupKey) {
List<InboundImp> inboundList = inboundGroupMap.get(groupKey);
if (inboundList == null || inboundList.isEmpty()) {
return;
}
InboundImp firstRecord = inboundList.get(0);
Map<String, Object> inboundMaster = createInboundMaster(firstRecord);
if (inboundMaster == null) {
throw new RuntimeException("创建入库单主表失败: " + groupKey);
}
// 插入主表
String inboundId = insertInboundMaster(inboundMaster);
if (inboundId == null) {
throw new RuntimeException("插入入库单主表失败: " + groupKey);
}
inboundCount.incrementAndGet();
// 插入明细
for (InboundImp detail : inboundList) {
Map<String, Object> inboundDetail = createInboundDetail(detail, inboundId);
if (inboundDetail != null) {
insertInboundDetail(inboundDetail);
insertCount.incrementAndGet();
}
}
}
/**
* 批量插入入库单主表
*/
private void batchInsertInboundMasters(List<Map<String, Object>> inboundMasters) {
if (inboundMasters == null || inboundMasters.isEmpty()) {
return;
}
try {
Map<String, Object> batchParam = new HashMap<>();
batchParam.put("list", inboundMasters);
batchParam.put("_user", user);
commonService.insert(namespace + "batchInsertInboundMasters", batchParam);
} catch (Exception e) {
log.error("批量插入入库单主表异常", e);
throw new RuntimeException("批量插入入库单主表失败", e);
}
}
/**
* 批量插入入库明细
*/
private void batchInsertInboundDetails(List<Map<String, Object>> inboundDetails) {
if (inboundDetails == null || inboundDetails.isEmpty()) {
return;
}
try {
Map<String, Object> batchParam = new HashMap<>();
batchParam.put("list", inboundDetails);
batchParam.put("_user", user);
commonService.insert(namespace + "batchInsertInboundDetails", batchParam);
} catch (Exception e) {
log.error("批量插入入库明细异常", e);
throw new RuntimeException("批量插入入库明细失败", e);
}
}
/**
* 批量更新物料库存
*/
private void batchUpdateMaterialStock(List<Map<String, Object>> inboundDetails) {
if (inboundDetails == null || inboundDetails.isEmpty()) {
return;
}
try {
// 按物料ID分组汇总数量
Map<String, BigDecimal> materialQuantityMap = new HashMap<>();
for (Map<String, Object> detail : inboundDetails) {
String materialId = (String) detail.get("material_id");
BigDecimal quantity = (BigDecimal) detail.get("inbound_quantity");
if (materialId != null && quantity != null) {
materialQuantityMap.merge(materialId, quantity, BigDecimal::add);
}
}
// 批量更新
List<Map<String, Object>> updateList = new ArrayList<>();
for (Map.Entry<String, BigDecimal> entry : materialQuantityMap.entrySet()) {
Map<String, Object> updateParam = new HashMap<>();
updateParam.put("material_id", entry.getKey());
updateParam.put("quantity", entry.getValue());
updateParam.put("update_time", new Date());
updateParam.put("_user", user);
updateList.add(updateParam);
}
if (!updateList.isEmpty()) {
Map<String, Object> batchParam = new HashMap<>();
batchParam.put("list", updateList);
batchParam.put("_user", user);
commonService.update(namespace + "batchUpdateMaterialStock", batchParam);
}
} catch (Exception e) {
log.error("批量更新物料库存异常", e);
throw new RuntimeException("批量更新物料库存失败", e);
}
}
/**
* 创建入库单主表记录
*/
private Map<String, Object> createInboundMaster(InboundImp data) {
try {
Map<String, Object> master = new HashMap<>();
master.put("id", UUIDUtil.getUUID());
master.put("inbound_no", data.getInbound_no());
master.put("batch_no", data.getBatch_no());
master.put("inbound_type", 1);
master.put("inbound_status", 1); // 1-已入库
// 处理入库日期
Date inboundDate = null;
if (StringUtils.isNotEmpty(data.getInbound_date())) {
try {
inboundDate = dateFormat.parse(data.getInbound_date().trim());
} catch (ParseException e) {
log.warn("日期格式解析失败: {}", data.getInbound_date());
}
}
if (inboundDate == null) {
inboundDate = new Date();
}
master.put("inbound_date", inboundDate);
master.put("is_used", 1);
master.put("storage_location", data.getStorage_location());
master.put("remark", data.getRemark());
master.put("create_by", user.get("id"));
master.put("create_time", new Date());
master.put("_user", user);
return master;
} catch (Exception e) {
log.error("创建入库单主表异常", e);
return null;
}
}
/**
* 创建入库明细记录
*/
private Map<String, Object> createInboundDetail(InboundImp data, String inboundId) {
try {
// 检查物料是否存在
Map<String, Object> materialTempMap = (Map<String, Object>) materialMap.get(data.getMaterial_code());
if (materialTempMap == null) {
log.warn("物料 {} 不存在,跳过该明细", data.getMaterial_code());
errInfo.add("物料不存在: " + data.getMaterial_code());
return null;
}
Map<String, Object> detail = new HashMap<>();
detail.put("id", UUIDUtil.getUUID());
detail.put("inbound_id", inboundId);
detail.put("material_id", materialTempMap.get("id"));
// 物料名称优先使用Excel中的,否则使用系统已有的
String materialName = StringUtils.isNotEmpty(data.getMaterial_name()) ?
data.getMaterial_name().trim() : (String) materialTempMap.get("material_name");
detail.put("material_name", materialName);
detail.put("material_code", data.getMaterial_code());
detail.put("inbound_type", 1);
detail.put("is_used", 1);
// 转换入库数量
BigDecimal inboundQuantity = BigDecimal.ZERO;
try {
if (StringUtils.isNotEmpty(data.getInbound_quantity())) {
inboundQuantity = new BigDecimal(data.getInbound_quantity().trim());
}
} catch (Exception e) {
log.warn("数量格式转换失败: {}", data.getInbound_quantity());
inboundQuantity = BigDecimal.ZERO;
}
detail.put("inbound_quantity", inboundQuantity);
// 转换单价
BigDecimal unitPrice = BigDecimal.ZERO;
try {
if (StringUtils.isNotEmpty(data.getUnit_price())) {
unitPrice = new BigDecimal(data.getUnit_price().trim());
}
} catch (Exception e) {
log.warn("单价格式转换失败: {}", data.getUnit_price());
unitPrice = BigDecimal.ZERO;
}
detail.put("unit_price", unitPrice);
// 计算总金额
BigDecimal totalAmount = inboundQuantity.multiply(unitPrice);
detail.put("total_amount", totalAmount);
// 处理生产日期
Date productionDate = null;
if (StringUtils.isNotEmpty(data.getProduction_date())) {
try {
productionDate = dateFormat.parse(data.getProduction_date().trim());
} catch (ParseException e) {
log.warn("生产日期格式解析失败: {}", data.getProduction_date());
}
}
detail.put("production_date", productionDate);
// 处理有效期
Date expiryDate = null;
if (StringUtils.isNotEmpty(data.getExpiry_date())) {
try {
expiryDate = dateFormat.parse(data.getExpiry_date().trim());
} catch (ParseException e) {
log.warn("有效期格式解析失败: {}", data.getExpiry_date());
}
}
detail.put("expiry_date", expiryDate);
detail.put("create_by", user.get("id"));
detail.put("create_time", new Date());
detail.put("_user", user);
return detail;
} catch (Exception e) {
log.error("创建入库明细异常", e);
return null;
}
}
/**
* 插入入库单主表
*/
private String insertInboundMaster(Map<String, Object> inboundMaster) {
try {
commonService.insert(namespace + "insertInboundMaster", inboundMaster);
return (String) inboundMaster.get("id");
} catch (Exception e) {
log.error("插入入库单主表异常", e);
return null;
}
}
/**
* 插入入库明细
*/
private void insertInboundDetail(Map<String, Object> inboundDetail) {
try {
commonService.insert(namespace + "insertInboundDetail", inboundDetail);
} catch (Exception e) {
log.error("插入入库明细异常", e);
throw e;
}
}
/**
* 数据校验方法
*/
private boolean isValidInbound(InboundImp data) {
// 必需字段校验
if (StringUtils.isEmpty(data.getInbound_no()) ||
StringUtils.isEmpty(data.getMaterial_code()) ||
StringUtils.isEmpty(data.getInbound_quantity())) {
return false;
}
// 数量校验
try {
BigDecimal quantity = new BigDecimal(data.getInbound_quantity().trim());
if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
return false;
}
} catch (Exception e) {
return false;
}
// 单价校验(可选)
if (StringUtils.isNotEmpty(data.getUnit_price())) {
try {
new BigDecimal(data.getUnit_price().trim());
} catch (Exception e) {
return false;
}
}
return true;
}
public Map<String,Object> getResult() {
Map<String,Object> r = new HashMap<>();
r.put("insert", insertCount.get()); // 明细条数
r.put("inbound_count", inboundCount.get()); // 入库单数量
r.put("error", errorCount.get());
r.put("errInfo", errInfo.size() > 0 ? String.join(", ", errInfo) : "无");
return r;
}
// 批量保存方法(如果需要保留原来的逻辑)
private void saveBatch() {
// 这里可以根据需要保留原有的批量处理逻辑
}
}
\ No newline at end of file
......@@ -6,7 +6,7 @@
INSERT INTO jl_key_dm_borrow_application_detail(
id,application_id,material_id,material_code,material_name,apply_quantity,unit,issue_remark,returned_quantity,damaged_quantity,is_used,create_by,create_time
) VALUES (
#{id},#{application_id},#{material_id},#{material_code},#{material_name},#{apply_quantity},#{unit},#{issue_remark},COALESCE(#{returned_quantity},0),COALESCE(#{damaged_quantity},0),1,#{_user.id},NOW()
UUID(),#{application_id},#{material_id},#{material_code},#{material_name},#{apply_quantity},#{unit},#{issue_remark},COALESCE(#{returned_quantity},0),COALESCE(#{damaged_quantity},0),1,#{_user.id},NOW()
)
</insert>
......
......@@ -185,6 +185,143 @@
left join jl_key_dm_borrow_application as ba on bad.application_id = ba.id
where ba.id = #{id}
</select>
<!-- 将物料code为键做成字典-->
<select id="getMaterialMap" resultType="map">
select *
from jl_key_dm_material
where is_used = 1
</select>
<insert id="batchInsertInboundMasters" parameterType="map">
INSERT INTO jl_key_dm_inbound_record (
id,
inbound_no,
batch_no,
inbound_type,
inbound_status,
inbound_date,
is_used,
storage_location,
remark,
create_by,
create_time
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.id},
#{item.inbound_no},
#{item.batch_no},
#{item.inbound_type},
#{item.inbound_status},
#{item.inbound_date},
#{item.is_used},
#{item.storage_location},
#{item.remark},
#{item.create_by},
#{item.create_time}
)
</foreach>
</insert>
<insert id="batchInsertInboundDetails" parameterType="map">
INSERT INTO jl_key_dm_inbound_detail (
id,
inbound_id,
material_id,
inbound_type,
inbound_quantity,
unit_price,
total_amount,
production_date,
expiry_date,
is_used,
create_by,
create_time
) VALUES
<foreach collection="list" item="item" separator=",">
(
#{item.id},
#{item.inbound_id},
#{item.material_id},
#{item.inbound_type},
#{item.inbound_quantity},
#{item.unit_price},
#{item.total_amount},
#{item.production_date},
#{item.expiry_date},
#{item.is_used},
#{item.create_by},
#{item.create_time}
)
</foreach>
</insert>
<update id="batchUpdateMaterialStock" parameterType="map">
UPDATE jl_key_dm_inventory
SET total_quantity = total_quantity + CASE id
<foreach collection="list" item="item">
WHEN #{item.material_id} THEN #{item.quantity}
</foreach>
ELSE 0
END,
update_time = NOW()
WHERE id IN
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item.material_id}
</foreach>
</update>
<insert id="insertInboundMaster" parameterType="map">
INSERT INTO jl_key_dm_inbound_record (
id,
inbound_no,
batch_no,
inbound_type,
inbound_status,
inbound_date,
is_used,
storage_location,
remark,
create_by,
create_time
) VALUES (
#{id},
#{inbound_no},
#{batch_no},
#{inbound_type},
#{inbound_status},
#{inbound_date},
#{is_used},
#{storage_location},
#{remark},
#{create_by},
#{create_time}
)
</insert>
<insert id="insertInboundDetail" parameterType="map">
INSERT INTO jl_key_dm_inbound_detail (
id,
inbound_id,
material_id,
inbound_type,
inbound_quantity,
unit_price,
total_amount,
production_date,
expiry_date,
create_by,
create_time
) VALUES (
#{id},
#{inbound_id},
#{material_id},
#{inbound_type},
#{inbound_quantity},
#{unit_price},
#{total_amount},
#{production_date},
#{expiry_date},
#{create_by},
#{create_time}
)
</insert>
</mapper>
......@@ -33,10 +33,12 @@
</foreach>
</insert>
<select id="selectLogsByApplicationId" parameterType="map" resultType="map">
SELECT ml.*,m.material_name
SELECT ml.*,m.material_name,u.name
FROM jl_key_dm_material_log as ml
left join jl_key_dm_material as m
on ml.material_id = m.id
left join jl_key_dm_user as u
on u.id = ml.create_by
WHERE relation_id = #{id}
AND ml.is_used = 1 ORDER BY ml.create_time ASC
</select>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论