Commit 92fcc23b by yubin

Merge remote-tracking branch 'origin/master'

parents 69db319b ab86dcba
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 库存管理系统
# 开发环境配置 # 开发环境配置
ENV = 'development' ENV = 'development'
......
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 库存管理系统
# 生产环境配置 # 生产环境配置
ENV = 'production' ENV = 'production'
......
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 库存管理系统
BABEL_ENV = production BABEL_ENV = production
......
{ {
"name": "ruoyi", "name": "ruoyi",
"version": "3.9.0", "version": "3.9.0",
"description": "若依管理系统", "description": "库存管理系统",
"author": "若依", "author": "",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "vue-cli-service serve", "dev": "vue-cli-service serve",
......
...@@ -110,7 +110,7 @@ ...@@ -110,7 +110,7 @@
height="100%" height="100%"
:data="inboundList" :data="inboundList"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
:row-key="(row) => row.materialId + '_' + row.orderId" :row-key="(row) => row.materialId + '_' + row.orderId + '_' + row.warehousesName + '_' + row.locationName"
> >
<el-table-column label="物料SAPNO" align="center" prop="sapNo" width="200"/> <el-table-column label="物料SAPNO" align="center" prop="sapNo" width="200"/>
<el-table-column label="物料名称" align="center" prop="materialName" width="200"/> <el-table-column label="物料名称" align="center" prop="materialName" width="200"/>
......
...@@ -177,9 +177,9 @@ ...@@ -177,9 +177,9 @@
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="200"> <el-table-column label="入库时间" align="center" prop="inboundDate" width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span> <span>{{ parseTime(scope.row.inboundDate,'{y}-{m}-{d}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column
...@@ -903,7 +903,7 @@ export default { ...@@ -903,7 +903,7 @@ export default {
const queryForm = { const queryForm = {
pageNum: 1, pageNum: 1,
pageSize: 9999, pageSize: 9999,
orderId: row.orderId inboundOrderId: row.id
} }
const response = await listInbound_itemsAndMname(queryForm) const response = await listInbound_itemsAndMname(queryForm)
row.inboundOrderItemsList = response.rows.map(item => { row.inboundOrderItemsList = response.rows.map(item => {
...@@ -915,7 +915,8 @@ export default { ...@@ -915,7 +915,8 @@ export default {
ownerId: row.ownerId, ownerId: row.ownerId,
quantity: item.actualQuantity, quantity: item.actualQuantity,
unitWeight: item.unitWeight, unitWeight: item.unitWeight,
isUsed: 1 isUsed: 1,
unitPrice: item.unitPrice
}; };
}) })
// 第三步:确保数据存在后调用入库接口 // 第三步:确保数据存在后调用入库接口
......
...@@ -183,7 +183,6 @@ ...@@ -183,7 +183,6 @@
type="text" type="text"
icon="el-icon-more" icon="el-icon-more"
@click="handleDetail(scope.row)" @click="handleDetail(scope.row)"
v-hasPermi="['inventory:return_orders:view']"
v-show="(scope.row.orderStatus === 2)" v-show="(scope.row.orderStatus === 2)"
>详情</el-button> >详情</el-button>
</template> </template>
......
...@@ -7,9 +7,9 @@ function resolve(dir) { ...@@ -7,9 +7,9 @@ function resolve(dir) {
const CompressionPlugin = require('compression-webpack-plugin') const CompressionPlugin = require('compression-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题 const name = process.env.VUE_APP_TITLE || '管理系统' // 网页标题
const baseUrl = 'http://localhost:8080' // 后端接口 const baseUrl = 'http://localhost:9001' // 后端接口
const port = process.env.port || process.env.npm_config_port || 80 // 端口 const port = process.env.port || process.env.npm_config_port || 80 // 端口
......
package com.ruoyi.web.controller.inventory; package com.ruoyi.web.controller.inventory;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import com.ruoyi.common.core.domain.entity.Materials; import com.ruoyi.common.core.domain.entity.Materials;
import com.ruoyi.common.utils.uuid.UUID; import com.ruoyi.common.utils.uuid.UUID;
...@@ -11,6 +13,7 @@ import com.ruoyi.inventory.domain.vo.InboundMaterialTotalVO; ...@@ -11,6 +13,7 @@ import com.ruoyi.inventory.domain.vo.InboundMaterialTotalVO;
import com.ruoyi.inventory.domain.vo.InboundTemplateVO; import com.ruoyi.inventory.domain.vo.InboundTemplateVO;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import com.ruoyi.common.annotation.Log; import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
...@@ -127,13 +130,63 @@ public class InboundOrdersController extends BaseController ...@@ -127,13 +130,63 @@ public class InboundOrdersController extends BaseController
@RequestParam("updateSupport") Integer updateSupport, @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")) {
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) + ")");
}
ExcelUtil<InboundTemplateVO> util = new ExcelUtil<InboundTemplateVO>(InboundTemplateVO.class); ExcelUtil<InboundTemplateVO> util = new ExcelUtil<InboundTemplateVO>(InboundTemplateVO.class);
List<InboundTemplateVO> inboundOrders = util.importExcel(file.getInputStream()); List<InboundTemplateVO> inboundOrders = util.importExcel(file.getInputStream());
// 防护3:拦截空列表,避免 Service 层处理空数据
if (CollectionUtils.isEmpty(inboundOrders)) {
return error("Excel中未解析到有效数据,请检查模板是否正确!");
}
String operName = getUsername(); String operName = getUsername();
String message = inboundOrdersService.importInboundOrders(inboundOrders, updateSupport, operName, orderType); String message = inboundOrdersService.importInboundOrders(inboundOrders, updateSupport, operName, orderType);
return success(message); return success(message);
} }
// 辅助方法:获取缺失的列名
private String getMissingColumns(List<String> template, List<String> excel) {
return template.stream()
.filter(col -> !excel.contains(col))
.collect(Collectors.joining("、"));
}
/** /**
* 首页入库次数统计api * 首页入库次数统计api
......
...@@ -2,14 +2,20 @@ ...@@ -2,14 +2,20 @@
spring: spring:
datasource: datasource:
type: com.alibaba.druid.pool.DruidDataSource type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver #driverClassName: com.mysql.cj.jdbc.Driver
driver-class-name: com.mysql.cj.jdbc.Driver # 配置MySQL的驱动程序类
druid: druid:
# 主库数据源 # 主库数据源
master: master:
# 数据库连接地址 # 数据库连接地址
url: jdbc:mysql://demo.docmis.cn:23500/inventory_manager?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&useSSL=false #生產
username: root #url: jdbc:mysql://172.19.1.150:9012/inventory_manager?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&useSSL=false
password: '!QAZ2wsx#EDC2022' #username: root # 数据库用户名
#password: 'Aa123456'
#測試
url: jdbc:mysql://demo.docmis.cn:23500/inventory_manager?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&useSSL=false
username: root
password: '!QAZ2wsx#EDC2022'
# 从库数据源 # 从库数据源
slave: slave:
# 从数据源开关/默认关闭 # 从数据源开关/默认关闭
......
...@@ -16,7 +16,7 @@ ruoyi: ...@@ -16,7 +16,7 @@ ruoyi:
# 开发环境配置 # 开发环境配置
server: server:
# 服务器的HTTP端口,默认为8080 # 服务器的HTTP端口,默认为8080
port: 8080 port: 9001
servlet: servlet:
# 应用的访问路径 # 应用的访问路径
context-path: / context-path: /
......
...@@ -12,17 +12,9 @@ import java.math.BigDecimal; ...@@ -12,17 +12,9 @@ import java.math.BigDecimal;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.RegExUtils; import org.apache.commons.lang3.RegExUtils;
...@@ -1909,4 +1901,34 @@ public class ExcelUtil<T> ...@@ -1909,4 +1901,34 @@ public class ExcelUtil<T>
} }
return method; return method;
} }
/**
* 读取Excel表头(第一行)
*/
public static List<String> getExcelHeader(InputStream inputStream) {
try (Workbook workbook = WorkbookFactory.create(inputStream)) {
Sheet sheet = workbook.getSheetAt(0);
Row headerRow = sheet.getRow(0); // 第一行是表头
if (headerRow == null) {
return Collections.emptyList();
}
return IntStream.range(0, headerRow.getLastCellNum())
.mapToObj(headerRow::getCell)
.map(cell -> {
// 单元格类型容错(数字/字符串统一转字符串)
if (cell == null) {
return "";
}
cell.setCellType(CellType.STRING);
return StringUtils.trim(cell.getStringCellValue());
})
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
} catch (Exception e) {
log.error("读取Excel表头失败", e);
return Collections.emptyList();
}
}
} }
...@@ -68,7 +68,7 @@ public class InventoryController extends BaseController ...@@ -68,7 +68,7 @@ public class InventoryController extends BaseController
/** /**
* 查询库存列表(按物料汇总统计) * 查询库存列表(按物料汇总统计)
*/ */
@PreAuthorize("@ss.hasPermi('inventory:inventory:list')") // @PreAuthorize("@ss.hasPermi('inventory:inventory:list')")
@GetMapping("/listCount") @GetMapping("/listCount")
public TableDataInfo listCount(Inventory inventory) public TableDataInfo listCount(Inventory inventory)
{ {
...@@ -80,7 +80,7 @@ public class InventoryController extends BaseController ...@@ -80,7 +80,7 @@ public class InventoryController extends BaseController
/** /**
* 查询库存明细列表(根据物料标识及检索条件) * 查询库存明细列表(根据物料标识及检索条件)
*/ */
@PreAuthorize("@ss.hasPermi('inventory:inventory:list')") // @PreAuthorize("@ss.hasPermi('inventory:inventory:list')")
@GetMapping("/detailList") @GetMapping("/detailList")
public TableDataInfo detailList(Inventory inventory) public TableDataInfo detailList(Inventory inventory)
{ {
......
...@@ -31,7 +31,7 @@ public class InventoryStatisticsController extends BaseController { ...@@ -31,7 +31,7 @@ public class InventoryStatisticsController extends BaseController {
/** /**
* 出入库统计列表 * 出入库统计列表
*/ */
@PreAuthorize("@ss.hasPermi('inventory:statistics:list')") // @PreAuthorize("@ss.hasPermi('inventory:statistics:list')")
@GetMapping("/inboundOutbound") @GetMapping("/inboundOutbound")
public TableDataInfo list(InboundOutboundStatisticsVO query) { public TableDataInfo list(InboundOutboundStatisticsVO query) {
startPage(); startPage();
......
...@@ -43,7 +43,7 @@ public class ReturnOrderItemsController extends BaseController ...@@ -43,7 +43,7 @@ public class ReturnOrderItemsController extends BaseController
/** /**
* 查询退库单明细列表 * 查询退库单明细列表
*/ */
@PreAuthorize("@ss.hasPermi('inventory:return_order_items:count_detail')") // @PreAuthorize("@ss.hasPermi('inventory:return_order_items:count_detail')")
@GetMapping("/count_detail") @GetMapping("/count_detail")
public TableDataInfo list(ReturnOrderItems returnOrderItems) public TableDataInfo list(ReturnOrderItems returnOrderItems)
{ {
...@@ -69,7 +69,7 @@ public class ReturnOrderItemsController extends BaseController ...@@ -69,7 +69,7 @@ public class ReturnOrderItemsController extends BaseController
/** /**
* 获取退库单明细详细信息 * 获取退库单明细详细信息
*/ */
@PreAuthorize("@ss.hasPermi('inventory:return_order_items:query')") // @PreAuthorize("@ss.hasPermi('inventory:return_order_items:query')")
@GetMapping(value = "/{id}") @GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id) public AjaxResult getInfo(@PathVariable("id") String id)
{ {
...@@ -112,7 +112,7 @@ public class ReturnOrderItemsController extends BaseController ...@@ -112,7 +112,7 @@ public class ReturnOrderItemsController extends BaseController
/** /**
* 查询退库单明细列表 * 查询退库单明细列表
*/ */
@PreAuthorize("@ss.hasPermi('inventory:return_order_items:count')") // @PreAuthorize("@ss.hasPermi('inventory:return_order_items:count')")
@PostMapping("/count") @PostMapping("/count")
public TableDataInfo count(ReturnOrderItems returnOrderItems) public TableDataInfo count(ReturnOrderItems returnOrderItems)
{ {
......
...@@ -178,4 +178,6 @@ public class Inventory extends BaseEntity ...@@ -178,4 +178,6 @@ public class Inventory extends BaseEntity
/** 特殊存储要求 */ /** 特殊存储要求 */
// @Excel(name = "特殊存储要求") // @Excel(name = "特殊存储要求")
private String specialRequirements; private String specialRequirements;
private Double unitPrice;
} }
...@@ -9,6 +9,7 @@ import com.ruoyi.inventory.domain.*; ...@@ -9,6 +9,7 @@ import com.ruoyi.inventory.domain.*;
import com.ruoyi.inventory.domain.vo.InboundMaterialTotalVO; import com.ruoyi.inventory.domain.vo.InboundMaterialTotalVO;
import com.ruoyi.inventory.mapper.*; import com.ruoyi.inventory.mapper.*;
import org.apache.commons.lang3.SystemUtils; import org.apache.commons.lang3.SystemUtils;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.DateUtils; import com.ruoyi.common.utils.DateUtils;
...@@ -186,15 +187,38 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -186,15 +187,38 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
StringBuilder failureMsg = new StringBuilder(); StringBuilder failureMsg = new StringBuilder();
Date now = DateUtils.getNowDate(); Date now = DateUtils.getNowDate();
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
String operId = userId.toString(); String operId = Optional.ofNullable(userId).map(String::valueOf).orElse("");
Warehouses warehouses = new Warehouses();
Owners owners = new Owners(); // 预加载物料SAP-ID映射
StorageLocations storageLocations = new StorageLocations(); Map<String, Map<String, String>> sapAndIdMap = Optional.ofNullable(materialsMapper.selectMaterialIdAndSapMap())
.orElse(Collections.emptyMap());
// 预加载仓库名称-ID映射
Map<String, String> warehouseNameIdMap = loadWarehouseNameIdMap();
// 预加载库位名称-ID映射
Map<String, String> storageLocationNameIdMap = loadStorageLocationNameIdMap();
// 3. 按入库单号分组(核心:同一入库单的多条明细归为一组) // 3. 按入库单号分组(核心:同一入库单的多条明细归为一组)
Map<String, List<InboundTemplateVO>> orderGroupMap = inboundOrdersList.stream() Map<String, List<InboundTemplateVO>> orderGroupMap = Optional.ofNullable(inboundOrdersList)
.filter(vo -> StringUtils.isNotBlank(vo.getOrderId())) // 过滤无入库单号的无效行 .orElse(Collections.emptyList()) // 空列表兜底,避免NPE
.collect(Collectors.groupingBy(InboundTemplateVO::getOrderId)); .stream()
Map<String,Map<String,String>> sapAndIdMap = materialsMapper.selectMaterialIdAndSapMap(); .filter(Objects::nonNull) // 过滤null的VO对象
.map(vo -> {
// 预处理:入库单号去空格,统一格式
String orderId = Optional.ofNullable(vo.getOrderId())
.map(String::trim) // 去除首尾空格
.orElse("");
vo.setOrderId(orderId); // 把处理后的单号回写,保证后续使用一致
return vo;
})
.filter(vo -> StringUtils.isNotBlank(vo.getOrderId())) // 过滤无/空白入库单号的无效行
.collect(Collectors.groupingBy(
InboundTemplateVO::getOrderId, // 分组Key:处理后的入库单号
Collectors.collectingAndThen(
Collectors.toList(),
list -> Collections.unmodifiableList(list) // 生成不可变列表,防止后续篡改
)
));
// 兜底:若分组结果为空,返回空的不可变Map(避免后续判空)
orderGroupMap = Optional.ofNullable(orderGroupMap).orElse(Collections.emptyMap());
// 4. 遍历每个入库单分组处理 // 4. 遍历每个入库单分组处理
for (Map.Entry<String, List<InboundTemplateVO>> entry : orderGroupMap.entrySet()) { for (Map.Entry<String, List<InboundTemplateVO>> entry : orderGroupMap.entrySet()) {
String orderId = entry.getKey(); String orderId = entry.getKey();
...@@ -207,6 +231,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -207,6 +231,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
InboundTemplateVO firstVO = voList.get(0); // 取第一条VO的主表信息 InboundTemplateVO firstVO = voList.get(0); // 取第一条VO的主表信息
// 检查入库单是否已存在 // 检查入库单是否已存在
InboundOrders existMain = inboundOrdersMapper.selectInboundOrdersByOrderId(orderId); InboundOrders existMain = inboundOrdersMapper.selectInboundOrdersByOrderId(orderId);
if (existMain != null) { if (existMain != null) {
if (isUpdateSupport == 0) { if (isUpdateSupport == 0) {
// 不支持更新,跳过该入库单 // 不支持更新,跳过该入库单
...@@ -222,7 +247,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -222,7 +247,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
mainDO.setUpdateBy(operId); mainDO.setUpdateBy(operId);
mainDO.setUpdateTime(now); mainDO.setUpdateTime(now);
mainDO.setUpdateUserCode(operId); mainDO.setUpdateUserCode(operId);
mainDO.setOrderTypeId(orderType+""); mainDO.setOrderTypeId(Optional.ofNullable(orderType).map(String::valueOf).orElse(""));
// 更新主表 // 更新主表
inboundOrdersMapper.updateInboundOrders(mainDO); inboundOrdersMapper.updateInboundOrders(mainDO);
totalMainSuccess++; totalMainSuccess++;
...@@ -235,20 +260,12 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -235,20 +260,12 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
"sapNo", "materialName", "plannedQuantity", "actualQuantity", "sapNo", "materialName", "plannedQuantity", "actualQuantity",
"plannedPackages", "materialUnit", "materialRemark"); // 排除子表字段 "plannedPackages", "materialUnit", "materialRemark"); // 排除子表字段
// 填充主表必填字段 // 填充主表必填字段
mainDO.setId(UUID.randomUUID().toString());
mainDO.setOrderId(orderId);
mainDO.setCreateBy(operId); mainDO.setCreateBy(operId);
mainDO.setCreateTime(now); mainDO.setCreateTime(now);
mainDO.setCreateUserCode(operId); mainDO.setCreateUserCode(operId);
// mainDO.setUpdateBy(operId); mainDO.setOrderTypeId(Optional.ofNullable(orderType).map(String::valueOf).orElse(""));
// mainDO.setUpdateTime(now);
// mainDO.setUpdateUserCode(operId);
owners.setOwnerName(mainDO.getOwnerName());
List<Owners> olist = ownersMapper.selectOwnersList(owners);
mainDO.setId(UUID.randomUUID().toString());
mainDO.setOrderId(orderId);
mainDO.setOrderTypeId(orderType+"");
mainDO.setOwnerId(olist.get(0).getId());
// 设置默认值 // 设置默认值
if (mainDO.getSortNo() == null) { if (mainDO.getSortNo() == null) {
mainDO.setSortNo(0L); mainDO.setSortNo(0L);
...@@ -256,6 +273,17 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -256,6 +273,17 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
if (mainDO.getOrderStatus() == null) { if (mainDO.getOrderStatus() == null) {
mainDO.setOrderStatus(1L); // 默认草稿状态 mainDO.setOrderStatus(1L); // 默认草稿状态
} }
// ========== 货主查询 ==========
Owners owners = new Owners();
owners.setOwnerName(mainDO.getOwnerId());
List<Owners> olist = ownersMapper.selectOwnersList(owners);
if (CollectionUtils.isEmpty(olist)) {
// 抛业务异常,携带具体单号/条件,方便排查
throw new ServiceException("入库单【" + orderId + "】关联的货主不存在,请检查模板数据!");
}
mainDO.setOwnerId(olist.get(0).getId());
// 插入主表 // 插入主表
inboundOrdersMapper.insertInboundOrders(mainDO); inboundOrdersMapper.insertInboundOrders(mainDO);
totalMainSuccess++; totalMainSuccess++;
...@@ -264,42 +292,69 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -264,42 +292,69 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
// 4.2 处理子表明细(每条VO对应一条明细) // 4.2 处理子表明细(每条VO对应一条明细)
for (InboundTemplateVO vo : voList) { for (InboundTemplateVO vo : voList) {
InboundOrderItems itemDO = new InboundOrderItems(); try {
// 复制子表字段(物料相关)
BeanUtils.copyProperties(vo, itemDO,
"orderId", "systemNo", "orderTypeId", "batchId"); // 排除主表字段
// 填充明细必填字段
itemDO.setCreateBy(operId);
itemDO.setCreateTime(now);
itemDO.setCreateUserCode(operId);
List<Warehouses> wlist = warehousesMapper.selectWarehousesList(warehouses);
List<StorageLocations> slist = storageLocationsMapper.selectStorageLocationsList(storageLocations);
itemDO.setId(UUID.randomUUID().toString()); InboundOrderItems itemDO = new InboundOrderItems();
Map<String,String> sapAndId = sapAndIdMap.get(vo.getSapNo()); // 复制子表字段(物料相关)
itemDO.setMaterialId(sapAndId.get("id")); BeanUtils.copyProperties(vo, itemDO,
itemDO.setOrderId(orderId); // 关联入库单号 "orderId", "systemNo", "orderTypeId", "batchId"); // 排除主表字段
itemDO.setBatchId(mainDO.getBatchId()); // 填充明细必填字段
itemDO.setInboundOrderId(mainDO.getId()); // 关联主表ID(核心!) itemDO.setId(UUID.randomUUID().toString());
itemDO.setWarehouseId(wlist.get(0).getId()); itemDO.setCreateBy(operId);
itemDO.setLocationId(slist.get(0).getId()); itemDO.setCreateTime(now);
itemDO.setSortNo(0L); itemDO.setCreateUserCode(operId);
// 校验物料字段(示例:必填sapNo) itemDO.setOrderId(orderId); // 关联入库单号
if (StringUtils.isBlank(vo.getSapNo())) { itemDO.setBatchId(mainDO.getBatchId());
throw new ServiceException(String.format("入库单号【%s】的物料SAP号为空,明细导入失败", orderId)); itemDO.setInboundOrderId(mainDO.getId()); // 关联主表ID(核心!)
itemDO.setSortNo(0L);
itemDO.setRemark(vo.getRemark2());
// ========== 物料SAPNO校验 ==========
String sapNo = Optional.ofNullable(vo.getSapNo()).map(String::trim).orElse("");
if (StringUtils.isBlank(sapNo)) {
throw new ServiceException("物料SAP号为空");
}
Map<String, String> sapAndId = sapAndIdMap.get(sapNo);
if (CollectionUtils.isEmpty(sapAndId) || StringUtils.isBlank(sapAndId.get("id"))) {
throw new ServiceException("物料SAP号【" + sapNo + "】不存在");
}
itemDO.setMaterialId(sapAndId.get("id"));
// ========== 仓库/库位查询 ==========
String warehouseName = Optional.ofNullable(itemDO.getWarehouseId()).map(String::trim).orElse("");
String warehouseId = warehouseNameIdMap.get(warehouseName);
if (StringUtils.isBlank(warehouseId)) {
throw new ServiceException("仓库【" + warehouseName + "】不存在");
}
itemDO.setWarehouseId(warehouseId);
String locationName = Optional.ofNullable(itemDO.getLocationId()).map(String::trim).orElse("");
String locationId = storageLocationNameIdMap.get(locationName);
if (StringUtils.isBlank(locationId)) {
throw new ServiceException("库位【" + locationName + "】不存在");
}
itemDO.setLocationId(locationId);
itemDOList.add(itemDO);
} catch (Exception e) {
// 单个明细失败:仅统计,不影响整单
totalItemFailure++;
failureMsg.append(String.format("入库单号【%s】的物料明细【%s】处理失败:%s;\n",
orderId, vo.getSapNo(), e.getMessage()));
log.error("导入明细失败-入库单【{}】-SAP【{}】", orderId, vo.getSapNo(), e);
} }
itemDOList.add(itemDO);
} }
// 4.3 批量插入明细 // 4.3 批量插入明细
if (!CollectionUtils.isEmpty(itemDOList)) { if (!CollectionUtils.isEmpty(itemDOList)) {
int itemSuccess = inboundOrdersMapper.batchInboundOrderItems(itemDOList); int itemSuccess = inboundOrdersMapper.batchInboundOrderItems(itemDOList);
if (itemSuccess != itemDOList.size()) {
// 批量插入部分失败,主动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new ServiceException("明细批量插入失败,成功" + itemSuccess + "条,总" + itemDOList.size() + "条");
}
totalItemSuccess += itemSuccess; totalItemSuccess += itemSuccess;
totalItemFailure += (itemDOList.size() - itemSuccess);
successMsg.append(String.format("入库单号【%s】成功导入%d条物料明细;\n", orderId, itemSuccess)); successMsg.append(String.format("入库单号【%s】成功导入%d条物料明细;\n", orderId, itemSuccess));
if (itemDOList.size() - itemSuccess > 0) {
failureMsg.append(String.format("入库单号【%s】有%d条物料明细导入失败;\n", orderId, itemDOList.size() - itemSuccess));
}
} }
} catch (Exception e) { } catch (Exception e) {
...@@ -309,27 +364,63 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -309,27 +364,63 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
failureMsg.append(String.format("入库单号【%s】处理失败:%s;\n", orderId, e.getMessage())); failureMsg.append(String.format("入库单号【%s】处理失败:%s;\n", orderId, e.getMessage()));
// 打印异常栈,方便调试 // 打印异常栈,方便调试
log.error("导入入库单【{}】失败", orderId, e); log.error("导入入库单【{}】失败", orderId, e);
// 整单失败回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
} }
} }
// 5. 结果汇总 // 5. 结果汇总
if (totalMainFailure > 0 || totalItemFailure > 0) { if (totalMainFailure > 0 || totalItemFailure > 0) {
// 有失败数据,抛出异常提示 // 有失败数据,抛出异常提示
String finalFailureMsg = String.format( String finalMsg = String.format(
"导入结果:成功新增/更新%d个入库单,失败%d个;成功导入%d条明细,失败%d条。失败详情:%s", "导入结果:成功新增/更新%d个入库单,失败%d个;成功导入%d条明细,失败%d条。失败详情:%s",
totalMainSuccess, totalMainFailure, totalItemSuccess, totalItemFailure, failureMsg.toString() totalMainSuccess, totalMainFailure, totalItemSuccess, totalItemFailure, failureMsg
); );
throw new ServiceException(finalFailureMsg); throw new ServiceException(finalMsg);
} else { } else {
// 全部成功 // 全部成功
String finalSuccessMsg = String.format( return String.format(
"恭喜您,数据已全部导入成功!共处理%d个入库单,成功导入%d条物料明细。详情:%s", "导入成功!共处理%d个入库单,成功导入%d条物料明细。",
totalMainSuccess, totalItemSuccess, successMsg.toString() totalMainSuccess, totalItemSuccess
); );
return finalSuccessMsg;
} }
} }
// ========== 辅助方法:预加载仓库名称-ID映射 ==========
private Map<String, String> loadWarehouseNameIdMap() {
Warehouses query = new Warehouses();
query.setIsUsed(1L); // 只查可用仓库
List<Warehouses> warehouseList = warehousesMapper.selectWarehousesList(query);
if (CollectionUtils.isEmpty(warehouseList)) {
return Collections.emptyMap();
}
Map<String, String> nameIdMap = new HashMap<>();
for (Warehouses warehouse : warehouseList) {
String name = Optional.ofNullable(warehouse.getWarehousesName()).map(String::trim).orElse("");
if (StringUtils.isNotBlank(name)) {
nameIdMap.put(name, warehouse.getId());
}
}
return Collections.unmodifiableMap(nameIdMap); // 不可变Map,防止篡改
}
// ========== 辅助方法:预加载库位名称-ID映射 ==========
private Map<String, String> loadStorageLocationNameIdMap() {
StorageLocations query = new StorageLocations();
query.setIsUsed(1L); // 只查可用库位
List<StorageLocations> locationList = storageLocationsMapper.selectStorageLocationsList(query);
if (CollectionUtils.isEmpty(locationList)) {
return Collections.emptyMap();
}
Map<String, String> nameIdMap = new HashMap<>();
for (StorageLocations location : locationList) {
String name = Optional.ofNullable(location.getLocationName()).map(String::trim).orElse("");
if (StringUtils.isNotBlank(name)) {
nameIdMap.put(name, location.getId());
}
}
return Collections.unmodifiableMap(nameIdMap);
}
/** /**
* 统计本月入库数量 * 统计本月入库数量
* *
......
...@@ -401,6 +401,6 @@ ...@@ -401,6 +401,6 @@
ioi.unit_price, ioi.unit_price,
ioi.remark, ioi.remark,
io.inbound_date io.inbound_date
order by ioi.order_id asc,ioi.material_id asc order by inbound_date desc,ioi.order_id asc,ioi.material_id asc
</select> </select>
</mapper> </mapper>
\ No newline at end of file
...@@ -93,6 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ...@@ -93,6 +93,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createUserCode != null and createUserCode != ''"> and create_user_code = #{createUserCode}</if> <if test="createUserCode != null and createUserCode != ''"> and create_user_code = #{createUserCode}</if>
<if test="updateUserCode != null and updateUserCode != ''"> and update_user_code = #{updateUserCode}</if> <if test="updateUserCode != null and updateUserCode != ''"> and update_user_code = #{updateUserCode}</if>
</where> </where>
order by inbound_date desc,create_time desc
</select> </select>
<select id="selectInboundOrdersById" parameterType="String" resultMap="InboundOrdersInboundOrderItemsResult"> <select id="selectInboundOrdersById" parameterType="String" resultMap="InboundOrdersInboundOrderItemsResult">
......
...@@ -459,7 +459,8 @@ and inventory_status = '1' ...@@ -459,7 +459,8 @@ and inventory_status = '1'
<if test="createUserCode != null">create_user_code,</if> <if test="createUserCode != null">create_user_code,</if>
<if test="updateTime != null">update_time,</if> <if test="updateTime != null">update_time,</if>
<if test="updateUserCode != null">update_user_code,</if> <if test="updateUserCode != null">update_user_code,</if>
<if test="warehousesId != null"> warehouses_id,</if> <if test="warehousesId != null">warehouses_id,</if>
<if test="unitPrice != null"> unit_price,</if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if> <if test="id != null">#{id},</if>
...@@ -486,6 +487,7 @@ and inventory_status = '1' ...@@ -486,6 +487,7 @@ and inventory_status = '1'
<if test="updateTime != null">#{updateTime},</if> <if test="updateTime != null">#{updateTime},</if>
<if test="updateUserCode != null">#{updateUserCode},</if> <if test="updateUserCode != null">#{updateUserCode},</if>
<if test="warehousesId != null"> #{warehousesId},</if> <if test="warehousesId != null"> #{warehousesId},</if>
<if test="unitPrice != null"> #{unitPrice},</if>
</trim> </trim>
</insert> </insert>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论