# 库存盘点组件库存调拨功能设计方案 ## 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 调拨 ``` ### 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. 总结 本方案将库存调拨限定在库存盘点组件内,采用“行内调拨弹窗 + 一键新建库位 + 后端事务扣减/增加库存”的轻量设计。用户在盘点时选择一行库存,输入调拨数量和目标库位,系统直接将调拨数量从原库位库存中减掉,并增加到目标库位库存中。 关键点是新增一个原子后端接口 `仓储管理_库存盘点_库存调拨`,避免前端多次请求造成库存不一致。同时保留调拨记录,满足后续盘点追溯。