From 23411ea1b5e30733d4583334a759ee71d7d674a7 Mon Sep 17 00:00:00 2001 From: KiriAky 107 Date: Tue, 7 Jul 2026 09:47:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=BA=93=E5=AD=98=E6=95=B0?= =?UTF-8?q?=E9=87=8F=E5=A4=A7=E4=BA=8E=E9=9B=B6=E7=9A=84=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在StockController的list和warning方法以及DashboardServiceImpl的 getExpiryWarning方法中添加了.gt(Stock::getQuantity, 0)条件, 避免返回无库存记录的药品信息 feat(stock-in): 实现出库单详情中的药品名称显示功能 在StockInController中注入medicineService,并在getById方法中 通过流式处理将药品ID映射为药品名称,填充到出库详情中 feat(stock-out): 实现出库单详情中的药品名称显示功能 在StockOutController中注入medicineService,并在getById方法中 通过流式处理将药品ID映射为药品名称,填充到出库详情中 refactor(stock-out): 优化出库库存检查逻辑并实现FIFO扣减 将原有的简单库存总量检查改为按批次和仓库精确检查, 实现先进先出(FIFO)的库存扣减策略,确保先过期的药品优先出库 feat(frontend): 提交表单时计算并包含明细总价字段 在StockInForm和StockOutForm中修改提交逻辑,为每个详情项 计算totalPrice字段,确保后端接收到完整的金额信息 --- .../backend/controller/StockController.java | 2 ++ .../backend/controller/StockInController.java | 14 +++++++-- .../controller/StockOutController.java | 15 +++++++-- .../service/impl/DashboardServiceImpl.java | 1 + .../service/impl/StockOutServiceImpl.java | 31 +++++++++---------- frontend/src/views/stockIn/StockInForm.vue | 3 +- frontend/src/views/stockOut/StockOutForm.vue | 4 ++- 7 files changed, 47 insertions(+), 23 deletions(-) diff --git a/backend/src/main/java/com/kronecker/backend/controller/StockController.java b/backend/src/main/java/com/kronecker/backend/controller/StockController.java index 18adb16..c55be2d 100644 --- a/backend/src/main/java/com/kronecker/backend/controller/StockController.java +++ b/backend/src/main/java/com/kronecker/backend/controller/StockController.java @@ -29,6 +29,7 @@ public class StockController { @RequestParam(required = false) String medicineName, @RequestParam(required = false) String batchNo) { var wrapper = new LambdaQueryWrapper() + .gt(Stock::getQuantity, 0) .like(StringUtils.hasText(batchNo), Stock::getBatchNo, batchNo) .orderByAsc(Stock::getExpiryDate); // 按药品名搜索需先查 medicine 表 @@ -48,6 +49,7 @@ public class StockController { public Result> warning(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size) { var wrapper = new LambdaQueryWrapper() + .gt(Stock::getQuantity, 0) .gt(Stock::getExpiryDate, java.time.LocalDate.now()) .orderByAsc(Stock::getExpiryDate); Page result = stockService.page(new Page<>(page, size), wrapper); diff --git a/backend/src/main/java/com/kronecker/backend/controller/StockInController.java b/backend/src/main/java/com/kronecker/backend/controller/StockInController.java index 04a4347..5db3651 100644 --- a/backend/src/main/java/com/kronecker/backend/controller/StockInController.java +++ b/backend/src/main/java/com/kronecker/backend/controller/StockInController.java @@ -7,8 +7,10 @@ import com.kronecker.backend.common.Result; import com.kronecker.backend.dto.StockInCreateDTO; import com.kronecker.backend.entity.*; import com.kronecker.backend.service.StockInService; +import com.baomidou.mybatisplus.extension.service.IService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import java.util.stream.Collectors; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; @@ -24,6 +26,7 @@ public class StockInController { private final IService recordService; private final IService detailService; private final IService supplierService; + private final IService medicineService; private final StockInService stockInService; @GetMapping @@ -39,8 +42,15 @@ public class StockInController { public Result getById(@PathVariable Long id) { StockInRecord record = recordService.getById(id); if (record != null) { - record.setDetails(detailService.list( - new LambdaQueryWrapper().eq(StockInDetail::getRecordId, id))); + var details = detailService.list( + new LambdaQueryWrapper().eq(StockInDetail::getRecordId, id)); + if (!details.isEmpty()) { + var medIds = details.stream().map(StockInDetail::getMedicineId).collect(Collectors.toSet()); + var medMap = medicineService.listByIds(medIds).stream() + .collect(Collectors.toMap(Medicine::getId, Medicine::getName)); + details.forEach(d -> d.setMedicineName(medMap.getOrDefault(d.getMedicineId(), ""))); + } + record.setDetails(details); var supp = supplierService.getById(record.getSupplierId()); if (supp != null) record.setSupplierName(supp.getName()); } diff --git a/backend/src/main/java/com/kronecker/backend/controller/StockOutController.java b/backend/src/main/java/com/kronecker/backend/controller/StockOutController.java index 7d92edb..ceea84e 100644 --- a/backend/src/main/java/com/kronecker/backend/controller/StockOutController.java +++ b/backend/src/main/java/com/kronecker/backend/controller/StockOutController.java @@ -7,8 +7,10 @@ import com.kronecker.backend.common.Result; import com.kronecker.backend.dto.StockOutCreateDTO; import com.kronecker.backend.entity.*; import com.kronecker.backend.service.StockOutService; +import com.baomidou.mybatisplus.extension.service.IService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import java.util.stream.Collectors; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.bind.annotation.*; @@ -24,6 +26,7 @@ public class StockOutController { private final IService recordService; private final IService detailService; private final IService customerService; + private final IService medicineService; private final StockOutService stockOutService; @GetMapping @@ -39,8 +42,16 @@ public class StockOutController { public Result getById(@PathVariable Long id) { StockOutRecord record = recordService.getById(id); if (record != null) { - record.setDetails(detailService.list( - new LambdaQueryWrapper().eq(StockOutDetail::getRecordId, id))); + var details = detailService.list( + new LambdaQueryWrapper().eq(StockOutDetail::getRecordId, id)); + // 填充药品名 + if (!details.isEmpty()) { + var medIds = details.stream().map(StockOutDetail::getMedicineId).collect(Collectors.toSet()); + var medMap = medicineService.listByIds(medIds).stream() + .collect(Collectors.toMap(Medicine::getId, Medicine::getName)); + details.forEach(d -> d.setMedicineName(medMap.getOrDefault(d.getMedicineId(), ""))); + } + record.setDetails(details); if (record.getCustomerId() != null) { var cust = customerService.getById(record.getCustomerId()); if (cust != null) record.setCustomerName(cust.getName()); diff --git a/backend/src/main/java/com/kronecker/backend/service/impl/DashboardServiceImpl.java b/backend/src/main/java/com/kronecker/backend/service/impl/DashboardServiceImpl.java index 4ccf57a..7ab61f1 100644 --- a/backend/src/main/java/com/kronecker/backend/service/impl/DashboardServiceImpl.java +++ b/backend/src/main/java/com/kronecker/backend/service/impl/DashboardServiceImpl.java @@ -38,6 +38,7 @@ public class DashboardServiceImpl implements DashboardService { public Page getExpiryWarning(Page page) { Page result = stockMapper.selectPage(page, new LambdaQueryWrapper() + .gt(Stock::getQuantity, 0) .le(Stock::getExpiryDate, LocalDate.now().plusDays(90)) .orderByAsc(Stock::getExpiryDate)); populateStockNames(result.getRecords()); diff --git a/backend/src/main/java/com/kronecker/backend/service/impl/StockOutServiceImpl.java b/backend/src/main/java/com/kronecker/backend/service/impl/StockOutServiceImpl.java index 7399b41..b94591d 100644 --- a/backend/src/main/java/com/kronecker/backend/service/impl/StockOutServiceImpl.java +++ b/backend/src/main/java/com/kronecker/backend/service/impl/StockOutServiceImpl.java @@ -64,26 +64,23 @@ public class StockOutServiceImpl implements StockOutService { var details = detailMapper.selectList( new LambdaQueryWrapper().eq(StockOutDetail::getRecordId, id)); - // 检查库存是否充足 + // 检查库存是否充足 + 扣减库存(FIFO: 先过期先出) for (var d : details) { - int totalStock = stockMapper.selectList(new LambdaQueryWrapper() - .eq(Stock::getMedicineId, d.getMedicineId()) - .eq(Stock::getWarehouseId, record.getWarehouseId()) - .eq(Stock::getBatchNo, d.getBatchNo())) - .stream().mapToInt(Stock::getQuantity).sum(); - if (totalStock < d.getQuantity()) { - throw new BusinessException("药品批次 " + d.getBatchNo() + " 库存不足"); - } - } - - // 扣减库存(FIFO: 先过期先出) - for (var d : details) { - int remaining = d.getQuantity(); - var stocks = stockMapper.selectList(new LambdaQueryWrapper() + var qw = new LambdaQueryWrapper() .eq(Stock::getMedicineId, d.getMedicineId()) .eq(Stock::getWarehouseId, record.getWarehouseId()) - .eq(Stock::getBatchNo, d.getBatchNo()) - .orderByAsc(Stock::getExpiryDate)); + .gt(Stock::getQuantity, 0); + if (d.getBatchNo() != null && !d.getBatchNo().isBlank()) { + qw.eq(Stock::getBatchNo, d.getBatchNo()); + } + qw.orderByAsc(Stock::getExpiryDate); + var stocks = stockMapper.selectList(qw); + + int remaining = d.getQuantity(); + int available = stocks.stream().mapToInt(Stock::getQuantity).sum(); + if (available < remaining) { + throw new BusinessException("药品库存不足,需要" + remaining + ",可用" + available); + } for (Stock s : stocks) { if (remaining <= 0) break; int deduct = Math.min(s.getQuantity(), remaining); diff --git a/frontend/src/views/stockIn/StockInForm.vue b/frontend/src/views/stockIn/StockInForm.vue index d882036..696d639 100644 --- a/frontend/src/views/stockIn/StockInForm.vue +++ b/frontend/src/views/stockIn/StockInForm.vue @@ -32,7 +32,8 @@ async function handleSubmit() { if (!form.supplierId) { ElMessage.warning('请选择供应商'); return } if (!details.value.length) { ElMessage.warning('请添加药品明细'); return } updateTotal() - await createStockIn({ ...form, details: details.value, totalAmount: totalAmount.value }) + const submitDetails = details.value.map(d => ({ ...d, totalPrice: d.costPrice * d.quantity })) + await createStockIn({ ...form, details: submitDetails, totalAmount: totalAmount.value }) ElMessage.success('入库单已创建,等待审核') router.push('/stock-in/list') } diff --git a/frontend/src/views/stockOut/StockOutForm.vue b/frontend/src/views/stockOut/StockOutForm.vue index b6e1669..3016b59 100644 --- a/frontend/src/views/stockOut/StockOutForm.vue +++ b/frontend/src/views/stockOut/StockOutForm.vue @@ -42,7 +42,9 @@ async function handleSubmit() { if (!form.customerId) { ElMessage.warning('请选择客户'); return } if (!details.value.length) { ElMessage.warning('请添加药品明细'); return } updateTotal() - await createStockOut({ ...form, details: details.value, totalAmount: totalAmount.value }) + // 补齐每条明细的 totalPrice + const submitDetails = details.value.map(d => ({ ...d, totalPrice: detailTotal(d) })) + await createStockOut({ ...form, details: submitDetails, totalAmount: totalAmount.value }) ElMessage.success('出库单已创建,等待审核') router.push('/stock-out/list') }