Commit ccd1dfaa by zhangtw

新增批量入库功能

parent 7896e6a3
...@@ -18,29 +18,6 @@ ...@@ -18,29 +18,6 @@
<i class="el-icon-upload"></i> <i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<div class="el-upload__tip text-center" slot="tip"> <div class="el-upload__tip text-center" slot="tip">
<div
class="order-type-radio"
v-if="showTrdcCheckbox && dict.type.inbound_outbound_type.length"
style="margin-bottom: 8px; text-align: left; padding-left: 20px;"
>
<el-radio-group v-model="upload.orderType">
<el-radio
v-for="item in dict.type.inbound_outbound_type"
:key="item.value"
:label="item.value"
style="margin-right: 20px;"
>
{{ item.label }}
</el-radio>
</el-radio-group>
</div>
<!-- 加载中提示 -->
<div
v-else-if="showTrdcCheckbox && !dict.type.inbound_outbound_type.length"
style="margin-bottom: 8px; text-align: left; padding-left: 20px;"
>
<i class="el-icon-loading" style="font-size: 14px;"></i> 加载入库类型...
</div>
<!-- <div class="el-upload__tip" slot="tip"> <!-- <div class="el-upload__tip" slot="tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的数据 <el-checkbox v-model="upload.updateSupport" />是否更新已经存在的数据
</div> --> </div> -->
......
...@@ -13,6 +13,16 @@ ...@@ -13,6 +13,16 @@
>新增</el-button> >新增</el-button>
<el-button <el-button
type="primary"
plain
icon="el-icon-success"
size="medium"
:disabled="multiple"
@click="handleConfirm()"
v-hasPermi="['inventory:inbound:add']"
>确认入库</el-button>
<el-button
type="success" type="success"
plain plain
icon="el-icon-edit" icon="el-icon-edit"
...@@ -101,7 +111,7 @@ ...@@ -101,7 +111,7 @@
<el-form-item label="入库类型" prop="orderTypeId"> <el-form-item label="入库类型" prop="orderTypeId">
<el-select v-model="queryParams.orderTypeId" placeholder="请选择入库类型" clearable> <el-select v-model="queryParams.orderTypeId" placeholder="请选择入库类型" clearable>
<el-option <el-option
v-for="dict in dict.type.inbound_outbound_type" v-for="dict in dict.type.inbound_type"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
...@@ -164,10 +174,10 @@ ...@@ -164,10 +174,10 @@
<el-table-column label="入库类型" align="center" prop="orderTypeId" show-overflow-tooltip min-width="200"> <el-table-column label="入库类型" align="center" prop="orderTypeId" show-overflow-tooltip min-width="200">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag <el-tag
:type="getDictTagClass('inbound_outbound_type', scope.row.orderTypeId)" :type="getDictTagClass('inbound_type', scope.row.orderTypeId)"
size="small" size="small"
> >
{{ getDictLabel('inbound_outbound_type', scope.row.orderTypeId) }} {{ getDictLabel('inbound_type', scope.row.orderTypeId) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
...@@ -272,7 +282,7 @@ ...@@ -272,7 +282,7 @@
<el-form-item label="入库类型" prop="orderTypeId"> <el-form-item label="入库类型" prop="orderTypeId">
<el-select v-model="form.orderTypeId" placeholder="请选择入库类型" clearable> <el-select v-model="form.orderTypeId" placeholder="请选择入库类型" clearable>
<el-option <el-option
v-for="dict in dict.type.inbound_outbound_type" v-for="dict in dict.type.inbound_type"
:key="dict.value" :key="dict.value"
:label="dict.label" :label="dict.label"
:value="dict.value" :value="dict.value"
...@@ -377,7 +387,7 @@ ...@@ -377,7 +387,7 @@
<el-descriptions-item label="批次号">{{ detailForm.batchId || '-' }}</el-descriptions-item> <el-descriptions-item label="批次号">{{ detailForm.batchId || '-' }}</el-descriptions-item>
<el-descriptions-item label="货主">{{ detailForm.ownerName || detailForm.ownerId || '-' }}</el-descriptions-item> <el-descriptions-item label="货主">{{ detailForm.ownerName || detailForm.ownerId || '-' }}</el-descriptions-item>
<el-descriptions-item label="入库类型"> <el-descriptions-item label="入库类型">
{{ getDictLabel('inbound_outbound_type', detailForm.orderTypeId) }} {{ getDictLabel('inbound_type', detailForm.orderTypeId) }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="订单类型"> <el-descriptions-item label="订单类型">
{{ getDictLabel('order_type', detailForm.orderType) }} {{ getDictLabel('order_type', detailForm.orderType) }}
...@@ -470,7 +480,7 @@ import { ...@@ -470,7 +480,7 @@ import {
updateInbound updateInbound
} from "@/api/inventory/inbound" } from "@/api/inventory/inbound"
import { listInbound_itemsAndMname } from "@/api/inventory/inbound_items" import { listInbound_itemsAndMname } from "@/api/inventory/inbound_items"
import { batchAddInventory } from "@/api/inventory/inventory" import { batchAddInventory} from "@/api/inventory/inventory"
import InboundItems from "@/views/inventory/inbound_items/index.vue" import InboundItems from "@/views/inventory/inbound_items/index.vue"
import PageTitle from "@/components/PageTitle" import PageTitle from "@/components/PageTitle"
import PageWrapperSearch from "@/components/Search/PageWrapperSearch" import PageWrapperSearch from "@/components/Search/PageWrapperSearch"
...@@ -482,7 +492,7 @@ import LocationSelector from "@/views/compononents/LocationSelector.vue" ...@@ -482,7 +492,7 @@ import LocationSelector from "@/views/compononents/LocationSelector.vue"
export default { export default {
name: "Inbound", name: "Inbound",
dicts: ['inbound_outbound_type', 'order_type', 'inbound_status'], dicts: ['inbound_outbound_type', 'order_type', 'inbound_status', 'inbound_type'],
components: { components: {
InboundItems, InboundItems,
PageTitle, PageTitle,
...@@ -930,53 +940,143 @@ export default { ...@@ -930,53 +940,143 @@ export default {
}, },
/** /**
* 确认入库操作 * 确认入库操作(支持单选/多选)
* @param {Object} row 选中行数据 * @param {Object} row 选中行数据(单选时有值,多选时为null/undefined)
*/ */
async handleConfirm(row) { async handleConfirm(row) {
// 1. 确定要处理的入库单ID列表(单选取row.id,多选取已选中的ids)
let handleIds = [];
if (row) {
// 单选场景:仅处理当前行
handleIds = [row.id];
} else {
// 多选场景:取表格选中的ids(需确保this.ids是已选中的入库单ID数组)
handleIds = this.ids;
if (handleIds.length === 0) {
return this.$message.warning('请选择需要入库的单据!');
}
}
// 2. 二次确认(批量时提示选中的数量)
try { try {
await this.$confirm('确认要入库吗?', '提示', { await this.$confirm(
confirmButtonText: '确定', `确认要对选中的 ${handleIds.length} 条入库单执行入库操作吗?`,
cancelButtonText: '取消', '批量入库提示',
type: 'warning' {
}); confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
);
} catch (error) {
// 取消确认时直接返回
return;
}
// ✨ 关键:打开全局遮罩层(Element UI 全局loading)
const loadingInstance = this.$loading({
lock: true, // 锁定屏幕,禁止滚动/点击
text: '正在入库,请稍候...', // 加载提示文字
spinner: 'el-icon-loading', // 加载图标
background: 'rgba(0, 0, 0, 0.7)' // 遮罩层背景(半透明黑色)
});
// 3. 批量处理核心逻辑(加loading防止重复操作)
let successCount = 0; // 成功数量
let failList = []; // 失败的单据记录
// 查询入库明细 try {
const queryForm = { pageNum: 1, pageSize: 9999, inboundOrderId: row.id }; // 遍历每个入库单ID,逐行处理
const res = await listInbound_itemsAndMname(queryForm); for (const inboundId of handleIds) {
const items = res.rows || []; try {
// 新增:前置校验入库单ID是否有效
if (!inboundId || inboundId === null || inboundId === undefined) {
failList.push({ id: inboundId, reason: '入库单ID为空,跳过处理' });
console.warn('入库单ID为空,跳过:', inboundId);
continue;
}
// 3.1 查询当前入库单的基础信息(根据ID查单条入库单)
// 需补充:getInboundById 接口(根据id查询入库单详情)
const currentRow = await getInbound(inboundId);
if (!currentRow) {
failList.push({ id: inboundId, reason: '入库单不存在' });
continue;
}
// 跳过已入库的单据
if (currentRow.data.orderStatus === 2) {
failList.push({ id: inboundId, reason: '该单据已完成入库,无需重复操作' });
continue;
}
// 新增:校验currentRow.id是否存在(防止后端返回空ID的对象)
if (!currentRow.data.id) {
failList.push({ id: inboundId, reason: '入库单详情ID为空' });
continue;
}
// 3.2 查询当前入库单的明细
const queryForm = {
pageNum: 1,
pageSize: 9999,
inboundOrderId: currentRow.data.id
};
console.log(`查询入库单${inboundId}的明细,参数:`, queryForm);
const res = await listInbound_itemsAndMname(queryForm);
const items = res.rows || [];
if (items.length === 0) {
failList.push({ id: inboundId, reason: '暂无入库明细数据' });
continue;
}
// 3.3 组装库存数据
const inventoryItems = items.map(item => ({
...item,
inventoryType: currentRow.data.orderTypeId,
warehousesId: item.warehouseId,
inventoryStatus: 1,
ownerId: currentRow.data.ownerId,
quantity: item.actualQuantity,
unitWeight: item.unitWeight,
isUsed: 1,
unitPrice: item.unitPrice
}));
// 3.4 批量添加库存
await batchAddInventory(inventoryItems);
// 3.5 更新当前入库单状态
currentRow.data.orderStatus = 2;
await updateInbound(currentRow.data);
successCount++; // 成功计数+1
} catch (error) {
// 单个单据失败不中断整体流程,记录失败原因
failList.push({
id: inboundId,
reason: error.msg || error.message || '处理失败'
});
console.error(`入库单${inboundId}处理失败:`, error);
}
}
if (items.length === 0) { // 4. 批量处理完成后,统一提示结果
return this.$message.warning('暂无入库明细数据,无法确认入库'); let message = '';
if (successCount > 0) {
message += `成功入库 ${successCount} 条单据;`;
} }
if (failList.length > 0) {
message += `失败 ${failList.length} 条单据:${failList.map(item => `ID[${item.id}](${item.reason})`).join(';')}`;
}
this.$message[successCount > 0 ? 'success' : 'error'](message);
// 组装库存数据 // 5. 刷新列表
const inventoryItems = items.map(item => ({
...item,
inventoryType: row.orderTypeId,
warehousesId: item.warehouseId,
inventoryStatus: 1,
ownerId: row.ownerId,
quantity: item.actualQuantity,
unitWeight: item.unitWeight,
isUsed: 1,
unitPrice: item.unitPrice
}));
// 批量添加库存
await batchAddInventory(inventoryItems);
// 更新入库单状态
row.orderStatus = 2;
await updateInbound(row);
this.$message.success('确认入库成功');
this.getList(); this.getList();
} catch (error) { } catch (error) {
if (error !== 'cancel') { // 全局异常捕获(如网络中断等)
this.$message.error('确认入库失败:' + (error.msg || error.message || '网络异常')); this.$message.error('批量入库操作异常:' + (error.msg || error.message || '系统异常'));
console.error('确认入库失败:', error); console.error('批量入库全局异常:', error);
} } finally {
// ✨ 关键:关闭全局遮罩层(无论成功/失败都关闭)
loadingInstance.close();
// 清空选中的ids(多选后重置)
this.ids = [];
} }
}, },
......
...@@ -293,7 +293,7 @@ public class InboundOrdersController extends BaseController ...@@ -293,7 +293,7 @@ public class InboundOrdersController extends BaseController
} }
// 3. 通用导入逻辑(抽取重复代码,避免冗余) // 3. 通用导入逻辑(抽取重复代码,避免冗余)
String message = handleImport(getVOClassByOrderType(orderType), file, headerList, updateSupport, getUsername(), orderType); String message = handleImport(InboundTemplateVO.class, file, headerList, updateSupport, getUsername(), orderType);
return success(message); return success(message);
} }
...@@ -309,6 +309,7 @@ public class InboundOrdersController extends BaseController ...@@ -309,6 +309,7 @@ public class InboundOrdersController extends BaseController
} }
} }
private static final Map<Class<?>, List<String>> REQUIRED_HEADS_CACHE = new ConcurrentHashMap<>(); private static final Map<Class<?>, List<String>> REQUIRED_HEADS_CACHE = new ConcurrentHashMap<>();
/** /**
* 通用导入逻辑(泛型适配不同VO) * 通用导入逻辑(泛型适配不同VO)
*/ */
......
...@@ -68,11 +68,11 @@ public class InboundTemplateVO extends BaseEntity { ...@@ -68,11 +68,11 @@ public class InboundTemplateVO extends BaseEntity {
private String warehouseId; private String warehouseId;
/** 备注 */ /** 备注 */
// @Excel(name = "备注") @Excel(name = "备注")
private String remark; private String remark;
/** 库位ID 检索条件 */ /** 库位ID 检索条件 */
@Excel(name = "收货库位") // @Excel(name = "收货库位")
private String remark2; private String remark2;
/** 标签颜色 字典,检索条件 */ /** 标签颜色 字典,检索条件 */
......
...@@ -297,7 +297,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -297,7 +297,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
InboundOrders mainDO = null; InboundOrders mainDO = null;
List<InboundOrderItems> itemDOList = new ArrayList<>(); List<InboundOrderItems> itemDOList = new ArrayList<>();
List<Inventory> inventoryList = new ArrayList<>(); // List<Inventory> inventoryList = new ArrayList<>();
try { try {
// 4.1 处理主表(每个入库单号只处理一次主表) // 4.1 处理主表(每个入库单号只处理一次主表)
T firstVO = voList.get(0); // 取第一条VO的主表信息 T firstVO = voList.get(0); // 取第一条VO的主表信息
...@@ -336,6 +336,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -336,6 +336,7 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
"plannedPackages", "materialUnit", "materialRemark"); // 排除子表字段 "plannedPackages", "materialUnit", "materialRemark"); // 排除子表字段
// 填充主表必填字段 // 填充主表必填字段
mainDO.setId(UUID.randomUUID().toString()); mainDO.setId(UUID.randomUUID().toString());
mainDO.setOrderTypeId(mainDO.getOrderType());
mainDO.setInboundDate(now); mainDO.setInboundDate(now);
mainDO.setOrderId(orderId); mainDO.setOrderId(orderId);
mainDO.setCreateBy(operId); mainDO.setCreateBy(operId);
...@@ -343,13 +344,13 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -343,13 +344,13 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
mainDO.setCreateUserCode(operId); mainDO.setCreateUserCode(operId);
mainDO.setIsImport(1); mainDO.setIsImport(1);
mainDO.setIsUsed(1L); mainDO.setIsUsed(1L);
mainDO.setOrderTypeId(Optional.ofNullable(orderType).map(String::valueOf).orElse("")); // mainDO.setOrderTypeId(Optional.ofNullable(orderType).map(String::valueOf).orElse(""));
// 设置默认值 // 设置默认值
if (mainDO.getSortNo() == null) { if (mainDO.getSortNo() == null) {
mainDO.setSortNo(0L); mainDO.setSortNo(0L);
} }
if (mainDO.getOrderStatus() == null) { if (mainDO.getOrderStatus() == null) {
mainDO.setOrderStatus(2L); // 默认草稿状态 mainDO.setOrderStatus(1L); // 默认草稿状态
} }
// ========== 货主查询(反射获取ownerId) ========== // ========== 货主查询(反射获取ownerId) ==========
...@@ -488,21 +489,21 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -488,21 +489,21 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
log.error("导入明细失败-入库单【{}】-SAP【{}】,实发数量和实际件数为空", orderId, sapNo, e); log.error("导入明细失败-入库单【{}】-SAP【{}】,实发数量和实际件数为空", orderId, sapNo, e);
throw new ServiceException("实发数量和实际件数为空"); throw new ServiceException("实发数量和实际件数为空");
} }
//
Inventory inventoryDO = new Inventory( // Inventory inventoryDO = new Inventory(
Long.parseLong(mainDO.getOrderTypeId()), // Long.parseLong(mainDO.getOrderTypeId()),
orderId, // orderId,
itemDO.getMaterialId(), // itemDO.getMaterialId(),
batchId, // batchId,
itemDO.getWarehouseId(), // itemDO.getWarehouseId(),
itemDO.getLocationId(), // itemDO.getLocationId(),
mainDO.getOwnerId(), // mainDO.getOwnerId(),
itemDO.getActualQuantity() == null ? 0L : itemDO.getActualQuantity(), // itemDO.getActualQuantity() == null ? 0L : itemDO.getActualQuantity(),
unitWeight, // unitWeight,
1L, // 1L,
itemDO.getUnitPrice() // itemDO.getUnitPrice()
); // );
inventoryList.add(inventoryDO); // inventoryList.add(inventoryDO);
} catch (Exception e) { } catch (Exception e) {
// 单个明细失败:仅统计,不影响整单 // 单个明细失败:仅统计,不影响整单
totalItemFailure++; totalItemFailure++;
...@@ -525,8 +526,8 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService ...@@ -525,8 +526,8 @@ public class InboundOrdersServiceImpl implements IInboundOrdersService
// 4.3 批量插入明细 // 4.3 批量插入明细
if (!CollectionUtils.isEmpty(itemDOList)) { if (!CollectionUtils.isEmpty(itemDOList)) {
int itemSuccess = inboundOrdersMapper.batchInboundOrderItems(itemDOList); int itemSuccess = inboundOrdersMapper.batchInboundOrderItems(itemDOList);
int inventorySuccess = iInventoryService.insertInventoryList(inventoryList); // int inventorySuccess = iInventoryService.insertInventoryList(inventoryList);
if (itemSuccess != itemDOList.size() && itemSuccess != inventorySuccess) { if (itemSuccess != itemDOList.size()) {
// 批量插入部分失败,主动回滚事务 // 批量插入部分失败,主动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new ServiceException("明细批量插入失败,成功" + itemSuccess + "条,总" + itemDOList.size() + "条"); throw new ServiceException("明细批量插入失败,成功" + itemSuccess + "条,总" + itemDOList.size() + "条");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论