This commit is contained in:
2026-08-20 18:06:07 +08:00
parent c9a05b108b
commit a38396ce3f
26 changed files with 532 additions and 21 deletions
@@ -1,6 +1,7 @@
package com.baiyue.lastleg.inventory.query.dto;
import java.time.LocalDateTime;
import java.util.List;
public record InventoryBalanceView(
Long id,
@@ -15,6 +16,10 @@ public record InventoryBalanceView(
Long inboundOrderId,
String inboundOrderNo,
String inboundCode,
List<String> skuCodes,
List<String> productNames,
List<Long> warehouseLocationIds,
List<String> warehouseLocationCodes,
long totalPackages,
long availablePackages,
long holdPackages,
@@ -13,6 +13,7 @@ public record InventoryLedgerView(
String palletNo,
String batchNo,
String warehouseLocationCode,
String skuCode,
String businessType,
String businessOrderNo,
String outboundOrderNo,
@@ -19,6 +19,10 @@ public final class InventoryQueryRequests {
String inboundOrderNo,
String inboundCode,
String businessOrderNo,
String skuCode,
String productName,
String batchNo,
String warehouseLocationCode,
String inventoryStatus
) {
public BalanceQuery {
@@ -42,6 +46,8 @@ public final class InventoryQueryRequests {
String outboundOrderNo,
String palletNo,
String batchNo,
String skuCode,
String warehouseLocationCode,
LocalDateTime occurredFrom,
LocalDateTime occurredTo
) {
@@ -5,6 +5,8 @@ import com.baiyue.lastleg.inventory.entity.InventoryLedger;
import com.baiyue.lastleg.inventory.entity.InventoryPosition;
import com.baiyue.lastleg.inventory.query.dto.InventoryQueryRequests;
import com.baiyue.lastleg.inventory.query.model.InventoryAggregateProjection;
import com.baiyue.lastleg.inventory.query.model.InventoryLocationFact;
import com.baiyue.lastleg.inventory.query.model.InventorySkuFact;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@@ -19,6 +21,10 @@ public interface InventoryQueryMapper {
"<if test='q.inboundOrderNo != null and q.inboundOrderNo != \"\"'> AND inbound_order_no LIKE CONCAT('%',#{q.inboundOrderNo},'%')</if>" +
"<if test='q.inboundCode != null and q.inboundCode != \"\"'> AND inbound_code LIKE CONCAT('%',#{q.inboundCode},'%')</if>" +
"<if test='q.businessOrderNo != null and q.businessOrderNo != \"\"'> AND EXISTS (SELECT 1 FROM ll_inventory_ledger l WHERE l.balance_id=ll_inventory_balance.id AND l.customer_id=#{customerId} AND l.business_order_no LIKE CONCAT('%',#{q.businessOrderNo},'%'))</if>" +
"<if test='q.skuCode != null and q.skuCode != \"\"'> AND EXISTS (SELECT 1 FROM ll_order_detail d WHERE d.order_id=ll_inventory_balance.inbound_order_id AND d.sku_code LIKE CONCAT('%',#{q.skuCode},'%'))</if>" +
"<if test='q.productName != null and q.productName != \"\"'> AND EXISTS (SELECT 1 FROM ll_order_detail d WHERE d.order_id=ll_inventory_balance.inbound_order_id AND (d.product_name_zh LIKE CONCAT('%',#{q.productName},'%') OR d.product_name_en LIKE CONCAT('%',#{q.productName},'%')))</if>" +
"<if test='q.batchNo != null and q.batchNo != \"\"'> AND EXISTS (SELECT 1 FROM ll_inventory_position p WHERE p.balance_id=ll_inventory_balance.id AND p.customer_id=#{customerId} AND p.batch_no LIKE CONCAT('%',#{q.batchNo},'%'))</if>" +
"<if test='q.warehouseLocationCode != null and q.warehouseLocationCode != \"\"'> AND EXISTS (SELECT 1 FROM ll_inventory_position p WHERE p.balance_id=ll_inventory_balance.id AND p.customer_id=#{customerId} AND p.warehouse_location_code LIKE CONCAT('%',#{q.warehouseLocationCode},'%'))</if>" +
"<choose>" +
"<when test='q.inventoryStatus == \"AVAILABLE\"'> AND available_packages &gt; 0</when>" +
"<when test='q.inventoryStatus == \"HOLD\"'> AND hold_packages &gt; 0</when>" +
@@ -37,6 +43,10 @@ public interface InventoryQueryMapper {
"<if test='q.inboundOrderNo != null and q.inboundOrderNo != \"\"'> AND b.inbound_order_no LIKE CONCAT('%',#{q.inboundOrderNo},'%')</if>" +
"<if test='q.inboundCode != null and q.inboundCode != \"\"'> AND b.inbound_code LIKE CONCAT('%',#{q.inboundCode},'%')</if>" +
"<if test='q.businessOrderNo != null and q.businessOrderNo != \"\"'> AND EXISTS (SELECT 1 FROM ll_inventory_ledger l WHERE l.balance_id=b.id AND l.customer_id=#{customerId} AND l.business_order_no LIKE CONCAT('%',#{q.businessOrderNo},'%'))</if>" +
"<if test='q.skuCode != null and q.skuCode != \"\"'> AND EXISTS (SELECT 1 FROM ll_order_detail d WHERE d.order_id=b.inbound_order_id AND d.sku_code LIKE CONCAT('%',#{q.skuCode},'%'))</if>" +
"<if test='q.productName != null and q.productName != \"\"'> AND EXISTS (SELECT 1 FROM ll_order_detail d WHERE d.order_id=b.inbound_order_id AND (d.product_name_zh LIKE CONCAT('%',#{q.productName},'%') OR d.product_name_en LIKE CONCAT('%',#{q.productName},'%')))</if>" +
"<if test='q.batchNo != null and q.batchNo != \"\"'> AND EXISTS (SELECT 1 FROM ll_inventory_position p2 WHERE p2.balance_id=b.id AND p2.customer_id=#{customerId} AND p2.batch_no LIKE CONCAT('%',#{q.batchNo},'%'))</if>" +
"<if test='q.warehouseLocationCode != null and q.warehouseLocationCode != \"\"'> AND EXISTS (SELECT 1 FROM ll_inventory_position p2 WHERE p2.balance_id=b.id AND p2.customer_id=#{customerId} AND p2.warehouse_location_code LIKE CONCAT('%',#{q.warehouseLocationCode},'%'))</if>" +
"<choose>" +
"<when test='q.inventoryStatus == \"AVAILABLE\"'> AND b.available_packages &gt; 0</when>" +
"<when test='q.inventoryStatus == \"HOLD\"'> AND b.hold_packages &gt; 0</when>" +
@@ -58,6 +68,22 @@ public interface InventoryQueryMapper {
long countBalances(@Param("customerId") String customerId,
@Param("q") InventoryQueryRequests.BalanceQuery query);
@Select("<script>SELECT DISTINCT b.id balance_id,d.sku_code," +
"COALESCE(NULLIF(d.product_name_zh,''),d.product_name_en) product_name " +
"FROM ll_inventory_balance b JOIN ll_order_detail d ON d.order_id=b.inbound_order_id " +
"WHERE b.customer_id=#{customerId} AND b.id IN " +
"<foreach collection='balanceIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
"AND d.sku_code IS NOT NULL AND d.sku_code&lt;&gt;'' ORDER BY b.id,d.sku_code</script>")
List<InventorySkuFact> skuFacts(@Param("customerId") String customerId,
@Param("balanceIds") List<Long> balanceIds);
@Select("<script>SELECT DISTINCT balance_id,warehouse_location_id,warehouse_location_code " +
"FROM ll_inventory_position WHERE customer_id=#{customerId} AND balance_id IN " +
"<foreach collection='balanceIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
"ORDER BY balance_id,warehouse_location_code</script>")
List<InventoryLocationFact> locationFacts(@Param("customerId") String customerId,
@Param("balanceIds") List<Long> balanceIds);
@Select("<script>SELECT COUNT(*) dimension_count," +
"COALESCE(SUM(available_packages),0) available_packages," +
"COALESCE(SUM(hold_packages),0) hold_packages," +
@@ -103,6 +129,8 @@ public interface InventoryQueryMapper {
"<if test='q.outboundOrderNo != null and q.outboundOrderNo != \"\"'> AND outbound_order_no LIKE CONCAT('%',#{q.outboundOrderNo},'%')</if>" +
"<if test='q.palletNo != null and q.palletNo != \"\"'> AND pallet_no LIKE CONCAT('%',#{q.palletNo},'%')</if>" +
"<if test='q.batchNo != null and q.batchNo != \"\"'> AND batch_no LIKE CONCAT('%',#{q.batchNo},'%')</if>" +
"<if test='q.skuCode != null and q.skuCode != \"\"'> AND sku_code LIKE CONCAT('%',#{q.skuCode},'%')</if>" +
"<if test='q.warehouseLocationCode != null and q.warehouseLocationCode != \"\"'> AND warehouse_location_code LIKE CONCAT('%',#{q.warehouseLocationCode},'%')</if>" +
"<if test='q.occurredFrom != null'> AND occurred_at &gt;=#{q.occurredFrom}</if>" +
"<if test='q.occurredTo != null'> AND occurred_at &lt;=#{q.occurredTo}</if>";
@@ -0,0 +1,10 @@
package com.baiyue.lastleg.inventory.query.model;
import lombok.Data;
@Data
public class InventoryLocationFact {
private Long balanceId;
private Long warehouseLocationId;
private String warehouseLocationCode;
}
@@ -0,0 +1,10 @@
package com.baiyue.lastleg.inventory.query.model;
import lombok.Data;
@Data
public class InventorySkuFact {
private Long balanceId;
private String skuCode;
private String productName;
}
@@ -11,6 +11,8 @@ import com.baiyue.lastleg.inventory.query.dto.InventoryPositionView;
import com.baiyue.lastleg.inventory.query.dto.InventoryQueryRequests;
import com.baiyue.lastleg.inventory.query.mapper.InventoryQueryMapper;
import com.baiyue.lastleg.inventory.query.model.InventoryAggregateProjection;
import com.baiyue.lastleg.inventory.query.model.InventoryLocationFact;
import com.baiyue.lastleg.inventory.query.model.InventorySkuFact;
import jakarta.annotation.Resource;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
@@ -19,7 +21,9 @@ import org.springframework.web.server.ResponseStatusException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@Service
public class InventoryQueryService {
@@ -51,8 +55,8 @@ public class InventoryQueryService {
public PageResult<InventoryBalanceView> balances(String customerId, InventoryQueryRequests.BalanceQuery query) {
validateStatus(query.inventoryStatus(), BALANCE_STATUSES);
long total = mapper.countBalances(customerId, query);
List<InventoryBalanceView> records = total == 0 ? List.of() : mapper.balances(
customerId, query, offset(query.page(), query.size()), query.size()).stream().map(this::balanceView).toList();
List<InventoryBalanceView> records = total == 0 ? List.of() : balanceViews(customerId,
mapper.balances(customerId, query, offset(query.page(), query.size()), query.size()));
return page(records, query.page(), query.size(), total);
}
@@ -78,12 +82,14 @@ public class InventoryQueryService {
validateStatus(query.inventoryStatus(), BALANCE_STATUSES);
List<String> rows = new ArrayList<>();
rows.add("customerId,customerName,overseasWarehouseCode,overseasWarehouseName,destinationWarehouseCode," +
"destinationWarehouseName,inboundOrderId,inboundOrderNo,inboundCode,totalPackages,availablePackages," +
"destinationWarehouseName,inboundOrderId,inboundOrderNo,inboundCode,skuCodes,productNames," +
"warehouseLocationCodes,totalPackages,availablePackages," +
"holdPackages,allocatedPackages,pickedPackages,inTransitPackages,receivingPackages,procurementPackages");
mapper.balances(customerId, query, 0, EXPORT_LIMIT).stream().map(this::balanceView).forEach(v -> rows.add(
balanceViews(customerId, mapper.balances(customerId, query, 0, EXPORT_LIMIT)).forEach(v -> rows.add(
String.join(",", csv(v.customerId()), csv(v.customerName()), csv(v.overseasWarehouseCode()), csv(v.overseasWarehouseName()),
csv(v.destinationWarehouseCode()), csv(v.destinationWarehouseName()), csv(v.inboundOrderId()),
csv(v.inboundOrderNo()), csv(v.inboundCode()),
csv(v.inboundOrderNo()), csv(v.inboundCode()), csv(String.join("|", v.skuCodes())),
csv(String.join("|", v.productNames())), csv(String.join("|", v.warehouseLocationCodes())),
csv(v.totalPackages()), csv(v.availablePackages()), csv(v.holdPackages()), csv(v.allocatedPackages()),
csv(v.pickedPackages()), csv(v.inTransitPackages()), csv(v.receivingPackages()),
csv(v.procurementPackages()))));
@@ -105,7 +111,19 @@ public class InventoryQueryService {
return ("\uFEFF" + String.join("\r\n", rows)).getBytes(StandardCharsets.UTF_8);
}
private InventoryBalanceView balanceView(InventoryBalance value) {
private List<InventoryBalanceView> balanceViews(String customerId, List<InventoryBalance> balances) {
if (balances.isEmpty()) return List.of();
List<Long> ids = balances.stream().map(InventoryBalance::getId).toList();
Map<Long, List<InventorySkuFact>> skus = mapper.skuFacts(customerId, ids).stream()
.collect(Collectors.groupingBy(InventorySkuFact::getBalanceId));
Map<Long, List<InventoryLocationFact>> locations = mapper.locationFacts(customerId, ids).stream()
.collect(Collectors.groupingBy(InventoryLocationFact::getBalanceId));
return balances.stream().map(value -> balanceView(value,
skus.getOrDefault(value.getId(), List.of()), locations.getOrDefault(value.getId(), List.of()))).toList();
}
private InventoryBalanceView balanceView(InventoryBalance value, List<InventorySkuFact> skus,
List<InventoryLocationFact> locations) {
long availablePackages = number(value.getAvailablePackages());
long holdPackages = number(value.getHoldPackages());
long allocatedPackages = number(value.getAllocatedPackages());
@@ -116,7 +134,12 @@ public class InventoryQueryService {
return new InventoryBalanceView(value.getId(), value.getCustomerId(), value.getCustomerName(), value.getOverseasWarehouseId(),
value.getOverseasWarehouseCode(), value.getOverseasWarehouseName(), value.getDestinationWarehouseId(),
value.getDestinationWarehouseCode(), value.getDestinationWarehouseName(), value.getInboundOrderId(),
value.getInboundOrderNo(), value.getInboundCode(), availablePackages + holdPackages + allocatedPackages + pickedPackages
value.getInboundOrderNo(), value.getInboundCode(),
skus.stream().map(InventorySkuFact::getSkuCode).filter(InventoryQueryService::hasText).distinct().toList(),
skus.stream().map(InventorySkuFact::getProductName).filter(InventoryQueryService::hasText).distinct().toList(),
locations.stream().map(InventoryLocationFact::getWarehouseLocationId).filter(java.util.Objects::nonNull).distinct().toList(),
locations.stream().map(InventoryLocationFact::getWarehouseLocationCode).filter(InventoryQueryService::hasText).distinct().toList(),
availablePackages + holdPackages + allocatedPackages + pickedPackages
+ inTransitPackages + receivingPackages + procurementPackages,
availablePackages, holdPackages, allocatedPackages, pickedPackages, inTransitPackages,
receivingPackages, procurementPackages, value.getUpdatedAt());
@@ -141,7 +164,7 @@ public class InventoryQueryService {
return new InventoryLedgerView(value.getId(), value.getCustomerId(), value.getOverseasWarehouseId(),
value.getDestinationWarehouseCode(), value.getInboundOrderId(), value.getInboundOrderNo(), value.getInboundCode(),
value.getPalletNo(), value.getBatchNo(),
value.getWarehouseLocationCode(), value.getBusinessType(), value.getBusinessOrderNo(),
value.getWarehouseLocationCode(), value.getSkuCode(), value.getBusinessType(), value.getBusinessOrderNo(),
value.getOutboundOrderNo(), value.getStatusFrom(), value.getStatusTo(),
number(value.getBeforePackages()), number(value.getDeltaPackages()), number(value.getAfterPackages()),
value.getOperatorId(), value.getRemark(), value.getOccurredAt());
@@ -168,6 +191,10 @@ public class InventoryQueryService {
return value == null ? 0 : value;
}
private static boolean hasText(String value) {
return value != null && !value.isBlank();
}
private static <T> PageResult<T> page(List<T> records, long page, long size, long total) {
long pages = total == 0 ? 0 : (total + size - 1) / size;
return new PageResult<>(records, page, size, total, pages);