APP订单列表显示未支付取消的订单 库存管理一些问题修改

This commit is contained in:
wyn
2026-07-23 13:32:10 +08:00
parent 84cdf95978
commit 6c86cc6a0d
3 changed files with 153 additions and 61 deletions

View File

@@ -161,6 +161,9 @@ public class InventoryManagementController {
titleRow.createCell(5).setCellValue("订单编号"); titleRow.createCell(5).setCellValue("订单编号");
titleRow.createCell(6).setCellValue("出库时间"); titleRow.createCell(6).setCellValue("出库时间");
titleRow.createCell(7).setCellValue("come"); titleRow.createCell(7).setCellValue("come");
titleRow.createCell(8).setCellValue("套书名称");
titleRow.createCell(9).setCellValue("套书商品ID");
titleRow.createCell(10).setCellValue("出库记录ID");
int rowIndex = 1; int rowIndex = 1;
for (Map<String, Object> map : maps) { for (Map<String, Object> map : maps) {
Row row = sheet.createRow(rowIndex++); Row row = sheet.createRow(rowIndex++);
@@ -172,6 +175,9 @@ public class InventoryManagementController {
row.createCell(5).setCellValue(cellStr(map.get("orderSn"))); row.createCell(5).setCellValue(cellStr(map.get("orderSn")));
row.createCell(6).setCellValue(cellStr(map.get("createTime"))); row.createCell(6).setCellValue(cellStr(map.get("createTime")));
row.createCell(7).setCellValue(cellStr(map.get("come"))); 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")));
} }
} }

View File

