fix: 按物理箱贯通一件代发履约箱数
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
package com.baiyue.lastleg.dropship.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** SKU facts inside one physical dropship carton. */
|
||||
@Data
|
||||
@TableName("ll_dropship_carton_sku")
|
||||
public class DropshipCartonSku {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private String customerId;
|
||||
private Long orderId;
|
||||
private Long profileId;
|
||||
private Long cartonId;
|
||||
private String skuCode;
|
||||
private String productName;
|
||||
private Long productQuantity;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
+4
@@ -27,6 +27,10 @@ public interface DropshipCartonMapper extends BaseMapper<DropshipCarton> {
|
||||
@Param("cartonMark") String cartonMark);
|
||||
@Select("SELECT * FROM ll_dropship_carton WHERE customer_id=#{customerId} AND profile_id=#{profileId} AND sku_code=#{sku} AND status='AVAILABLE' ORDER BY carton_sequence LIMIT #{packages} FOR UPDATE")
|
||||
List<DropshipCarton> lockAvailable(@Param("customerId") String customerId, @Param("profileId") Long profileId, @Param("sku") String sku, @Param("packages") long packages);
|
||||
@Select("SELECT * FROM ll_dropship_carton WHERE customer_id=#{customerId} AND profile_id=#{profileId} AND status='AVAILABLE' ORDER BY carton_sequence LIMIT #{packages} FOR UPDATE")
|
||||
List<DropshipCarton> lockAvailablePhysical(@Param("customerId") String customerId,
|
||||
@Param("profileId") Long profileId,
|
||||
@Param("packages") long packages);
|
||||
@Update("UPDATE ll_dropship_carton SET status='AVAILABLE', outbound_order_no=NULL, updated_at=NOW(3) WHERE customer_id=#{customerId} AND outbound_order_no=#{outboundNo} AND status='SHIPPED'")
|
||||
int restoreOutbound(@Param("customerId") String customerId, @Param("outboundNo") String outboundNo);
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.baiyue.lastleg.dropship.mapper;
|
||||
|
||||
import com.baiyue.lastleg.dropship.entity.DropshipCartonSku;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface DropshipCartonSkuMapper extends BaseMapper<DropshipCartonSku> {
|
||||
@Select("SELECT * FROM ll_dropship_carton_sku WHERE customer_id=#{customerId} AND carton_id=#{cartonId} ORDER BY id")
|
||||
List<DropshipCartonSku> listCarton(@Param("customerId") String customerId,
|
||||
@Param("cartonId") Long cartonId);
|
||||
}
|
||||
+16
-10
@@ -10,21 +10,24 @@ import java.util.List;
|
||||
public interface DropshipOrderProfileMapper extends BaseMapper<DropshipOrderProfile> {
|
||||
String OUTBOUND_CANDIDATE_SELECT = "SELECT pos.id inventory_position_id,p.id profile_id," +
|
||||
"p.warehouse_pallet_id,pos.overseas_warehouse_id,b.overseas_warehouse_name," +
|
||||
"p.pallet_no,b.inbound_code,o.order_no,o.customer_id,o.customer_name,s.sku_code,s.product_name," +
|
||||
"LEAST(pos.available_packages,s.package_count,(SELECT COUNT(*) FROM ll_dropship_carton c " +
|
||||
"WHERE c.customer_id=p.customer_id AND c.profile_id=p.id AND c.sku_code=s.sku_code " +
|
||||
"p.pallet_no,b.inbound_code,o.order_no,o.customer_id,o.customer_name," +
|
||||
"(SELECT CASE WHEN COUNT(*)=1 THEN MAX(s.sku_code) ELSE 'MIXED' END FROM ll_dropship_sku_inventory s " +
|
||||
"WHERE s.customer_id=p.customer_id AND s.profile_id=p.id) sku_code," +
|
||||
"(SELECT CASE WHEN COUNT(*)=1 THEN MAX(s.product_name) ELSE '混合SKU' END FROM ll_dropship_sku_inventory s " +
|
||||
"WHERE s.customer_id=p.customer_id AND s.profile_id=p.id) product_name," +
|
||||
"LEAST(pos.available_packages,(SELECT COUNT(*) FROM ll_dropship_carton c " +
|
||||
"WHERE c.customer_id=p.customer_id AND c.profile_id=p.id " +
|
||||
"AND c.status='AVAILABLE')) available_packages FROM ll_dropship_order_profile p " +
|
||||
"JOIN ll_order o ON o.id=p.order_id AND o.customer_id=p.customer_id " +
|
||||
"AND o.order_type='ONE_CLICK_DROPSHIP' AND COALESCE(o.detained,0)=0 " +
|
||||
"JOIN ll_inventory_position pos ON pos.id=p.inventory_position_id AND pos.customer_id=p.customer_id " +
|
||||
"AND pos.pallet_id=p.warehouse_pallet_id " +
|
||||
"JOIN ll_inventory_balance b ON b.id=pos.balance_id AND b.customer_id=pos.customer_id " +
|
||||
"JOIN ll_dropship_sku_inventory s ON s.customer_id=p.customer_id AND s.order_id=p.order_id " +
|
||||
"AND s.profile_id=p.id ";
|
||||
"";
|
||||
String OUTBOUND_CANDIDATE_WHERE = "WHERE (#{customerId} IS NULL OR #{customerId}='' OR p.customer_id=#{customerId}) AND p.status='ACTIVE' " +
|
||||
"AND pos.status='OCCUPIED' AND pos.available_packages>0 AND s.package_count>0 " +
|
||||
"AND pos.status='OCCUPIED' AND pos.available_packages>0 " +
|
||||
"AND EXISTS (SELECT 1 FROM ll_dropship_carton c WHERE c.customer_id=p.customer_id " +
|
||||
"AND c.profile_id=p.id AND c.sku_code=s.sku_code AND c.status='AVAILABLE') " +
|
||||
"AND c.profile_id=p.id AND c.status='AVAILABLE') " +
|
||||
"AND NOT EXISTS (SELECT 1 FROM ll_dispatch_pallet_lock dl WHERE dl.customer_id=p.customer_id " +
|
||||
"AND dl.source_pallet_key=CONCAT('RECEIVED:',p.warehouse_pallet_id) AND dl.status='ACTIVE' " +
|
||||
"AND (dl.expires_at IS NULL OR dl.expires_at>CURRENT_TIMESTAMP(3))) " +
|
||||
@@ -33,7 +36,7 @@ public interface DropshipOrderProfileMapper extends BaseMapper<DropshipOrderProf
|
||||
"AND NOT EXISTS (SELECT 1 FROM ll_outbound_pallet_item oi JOIN ll_outbound_order oo " +
|
||||
"ON oo.id=oi.outbound_order_id AND oo.customer_id=oi.customer_id " +
|
||||
"WHERE oi.customer_id=p.customer_id AND oi.warehouse_pallet_id=p.warehouse_pallet_id " +
|
||||
"AND oi.inbound_code=b.inbound_code AND oi.sku_code=s.sku_code AND oo.status IN ('WAIT_PICK','PICKING','PICKED')) ";
|
||||
"AND oi.inbound_code=b.inbound_code AND oo.status IN ('WAIT_PICK','PICKING','PICKED')) ";
|
||||
|
||||
@Select("SELECT * FROM ll_dropship_order_profile WHERE customer_id=#{customerId} AND order_id=#{orderId} ORDER BY source_pallet_no")
|
||||
List<DropshipOrderProfile> listOrder(@Param("customerId") String customerId, @Param("orderId") Long orderId);
|
||||
@@ -50,12 +53,15 @@ public interface DropshipOrderProfileMapper extends BaseMapper<DropshipOrderProf
|
||||
|
||||
@Select("<script>" + OUTBOUND_CANDIDATE_SELECT + OUTBOUND_CANDIDATE_WHERE +
|
||||
"<if test='warehouseId != null and warehouseId != \"\"'>AND pos.overseas_warehouse_id=#{warehouseId} </if>" +
|
||||
"ORDER BY b.overseas_warehouse_name,p.pallet_no,s.sku_code LIMIT 500</script>")
|
||||
"ORDER BY b.overseas_warehouse_name,p.pallet_no,sku_code LIMIT 500</script>")
|
||||
List<DropshipOutboundCandidate> listOutboundCandidates(@Param("customerId") String customerId,
|
||||
@Param("warehouseId") String warehouseId);
|
||||
|
||||
@Select(OUTBOUND_CANDIDATE_SELECT + OUTBOUND_CANDIDATE_WHERE +
|
||||
"AND pos.id=#{positionId} AND s.sku_code=#{skuCode} LIMIT 1 FOR UPDATE")
|
||||
"AND pos.id=#{positionId} " +
|
||||
"AND #{skuCode}=(SELECT CASE WHEN COUNT(*)=1 THEN MAX(s.sku_code) ELSE 'MIXED' END " +
|
||||
"FROM ll_dropship_sku_inventory s WHERE s.customer_id=p.customer_id AND s.profile_id=p.id) " +
|
||||
"LIMIT 1 FOR UPDATE")
|
||||
DropshipOutboundCandidate lockOutboundCandidate(@Param("customerId") String customerId,
|
||||
@Param("positionId") Long positionId,
|
||||
@Param("skuCode") String skuCode);
|
||||
|
||||
+47
-16
@@ -27,6 +27,7 @@ public class DropshipOrderService {
|
||||
@Resource private DropshipOrderProfileMapper profiles;
|
||||
@Resource private DropshipSkuInventoryMapper skuInventory;
|
||||
@Resource private DropshipCartonMapper cartons;
|
||||
@Resource private DropshipCartonSkuMapper cartonSkus;
|
||||
@Resource private DropshipCartonLedgerMapper ledger;
|
||||
|
||||
@Transactional
|
||||
@@ -34,8 +35,9 @@ public class DropshipOrderService {
|
||||
LastlegOrder order = orders.selectById(request.orderId());
|
||||
if (order == null || !request.customerId().equals(order.getCustomerId())) throw conflict("Tail order not found");
|
||||
if (!"ONE_CLICK_DROPSHIP".equals(order.getOrderType())) throw conflict("Order is not a one-click dropship order");
|
||||
long totalPackages = request.pallets().stream().flatMap(value -> value.skuPackages().stream())
|
||||
.mapToLong(DropshipRequests.SkuPackages::packageCount).sum();
|
||||
validateMarks(request);
|
||||
long totalPackages = request.pallets().stream()
|
||||
.mapToLong(DropshipRequests.Pallet::manifestPackageCount).sum();
|
||||
long totalQuantity = request.pallets().stream().flatMap(value -> value.skuPackages().stream())
|
||||
.mapToLong(DropshipRequests.SkuPackages::productQuantity).sum();
|
||||
if (order.getPackageCount() != null && order.getPackageCount().longValue() != totalPackages) {
|
||||
@@ -65,12 +67,11 @@ public class DropshipOrderService {
|
||||
return Math.toIntExact(totalPackages);
|
||||
}
|
||||
|
||||
validateMarks(request);
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (DropshipRequests.Pallet inputPallet : request.pallets()) {
|
||||
DropshipOrderProfile profile = profile(request, order, inputPallet, now);
|
||||
profiles.insert(profile);
|
||||
int sequence = 0;
|
||||
Map<String, List<CartonSkuFact>> factsByMark = new java.util.LinkedHashMap<>();
|
||||
for (DropshipRequests.SkuPackages input : inputPallet.skuPackages()) {
|
||||
DropshipSkuInventory inventory = new DropshipSkuInventory();
|
||||
inventory.setId(IdWorker.getId()); inventory.setCustomerId(request.customerId()); inventory.setOrderId(order.getId());
|
||||
@@ -79,26 +80,43 @@ public class DropshipOrderService {
|
||||
inventory.setCreatedAt(now); inventory.setUpdatedAt(now);
|
||||
skuInventory.insert(inventory);
|
||||
for (String mark : input.cartonMarks()) {
|
||||
DropshipCarton carton = new DropshipCarton();
|
||||
carton.setId(IdWorker.getId()); carton.setCustomerId(request.customerId()); carton.setOrderId(order.getId());
|
||||
carton.setProfileId(profile.getId()); carton.setWarehousePalletId(null); carton.setPalletNo(null);
|
||||
carton.setSkuCode(input.skuCode()); carton.setCartonMark(mark.trim()); carton.setCartonSequence(++sequence);
|
||||
carton.setProductQuantity(input.cartonQuantities().get(mark));
|
||||
carton.setStatus("PENDING_INBOUND"); carton.setVersion(0L); carton.setCreatedAt(now); carton.setUpdatedAt(now);
|
||||
cartons.insert(carton);
|
||||
ledger.insert(stockInFact(request, order, carton, now));
|
||||
factsByMark.computeIfAbsent(mark.trim(), ignored -> new java.util.ArrayList<>())
|
||||
.add(new CartonSkuFact(input.skuCode().trim(), input.productName(),
|
||||
input.cartonQuantities().get(mark)));
|
||||
}
|
||||
}
|
||||
int sequence = 0;
|
||||
for (Map.Entry<String, List<CartonSkuFact>> entry : factsByMark.entrySet()) {
|
||||
List<CartonSkuFact> facts = entry.getValue();
|
||||
DropshipCarton carton = new DropshipCarton();
|
||||
carton.setId(IdWorker.getId()); carton.setCustomerId(request.customerId()); carton.setOrderId(order.getId());
|
||||
carton.setProfileId(profile.getId()); carton.setWarehousePalletId(null); carton.setPalletNo(null);
|
||||
carton.setSkuCode(facts.size() == 1 ? facts.get(0).skuCode() : "MIXED");
|
||||
carton.setCartonMark(entry.getKey()); carton.setCartonSequence(++sequence);
|
||||
carton.setProductQuantity(facts.stream().mapToLong(CartonSkuFact::quantity).sum());
|
||||
carton.setStatus("PENDING_INBOUND"); carton.setVersion(0L); carton.setCreatedAt(now); carton.setUpdatedAt(now);
|
||||
cartons.insert(carton);
|
||||
for (CartonSkuFact fact : facts) {
|
||||
DropshipCartonSku sku = new DropshipCartonSku();
|
||||
sku.setId(IdWorker.getId()); sku.setCustomerId(request.customerId()); sku.setOrderId(order.getId());
|
||||
sku.setProfileId(profile.getId()); sku.setCartonId(carton.getId()); sku.setSkuCode(fact.skuCode());
|
||||
sku.setProductName(fact.productName()); sku.setProductQuantity(fact.quantity());
|
||||
sku.setCreatedAt(now); sku.setUpdatedAt(now); cartonSkus.insert(sku);
|
||||
}
|
||||
ledger.insert(stockInFact(request, order, carton, now));
|
||||
}
|
||||
}
|
||||
return Math.toIntExact(totalPackages);
|
||||
}
|
||||
|
||||
private void validateMarks(DropshipRequests.Receive request) {
|
||||
Set<String> marks = new HashSet<>();
|
||||
Map<String, String> palletByMark = new java.util.HashMap<>();
|
||||
Set<String> palletNos = new HashSet<>();
|
||||
for (DropshipRequests.Pallet pallet : request.pallets()) {
|
||||
if (!palletNos.add(pallet.palletNo().trim())) throw conflict("Duplicate pallet number: " + pallet.palletNo());
|
||||
String palletNo = pallet.palletNo().trim();
|
||||
if (!palletNos.add(palletNo)) throw conflict("Duplicate pallet number: " + pallet.palletNo());
|
||||
Set<String> skus = new HashSet<>();
|
||||
Set<String> physicalMarks = new HashSet<>();
|
||||
for (DropshipRequests.SkuPackages item : pallet.skuPackages()) {
|
||||
if (item.packageCount() != item.cartonMarks().size()) {
|
||||
throw conflict("SKU packageCount must equal cartonMarks count: " + item.skuCode());
|
||||
@@ -110,10 +128,20 @@ public class DropshipOrderService {
|
||||
throw conflict("逐箱实收件数必须完整且与 SKU 汇总件数一致");
|
||||
}
|
||||
if (!skus.add(item.skuCode().trim())) throw conflict("Duplicate SKU on pallet: " + item.skuCode());
|
||||
Set<String> skuMarks = new HashSet<>();
|
||||
for (String mark : item.cartonMarks()) {
|
||||
if (!marks.add(mark.trim())) throw conflict("Duplicate carton mark: " + mark);
|
||||
String normalized = mark.trim();
|
||||
if (!skuMarks.add(normalized)) throw conflict("Duplicate carton mark for SKU: " + mark);
|
||||
String existingPallet = palletByMark.putIfAbsent(normalized, palletNo);
|
||||
if (existingPallet != null && !existingPallet.equals(palletNo)) {
|
||||
throw conflict("Carton mark belongs to multiple pallets: " + mark);
|
||||
}
|
||||
physicalMarks.add(normalized);
|
||||
}
|
||||
}
|
||||
if (physicalMarks.size() != pallet.manifestPackageCount()) {
|
||||
throw conflict("Pallet physical carton total does not equal manifest package count: " + palletNo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +171,7 @@ public class DropshipOrderService {
|
||||
value(sku.getPackageCount()), value(sku.getProductQuantity())))
|
||||
.toList();
|
||||
return new DropshipPalletSnapshot(profile.getSourcePalletNo(),
|
||||
skus.stream().mapToLong(DropshipPalletSnapshot.Sku::packageCount).sum(), skus);
|
||||
value(profile.getManifestPackageCount()), skus);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
@@ -213,6 +241,9 @@ public class DropshipOrderService {
|
||||
}
|
||||
|
||||
private static long value(Long value) { return value == null ? 0L : value; }
|
||||
private static long value(Integer value) { return value == null ? 0L : value.longValue(); }
|
||||
|
||||
private record CartonSkuFact(String skuCode, String productName, long quantity) {}
|
||||
|
||||
private static ResponseStatusException conflict(String message) {
|
||||
return new ResponseStatusException(HttpStatus.CONFLICT, message);
|
||||
|
||||
Reference in New Issue
Block a user