Files
JY1.0/docs/库存调拨/库存调拨功能设计方案.md

542 lines
16 KiB
Markdown
Raw Blame History

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