From 6c86cc6a0dd87abeaf777510d02f0db9b3d01b63 Mon Sep 17 00:00:00 2001 From: wyn <1074145239@qq.com> Date: Thu, 23 Jul 2026 13:32:10 +0800 Subject: [PATCH] =?UTF-8?q?APP=E8=AE=A2=E5=8D=95=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=9C=AA=E6=94=AF=E4=BB=98=E5=8F=96=E6=B6=88?= =?UTF-8?q?=E7=9A=84=E8=AE=A2=E5=8D=95=20=20=20=E5=BA=93=E5=AD=98=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E4=B8=80=E4=BA=9B=E9=97=AE=E9=A2=98=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InventoryManagementController.java | 6 + .../impl/InventoryManagementServiceImpl.java | 204 +++++++++++++----- .../mq/Consumer/OrderCancelConsumer.java | 4 +- 3 files changed, 153 insertions(+), 61 deletions(-) diff --git a/src/main/java/com/peanut/modules/master/controller/InventoryManagementController.java b/src/main/java/com/peanut/modules/master/controller/InventoryManagementController.java index b573997..2be88bd 100644 --- a/src/main/java/com/peanut/modules/master/controller/InventoryManagementController.java +++ b/src/main/java/com/peanut/modules/master/controller/InventoryManagementController.java @@ -161,6 +161,9 @@ public class InventoryManagementController { titleRow.createCell(5).setCellValue("订单编号"); titleRow.createCell(6).setCellValue("出库时间"); titleRow.createCell(7).setCellValue("come"); + titleRow.createCell(8).setCellValue("套书名称"); + titleRow.createCell(9).setCellValue("套书商品ID"); + titleRow.createCell(10).setCellValue("出库记录ID"); int rowIndex = 1; for (Map map : maps) { Row row = sheet.createRow(rowIndex++); @@ -172,6 +175,9 @@ public class InventoryManagementController { row.createCell(5).setCellValue(cellStr(map.get("orderSn"))); row.createCell(6).setCellValue(cellStr(map.get("createTime"))); row.createCell(7).setCellValue(cellStr(map.get("come"))); + row.createCell(8).setCellValue(cellStr(map.get("setProductName"))); + row.createCell(9).setCellValue(cellStr(map.get("setProductId"))); + row.createCell(10).setCellValue(cellStr(map.get("stockRecordId"))); } } diff --git a/src/main/java/com/peanut/modules/master/service/impl/InventoryManagementServiceImpl.java b/src/main/java/com/peanut/modules/master/service/impl/InventoryManagementServiceImpl.java index b9f822b..6cdd1ca 100644 --- a/src/main/java/com/peanut/modules/master/service/impl/InventoryManagementServiceImpl.java +++ b/src/main/java/com/peanut/modules/master/service/impl/InventoryManagementServiceImpl.java @@ -44,7 +44,6 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -231,8 +230,10 @@ public class InventoryManagementServiceImpl extends ServiceImpl splits = deductPurchaseStockFifo(dto.getProductId(), dto.getMerchantId(), dto.getQuantity()); for (CostSplit split : splits) { + BigDecimal saleUnitCost = (dto.getUnitCost() != null && dto.getUnitCost().compareTo(BigDecimal.ZERO) > 0) + ? dto.getUnitCost() : split.unitCost; insertSaleRecord(dto.getProductId(), dto.getMerchantId(), outboundType, - -split.quantity, split.unitCost, dto.getRemark(), null, null, null, split.purchaseRecordId); + -split.quantity, saleUnitCost, dto.getRemark(), null, null, null, split.purchaseRecordId); } adjustShopProductStock(dto.getProductId(), -dto.getQuantity()); return; @@ -244,12 +245,13 @@ public class InventoryManagementServiceImpl extends ServiceImpl splits = deductPurchaseStockFifo(dto.getProductId(), sourceMerchantId, dto.getQuantity()); for (CostSplit split : splits) { - insertRecord(dto.getProductId(), sourceMerchantId, saleTargetSource, null, + insertRecord(dto.getProductId(), sourceMerchantId, saleTargetSource, saleTargetSource, InvStockRecord.BIZ_TYPE_SALE, -split.quantity, split.unitCost, dto.getRemark(), null, null, null, split.purchaseRecordId, null); } @@ -1166,7 +1168,6 @@ public class InventoryManagementServiceImpl extends ServiceImpl(); } SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - Map setPurchaseQtyCache = new HashMap<>(); Map> bindCache = new HashMap<>(); List> result = new ArrayList<>(); for (Map item : list) { @@ -1175,10 +1176,11 @@ public class InventoryManagementServiceImpl extends ServiceImpl> expandSaleRecordForExport(Map item, SimpleDateFormat sdf, - Map setPurchaseQtyCache, Map> bindCache) { Integer productId = toIntegerSafe(item.get("productId")); - Long purchaseRecordId = toLongSafe(item.get("purchaseRecordId")); - if (productId != null && purchaseRecordId != null && isSetBookProductByProductId(productId)) { - List binds = bindCache.computeIfAbsent(purchaseRecordId, this::listSetBookComponentOutbounds); - if (!binds.isEmpty()) { - int saleQty = Math.abs(toIntValue(item.get("quantity"))); - int setPurchaseQty = setPurchaseQtyCache.computeIfAbsent(purchaseRecordId, this::resolveSetPurchaseQuantity); - return buildSetBookExpandedSaleRows(item, binds, saleQty, setPurchaseQty, sdf); - } + if (productId == null || !isSetBookProductByProductId(productId)) { + return Collections.singletonList(buildSaleExportRow(item, sdf)); } - return Collections.singletonList(buildSaleExportRow(item, sdf)); + List components = listSetBookComponentProducts(productId); + if (components.isEmpty()) { + return Collections.singletonList(buildSaleExportRow(item, sdf)); + } + int saleQty = Math.abs(toIntValue(item.get("quantity"))); + Integer merchantId = toIntegerSafe(item.get("merchantId")); + // 套书售出流水的 purchase_record_id = 套书采购流水 id,对应单品出库的 set_purchase_record_id + Long setPurchaseRecordId = toLongSafe(item.get("purchaseRecordId")); + Map costByProductId = Collections.emptyMap(); + if (setPurchaseRecordId != null) { + List binds = bindCache.computeIfAbsent(setPurchaseRecordId, this::listSetBookComponentOutbounds); + costByProductId = resolveComponentCostFromBinds(binds); + } + String setProductName = item.get("productName") == null ? "" : item.get("productName").toString(); + return buildSetBookExpandedSaleRows(item, components, saleQty, merchantId, costByProductId, + productId, setProductName, sdf); } - private List> buildSetBookExpandedSaleRows(Map parent, - List binds, - int saleQty, int setPurchaseQty, - SimpleDateFormat sdf) { - Map productNames = loadProductNameMap( - binds.stream().map(InvStockRecord::getProductId).filter(Objects::nonNull).collect(Collectors.toSet())); - List> rows = new ArrayList<>(); - for (InvStockRecord bind : binds) { - int bindQty = bind.getQuantity() == null ? 0 : Math.abs(bind.getQuantity()); - int exportQty = setPurchaseQty > 0 - ? (int) Math.round(bindQty * (double) saleQty / setPurchaseQty) - : saleQty; - if (exportQty <= 0) { + /** 套书关联图书 → 只取「只绑了 1 本书」的单品商品 */ + private List listSetBookComponentProducts(Integer setProductId) { + List bookIds = listBookIdsByProductId(setProductId); + if (bookIds.isEmpty()) { + return Collections.emptyList(); + } + List components = new ArrayList<>(); + Set seen = new HashSet<>(); + for (Integer bookId : bookIds) { + ShopProduct component = findComponentProductByBookId(bookId, setProductId); + if (component == null || component.getProductId() == null) { continue; } + // findComponentProductByBookId 已优先只绑 1 本书;再兜底确认 + if (listBookIdsByProductId(component.getProductId()).size() != 1) { + continue; + } + if (seen.add(component.getProductId())) { + components.add(component); + } + } + return components; + } + + /** + * 套书售出导出:按套内单品展开,数量=套书出库数量; + * 成本优先:单品 books 出库.purchase_record_id → 入库记录.unit_cost; + * 无 set_purchase_record_id / 无绑定时:inv_product_cost.cost_price。 + */ + private List> buildSetBookExpandedSaleRows(Map parent, + List components, + int saleQty, + Integer merchantId, + Map costByProductId, + Integer setProductId, + String setProductName, + SimpleDateFormat sdf) { + List> rows = new ArrayList<>(); + for (ShopProduct component : components) { + Integer componentId = component.getProductId(); + BigDecimal unitCost = costByProductId == null ? null : costByProductId.get(componentId); + if (unitCost == null) { + unitCost = lookupInvProductCostPrice(componentId, merchantId); + } Map row = new LinkedHashMap<>(); row.put("exportCategory", "sale"); - row.put("productId", bind.getProductId()); - row.put("productName", productNames.getOrDefault(bind.getProductId(), "")); + row.put("productId", componentId); + row.put("productName", component.getProductName() == null ? "" : component.getProductName()); row.put("outboundTypeLabel", parent.get("outboundTypeLabel")); - row.put("unitCost", bind.getUnitCost()); - row.put("quantity", exportQty); + row.put("unitCost", unitCost); + row.put("quantity", saleQty); row.put("orderSn", parent.get("orderSn") == null ? "" : parent.get("orderSn")); row.put("createTime", formatExportTime(parent.get("createTime"), sdf)); row.put("come", parent.get("come")); + row.put("setProductId", setProductId); + row.put("setProductName", setProductName == null ? "" : setProductName); + row.put("stockRecordId", parent.get("id")); rows.add(row); } if (rows.isEmpty()) { @@ -1261,15 +1306,76 @@ public class InventoryManagementServiceImpl extends ServiceImpl resolveComponentCostFromBinds(List binds) { + if (binds == null || binds.isEmpty()) { + return Collections.emptyMap(); } - InvStockRecord purchase = invStockRecordDao.selectById(setPurchaseRecordId); - if (purchase == null || purchase.getQuantity() == null) { - return 0; + Map costSum = new HashMap<>(); + Map qtySum = new HashMap<>(); + Map purchaseCostCache = new HashMap<>(); + for (InvStockRecord bind : binds) { + Integer componentId = bind.getProductId(); + if (componentId == null) { + continue; + } + int qty = bind.getQuantity() == null ? 0 : Math.abs(bind.getQuantity()); + if (qty <= 0) { + continue; + } + BigDecimal unitCost = resolveInboundUnitCost(bind.getPurchaseRecordId(), purchaseCostCache); + if (unitCost == null) { + unitCost = bind.getUnitCost(); + } + if (unitCost == null) { + continue; + } + costSum.merge(componentId, unitCost.multiply(BigDecimal.valueOf(qty)), BigDecimal::add); + qtySum.merge(componentId, qty, Integer::sum); } - return Math.abs(purchase.getQuantity()); + Map result = new HashMap<>(); + for (Map.Entry entry : costSum.entrySet()) { + int qty = qtySum.getOrDefault(entry.getKey(), 0); + if (qty <= 0) { + continue; + } + result.put(entry.getKey(), + entry.getValue().divide(BigDecimal.valueOf(qty), 2, java.math.RoundingMode.HALF_UP)); + } + return result; + } + + private BigDecimal resolveInboundUnitCost(Long purchaseRecordId, Map cache) { + if (purchaseRecordId == null) { + return null; + } + if (cache.containsKey(purchaseRecordId)) { + return cache.get(purchaseRecordId); + } + InvStockRecord purchase = invStockRecordDao.selectById(purchaseRecordId); + BigDecimal unitCost = null; + if (purchase != null && purchase.getBizType() != null + && purchase.getBizType() == InvStockRecord.BIZ_TYPE_PURCHASE) { + unitCost = purchase.getUnitCost(); + } + cache.put(purchaseRecordId, unitCost); + return unitCost; + } + + private BigDecimal lookupInvProductCostPrice(Integer productId, Integer merchantId) { + if (productId == null || merchantId == null) { + return null; + } + InvProductCost cost = invProductCostDao.selectOne( + new LambdaQueryWrapper() + .eq(InvProductCost::getProductId, productId) + .eq(InvProductCost::getMerchantId, merchantId) + .last("LIMIT 1") + ); + return cost == null ? null : cost.getCostPrice(); } private List listSetBookComponentOutbounds(Long setPurchaseRecordId) { @@ -1290,26 +1396,6 @@ public class InventoryManagementServiceImpl extends ServiceImpl 1; } - private Map loadProductNameMap(Set productIds) { - if (productIds == null || productIds.isEmpty()) { - return Collections.emptyMap(); - } - List products = shopProductDao.selectList( - new LambdaQueryWrapper() - .select(ShopProduct::getProductId, ShopProduct::getProductName) - .in(ShopProduct::getProductId, productIds) - ); - Map map = new HashMap<>(); - if (products != null) { - for (ShopProduct product : products) { - if (product.getProductId() != null) { - map.put(product.getProductId(), product.getProductName()); - } - } - } - return map; - } - private String formatExportTime(Object createTime, SimpleDateFormat sdf) { if (createTime instanceof Date) { return sdf.format((Date) createTime); diff --git a/src/main/java/com/peanut/modules/mq/Consumer/OrderCancelConsumer.java b/src/main/java/com/peanut/modules/mq/Consumer/OrderCancelConsumer.java index db14356..c5c9a61 100644 --- a/src/main/java/com/peanut/modules/mq/Consumer/OrderCancelConsumer.java +++ b/src/main/java/com/peanut/modules/mq/Consumer/OrderCancelConsumer.java @@ -61,11 +61,11 @@ public class OrderCancelConsumer { if (!Constants.ORDER_STATUS_TO_BE_PAID.equals(buyOrder.getOrderStatus())) { return; } - // TODO 上线全量后删除此测试账号限制 + /* // TODO 上线全量后删除此测试账号限制 if (!isTesterOrder(buyOrder)) { log.info("订单超时取消跳过:非测试账号, orderId={}, userId={}", buyOrder.getOrderId(), buyOrder.getUserId()); return; - } + }*/ buyOrder.setOrderStatus(Constants.ORDER_STATUS_OUT_OF_TIME); // 回滚优惠券(buy_order.coupon_id 存的是 coupon_history.id),保留订单 couponId 便于查单