This commit is contained in:
2026-10-20 11:30:34 +08:00
parent c13175bf77
commit 16ac817524
92 changed files with 1916 additions and 190 deletions
@@ -40,6 +40,28 @@ import org.apache.ibatis.annotations.Select;
)
UNION ALL
SELECT c.carton_mark,c.sku_code,NULL,c.product_quantity,NULL,NULL,
(SELECT JSON_ARRAYAGG(JSON_OBJECT('skuCode',s.sku_code,'quantity',s.product_quantity))
FROM ll_dropship_carton_sku s WHERE s.customer_id=c.customer_id AND s.carton_id=c.id)
FROM ll_dropship_carton c
JOIN ll_order o ON o.id=c.order_id AND o.customer_id=c.customer_id
JOIN ll_dropship_order_profile p ON p.id=c.profile_id AND p.customer_id=c.customer_id
JOIN ll_inventory_position pos ON pos.id=p.inventory_position_id AND pos.customer_id=c.customer_id
JOIN ll_inventory_balance b ON b.id=pos.balance_id AND b.customer_id=c.customer_id
WHERE c.customer_id=#{customerId} AND o.inbound_code=#{inboundCode}
AND o.overseas_warehouse_id=#{warehouseId} AND c.status='AVAILABLE'
AND p.status='ACTIVE' AND pos.available_packages>0 AND b.available_packages>0
AND NOT EXISTS (SELECT 1 FROM ll_inbound_plan_carton existing
WHERE existing.customer_id=c.customer_id AND existing.order_id=c.order_id
AND existing.carton_mark=c.carton_mark)
AND NOT EXISTS (
SELECT 1 FROM ll_relabel_item ri
JOIN ll_relabel_application ra ON ra.id=ri.application_id AND ra.customer_id=ri.customer_id
WHERE ri.customer_id=c.customer_id AND ri.source_inbound_code=o.inbound_code
AND ra.overseas_warehouse_id=#{warehouseId}
AND UPPER(TRIM(COALESCE(NULLIF(ri.source_carton_mark,''),ri.carton_mark)))=UPPER(TRIM(c.carton_mark))
AND ra.status NOT IN ('CANCELLED','REJECTED'))
ORDER BY cartonMark
""")
java.util.List<SourceCarton> sourceCartons(@Param("customerId") String customerId,
@@ -3,11 +3,30 @@ package com.baiyue.lastleg.transfer.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/** 使用提交单据的原箱快照表达业务占用,不提前扣减实物库存。 */
@Mapper
public interface TransferSourceReservationMapper {
/** 在出库事务中按原箱身份更新来源箱,保留箱内件数和清点时间。 */
@Update("""
UPDATE ll_warehouse_transfer_carton original
JOIN ll_warehouse_transfer_order origin_order
ON origin_order.id=original.transfer_order_id AND origin_order.customer_id=original.customer_id
JOIN ll_warehouse_transfer_order outgoing
ON outgoing.customer_id=origin_order.customer_id AND outgoing.inbound_code=origin_order.inbound_code
AND outgoing.operating_warehouse_id=origin_order.operating_warehouse_id
JOIN ll_warehouse_transfer_carton selected
ON selected.customer_id=outgoing.customer_id AND selected.transfer_order_id=outgoing.id
AND COALESCE(NULLIF(selected.customer_carton_mark,''),selected.carton_mark)=original.carton_mark
SET original.status='SHIPPED',original.updated_at=CURRENT_TIMESTAMP
WHERE outgoing.customer_id=#{customerId} AND outgoing.id=#{transferOrderId}
AND outgoing.transfer_no<>outgoing.inbound_code AND origin_order.transfer_no=origin_order.inbound_code
AND original.status='COUNTED'
""")
int shipSelectedCartons(@Param("customerId") String customerId, @Param("transferOrderId") Long transferOrderId);
/** 按同一入仓快照串行提交,防止不同草稿同时抢占原箱。 */
@Select("SELECT id FROM ll_inbound_order WHERE customer_id=#{customerId} AND id=#{inboundOrderId} FOR UPDATE")
Long lockInbound(@Param("customerId") String customerId, @Param("inboundOrderId") Long inboundOrderId);
@@ -21,6 +21,11 @@ public class TransferInventorySourceRepository {
return reservations.occupiedMarks(customerId, warehouseId, sourceCode);
}
/** 按当前转运实际选择的箱唛记录来源箱出库,不按箱数截断历史箱集合。 */
public int shipSelectedCartons(String customerId, Long transferOrderId) {
return reservations.shipSelectedCartons(customerId, transferOrderId);
}
/** 读取当前来源单未被有效换标申请使用的箱级快照。 */
public List<InboundSnapshotMapper.SourceCarton> sourceCartons(String customerId, String warehouseId, String inboundCode) {
return snapshots.sourceCartons(customerId, warehouseId, inboundCode);
@@ -1063,8 +1063,13 @@ public class WarehouseTransferService {
boolean hasSkuReceiptDifference(List<WarehouseTransferCarton> cartons, List<WarehouseTransferItem> items) {
Map<String, Long> expected = new java.util.HashMap<>();
Map<String, Long> received = new java.util.HashMap<>();
Set<String> unknown = new java.util.HashSet<>();
Map<Long, WarehouseTransferItem> byId = items.stream().collect(Collectors.toMap(WarehouseTransferItem::getId, Function.identity()));
for (var item : items) expected.merge(item.getSkuCode(), item.getDeclaredQuantity() == null ? 0L : item.getDeclaredQuantity(), Long::sum);
// 同SKU任一申报缺失时整体不可比较,不能把未知件数视作零制造差异。
for (var item : items) {
if (item.getDeclaredQuantity() == null) unknown.add(item.getSkuCode());
else expected.merge(item.getSkuCode(), item.getDeclaredQuantity(), Long::sum);
}
for (var carton : cartons) {
var contents = cartonContents(carton);
if (contents.isEmpty()) {
@@ -1075,8 +1080,10 @@ public class WarehouseTransferService {
for (var line : contents) received.merge(line.skuCode(), line.quantity(), Long::sum);
}
}
return expected.entrySet().stream().anyMatch(entry -> !entry.getValue().equals(received.getOrDefault(entry.getKey(), 0L)))
|| received.entrySet().stream().anyMatch(entry -> !entry.getValue().equals(expected.getOrDefault(entry.getKey(), 0L)));
return expected.entrySet().stream().anyMatch(entry -> !unknown.contains(entry.getKey())
&& !entry.getValue().equals(received.getOrDefault(entry.getKey(), 0L)))
|| received.entrySet().stream().anyMatch(entry -> !unknown.contains(entry.getKey())
&& !entry.getValue().equals(expected.getOrDefault(entry.getKey(), 0L)));
}
private List<WarehouseTransferCarton> requireCountedCartons(WarehouseTransferOrder transfer) {
@@ -1520,6 +1527,8 @@ public class WarehouseTransferService {
allocation.setStatus("SHIPPED"); allocation.setActivePositionKey(null);
allocation.setShippedAt(now); allocation.setUpdatedAt(now); inventoryAllocations.updateById(allocation);
}
// 同一事务记录所选原箱已出库,当前库存候选据此排除;签收快照仍完整保留。
inventorySources.shipSelectedCartons(customerId, transfer.getId());
transfer.setStatus(OUTBOUNDED); transfer.setOutboundIdempotencyKey(key);
transfer.setOutboundRequestHash(requestHash); transfer.setOutboundAt(now); transfer.setUpdatedAt(now);
transfers.updateById(transfer);