|
|
@@ -0,0 +1,473 @@
|
|
|
+package com.etotem.cfc.service;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.etotem.cfc.common.Result;
|
|
|
+import com.etotem.cfc.entity.*;
|
|
|
+import com.etotem.cfc.mapper.*;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+@Service
|
|
|
+public class InventoryService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private InventoryTransactionMapper transactionMapper;
|
|
|
+ @Resource
|
|
|
+ private InventoryInboundOrderMapper inboundOrderMapper;
|
|
|
+ @Resource
|
|
|
+ private InventoryInboundItemMapper inboundItemMapper;
|
|
|
+ @Resource
|
|
|
+ private InventoryCountingRecordMapper countingRecordMapper;
|
|
|
+ @Resource
|
|
|
+ private ProductBundleItemMapper bundleItemMapper;
|
|
|
+ @Resource
|
|
|
+ private ProductMapper productMapper;
|
|
|
+ @Resource
|
|
|
+ private ProductSkuMapper productSkuMapper;
|
|
|
+
|
|
|
+ // ========== 入库单 ==========
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public Result<Map<String, Object>> createInboundOrder(Long supplierId, String supplierName,
|
|
|
+ List<Map<String, Object>> items, Long operatorId, String remark) {
|
|
|
+ if (items == null || items.isEmpty()) {
|
|
|
+ return Result.error("入库明细不能为空");
|
|
|
+ }
|
|
|
+ String orderNo = "IN-" + new SimpleDateFormat("yyyyMMdd").format(new Date())
|
|
|
+ + "-" + System.currentTimeMillis() % 10000;
|
|
|
+
|
|
|
+ InventoryInboundOrder order = new InventoryInboundOrder();
|
|
|
+ order.setOrderNo(orderNo);
|
|
|
+ order.setSupplierId(supplierId);
|
|
|
+ order.setSupplierName(supplierName);
|
|
|
+ order.setStatus("pending");
|
|
|
+ order.setTotalItems(items.size());
|
|
|
+ order.setOperatorId(operatorId);
|
|
|
+ order.setRemark(remark);
|
|
|
+ order.setCreatedAt(new Date());
|
|
|
+ order.setUpdatedAt(new Date());
|
|
|
+ inboundOrderMapper.insert(order);
|
|
|
+
|
|
|
+ for (Map<String, Object> item : items) {
|
|
|
+ InventoryInboundItem inboundItem = new InventoryInboundItem();
|
|
|
+ inboundItem.setInboundOrderId(order.getId());
|
|
|
+ inboundItem.setProductId(Long.valueOf(item.get("productId").toString()));
|
|
|
+ if (item.get("skuId") != null) {
|
|
|
+ inboundItem.setSkuId(Long.valueOf(item.get("skuId").toString()));
|
|
|
+ }
|
|
|
+ inboundItem.setQuantity(Integer.valueOf(item.get("quantity").toString()));
|
|
|
+ inboundItem.setCreatedAt(new Date());
|
|
|
+ inboundItemMapper.insert(inboundItem);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("id", order.getId());
|
|
|
+ result.put("orderNo", orderNo);
|
|
|
+ return Result.success(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public Result<String> confirmInbound(Long orderId, Long operatorId) {
|
|
|
+ InventoryInboundOrder order = inboundOrderMapper.selectById(orderId);
|
|
|
+ if (order == null) return Result.error("入库单不存在");
|
|
|
+ if (!"pending".equals(order.getStatus())) return Result.error("入库单状态异常");
|
|
|
+
|
|
|
+ List<InventoryInboundItem> items = inboundItemMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<InventoryInboundItem>().eq(InventoryInboundItem::getInboundOrderId, orderId));
|
|
|
+
|
|
|
+ for (InventoryInboundItem item : items) {
|
|
|
+ Product product = productMapper.selectById(item.getProductId());
|
|
|
+ if (product == null) continue;
|
|
|
+
|
|
|
+ int beforeStock = product.getStock() != null ? product.getStock() : 0;
|
|
|
+ int afterStock = beforeStock + item.getQuantity();
|
|
|
+
|
|
|
+ if (item.getSkuId() != null) {
|
|
|
+ ProductSku sku = productSkuMapper.selectById(item.getSkuId());
|
|
|
+ if (sku != null) {
|
|
|
+ int skuBefore = sku.getStock() != null ? sku.getStock() : 0;
|
|
|
+ sku.setStock(skuBefore + item.getQuantity());
|
|
|
+ sku.setUpdateTime(new Date());
|
|
|
+ productSkuMapper.updateById(sku);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ product.setStock(afterStock);
|
|
|
+ product.setUpdatedAt(new Date());
|
|
|
+ productMapper.updateById(product);
|
|
|
+ }
|
|
|
+
|
|
|
+ InventoryTransaction tx = new InventoryTransaction();
|
|
|
+ tx.setProductId(item.getProductId());
|
|
|
+ tx.setSkuId(item.getSkuId());
|
|
|
+ tx.setType("inbound");
|
|
|
+ tx.setDirection(1);
|
|
|
+ tx.setQuantity(item.getQuantity());
|
|
|
+ tx.setBeforeStock(beforeStock);
|
|
|
+ tx.setAfterStock(afterStock);
|
|
|
+ tx.setReferenceType("inbound_order");
|
|
|
+ tx.setReferenceId(orderId);
|
|
|
+ tx.setOperatorId(operatorId);
|
|
|
+ tx.setCreatedAt(new Date());
|
|
|
+ transactionMapper.insert(tx);
|
|
|
+ }
|
|
|
+
|
|
|
+ order.setStatus("completed");
|
|
|
+ order.setUpdatedAt(new Date());
|
|
|
+ inboundOrderMapper.updateById(order);
|
|
|
+ return Result.success("入库成功");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public Result<String> cancelInbound(Long orderId) {
|
|
|
+ InventoryInboundOrder order = inboundOrderMapper.selectById(orderId);
|
|
|
+ if (order == null) return Result.error("入库单不存在");
|
|
|
+ if (!"pending".equals(order.getStatus())) return Result.error("仅待确认入库单可取消");
|
|
|
+ order.setStatus("cancelled");
|
|
|
+ order.setUpdatedAt(new Date());
|
|
|
+ inboundOrderMapper.updateById(order);
|
|
|
+ return Result.success("已取消");
|
|
|
+ }
|
|
|
+
|
|
|
+ public Result<Map<String, Object>> listInboundOrders(int page, int size, String status, String keyword) {
|
|
|
+ Page<InventoryInboundOrder> pageParam = new Page<>(page, size);
|
|
|
+ LambdaQueryWrapper<InventoryInboundOrder> wrapper = new LambdaQueryWrapper<InventoryInboundOrder>()
|
|
|
+ .orderByDesc(InventoryInboundOrder::getCreatedAt);
|
|
|
+ if (status != null && !status.isEmpty()) {
|
|
|
+ wrapper.eq(InventoryInboundOrder::getStatus, status);
|
|
|
+ }
|
|
|
+ Page<InventoryInboundOrder> result = inboundOrderMapper.selectPage(pageParam, wrapper);
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("records", result.getRecords());
|
|
|
+ data.put("total", result.getTotal());
|
|
|
+ data.put("page", result.getCurrent());
|
|
|
+ data.put("size", result.getSize());
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ public Result<Map<String, Object>> detailInboundOrder(Long id) {
|
|
|
+ InventoryInboundOrder order = inboundOrderMapper.selectById(id);
|
|
|
+ if (order == null) return Result.error("入库单不存在");
|
|
|
+ List<InventoryInboundItem> items = inboundItemMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<InventoryInboundItem>().eq(InventoryInboundItem::getInboundOrderId, id));
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("order", order);
|
|
|
+ data.put("items", items);
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 库存流水 ==========
|
|
|
+
|
|
|
+ public Result<Map<String, Object>> listTransactions(int page, int size, Long productId, Long skuId,
|
|
|
+ String type, String startDate, String endDate) {
|
|
|
+ Page<InventoryTransaction> pageParam = new Page<>(page, size);
|
|
|
+ LambdaQueryWrapper<InventoryTransaction> wrapper = new LambdaQueryWrapper<InventoryTransaction>()
|
|
|
+ .orderByDesc(InventoryTransaction::getCreatedAt);
|
|
|
+ if (productId != null) wrapper.eq(InventoryTransaction::getProductId, productId);
|
|
|
+ if (skuId != null) wrapper.eq(InventoryTransaction::getSkuId, skuId);
|
|
|
+ if (type != null && !type.isEmpty()) wrapper.eq(InventoryTransaction::getType, type);
|
|
|
+ if (startDate != null && !startDate.isEmpty()) {
|
|
|
+ try {
|
|
|
+ wrapper.ge(InventoryTransaction::getCreatedAt, new SimpleDateFormat("yyyy-MM-dd").parse(startDate));
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+ }
|
|
|
+ if (endDate != null && !endDate.isEmpty()) {
|
|
|
+ try {
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.setTime(new SimpleDateFormat("yyyy-MM-dd").parse(endDate));
|
|
|
+ cal.add(Calendar.DAY_OF_MONTH, 1);
|
|
|
+ wrapper.lt(InventoryTransaction::getCreatedAt, cal.getTime());
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+ }
|
|
|
+ Page<InventoryTransaction> result = transactionMapper.selectPage(pageParam, wrapper);
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("records", result.getRecords());
|
|
|
+ data.put("total", result.getTotal());
|
|
|
+ data.put("page", result.getCurrent());
|
|
|
+ data.put("size", result.getSize());
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 记录出库 ==========
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public void recordOutbound(Long productId, Long skuId, Integer quantity, String referenceType,
|
|
|
+ Long referenceId, Long operatorId, String remark) {
|
|
|
+ Product product = productMapper.selectById(productId);
|
|
|
+ if (product == null) return;
|
|
|
+
|
|
|
+ int beforeStock = product.getStock() != null ? product.getStock() : 0;
|
|
|
+ int afterStock = beforeStock - quantity;
|
|
|
+
|
|
|
+ InventoryTransaction tx = new InventoryTransaction();
|
|
|
+ tx.setProductId(productId);
|
|
|
+ tx.setSkuId(skuId);
|
|
|
+ tx.setType("outbound");
|
|
|
+ tx.setDirection(-1);
|
|
|
+ tx.setQuantity(quantity);
|
|
|
+ tx.setBeforeStock(beforeStock);
|
|
|
+ tx.setAfterStock(afterStock);
|
|
|
+ tx.setReferenceType(referenceType);
|
|
|
+ tx.setReferenceId(referenceId);
|
|
|
+ tx.setOperatorId(operatorId);
|
|
|
+ tx.setRemark(remark);
|
|
|
+ tx.setCreatedAt(new Date());
|
|
|
+ transactionMapper.insert(tx);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public void recordRefundInbound(Long productId, Long skuId, Integer quantity, String referenceType,
|
|
|
+ Long referenceId, Long operatorId, String remark) {
|
|
|
+ Product product = productMapper.selectById(productId);
|
|
|
+ if (product == null) return;
|
|
|
+
|
|
|
+ int beforeStock = product.getStock() != null ? product.getStock() : 0;
|
|
|
+ int afterStock = beforeStock + quantity;
|
|
|
+
|
|
|
+ InventoryTransaction tx = new InventoryTransaction();
|
|
|
+ tx.setProductId(productId);
|
|
|
+ tx.setSkuId(skuId);
|
|
|
+ tx.setType("refund");
|
|
|
+ tx.setDirection(1);
|
|
|
+ tx.setQuantity(quantity);
|
|
|
+ tx.setBeforeStock(beforeStock);
|
|
|
+ tx.setAfterStock(afterStock);
|
|
|
+ tx.setReferenceType(referenceType);
|
|
|
+ tx.setReferenceId(referenceId);
|
|
|
+ tx.setOperatorId(operatorId);
|
|
|
+ tx.setRemark(remark);
|
|
|
+ tx.setCreatedAt(new Date());
|
|
|
+ transactionMapper.insert(tx);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 盘点 ==========
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public Result<String> createCounting(Long productId, Long skuId, Integer actualStock, Long operatorId, String remark) {
|
|
|
+ Product product = productMapper.selectById(productId);
|
|
|
+ if (product == null) return Result.error("商品不存在");
|
|
|
+
|
|
|
+ int expectedStock = 0;
|
|
|
+ if (skuId != null) {
|
|
|
+ ProductSku sku = productSkuMapper.selectById(skuId);
|
|
|
+ if (sku != null) expectedStock = sku.getStock() != null ? sku.getStock() : 0;
|
|
|
+ } else {
|
|
|
+ expectedStock = product.getStock() != null ? product.getStock() : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ InventoryCountingRecord record = new InventoryCountingRecord();
|
|
|
+ record.setProductId(productId);
|
|
|
+ record.setSkuId(skuId);
|
|
|
+ record.setExpectedStock(expectedStock);
|
|
|
+ record.setActualStock(actualStock);
|
|
|
+ record.setDifference(actualStock - expectedStock);
|
|
|
+ record.setStatus("pending");
|
|
|
+ record.setOperatorId(operatorId);
|
|
|
+ record.setRemark(remark);
|
|
|
+ record.setCreatedAt(new Date());
|
|
|
+ countingRecordMapper.insert(record);
|
|
|
+ return Result.success("盘点记录已创建");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public Result<String> confirmCounting(Long recordId, Long operatorId) {
|
|
|
+ InventoryCountingRecord record = countingRecordMapper.selectById(recordId);
|
|
|
+ if (record == null) return Result.error("盘点记录不存在");
|
|
|
+ if (!"pending".equals(record.getStatus())) return Result.error("盘点记录已确认");
|
|
|
+
|
|
|
+ if (record.getDifference() != 0) {
|
|
|
+ if (record.getSkuId() != null) {
|
|
|
+ ProductSku sku = productSkuMapper.selectById(record.getSkuId());
|
|
|
+ if (sku != null) {
|
|
|
+ sku.setStock(record.getActualStock());
|
|
|
+ sku.setUpdateTime(new Date());
|
|
|
+ productSkuMapper.updateById(sku);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Product product = productMapper.selectById(record.getProductId());
|
|
|
+ if (product != null) {
|
|
|
+ product.setStock(record.getActualStock());
|
|
|
+ product.setUpdatedAt(new Date());
|
|
|
+ productMapper.updateById(product);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ InventoryTransaction tx = new InventoryTransaction();
|
|
|
+ tx.setProductId(record.getProductId());
|
|
|
+ tx.setSkuId(record.getSkuId());
|
|
|
+ tx.setType("adjustment");
|
|
|
+ tx.setDirection(record.getDifference() > 0 ? 1 : -1);
|
|
|
+ tx.setQuantity(Math.abs(record.getDifference()));
|
|
|
+ tx.setBeforeStock(record.getExpectedStock());
|
|
|
+ tx.setAfterStock(record.getActualStock());
|
|
|
+ tx.setReferenceType("counting");
|
|
|
+ tx.setReferenceId(recordId);
|
|
|
+ tx.setOperatorId(operatorId);
|
|
|
+ tx.setRemark("盘点调账: " + record.getRemark());
|
|
|
+ tx.setCreatedAt(new Date());
|
|
|
+ transactionMapper.insert(tx);
|
|
|
+ }
|
|
|
+
|
|
|
+ record.setStatus("confirmed");
|
|
|
+ countingRecordMapper.updateById(record);
|
|
|
+ return Result.success("盘点已确认");
|
|
|
+ }
|
|
|
+
|
|
|
+ public Result<Map<String, Object>> listCountingRecords(int page, int size, String status) {
|
|
|
+ Page<InventoryCountingRecord> pageParam = new Page<>(page, size);
|
|
|
+ LambdaQueryWrapper<InventoryCountingRecord> wrapper = new LambdaQueryWrapper<InventoryCountingRecord>()
|
|
|
+ .orderByDesc(InventoryCountingRecord::getCreatedAt);
|
|
|
+ if (status != null && !status.isEmpty()) {
|
|
|
+ wrapper.eq(InventoryCountingRecord::getStatus, status);
|
|
|
+ }
|
|
|
+ Page<InventoryCountingRecord> result = countingRecordMapper.selectPage(pageParam, wrapper);
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("records", result.getRecords());
|
|
|
+ data.put("total", result.getTotal());
|
|
|
+ data.put("page", result.getCurrent());
|
|
|
+ data.put("size", result.getSize());
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 库存预警 ==========
|
|
|
+
|
|
|
+ public Result<List<Map<String, Object>>> listStockAlerts() {
|
|
|
+ List<Map<String, Object>> alerts = new ArrayList<>();
|
|
|
+
|
|
|
+ List<Product> products = productMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<Product>()
|
|
|
+ .eq(Product::getProductType, "physical")
|
|
|
+ .apply("min_stock_alert IS NOT NULL AND stock < min_stock_alert"));
|
|
|
+ for (Product p : products) {
|
|
|
+ Map<String, Object> alert = new HashMap<>();
|
|
|
+ alert.put("productId", p.getId());
|
|
|
+ alert.put("productName", p.getName());
|
|
|
+ alert.put("skuId", null);
|
|
|
+ alert.put("specs", null);
|
|
|
+ alert.put("currentStock", p.getStock());
|
|
|
+ alert.put("minStockAlert", p.getMinStockAlert());
|
|
|
+ alerts.add(alert);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<ProductSku> skus = productSkuMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProductSku>()
|
|
|
+ .apply("min_stock_alert IS NOT NULL AND stock < min_stock_alert AND enabled = 1"));
|
|
|
+ for (ProductSku sku : skus) {
|
|
|
+ Product p = productMapper.selectById(sku.getProductId());
|
|
|
+ Map<String, Object> alert = new HashMap<>();
|
|
|
+ alert.put("productId", sku.getProductId());
|
|
|
+ alert.put("productName", p != null ? p.getName() : "未知");
|
|
|
+ alert.put("skuId", sku.getId());
|
|
|
+ alert.put("specs", sku.getSpecs());
|
|
|
+ alert.put("currentStock", sku.getStock());
|
|
|
+ alert.put("minStockAlert", sku.getMinStockAlert());
|
|
|
+ alerts.add(alert);
|
|
|
+ }
|
|
|
+
|
|
|
+ return Result.success(alerts);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 商品库存详情 ==========
|
|
|
+
|
|
|
+ public Result<Map<String, Object>> getProductStock(Long productId) {
|
|
|
+ Product product = productMapper.selectById(productId);
|
|
|
+ if (product == null) return Result.error("商品不存在");
|
|
|
+
|
|
|
+ Map<String, Object> data = new HashMap<>();
|
|
|
+ data.put("productId", product.getId());
|
|
|
+ data.put("productName", product.getName());
|
|
|
+ data.put("productType", product.getProductType());
|
|
|
+ data.put("stock", product.getStock());
|
|
|
+ data.put("minStockAlert", product.getMinStockAlert());
|
|
|
+
|
|
|
+ List<ProductSku> skus = productSkuMapper.findByProductIdAll(productId);
|
|
|
+ data.put("skus", skus);
|
|
|
+
|
|
|
+ List<InventoryTransaction> recentTx = transactionMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<InventoryTransaction>()
|
|
|
+ .eq(InventoryTransaction::getProductId, productId)
|
|
|
+ .orderByDesc(InventoryTransaction::getCreatedAt)
|
|
|
+ .last("LIMIT 10"));
|
|
|
+ data.put("recentTransactions", recentTx);
|
|
|
+
|
|
|
+ if ("bundle".equals(product.getProductType())) {
|
|
|
+ List<ProductBundleItem> bundleItems = bundleItemMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProductBundleItem>()
|
|
|
+ .eq(ProductBundleItem::getBundleProductId, productId));
|
|
|
+ List<Map<String, Object>> childProducts = new ArrayList<>();
|
|
|
+ for (ProductBundleItem bi : bundleItems) {
|
|
|
+ Product child = productMapper.selectById(bi.getChildProductId());
|
|
|
+ if (child != null) {
|
|
|
+ Map<String, Object> cp = new HashMap<>();
|
|
|
+ cp.put("productId", child.getId());
|
|
|
+ cp.put("productName", child.getName());
|
|
|
+ cp.put("stock", child.getStock());
|
|
|
+ cp.put("quantity", bi.getQuantity());
|
|
|
+ cp.put("skuId", bi.getChildSkuId());
|
|
|
+ childProducts.add(cp);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ data.put("bundleItems", childProducts);
|
|
|
+ }
|
|
|
+
|
|
|
+ return Result.success(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 套餐商品组成 ==========
|
|
|
+
|
|
|
+ @Transactional
|
|
|
+ public Result<String> saveBundleItems(Long bundleProductId, List<Map<String, Object>> items) {
|
|
|
+ bundleItemMapper.delete(new LambdaQueryWrapper<ProductBundleItem>()
|
|
|
+ .eq(ProductBundleItem::getBundleProductId, bundleProductId));
|
|
|
+
|
|
|
+ for (Map<String, Object> item : items) {
|
|
|
+ ProductBundleItem bi = new ProductBundleItem();
|
|
|
+ bi.setBundleProductId(bundleProductId);
|
|
|
+ bi.setChildProductId(Long.valueOf(item.get("childProductId").toString()));
|
|
|
+ if (item.get("childSkuId") != null) {
|
|
|
+ bi.setChildSkuId(Long.valueOf(item.get("childSkuId").toString()));
|
|
|
+ }
|
|
|
+ bi.setQuantity(item.get("quantity") != null ? Integer.valueOf(item.get("quantity").toString()) : 1);
|
|
|
+ bi.setCreatedAt(new Date());
|
|
|
+ bundleItemMapper.insert(bi);
|
|
|
+ }
|
|
|
+ return Result.success("套餐商品配置已保存");
|
|
|
+ }
|
|
|
+
|
|
|
+ public Result<List<ProductBundleItem>> listBundleItems(Long bundleProductId) {
|
|
|
+ List<ProductBundleItem> items = bundleItemMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProductBundleItem>()
|
|
|
+ .eq(ProductBundleItem::getBundleProductId, bundleProductId));
|
|
|
+ return Result.success(items);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 套餐库存检查 ==========
|
|
|
+
|
|
|
+ public Result<String> checkBundleStock(Long bundleProductId, Integer quantity) {
|
|
|
+ List<ProductBundleItem> items = bundleItemMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ProductBundleItem>()
|
|
|
+ .eq(ProductBundleItem::getBundleProductId, bundleProductId));
|
|
|
+ if (items.isEmpty()) return Result.error("套餐未配置子商品");
|
|
|
+
|
|
|
+ for (ProductBundleItem bi : items) {
|
|
|
+ int need = bi.getQuantity() * quantity;
|
|
|
+ if (bi.getChildSkuId() != null) {
|
|
|
+ ProductSku sku = productSkuMapper.selectById(bi.getChildSkuId());
|
|
|
+ if (sku == null || sku.getStock() == null || sku.getStock() < need) {
|
|
|
+ Product p = productMapper.selectById(bi.getChildProductId());
|
|
|
+ return Result.error("子商品库存不足: " + (p != null ? p.getName() : "未知"));
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Product p = productMapper.selectById(bi.getChildProductId());
|
|
|
+ if (p == null || p.getStock() == null || p.getStock() < need) {
|
|
|
+ return Result.error("子商品库存不足: " + (p != null ? p.getName() : "未知"));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return Result.success("库存充足");
|
|
|
+ }
|
|
|
+}
|