16 KiB
库存盘点组件库存调拨功能设计方案
0. 需求背景
当前库存盘点页面 src/views/WarehouseManagement/InventoryCheck/index.vue 已支持查询物料库存、编辑库存数、选择库位,并已存在新增物料货位数量的相关接口。现场盘点时,经常会发现同一物料的实际库存需要从原库位拆分到另一个库位,例如:
- 盘点发现物料实际摆放到了新库位。
- 原库位库存数量过多,需要拆一部分到新库位。
- 系统中还没有目标库位,需要现场一键新建库位后立即调拨。
- 需要保留原库位库存,只把输入的调拨数量从原库位扣掉,并增加到目标库位。
因此本次功能不设计完整调拨单流程,而是在“库存盘点”组件内增加轻量级库存调拨能力:选择一行库存,输入调拨数量,选择或新建目标库位,提交后直接完成原库位库存扣减和目标库位库存增加。
1. 设计目标
本功能面向库存盘点页面,核心目标如下:
- 在库存盘点表格行上增加“调拨”操作。
- 调拨弹窗中自动带出当前物料、原库位、原库存数。
- 支持选择已有目标库位。
- 支持一键新建目标库位,并自动回填为调入库位。
- 输入调拨数量后,提交时从原库位库存数中减掉该调拨数量。
- 如果目标库位已有同物料库存,则目标库位库存数增加调拨数量。
- 如果目标库位没有同物料库存,则新增一条物料与货位数量记录。
- 调拨后刷新库存盘点列表,保持页面显示与数据库一致。
2. 功能范围
本次只处理库存盘点组件内的库位库存调整,不新增独立“库存调拨单”页面。
包含:
- 盘点列表行内调拨。
- 一键新增库位。
- 原库位扣减库存。
- 目标库位增加库存。
- 调拨操作记录。
不包含:
- 调拨单主表和明细表。
- 调出确认、调入确认两段式流程。
- 审批流程。
- 跨仓库复杂调拨。
- 打印调拨单。
- 外协调拨。
3. 页面改造位置
现有页面:
src/views/WarehouseManagement/InventoryCheck/index.vue
当前页面已有相关字段和接口:
- 表格字段:
库存、货位流水号、货位名称、物料与货位流水号 - 库位下拉查询:
仓储管理_库位_查询 - 盘点查询:
仓储管理_库存盘点_查询 - 库存编辑:
仓储管理_库存盘点_货位数量_编辑数据 - 新增物料货位数量验证:
仓储管理_库存盘点_物料货位数量_增加数据_验证是否存在 - 新增物料货位数量:
仓储管理_库存盘点_物料货位数量_增加数据
4. 前端交互设计
4.1 表格增加操作按钮
在库存盘点表格操作列增加:
调拨
按钮显示规则:
- 当前行
物料与货位流水号存在时显示。 - 当前行
库存 > 0时允许点击。 - 如果库存为 0,按钮禁用或点击提示“当前库位库存为0,不能调拨”。
建议按钮:
<el-button type="text" size="mini" @click="openTransfer(scope.row)">调拨</el-button>
4.2 调拨弹窗
新增 el-dialog:
库存调拨
弹窗字段:
| 字段 | 控件 | 说明 |
|---|---|---|
| 物料名称 | 文本 | 当前行带出,只读 |
| 图号或型号 | 文本 | 当前行带出,只读 |
| 原库位 | 文本 | 当前行 货位名称,只读 |
| 原库存数 | 文本 | 当前行 库存,只读 |
| 目标库位 | el-select | 选择已有库位 |
| 新库位名称 | el-input | 一键新建库位时填写 |
| 调拨数量 | el-input-number | 需要从原库位调出的数量 |
| 调拨备注 | el-input | 可选 |
弹窗按钮:
- 新建库位
- 确定调拨
- 取消
4.3 一键新建库位
在调拨弹窗中增加“新建库位”小区域:
新库位名称 + 新建按钮
用户输入新库位名称后点击“新建库位”:
- 校验库位名称不能为空。
- 调用后端新增库位接口。
- 新增成功后重新查询库位下拉。
- 将新库位流水号自动赋值给
transferForm.目标货位流水号。 - 提示“库位新增成功”。
接口建议:
var Data = this.CreateData('12', '仓储管理_库位_增加', param)
参数建议:
| 参数名 | 说明 |
|---|---|
| 货位名称 | 新库位名称 |
| UserID | 当前用户 |
| ModularID | 当前路由 |
如果后端已有通用库位新增接口,应优先复用现有接口,不重复新增存储过程。
5. 调拨业务规则
5.1 数量规则
调拨数量必须满足:
0 < 调拨数量 <= 原库存数
前端校验:
if (Number(this.transferForm.调拨数量) <= 0) {
this.$message.warning('调拨数量必须大于0')
return
}
if (Number(this.transferForm.调拨数量) > Number(this.transferForm.原库存数)) {
this.$message.warning('调拨数量不能大于原库存数')
return
}
后端也必须重复校验,避免多人同时操作导致库存被扣成负数。
5.2 库位规则
目标库位必须满足:
- 目标库位不能为空。
- 目标库位不能等于原库位。
- 新建库位名称不能与已有库位重复。
5.3 库存更新规则
提交调拨后,后端一次性完成两个动作:
原库位库存 = 原库位库存 - 调拨数量
目标库位库存 = 目标库位库存 + 调拨数量
如果目标库位不存在该物料库存记录:
新增 物料 + 目标库位 的库存记录,库存数 = 调拨数量
如果目标库位已存在该物料库存记录:
更新目标库位库存数,库存数 = 原目标库位库存数 + 调拨数量
5.4 原库位库存为 0 的处理
调拨后如果原库位库存变为 0,建议保留该物料货位记录,并显示库存为 0。
原因:
- 便于追溯原库位曾经存在该物料。
- 避免删除记录影响历史盘点记录。
- 与当前盘点页面“库存数可为 0”的逻辑一致。
如果现场明确要求隐藏 0 库存,可以在查询接口中过滤,不建议物理删除。
6. 数据字段设计
6.1 前端弹窗对象
建议新增:
transferForm: {
物料与货位流水号: '',
物料流水号: '',
物料名称: '',
图号或型号: '',
原货位流水号: '',
原货位名称: '',
原库存数: 0,
目标货位流水号: '',
目标货位名称: '',
新货位名称: '',
调拨数量: 1,
调拨备注: ''
}
6.2 后端调拨接口参数
建议新增一个原子接口,避免前端连续调用两个编辑接口导致只扣不加或只加不扣。
接口:
var Data = this.CreateData('12', '仓储管理_库存盘点_库存调拨', param)
参数:
| 参数名 | 说明 |
|---|---|
| 物料与货位流水号 | 原库存记录流水号 |
| 物料流水号 | 当前物料流水号 |
| 原货位流水号 | 原库位流水号 |
| 目标货位流水号 | 目标库位流水号 |
| 调拨数量 | 本次调拨数量 |
| 原库存数 | 页面带出的原库存数,仅用于校验参考 |
| 调拨备注 | 调拨备注 |
| 操作人 | UserID |
后端处理必须放在同一个事务中:
- 查询原库存记录,并锁定或重新校验当前库存。
- 校验当前库存是否大于等于调拨数量。
- 扣减原库位库存。
- 查询目标库位是否已有同物料记录。
- 有则累加目标库位库存。
- 无则新增目标库位库存记录。
- 写入库存调拨记录。
- 返回成功。
7. 调拨记录设计
建议新增轻量级调拨记录表,便于盘点后追溯谁把多少库存从哪个库位调到哪个库位。
字段建议:
| 字段名 | 说明 |
|---|---|
| 库存调拨流水号 | 主键 |
| 物料流水号 | 物料流水号 |
| 物料名称 | 冗余名称,便于查询 |
| 图号或型号 | 冗余图号 |
| 原物料与货位流水号 | 原库存记录 |
| 原货位流水号 | 原库位 |
| 原货位名称 | 原库位名称 |
| 目标物料与货位流水号 | 目标库存记录 |
| 目标货位流水号 | 目标库位 |
| 目标货位名称 | 目标库位名称 |
| 调拨数量 | 调拨数量 |
| 调拨前原库存 | 扣减前原库位库存 |
| 调拨后原库存 | 扣减后原库位库存 |
| 调拨前目标库存 | 增加前目标库位库存 |
| 调拨后目标库存 | 增加后目标库位库存 |
| 调拨备注 | 备注 |
| 操作人 | UserID |
| 操作时间 | 调拨时间 |
查询接口建议:
var Data = this.CreateData('11', '仓储管理_库存盘点_库存调拨记录_查询', param, this.pageSize, this.pageCurrent)
第一版可以不新增独立页面,只在库存盘点页面增加“调拨记录”按钮或弹窗。
8. API 设计
8.1 查询库位
沿用现有:
var Data = this.CreateData('11', '仓储管理_库位_查询', param)
8.2 新增库位
建议新增或复用:
var Data = this.CreateData('12', '仓储管理_库位_增加', param)
参数:
| 参数名 | 说明 |
|---|---|
| 货位名称 | 新库位名称 |
| 备注 | 可选 |
| UserID | 当前用户 |
返回:
| 字段 | 说明 |
|---|---|
| result | 是否成功 |
| 货位流水号 | 新增库位流水号 |
| 货位名称 | 新增库位名称 |
8.3 库存调拨
建议新增:
var Data = this.CreateData('12', '仓储管理_库存盘点_库存调拨', param)
这是本功能最核心接口,必须由后端事务保证扣减和增加同时成功或同时失败。
8.4 调拨记录查询
建议新增:
var Data = this.CreateData('11', '仓储管理_库存盘点_库存调拨记录_查询', param, this.pageSize, this.pageCurrent)
查询参数:
| 参数名 | 说明 |
|---|---|
| 物料名称_check | 是否按物料名称查询 |
| 物料名称 | 物料名称 |
| 图号或型号_check | 是否按图号查询 |
| 图号或型号 | 图号或型号 |
| 原货位流水号_check | 是否按原库位查询 |
| 原货位流水号 | 原库位 |
| 目标货位流水号_check | 是否按目标库位查询 |
| 目标货位流水号 | 目标库位 |
| 操作时间_check | 是否按时间查询 |
| 开始时间 | 开始时间 |
| 结束时间 | 结束时间 |
9. 前端方法设计
9.1 打开调拨弹窗
openTransfer(row) {
if (Number(row.库存) <= 0) {
this.$message.warning('当前库位库存为0,不能调拨')
return
}
this.transferForm.物料与货位流水号 = row.物料与货位流水号
this.transferForm.物料流水号 = row.物料流水号
this.transferForm.物料名称 = row.物料名称
this.transferForm.图号或型号 = row.图号或型号
this.transferForm.原货位流水号 = row.货位流水号
this.transferForm.原货位名称 = row.货位名称
this.transferForm.原库存数 = Number(row.库存)
this.transferForm.目标货位流水号 = ''
this.transferForm.目标货位名称 = ''
this.transferForm.新货位名称 = ''
this.transferForm.调拨数量 = 1
this.transferForm.调拨备注 = ''
this.transferDialogVisible = true
}
9.2 新建库位
addTransferLocation() {
if (!this.transferForm.新货位名称) {
this.$message.warning('请输入新库位名称')
return
}
var param = []
param[0] = ['货位名称', this.transferForm.新货位名称]
var Data = this.CreateData('12', '仓储管理_库位_增加', param)
this.ExecDatabase(Data).then(response => {
if (response.data && response.data.length > 0) {
this.getStorageLocation()
this.transferForm.目标货位流水号 = response.data[0].货位流水号
this.$message.success('库位新增成功')
}
})
}
说明:具体返回结构以现有后端为准,若只返回成功标识,则新增后通过库位名称重新查询并回填。
9.3 提交调拨
submitTransfer() {
if (!this.transferForm.目标货位流水号) {
this.$message.warning('请选择目标库位')
return
}
if (this.transferForm.目标货位流水号 === this.transferForm.原货位流水号) {
this.$message.warning('目标库位不能与原库位相同')
return
}
if (Number(this.transferForm.调拨数量) <= 0) {
this.$message.warning('调拨数量必须大于0')
return
}
if (Number(this.transferForm.调拨数量) > Number(this.transferForm.原库存数)) {
this.$message.warning('调拨数量不能大于原库存数')
return
}
var param = []
param[0] = ['物料与货位流水号', this.transferForm.物料与货位流水号]
param[1] = ['物料流水号', this.transferForm.物料流水号]
param[2] = ['原货位流水号', this.transferForm.原货位流水号]
param[3] = ['目标货位流水号', this.transferForm.目标货位流水号]
param[4] = ['调拨数量', this.transferForm.调拨数量]
param[5] = ['原库存数', this.transferForm.原库存数]
param[6] = ['调拨备注', this.transferForm.调拨备注]
var Data = this.CreateData('12', '仓储管理_库存盘点_库存调拨', param)
this.ExecDatabase(Data).then(response => {
this.$message.success('调拨成功')
this.transferDialogVisible = false
this.searchTable()
})
}
10. 后端事务逻辑
后端存储过程建议逻辑:
开始事务
查询原物料货位库存记录
如果不存在,返回“原库存记录不存在”
如果当前库存 < 调拨数量,返回“库存不足”
如果原货位流水号 = 目标货位流水号,返回“目标库位不能与原库位相同”
更新原库位库存 = 当前库存 - 调拨数量
查询目标货位是否存在同物料库存记录
如果存在:
更新目标库位库存 = 当前目标库存 + 调拨数量
如果不存在:
新增目标货位库存记录,库存 = 调拨数量
写入库存调拨记录
提交事务
任何一步失败必须回滚,避免库存只扣不加。
11. 与现有编辑库存功能的关系
当前库存盘点页面已有“编辑库存数”和“编辑库位”的能力:
var Data = this.CreateData('12', '仓储管理_库存盘点_货位数量_编辑数据', param)
新调拨功能不建议复用前端两次调用该接口来完成扣减和增加,因为:
- 两次请求中间失败会造成库存不一致。
- 多人并发时容易出现负库存。
- 目标库位不存在同物料记录时还需要新增记录。
因此建议新增一个后端原子接口:
仓储管理_库存盘点_库存调拨
该接口内部统一完成扣减、增加、新增目标记录、写调拨记录。
12. 实施步骤
- 后端新增或确认库位新增接口:
仓储管理_库位_增加。 - 后端新增库存调拨接口:
仓储管理_库存盘点_库存调拨。 - 后端新增库存调拨记录表。
- 后端新增库存调拨记录查询接口。
- 前端
InventoryCheck/index.vue增加“调拨”按钮。 - 前端增加调拨弹窗和
transferForm。 - 前端增加“一键新建库位”方法。
- 前端增加“确定调拨”方法。
- 调拨成功后调用
searchTable()刷新盘点列表。 - 联调库存扣减、目标库位增加、目标库位不存在时新增记录。
13. 风险点
13.1 并发扣减风险
盘点期间多人可能同时调拨同一库位库存。后端必须以数据库当前库存为准,不能只相信前端传入的 原库存数。
13.2 目标库位重复风险
一键新建库位时需要校验 货位名称 是否已存在。若已存在,应直接提示或返回已有库位,不应重复新增同名库位。
13.3 只扣不加风险
调拨不能由前端拆成“编辑原库存”和“新增目标库存”两个独立请求。必须由一个后端事务接口完成。
13.4 数据追溯风险
虽然不做完整调拨单,也必须保留调拨记录,记录调拨前后库存、原库位、目标库位、操作人和时间。
14. 总结
本方案将库存调拨限定在库存盘点组件内,采用“行内调拨弹窗 + 一键新建库位 + 后端事务扣减/增加库存”的轻量设计。用户在盘点时选择一行库存,输入调拨数量和目标库位,系统直接将调拨数量从原库位库存中减掉,并增加到目标库位库存中。
关键点是新增一个原子后端接口 仓储管理_库存盘点_库存调拨,避免前端多次请求造成库存不一致。同时保留调拨记录,满足后续盘点追溯。