feat(oms): 出库/入库业务单导入支持多页签——每个工作表独立生成一张单据
一、行为变更(ReservationStockInOrderImpl) - 出库导入(parseExcelFile)/入库导入(parseStockInExcel):不再固定读单一页签,枚举Excel全部工作表,每个页签独立解析为一张单据(5个页签=5张单5个单号) - 全部回滚策略:任一页签校验失败整体不落库,报错按页签聚合列出(页签[x]:原因;页签[y]:原因) - 空页签(无货品/客户明细)自动跳过;单页签文件行为与原先完全一致 - 出库单号改到落库阶段逐张生成:多页签下校验阶段统一生成会因全部未入库而取到相同单号 - 入库模板OrderNO增加本文件内跨页签查重:各页签都未入库时库查重互相不可见,防两页签同OrderNO重复落库 - 页签解析异常message为空时兜底用异常类名,避免报错显示null - 方法级@Transactional(rollbackFor=Exception.class):顺带修复旧入库导入因this自调用导致事务代理失效的问题 二、接口与返回结构(IReservationStockInOrderService / ReservationStockInOrderApi) - 两个导入方法返回类型改为List<SheetImportResultDTO>(每页签一条:页签名/单号/物料行数/数量合计/解析数据) - /parseExcel与/parse-excel返回:result=第一个页签解析数据(兼容老前端)、results=全部页签结果、msg=成功导入N张单:单号1、单号2… - 入库接口不传sheetName为多页签模式,显式传页签名保持单页签老行为
This commit is contained in:
+11
-8
@@ -6,6 +6,7 @@ import com.mhd.oms.domain.reservationStockInOrder.entity.ReservationStockInOrder
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.po.ReservationStockInOrderPO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.todo.ReservationStockInOrderDO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.DeliveryDataDTO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.SheetImportResultDTO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.StockInDataDTO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@@ -50,21 +51,23 @@ public interface IReservationStockInOrderService extends IService<ReservationSto
|
||||
List<ReservationInMaterialDetailPO> queryNoticePrintDetail(ReservationStockInOrderDO stockInOrderDTO);
|
||||
|
||||
/**
|
||||
* 解析Excel文件并获取出库数据
|
||||
* 解析Excel文件并导入出库单(多页签:文件中每个工作表独立解析为一张出库单,单页签文件行为不变)。
|
||||
* 任一页签校验失败则整体不落库,报错聚合列出所有失败页签
|
||||
* @param file Excel文件
|
||||
* @param sheetName 工作表名称(出库导入/参考数据)
|
||||
* @param sheetName 兼容参数(多页签模式下不再按名字筛选,传空即可)
|
||||
* @param warehouseId 当前仓库id(非空时校验导入物料是否属于该仓库,并按该仓库过滤批次库存)
|
||||
* @return 出库数据DTO
|
||||
* @return 每个工作表的导入结果(含生成的出库单号与解析数据)
|
||||
* @throws Exception 解析异常
|
||||
*/
|
||||
DeliveryDataDTO parseExcelFile(MultipartFile file, String sheetName, Long warehouseId) throws Exception;
|
||||
List<SheetImportResultDTO> parseExcelFile(MultipartFile file, String sheetName, Long warehouseId) throws Exception;
|
||||
|
||||
/**
|
||||
* 解析入库单Excel文件
|
||||
* 解析并导入入库单Excel(多页签:未指定页签名时文件中每个工作表独立解析为一张入库单,单页签文件行为不变)。
|
||||
* 任一页签校验失败则整体不落库,报错聚合列出所有失败页签
|
||||
* @param file Excel文件
|
||||
* @param sheetName 工作表名称(默认:入库模版)
|
||||
* @return 入库单完整数据(表头+明细)
|
||||
* @param sheetName 显式指定页签时保持单页签行为;不传/为空则为多页签模式
|
||||
* @return 每个工作表的导入结果(含生成的入库单号与解析数据)
|
||||
* @throws Exception 解析异常
|
||||
*/
|
||||
StockInDataDTO parseStockInExcel(MultipartFile file, String sheetName) throws Exception;
|
||||
List<SheetImportResultDTO> parseStockInExcel(MultipartFile file, String sheetName) throws Exception;
|
||||
}
|
||||
+193
-29
@@ -4,6 +4,8 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONArray;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.alibaba.excel.ExcelReader;
|
||||
import com.alibaba.excel.read.metadata.ReadSheet;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -57,6 +59,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.text.SimpleDateFormat;
|
||||
@@ -220,19 +223,90 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeliveryDataDTO parseExcelFile(MultipartFile file, String sheetName, Long warehouseId) throws Exception {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<SheetImportResultDTO> parseExcelFile(MultipartFile file, String sheetName, Long warehouseId) throws Exception {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
// 校验文件和工作表名称(逻辑不变)
|
||||
// 校验文件(逻辑不变)
|
||||
if (file.isEmpty()) {
|
||||
throw new IllegalArgumentException("上传的Excel文件不能为空");
|
||||
}
|
||||
if (!"出库导入".equals(sheetName) && !"参考数据".equals(sheetName)) {
|
||||
sheetName = "出库导入";
|
||||
// 多页签模式:文件中每个工作表独立解析为一张出库单(老逻辑固定只读"出库导入"页签)
|
||||
byte[] fileBytes = file.getBytes();
|
||||
List<String> sheetNames = listSheetNames(fileBytes);
|
||||
if (sheetNames.isEmpty()) {
|
||||
throw new IllegalArgumentException("Excel文件中没有工作表");
|
||||
}
|
||||
// ====== 第一阶段:逐页签解析+校验+组装(不落库),失败按页签聚合收集,全部页签处理完统一报错 ======
|
||||
List<PreparedDeliveryOrder> preparedOrders = new ArrayList<>();
|
||||
List<String> sheetProblems = new ArrayList<>();
|
||||
for (String name : sheetNames) {
|
||||
try {
|
||||
PreparedDeliveryOrder prepared = buildDeliveryOrder(fileBytes, name, warehouseId, loginUser);
|
||||
if (prepared != null) {
|
||||
preparedOrders.add(prepared); // null=空页签,跳过
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 非模板页签的解析异常(如NPE)message 可能为空,兜底用异常类名,避免报错显示"页签[x]:null"
|
||||
sheetProblems.add("页签[" + name + "]:" + (e.getMessage() == null ? e.toString() : e.getMessage()));
|
||||
}
|
||||
}
|
||||
if (!sheetProblems.isEmpty()) {
|
||||
throw new Exception(String.join(";", sheetProblems));
|
||||
}
|
||||
if (preparedOrders.isEmpty()) {
|
||||
throw new IllegalArgumentException("Excel中没有可导入的数据页签");
|
||||
}
|
||||
// ====== 第二阶段:全部页签校验通过后统一落库(同一事务,任一失败整体回滚) ======
|
||||
List<SheetImportResultDTO> results = new ArrayList<>();
|
||||
for (PreparedDeliveryOrder prepared : preparedOrders) {
|
||||
// 出库单号在落库阶段逐张生成:此时上一张已入库(同事务内可见),取号递增不会重复;
|
||||
// 校验阶段统一生成会因全部页签都未入库而取到相同号
|
||||
String orderNumber = reservationStockOutOrderApplicationService.generateReserveOrderNumber();
|
||||
ReservationStockOutOrderDO order = prepared.order;
|
||||
order.setOutOrderNumber(orderNumber);
|
||||
order.setBusinessOrderNo(orderNumber);
|
||||
if (order.getMaterialDetailList() != null) {
|
||||
for (ReservationOutMaterialDetailDO detail : order.getMaterialDetailList()) {
|
||||
detail.setOutOrderNumber(orderNumber);
|
||||
}
|
||||
}
|
||||
reservationStockOutOrderApplicationService.insert(order);
|
||||
saveDeliveryLinks(prepared);
|
||||
prepared.result.setOrderNumber(orderNumber);
|
||||
results.add(prepared.result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 枚举Excel全部工作表名(EasyExcel 3.x:excelExecutor().sheetList())
|
||||
*/
|
||||
private List<String> listSheetNames(byte[] fileBytes) {
|
||||
List<String> sheetNames = new ArrayList<>();
|
||||
try (ExcelReader reader = EasyExcel.read(new ByteArrayInputStream(fileBytes)).build()) {
|
||||
for (ReadSheet readSheet : reader.excelExecutor().sheetList()) {
|
||||
sheetNames.add(readSheet.getSheetName());
|
||||
}
|
||||
}
|
||||
return sheetNames;
|
||||
}
|
||||
|
||||
/** 出库单页签解析/校验/组装完成后的暂存容器(统一落库阶段使用) */
|
||||
private static class PreparedDeliveryOrder {
|
||||
ReservationStockOutOrderDO order;
|
||||
List<CustomerDeliveryDetail> customerDeliveryDetailList;
|
||||
Map<String, UserPo> customerCache;
|
||||
SheetImportResultDTO result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个页签的出库单解析+校验+组装(不落库,由调用方在全部页签通过后统一插入)。
|
||||
* 空页签(货品明细与客户配送明细均为空)返回 null 由调用方跳过。
|
||||
*/
|
||||
private PreparedDeliveryOrder buildDeliveryOrder(byte[] fileBytes, String sheetName, Long warehouseId, LoginUser loginUser) throws Exception {
|
||||
// 关键变更:使用动态行数监听器
|
||||
DynamicDeliveryExcelListener listener = new DynamicDeliveryExcelListener();
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
try (InputStream inputStream = new ByteArrayInputStream(fileBytes)) {
|
||||
EasyExcel.read(inputStream, listener)
|
||||
.sheet(sheetName)
|
||||
.headRowNumber(0) // 无固定表头,全部行交由监听器处理
|
||||
@@ -254,8 +328,17 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
}
|
||||
deliveryDataDTO.setCustomerDeliveryDetailList(customerDeliveryDetailList);
|
||||
|
||||
LOGGER.info("Excel解析完成,货品明细{}条,客户配送明细{}条",
|
||||
productDetailList.size(), customerDeliveryDetailList.size());
|
||||
// 空页签跳过:货品明细与客户配送明细均为空时视为非数据页签(说明页/空表),不算失败
|
||||
if (productDetailList.isEmpty() && customerDeliveryDetailList.isEmpty()) {
|
||||
LOGGER.info("页签[{}]无货品明细与客户配送明细,跳过", sheetName);
|
||||
return null;
|
||||
}
|
||||
if (productDetailList.isEmpty()) {
|
||||
throw new Exception("未解析到产品明细数据,请检查模板中是否填写了產品編號");
|
||||
}
|
||||
|
||||
LOGGER.info("页签[{}]解析完成,货品明细{}条,客户配送明细{}条",
|
||||
sheetName, productDetailList.size(), customerDeliveryDetailList.size());
|
||||
|
||||
List<Map<String,Object>> shipperIds = new ArrayList<>();
|
||||
Set<Long> processedShipperIds = new HashSet<>();
|
||||
@@ -356,10 +439,8 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
}
|
||||
|
||||
ReservationStockOutOrderDO reservationStockOutOrderDO = new ReservationStockOutOrderDO();
|
||||
// 生成出库业务单号
|
||||
String orderNumber = reservationStockOutOrderApplicationService.generateReserveOrderNumber();
|
||||
reservationStockOutOrderDO.setOutOrderNumber(orderNumber);
|
||||
reservationStockOutOrderDO.setBusinessOrderNo(orderNumber);
|
||||
// 出库业务单号不在校验阶段生成:多页签时全部页签落库前都未入库,逐张生成会取到相同号;
|
||||
// 单号在统一落库阶段(第二阶段)逐张生成后回填单据/明细/结果
|
||||
// 货主信息:有货品明细时从解析结果取,无货品明细时从登录用户取
|
||||
if (!shipperIds.isEmpty()) {
|
||||
reservationStockOutOrderDO.setShipperId((Long) shipperIds.get(0).get("shipperId"));
|
||||
@@ -480,10 +561,31 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
reservationOutMaterialDetailDOList.add(reservationOutMaterialDetailDO);
|
||||
}
|
||||
reservationStockOutOrderDO.setMaterialDetailList(reservationOutMaterialDetailDOList);
|
||||
reservationStockOutOrderApplicationService.insert(reservationStockOutOrderDO);
|
||||
|
||||
// 组装完成暂存不落库:由调用方在全部页签校验通过后统一插入(整体事务,任一页签失败全部回滚)
|
||||
PreparedDeliveryOrder prepared = new PreparedDeliveryOrder();
|
||||
prepared.order = reservationStockOutOrderDO;
|
||||
prepared.customerDeliveryDetailList = customerDeliveryDetailList;
|
||||
prepared.customerCache = customerCache;
|
||||
SheetImportResultDTO result = new SheetImportResultDTO();
|
||||
result.setSheetName(sheetName);
|
||||
result.setMaterialQuantity(productDetailList.size());
|
||||
result.setQuantity(reservationOutMaterialDetailDOList.stream()
|
||||
.map(ReservationOutMaterialDetailDO::getQuantity).filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
result.setDeliveryData(deliveryDataDTO);
|
||||
prepared.result = result;
|
||||
return prepared;
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户配送关联落库(原出库导入方法内的保存段,挪到统一落库阶段逐页签执行)
|
||||
*/
|
||||
private void saveDeliveryLinks(PreparedDeliveryOrder prepared) {
|
||||
ReservationStockOutOrderDO reservationStockOutOrderDO = prepared.order;
|
||||
Map<String, UserPo> customerCache = prepared.customerCache;
|
||||
// 客户配送明细判空处理:为空时跳过关联保存
|
||||
for (CustomerDeliveryDetail customerDeliveryDetail : customerDeliveryDetailList) {
|
||||
for (CustomerDeliveryDetail customerDeliveryDetail : prepared.customerDeliveryDetailList) {
|
||||
ReservationDeliveryDetailsLink reservationDeliveryDetailsLink = new ReservationDeliveryDetailsLink();
|
||||
reservationDeliveryDetailsLink.setInvoiceNumber(customerDeliveryDetail.getInvoiceNo());
|
||||
reservationDeliveryDetailsLink.setAmount(customerDeliveryDetail.getAmount());
|
||||
@@ -507,7 +609,6 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
}
|
||||
reservationDeliveryDetailsLinkService.save(reservationDeliveryDetailsLink);
|
||||
}
|
||||
return deliveryDataDTO;
|
||||
}
|
||||
|
||||
public String generateDateSerialNumber() {
|
||||
@@ -525,11 +626,10 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ReservationStockInOrderImpl.class);
|
||||
// 默认工作表名称(与Excel一致)
|
||||
private static final String DEFAULT_SHEET_NAME = "入库模版";
|
||||
|
||||
@Override
|
||||
public StockInDataDTO parseStockInExcel(MultipartFile file, String sheetName) throws Exception {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<SheetImportResultDTO> parseStockInExcel(MultipartFile file, String sheetName) throws Exception {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
// 1. 校验文件
|
||||
if (file.isEmpty()) {
|
||||
@@ -537,29 +637,78 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
throw new IllegalArgumentException("Excel文件不能为空");
|
||||
}
|
||||
|
||||
// 2. 校验工作表名称(默认使用"入库模版")
|
||||
String targetSheetName = (sheetName == null || sheetName.trim().isEmpty())
|
||||
? DEFAULT_SHEET_NAME : sheetName.trim();
|
||||
// 2. 多页签模式:未显式指定页签名时,文件中每个工作表独立解析为一张入库单;显式指定则保持单页签行为
|
||||
byte[] fileBytes = file.getBytes();
|
||||
List<String> sheetNames;
|
||||
if (sheetName != null && !sheetName.trim().isEmpty()) {
|
||||
sheetNames = Collections.singletonList(sheetName.trim());
|
||||
} else {
|
||||
sheetNames = listSheetNames(fileBytes);
|
||||
}
|
||||
|
||||
// 3. 使用EasyExcel解析(指定监听器)
|
||||
// ====== 第一阶段:逐页签解析+校验+组装(不落库),失败按页签聚合收集,全部页签处理完统一报错 ======
|
||||
List<PreparedStockInOrder> preparedOrders = new ArrayList<>();
|
||||
List<String> sheetProblems = new ArrayList<>();
|
||||
// 本文件内已使用的模板OrderNO:各页签都未入库时 existsByBusinessOrderNo 查不到彼此,需按页签追加查重防跨页签重复
|
||||
Set<String> usedBusinessOrderNos = new HashSet<>();
|
||||
for (String name : sheetNames) {
|
||||
try {
|
||||
PreparedStockInOrder prepared = buildStockInOrder(fileBytes, name, loginUser, usedBusinessOrderNos);
|
||||
if (prepared != null) {
|
||||
preparedOrders.add(prepared); // null=空页签,跳过
|
||||
}
|
||||
} catch (Exception e) {
|
||||
sheetProblems.add("页签[" + name + "]:" + (e.getMessage() == null ? e.toString() : e.getMessage()));
|
||||
}
|
||||
}
|
||||
if (!sheetProblems.isEmpty()) {
|
||||
throw new Exception(String.join(";", sheetProblems));
|
||||
}
|
||||
if (preparedOrders.isEmpty()) {
|
||||
throw new IllegalArgumentException("未解析到产品明细数据,请检查模板中是否填写了產品編號");
|
||||
}
|
||||
|
||||
// ====== 第二阶段:全部页签校验通过后统一落库(同一事务,任一失败整体回滚) ======
|
||||
List<SheetImportResultDTO> results = new ArrayList<>();
|
||||
for (PreparedStockInOrder prepared : preparedOrders) {
|
||||
insertApplication(prepared.order);
|
||||
// 入库单号在落库阶段生成(insertDomain),回填到页签结果
|
||||
prepared.result.setOrderNumber(prepared.order.getInOrderNumber());
|
||||
results.add(prepared.result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/** 入库单页签解析/校验/组装完成后的暂存容器(统一落库阶段使用) */
|
||||
private static class PreparedStockInOrder {
|
||||
ReservationStockInOrderDO order;
|
||||
SheetImportResultDTO result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个页签的入库单解析+校验+组装(不落库,由调用方在全部页签通过后统一插入)。
|
||||
* 空页签(未解析到产品明细)返回 null 由调用方跳过。
|
||||
*/
|
||||
private PreparedStockInOrder buildStockInOrder(byte[] fileBytes, String sheetName, LoginUser loginUser, Set<String> usedBusinessOrderNos) throws Exception {
|
||||
// 3. 使用EasyExcel解析(指定监听器,多页签模式下 sheetName 为实际页签名)
|
||||
StockInExcelListener listener = new StockInExcelListener();
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
try (InputStream inputStream = new ByteArrayInputStream(fileBytes)) {
|
||||
EasyExcel.read(inputStream, listener)
|
||||
.sheet(targetSheetName) // 指定工作表
|
||||
.sheet(sheetName) // 指定工作表
|
||||
.headRowNumber(0) // 从第1行开始解析(不跳过任何行)
|
||||
.doRead();
|
||||
}
|
||||
|
||||
// 4. 封装DTO返回
|
||||
// 4. 封装DTO
|
||||
StockInDataDTO resultDTO = new StockInDataDTO();
|
||||
resultDTO.setStockInHeader(listener.getStockInHeader());
|
||||
resultDTO.setProductDetailList(listener.getProductDetailList());
|
||||
|
||||
LOGGER.info("Excel解析成功,工作表:{},产品明细数:{}",
|
||||
targetSheetName, resultDTO.getProductDetailList().size());
|
||||
LOGGER.info("页签[{}]解析成功,产品明细数:{}", sheetName, resultDTO.getProductDetailList().size());
|
||||
List<StockInProductDetail> productDetailList = resultDTO.getProductDetailList();
|
||||
// 空页签跳过:该页签未解析到产品明细时视为非数据页签(说明页/空表),不算失败
|
||||
if (productDetailList == null || productDetailList.isEmpty()) {
|
||||
throw new ServiceException("未解析到产品明细数据,请检查模板中是否填写了產品編號");
|
||||
return null;
|
||||
}
|
||||
StockInHeader stockInHeader = resultDTO.getStockInHeader();
|
||||
// ====== 第一步:物料编号存在性校验(模板只填物料编号即可,名称按编号自动匹配) ======
|
||||
@@ -664,6 +813,10 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
if (stockInOrderMapper.existsByBusinessOrderNo(templateOrderNo) > 0) {
|
||||
throw new ServiceException("业务单号已存在:" + templateOrderNo + ",请修改模板中的OrderNO后重新导入");
|
||||
}
|
||||
// 多页签查重:各页签都未入库时上面的库查重互相看不见,需对本文件内已使用的OrderNO追加拦截
|
||||
if (!usedBusinessOrderNos.add(templateOrderNo)) {
|
||||
throw new ServiceException("业务单号已存在:" + templateOrderNo + "(与本文件其他页签的OrderNO重复),请修改模板中的OrderNO后重新导入");
|
||||
}
|
||||
reservationStockInOrderDO.setBusinessOrderNo(templateOrderNo);
|
||||
}
|
||||
reservationStockInOrderDO.setOrganizationId(loginUser.getUserPo().getOrganizationId());
|
||||
@@ -740,8 +893,19 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
|
||||
reservationInMaterialDetailDOList.add(reservationInMaterialDetailDO);
|
||||
}
|
||||
reservationStockInOrderDO.setMaterialDetailList(reservationInMaterialDetailDOList);
|
||||
insertApplication(reservationStockInOrderDO);
|
||||
return resultDTO;
|
||||
|
||||
// 组装完成暂存不落库:由调用方在全部页签校验通过后统一插入(整体事务,任一页签失败全部回滚)
|
||||
SheetImportResultDTO sheetResult = new SheetImportResultDTO();
|
||||
sheetResult.setSheetName(sheetName);
|
||||
sheetResult.setMaterialQuantity(productDetailList.size());
|
||||
sheetResult.setQuantity(reservationInMaterialDetailDOList.stream()
|
||||
.map(ReservationInMaterialDetailDO::getQuantity).filter(Objects::nonNull)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
sheetResult.setStockInData(resultDTO);
|
||||
PreparedStockInOrder prepared = new PreparedStockInOrder();
|
||||
prepared.order = reservationStockInOrderDO;
|
||||
prepared.result = sheetResult;
|
||||
return prepared;
|
||||
}
|
||||
|
||||
public Boolean insertApplication(ReservationStockInOrderDO stockInOrderDO) {
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.mhd.oms.domain.reservationStockInOrder.repository.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 多页签导入的单页签结果(出库/入库导入通用)
|
||||
* 一个Excel文件中每个工作表独立解析为一张单据,每个页签对应一条结果
|
||||
*/
|
||||
@Data
|
||||
public class SheetImportResultDTO {
|
||||
|
||||
/** 来源工作表名 */
|
||||
private String sheetName;
|
||||
|
||||
/** 生成的单据号(出库=出库业务单号,入库=入库业务单号;入库单号在落库阶段生成后回填) */
|
||||
private String orderNumber;
|
||||
|
||||
/** 物料明细行数 */
|
||||
private Integer materialQuantity;
|
||||
|
||||
/** 明细数量合计 */
|
||||
private BigDecimal quantity;
|
||||
|
||||
/** 出库单解析数据(仅出库导入时有值) */
|
||||
private DeliveryDataDTO deliveryData;
|
||||
|
||||
/** 入库单解析数据(仅入库导入时有值) */
|
||||
private StockInDataDTO stockInData;
|
||||
}
|
||||
+19
-7
@@ -9,6 +9,7 @@ import com.mhd.oms.domain.reservationStockInOrder.repository.po.ReservationStock
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.todo.ReservationStockInOrderDO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.todo.ReservationStockInOrderDTO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.DeliveryDataDTO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.SheetImportResultDTO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.repository.vo.StockInDataDTO;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.service.ReservationStockInOrderApplicationService;
|
||||
import com.mhd.oms.domain.reservationStockInOrder.service.ReservationStockInOrderAssembler;
|
||||
@@ -220,10 +221,16 @@ public class ReservationStockInOrderApi extends BaseController {
|
||||
@RequestParam(value = "warehouseId", required = false) Long warehouseId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
try {
|
||||
DeliveryDataDTO deliveryDataDTO = reservationStockInOrderService.parseExcelFile(file, "", warehouseId);
|
||||
result.put("result", deliveryDataDTO);
|
||||
// 多页签导入:Excel中每个工作表独立生成一张出库单,任一页签校验失败整体不落库
|
||||
List<SheetImportResultDTO> results = reservationStockInOrderService.parseExcelFile(file, "", warehouseId);
|
||||
// result 保留第一个页签的解析数据(兼容老前端);results 为全部页签结果
|
||||
result.put("result", results.isEmpty() ? null : results.get(0).getDeliveryData());
|
||||
result.put("results", results);
|
||||
result.put("code", 200);
|
||||
result.put("msg", "解析成功");
|
||||
String orderNumbers = results.stream()
|
||||
.map(SheetImportResultDTO::getOrderNumber)
|
||||
.collect(java.util.stream.Collectors.joining("、"));
|
||||
result.put("msg", "成功导入" + results.size() + "张出库单:" + orderNumbers);
|
||||
return new ResponseEntity<>(result, HttpStatus.OK);
|
||||
} catch (IllegalArgumentException e) {
|
||||
result.put("code", 400);
|
||||
@@ -237,18 +244,23 @@ public class ReservationStockInOrderApi extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 接口1:仅解析Excel文件(返回解析结果,不保存)
|
||||
* 用途:预览Excel解析数据
|
||||
* 接口1:解析并导入入库单Excel(多页签:不传sheetName时每个工作表独立生成一张入库单,任一页签校验失败整体不落库)
|
||||
*/
|
||||
@PostMapping("/parse-excel")
|
||||
public ResponseEntity<Map<String, Object>> parseStockInExcel(
|
||||
@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "sheetName", required = false) String sheetName) {
|
||||
try {
|
||||
StockInDataDTO resultDTO = reservationStockInOrderService.parseStockInExcel(file, sheetName);
|
||||
List<SheetImportResultDTO> results = reservationStockInOrderService.parseStockInExcel(file, sheetName);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("result", resultDTO);
|
||||
// result 保留第一个页签的解析数据(兼容老前端);results 为全部页签结果
|
||||
result.put("result", results.isEmpty() ? null : results.get(0).getStockInData());
|
||||
result.put("results", results);
|
||||
result.put("code", 200);
|
||||
String orderNumbers = results.stream()
|
||||
.map(SheetImportResultDTO::getOrderNumber)
|
||||
.collect(java.util.stream.Collectors.joining("、"));
|
||||
result.put("msg", "成功导入" + results.size() + "张入库单:" + orderNumbers);
|
||||
return new ResponseEntity<>(result, HttpStatus.OK);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 参数错误(如文件为空)
|
||||
|
||||
Reference in New Issue
Block a user