添加库存数量大于零的查询条件
在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字段,确保后端接收到完整的金额信息
This commit is contained in:
@@ -29,6 +29,7 @@ public class StockController {
|
||||
@RequestParam(required = false) String medicineName,
|
||||
@RequestParam(required = false) String batchNo) {
|
||||
var wrapper = new LambdaQueryWrapper<Stock>()
|
||||
.gt(Stock::getQuantity, 0)
|
||||
.like(StringUtils.hasText(batchNo), Stock::getBatchNo, batchNo)
|
||||
.orderByAsc(Stock::getExpiryDate);
|
||||
// 按药品名搜索需先查 medicine 表
|
||||
@@ -48,6 +49,7 @@ public class StockController {
|
||||
public Result<Page<Stock>> warning(@RequestParam(defaultValue = "1") Integer page,
|
||||
@RequestParam(defaultValue = "10") Integer size) {
|
||||
var wrapper = new LambdaQueryWrapper<Stock>()
|
||||
.gt(Stock::getQuantity, 0)
|
||||
.gt(Stock::getExpiryDate, java.time.LocalDate.now())
|
||||
.orderByAsc(Stock::getExpiryDate);
|
||||
Page<Stock> result = stockService.page(new Page<>(page, size), wrapper);
|
||||
|
||||
@@ -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<StockInRecord> recordService;
|
||||
private final IService<StockInDetail> detailService;
|
||||
private final IService<Supplier> supplierService;
|
||||
private final IService<Medicine> medicineService;
|
||||
private final StockInService stockInService;
|
||||
|
||||
@GetMapping
|
||||
@@ -39,8 +42,15 @@ public class StockInController {
|
||||
public Result<StockInRecord> getById(@PathVariable Long id) {
|
||||
StockInRecord record = recordService.getById(id);
|
||||
if (record != null) {
|
||||
record.setDetails(detailService.list(
|
||||
new LambdaQueryWrapper<StockInDetail>().eq(StockInDetail::getRecordId, id)));
|
||||
var details = detailService.list(
|
||||
new LambdaQueryWrapper<StockInDetail>().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());
|
||||
}
|
||||
|
||||
@@ -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<StockOutRecord> recordService;
|
||||
private final IService<StockOutDetail> detailService;
|
||||
private final IService<Customer> customerService;
|
||||
private final IService<Medicine> medicineService;
|
||||
private final StockOutService stockOutService;
|
||||
|
||||
@GetMapping
|
||||
@@ -39,8 +42,16 @@ public class StockOutController {
|
||||
public Result<StockOutRecord> getById(@PathVariable Long id) {
|
||||
StockOutRecord record = recordService.getById(id);
|
||||
if (record != null) {
|
||||
record.setDetails(detailService.list(
|
||||
new LambdaQueryWrapper<StockOutDetail>().eq(StockOutDetail::getRecordId, id)));
|
||||
var details = detailService.list(
|
||||
new LambdaQueryWrapper<StockOutDetail>().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());
|
||||
|
||||
@@ -38,6 +38,7 @@ public class DashboardServiceImpl implements DashboardService {
|
||||
public Page<Stock> getExpiryWarning(Page<Stock> page) {
|
||||
Page<Stock> result = stockMapper.selectPage(page,
|
||||
new LambdaQueryWrapper<Stock>()
|
||||
.gt(Stock::getQuantity, 0)
|
||||
.le(Stock::getExpiryDate, LocalDate.now().plusDays(90))
|
||||
.orderByAsc(Stock::getExpiryDate));
|
||||
populateStockNames(result.getRecords());
|
||||
|
||||
@@ -64,26 +64,23 @@ public class StockOutServiceImpl implements StockOutService {
|
||||
var details = detailMapper.selectList(
|
||||
new LambdaQueryWrapper<StockOutDetail>().eq(StockOutDetail::getRecordId, id));
|
||||
|
||||
// 检查库存是否充足
|
||||
// 检查库存是否充足 + 扣减库存(FIFO: 先过期先出)
|
||||
for (var d : details) {
|
||||
int totalStock = stockMapper.selectList(new LambdaQueryWrapper<Stock>()
|
||||
var qw = new LambdaQueryWrapper<Stock>()
|
||||
.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() + " 库存不足");
|
||||
}
|
||||
.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);
|
||||
|
||||
// 扣减库存(FIFO: 先过期先出)
|
||||
for (var d : details) {
|
||||
int remaining = d.getQuantity();
|
||||
var stocks = stockMapper.selectList(new LambdaQueryWrapper<Stock>()
|
||||
.eq(Stock::getMedicineId, d.getMedicineId())
|
||||
.eq(Stock::getWarehouseId, record.getWarehouseId())
|
||||
.eq(Stock::getBatchNo, d.getBatchNo())
|
||||
.orderByAsc(Stock::getExpiryDate));
|
||||
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);
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user