Fix Bug #517: [库房管理-领用管理] 业务逻辑校验缺失:允许保存并提交领用数量大于库存数量(零库存领用)的单据

根因分析:
- 前端 handleSubmitApproval(提交审批)未做库存校验,直接调用后端 API
- 后端 submitApproval 也未做库存校验,仅在保存时(addOrEditIssueReceipt)有 validateRequisitionStock
- 用户可绕过前端保存校验(如编辑已有草稿后直接提交审批),将超库存单据提交审批流

修复方案:
1. 后端:在 submitApproval 方法中增加 validateRequisitionStockByBusNo,通过单据详情查询已保存明细,逐行校验领用数量是否超过源仓库库存
2. 前端:在 handleSubmitApproval 提交前逐行调用 validateRequisitionQtyVsStock 校验库存,超库存时拦截并提示

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-17 21:31:20 +08:00
parent 14dc9964d5
commit fd34fe0c72
2 changed files with 89 additions and 0 deletions

View File

@@ -256,6 +256,9 @@ public class RequisitionIssueAppServiceImpl implements IRequisitionIssueAppServi
*/ */
@Override @Override
public R<?> submitApproval(String busNo) { public R<?> submitApproval(String busNo) {
// 提交审批前校验库存,防止超库存单据进入审批流
this.validateRequisitionStockByBusNo(busNo);
// 单据提交审核 // 单据提交审核
boolean result = supplyRequestService.submitApproval(busNo); boolean result = supplyRequestService.submitApproval(busNo);
return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null)) return result ? R.ok(null, MessageUtils.createMessage(PromptMsgConstant.Common.M00004, null))
@@ -419,4 +422,80 @@ public class RequisitionIssueAppServiceImpl implements IRequisitionIssueAppServi
} }
} }
} }
/**
* 根据单据号校验领用数量是否超过源仓库实际库存(提交审批前调用)
*
* @param busNo 单据号
*/
private void validateRequisitionStockByBusNo(String busNo) {
Integer tenantId = SecurityUtils.getLoginUser().getTenantId();
// 通过单据详情查询已保存的单据明细
R<List<IssueDetailDto>> detailResult = this.getDetail(busNo);
if (detailResult.getCode() != 200 || detailResult.getData() == null) {
return;
}
List<IssueDetailDto> detailList = detailResult.getData();
for (IssueDetailDto detail : detailList) {
Long itemId = detail.getItemId();
String lotNumber = detail.getLotNumber();
Long sourceLocationId = detail.getSourceLocationId();
BigDecimal reqQuantity = detail.getItemQuantity();
String itemTable = CommonConstants.TableName.MED_MEDICATION_DEFINITION;
// 根据药品类型判断表名
if (ItemType.DEVICE.getValue().equals(detail.getItemType())) {
itemTable = CommonConstants.TableName.ADM_DEVICE_DEFINITION;
}
// 查询定义信息(拆零比、单位)
BigDecimal partPercent = BigDecimal.ONE;
String unitCode = detail.getUnitCode();
String minUnitCode = detail.getMinUnitCode();
if (CommonConstants.TableName.MED_MEDICATION_DEFINITION.equals(itemTable)) {
MedicationDefinition medDef = medicationDefinitionService.getById(itemId);
if (medDef != null) {
unitCode = medDef.getUnitCode();
minUnitCode = medDef.getMinUnitCode();
if (medDef.getPartPercent() != null) {
partPercent = medDef.getPartPercent();
}
}
} else if (CommonConstants.TableName.ADM_DEVICE_DEFINITION.equals(itemTable)) {
DeviceDefinition devDef = deviceDefinitionService.getById(itemId);
if (devDef != null) {
unitCode = devDef.getUnitCode();
minUnitCode = devDef.getMinUnitCode();
if (devDef.getPartPercent() != null) {
partPercent = devDef.getPartPercent();
}
}
}
// 计算领用数量折合最小单位的值
BigDecimal reqQuantityInMinUnit;
if (unitCode != null && detail.getUnitCode().equals(unitCode)) {
reqQuantityInMinUnit = reqQuantity.multiply(partPercent);
} else {
reqQuantityInMinUnit = reqQuantity;
}
// 查询源仓库实际库存
List<InventoryItem> inventoryItemList = inventoryItemService.selectInventoryByItemId(
itemId, lotNumber, sourceLocationId, tenantId);
// 累加总库存
BigDecimal totalStock = BigDecimal.ZERO;
for (InventoryItem inventoryItem : inventoryItemList) {
if (inventoryItem.getLocationId().equals(sourceLocationId)) {
totalStock = totalStock.add(inventoryItem.getQuantity());
}
}
// 比较领用数量与库存
if (reqQuantityInMinUnit.compareTo(totalStock) > 0) {
throw new ServiceException("提交失败,单据中存在领用数量超过库存的明细,请核对后重新保存");
}
}
}
} }

View File

@@ -982,6 +982,16 @@ function handleSubmitApproval() {
} else if (!form.purchaseinventoryList[length - 1].isSave) { } else if (!form.purchaseinventoryList[length - 1].isSave) {
proxy.$modal.msgWarning('第' + length + '行单据未保存,请先保存'); proxy.$modal.msgWarning('第' + length + '行单据未保存,请先保存');
} else { } else {
// 提交审批前逐行校验库存,防止超库存单据进入审批
for (let i = 0; i < form.purchaseinventoryList.length; i++) {
const line = form.purchaseinventoryList[i];
if (!line) continue;
const err = validateRequisitionQtyVsStock(line, i + 1);
if (err) {
proxy.$modal.msgWarning(err);
return;
}
}
submitApproval(receiptHeaderForm.busNo).then((response) => { submitApproval(receiptHeaderForm.busNo).then((response) => {
if (response.code == 200) { if (response.code == 200) {
proxy.$modal.msgSuccess('提交审批成功'); proxy.$modal.msgSuccess('提交审批成功');