feat: complete finance and overseas receiving workflow

This commit is contained in:
2026-09-10 22:43:11 +08:00
parent e531ae9407
commit 56635ad4e4
66 changed files with 1601 additions and 417 deletions
@@ -8,7 +8,11 @@ public final class DropshipRequests {
private DropshipRequests() {}
public record Receive(@NotBlank String customerId, @NotNull Long orderId, @NotNull Long warehouseId,
@NotBlank String idempotencyKey, @NotEmpty List<@Valid Pallet> pallets) {}
public record Pallet(@NotBlank String palletNo, @NotEmpty List<@Valid SkuPackages> skuPackages) {}
public record Pallet(@NotBlank String palletNo, @NotEmpty List<@Valid SkuPackages> skuPackages,
@NotNull @Positive Integer manifestVersion,
@NotNull @Positive Integer manifestPackageCount,
@NotNull @Positive Integer manifestFragmentCount,
@NotBlank @Size(max = 64) String manifestHash) {}
public record SkuPackages(@NotBlank String skuCode, String productName, @Positive long packageCount,
@Positive long productQuantity,
@NotEmpty List<@NotBlank String> cartonMarks) {}
@@ -13,6 +13,10 @@ public class DropshipOrderProfile {
private String palletNo;
/** Immutable pallet number snapshot supplied by the first-leg handover event. */
private String sourcePalletNo;
private Integer manifestVersion;
private Integer manifestPackageCount;
private Integer manifestFragmentCount;
private String manifestHash;
private Long inventoryPositionId;
private String status;
private String idempotencyKey;
@@ -17,6 +17,14 @@ public interface DropshipCartonMapper extends BaseMapper<DropshipCarton> {
@Select("SELECT * FROM ll_dropship_carton WHERE customer_id=#{customerId} AND order_id=#{orderId} ORDER BY profile_id,carton_sequence")
List<DropshipCarton> listOrder(@Param("customerId") String customerId, @Param("orderId") Long orderId);
@Select("""
SELECT DISTINCT p.source_pallet_no FROM ll_dropship_carton c
JOIN ll_dropship_order_profile p ON p.id=c.profile_id AND p.customer_id=c.customer_id
JOIN ll_order o ON o.id=c.order_id AND o.customer_id=c.customer_id
WHERE o.container_no=#{containerNo} AND c.carton_mark=#{cartonMark}
""")
List<String> sourcePalletsInContainer(@Param("containerNo") String containerNo,
@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);
@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'")
@@ -39,6 +39,14 @@ public interface DropshipOrderProfileMapper extends BaseMapper<DropshipOrderProf
List<DropshipOrderProfile> listOrder(@Param("customerId") String customerId, @Param("orderId") Long orderId);
@Select("SELECT * FROM ll_dropship_order_profile WHERE customer_id=#{customerId} AND inventory_position_id=#{positionId} LIMIT 1")
DropshipOrderProfile findPosition(@Param("customerId") String customerId, @Param("positionId") Long positionId);
@Select("""
SELECT p.* FROM ll_dropship_order_profile p
JOIN ll_order o ON o.id=p.order_id AND o.customer_id=p.customer_id
WHERE o.container_no=#{containerNo} AND p.source_pallet_no=#{sourcePalletNo}
ORDER BY p.customer_id,p.order_id,p.id
""")
List<DropshipOrderProfile> listContainerPallet(@Param("containerNo") String containerNo,
@Param("sourcePalletNo") String sourcePalletNo);
@Select("<script>" + OUTBOUND_CANDIDATE_SELECT + OUTBOUND_CANDIDATE_WHERE +
"<if test='warehouseId != null and warehouseId != \"\"'>AND pos.overseas_warehouse_id=#{warehouseId} </if>" +
@@ -51,6 +51,17 @@ public class DropshipOrderService {
Set<String> requestedPallets = request.pallets().stream().map(value -> value.palletNo().trim())
.collect(java.util.stream.Collectors.toSet());
if (!existingPallets.equals(requestedPallets)) throw conflict("Dropship order pallet snapshot conflicts with existing data");
Map<String, DropshipOrderProfile> byPallet = existing.stream().collect(Collectors.toMap(
DropshipOrderProfile::getSourcePalletNo, value -> value));
for (DropshipRequests.Pallet requested : request.pallets()) {
DropshipOrderProfile stored = byPallet.get(requested.palletNo().trim());
if (!java.util.Objects.equals(stored.getManifestVersion(), requested.manifestVersion())
|| !java.util.Objects.equals(stored.getManifestPackageCount(), requested.manifestPackageCount())
|| !java.util.Objects.equals(stored.getManifestFragmentCount(), requested.manifestFragmentCount())
|| !java.util.Objects.equals(stored.getManifestHash(), requested.manifestHash())) {
throw conflict("Dropship pallet manifest conflicts with existing data: " + requested.palletNo());
}
}
return Math.toIntExact(totalPackages);
}
@@ -104,6 +115,10 @@ public class DropshipOrderService {
DropshipOrderProfile profile = new DropshipOrderProfile();
profile.setId(IdWorker.getId()); profile.setCustomerId(request.customerId()); profile.setOrderId(order.getId());
profile.setSourcePalletNo(input.palletNo().trim());
profile.setManifestVersion(input.manifestVersion());
profile.setManifestPackageCount(input.manifestPackageCount());
profile.setManifestFragmentCount(input.manifestFragmentCount());
profile.setManifestHash(input.manifestHash().trim().toLowerCase(java.util.Locale.ROOT));
profile.setStatus("PENDING_INBOUND"); profile.setVersion(0L);
profile.setIdempotencyKey(request.idempotencyKey() + ":" + input.palletNo().trim());
profile.setCreatedAt(now); profile.setUpdatedAt(now);