@@ -44,7 +44,6 @@ import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -231,8 +230,10 @@ public class InventoryManagementServiceImpl extends ServiceImpl<InvStockRecordDa
String outboundType = resolveOutboundType(dto.getOutboundType()); String outboundType = resolveOutboundType(dto.getOutboundType());
List<CostSplit> splits = deductPurchaseStockFifo(dto.getProductId(), dto.getMerchantId(), dto.getQuantity()); List<CostSplit> splits = deductPurchaseStockFifo(dto.getProductId(), dto.getMerchantId(), dto.getQuantity());
for (CostSplit split : splits) { 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, 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()); adjustShopProductStock(dto.getProductId(), -dto.getQuantity());
return; return;
@@ -244,12 +245,13 @@ public class InventoryManagementServiceImpl extends ServiceImpl<InvStockRecordDa
} }
// 采购:跨商户调拨 = 来源方按成本批次多条出库 + 本商户一条采购(成本取接口传值) // 采购:跨商户调拨 = 来源方按成本批次多条出库 + 本商户一条采购(成本取接口传值)
// 灵枢→众秒:灵枢出库 outbound_type=zm众秒→灵枢众秒出库 outbound_type=ls
if (isCrossMerchantTransfer(dto.getMerchantId(), dto.getSource())) { if (isCrossMerchantTransfer(dto.getMerchantId(), dto.getSource())) {
int sourceMerchantId = sourceToMerchantId(dto.getSource()); int sourceMerchantId = sourceToMerchantId(dto.getSource());
String saleTargetSource = merchantIdToSource(dto.getMerchantId()); String saleTargetSource = merchantIdToSource(dto.getMerchantId());
List<CostSplit> splits = deductPurchaseStockFifo(dto.getProductId(), sourceMerchantId, dto.getQuantity()); List<CostSplit> splits = deductPurchaseStockFifo(dto.getProductId(), sourceMerchantId, dto.getQuantity());
for (CostSplit split : splits) { 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(), InvStockRecord.BIZ_TYPE_SALE, -split.quantity, split.unitCost, dto.getRemark(),
null, null, null, split.purchaseRecordId, null); null, null, null, split.purchaseRecordId, null);
} }
@@ -1166,7 +1168,6 @@ public class InventoryManagementServiceImpl extends ServiceImpl<InvStockRecordDa
return new ArrayList<>(); return new ArrayList<>();
} }
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Map<Long, Integer> setPurchaseQtyCache = new HashMap<>();
Map<Long, List<InvStockRecord>> bindCache = new HashMap<>(); Map<Long, List<InvStockRecord>> bindCache = new HashMap<>();
List<Map<String, Object>> result = new ArrayList<>(); List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> item : list) { for (Map<String, Object> item : list) {
@@ -1175,10 +1176,11 @@ public class InventoryManagementServiceImpl extends ServiceImpl<InvStockRecordDa
continue; continue;
} }
if (recordBizType == InvStockRecord.BIZ_TYPE_SALE) { if (recordBizType == InvStockRecord.BIZ_TYPE_SALE) {
// 套书绑定内部出库不进入售出明细导出
if (isSetBookBindOutbound(item.get("outboundType"))) { if (isSetBookBindOutbound(item.get("outboundType"))) {
continue; continue;
} }
result.addAll(expandSaleRecordForExport(item, sdf, setPurchaseQtyCache, bindCache)); result.addAll(expandSaleRecordForExport(item, sdf, bindCache));
} else if (recordBizType == InvStockRecord.BIZ_TYPE_PURCHASE) { } else if (recordBizType == InvStockRecord.BIZ_TYPE_PURCHASE) {
result.add(buildPurchaseExportRow(item, sdf)); result.add(buildPurchaseExportRow(item, sdf));
} }
@@ -1209,50 +1211,93 @@ public class InventoryManagementServiceImpl extends ServiceImpl<InvStockRecordDa
row.put("orderSn", item.get("orderSn") == null ? "" : item.get("orderSn")); row.put("orderSn", item.get("orderSn") == null ? "" : item.get("orderSn"));
row.put("createTime", formatExportTime(item.get("createTime"), sdf)); row.put("createTime", formatExportTime(item.get("createTime"), sdf));
row.put("come", item.get("come")); row.put("come", item.get("come"));
row.put("setProductId", "");
row.put("setProductName", "");
row.put("stockRecordId", item.get("id"));
return row; return row;
} }
private List<Map<String, Object>> expandSaleRecordForExport(Map<String, Object> item, SimpleDateFormat sdf, private List<Map<String, Object>> expandSaleRecordForExport(Map<String, Object> item, SimpleDateFormat sdf,
Map<Long, Integer> setPurchaseQtyCache,
Map<Long, List<InvStockRecord>> bindCache) { Map<Long, List<InvStockRecord>> bindCache) {
Integer productId = toIntegerSafe(item.get("productId")); Integer productId = toIntegerSafe(item.get("productId"));
Long purchaseRecordId = toLongSafe(item.get("purchaseRecordId")); if (productId == null || !isSetBookProductByProductId(productId)) {
if (productId != null && purchaseRecordId != null && isSetBookProductByProductId(productId)) {
List<InvStockRecord> 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);
}
}
return Collections.singletonList(buildSaleExportRow(item, sdf)); return Collections.singletonList(buildSaleExportRow(item, sdf));
} }
List<ShopProduct> 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<Integer, BigDecimal> costByProductId = Collections.emptyMap();
if (setPurchaseRecordId != null) {
List<InvStockRecord> 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<Map<String, Object>> buildSetBookExpandedSaleRows(Map<String, Object> parent, /** 套书关联图书 → 只取「只绑了 1 本书」的单品商品 */
List<InvStockRecord> binds, private List<ShopProduct> listSetBookComponentProducts(Integer setProductId) {
int saleQty, int setPurchaseQty, List<Integer> bookIds = listBookIdsByProductId(setProductId);
SimpleDateFormat sdf) { if (bookIds.isEmpty()) {
Map<Integer, String> productNames = loadProductNameMap( return Collections.emptyList();
binds.stream().map(InvStockRecord::getProductId).filter(Objects::nonNull).collect(Collectors.toSet())); }
List<Map<String, Object>> rows = new ArrayList<>(); List<ShopProduct> components = new ArrayList<>();
for (InvStockRecord bind : binds) { Set<Integer> seen = new HashSet<>();
int bindQty = bind.getQuantity() == null ? 0 : Math.abs(bind.getQuantity()); for (Integer bookId : bookIds) {
int exportQty = setPurchaseQty > 0 ShopProduct component = findComponentProductByBookId(bookId, setProductId);
? (int) Math.round(bindQty * (double) saleQty / setPurchaseQty) if (component == null || component.getProductId() == null) {
: saleQty;
if (exportQty <= 0) {
continue; 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<Map<String, Object>> buildSetBookExpandedSaleRows(Map<String, Object> parent,
List<ShopProduct> components,
int saleQty,
Integer merchantId,
Map<Integer, BigDecimal> costByProductId,
Integer setProductId,
String setProductName,
SimpleDateFormat sdf) {
List<Map<String, Object>> 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<String, Object> row = new LinkedHashMap<>(); Map<String, Object> row = new LinkedHashMap<>();
row.put("exportCategory", "sale"); row.put("exportCategory", "sale");
row.put("productId", bind.getProductId()); row.put("productId", componentId);
row.put("productName", productNames.getOrDefault(bind.getProductId(), "")); row.put("productName", component.getProductName() == null ? "" : component.getProductName());
row.put("outboundTypeLabel", parent.get("outboundTypeLabel")); row.put("outboundTypeLabel", parent.get("outboundTypeLabel"));
row.put("unitCost", bind.getUnitCost()); row.put("unitCost", unitCost);
row.put("quantity", exportQty); row.put("quantity", saleQty);
row.put("orderSn", parent.get("orderSn") == null ? "" : parent.get("orderSn")); row.put("orderSn", parent.get("orderSn") == null ? "" : parent.get("orderSn"));
row.put("createTime", formatExportTime(parent.get("createTime"), sdf)); row.put("createTime", formatExportTime(parent.get("createTime"), sdf));
row.put("come", parent.get("come")); 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); rows.add(row);
} }
if (rows.isEmpty()) { if (rows.isEmpty()) {
@@ -1261,15 +1306,76 @@ public class InventoryManagementServiceImpl extends ServiceImpl<InvStockRecordDa
return rows; return rows;
} }
private int resolveSetPurchaseQuantity(Long setPurchaseRecordId) { /**
if (setPurchaseRecordId == null) { * 根据套书绑定的单品出库流水purchase_record_id → 入库记录取 unit_cost
return 0; * 同一单品多条 FIFO 按数量加权平均。
*/
private Map<Integer, BigDecimal> resolveComponentCostFromBinds(List<InvStockRecord> binds) {
if (binds == null || binds.isEmpty()) {
return Collections.emptyMap();
} }
InvStockRecord purchase = invStockRecordDao.selectById(setPurchaseRecordId); Map<Integer, BigDecimal> costSum = new HashMap<>();
if (purchase == null || purchase.getQuantity() == null) { Map<Integer, Integer> qtySum = new HashMap<>();
return 0; Map<Long, BigDecimal> purchaseCostCache = new HashMap<>();
for (InvStockRecord bind : binds) {
Integer componentId = bind.getProductId();
if (componentId == null) {
continue;
} }
return Math.abs(purchase.getQuantity()); 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);
}
Map<Integer, BigDecimal> result = new HashMap<>();
for (Map.Entry<Integer, BigDecimal> 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<Long, BigDecimal> 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<InvProductCost>()
.eq(InvProductCost::getProductId, productId)
.eq(InvProductCost::getMerchantId, merchantId)
.last("LIMIT 1")
);
return cost == null ? null : cost.getCostPrice();
} }
private List<InvStockRecord> listSetBookComponentOutbounds(Long setPurchaseRecordId) { private List<InvStockRecord> listSetBookComponentOutbounds(Long setPurchaseRecordId) {
@@ -1290,26 +1396,6 @@ public class InventoryManagementServiceImpl extends ServiceImpl<InvStockRecordDa
return listBookIdsByProductId(productId).size() > 1; return listBookIdsByProductId(productId).size() > 1;
} }
private Map<Integer, String> loadProductNameMap(Set<Integer> productIds) {
if (productIds == null || productIds.isEmpty()) {
return Collections.emptyMap();
}
List<ShopProduct> products = shopProductDao.selectList(
new LambdaQueryWrapper<ShopProduct>()
.select(ShopProduct::getProductId, ShopProduct::getProductName)
.in(ShopProduct::getProductId, productIds)
);
Map<Integer, String> 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) { private String formatExportTime(Object createTime, SimpleDateFormat sdf) {
if (createTime instanceof Date) { if (createTime instanceof Date) {
return sdf.format((Date) createTime); return sdf.format((Date) createTime);

View File

@@ -61,11 +61,11 @@ public class OrderCancelConsumer {
if (!Constants.ORDER_STATUS_TO_BE_PAID.equals(buyOrder.getOrderStatus())) { if (!Constants.ORDER_STATUS_TO_BE_PAID.equals(buyOrder.getOrderStatus())) {
return; return;
} }
// TODO 上线全量后删除此测试账号限制 /* // TODO 上线全量后删除此测试账号限制
if (!isTesterOrder(buyOrder)) { if (!isTesterOrder(buyOrder)) {
log.info("订单超时取消跳过:非测试账号, orderId={}, userId={}", buyOrder.getOrderId(), buyOrder.getUserId()); log.info("订单超时取消跳过:非测试账号, orderId={}, userId={}", buyOrder.getOrderId(), buyOrder.getUserId());
return; return;
} }*/
buyOrder.setOrderStatus(Constants.ORDER_STATUS_OUT_OF_TIME); buyOrder.setOrderStatus(Constants.ORDER_STATUS_OUT_OF_TIME);
// 回滚优惠券buy_order.coupon_id 存的是 coupon_history.id保留订单 couponId 便于查单 // 回滚优惠券buy_order.coupon_id 存的是 coupon_history.id保留订单 couponId 便于查单