修改提交

This commit is contained in:
2026-09-06 15:02:33 +08:00
parent 42ddc19826
commit 3ae8964fe5
15 changed files with 242 additions and 137 deletions
@@ -19,7 +19,7 @@ public final class RelabelRequests {
public record Create(
@NotBlank @Size(max = 128) String customerOrderNo,
@NotBlank @Size(max = 128) String overseasWarehouseId,
@Size(max = 128) String overseasWarehouseId,
@NotNull LocalDateTime labelDeadline,
@NotBlank @Size(max = 128) String contactName,
@NotBlank @Size(max = 64) String contactPhone,
@@ -41,10 +41,23 @@ public final class RelabelRequests {
@Size(max = 255) String trackingCode,
@NotBlank @Size(max = 128) String newSkuCode,
@Size(max = 1000) String labelRequirement,
@Size(max = 1000) String remark,
@Size(max = 128) String relabelFileId,
@Size(max = 255) String relabelFileName,
@Size(max = 1000) String relabelFileUrl
) {}
@Size(max = 1000) String relabelFileUrl,
@NotBlank @Size(max = 255) String cartonMark,
@NotBlank @Size(max = 255) String fbaCartonMark,
@Size(max = 128) String destinationWarehouseId
) {
public Item(String skuCode, String productImageFileId, String productImageFileName,
String productImageUrl, String productNameZh, int totalPackageCount,
int notifiedPackageCount, String shipmentNo, String trackingCode, String newSkuCode,
String labelRequirement, String relabelFileId, String relabelFileName, String relabelFileUrl) {
this(skuCode, productImageFileId, productImageFileName, productImageUrl, productNameZh,
totalPackageCount, notifiedPackageCount, shipmentNo, trackingCode, newSkuCode,
labelRequirement, null, relabelFileId, relabelFileName, relabelFileUrl, skuCode, shipmentNo, null);
}
}
public record Query(@Min(1) long page, @Min(1) @Max(200) long size, String relabelNo,
String customerOrderNo, String skuCode, String status,
@@ -17,9 +17,13 @@ public final class RelabelResponses {
public record Item(Long id, String skuCode, String productImageFileId, String productImageFileName,
String productImageUrl, String productNameZh, int totalPackageCount,
int notifiedPackageCount, String shipmentNo, String trackingCode,
String newSkuCode, String labelRequirement, String relabelFileId,
String relabelFileName, String relabelFileUrl) {}
String newSkuCode, String labelRequirement, String remark, String relabelFileId,
String relabelFileName, String relabelFileUrl,
String cartonMark, String fbaCartonMark, String destinationWarehouseId) {}
public record TailOrder(Long id, String orderNo, String overseasWarehouseId, int packageCount,
String status) {}
public record History(String fromStatus, String toStatus, String operatorId,
String remark, LocalDateTime createdAt) {}
public record Detail(Summary application, String remark, List<Item> items, List<History> history) {}
public record Detail(Summary application, String remark, List<Item> items, List<History> history,
List<TailOrder> tailOrders) {}
}
@@ -24,8 +24,12 @@ public class RelabelItem {
private String trackingCode;
private String newSkuCode;
private String labelRequirement;
private String remark;
private String relabelFileId;
private String relabelFileName;
private String relabelFileUrl;
private String cartonMark;
private String fbaCartonMark;
private String destinationWarehouseId;
private LocalDateTime createdAt;
}
@@ -33,7 +33,9 @@ import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@@ -56,12 +58,15 @@ public class RelabelApplicationService implements WalletResultHandler {
public RelabelResponses.Detail create(String customerId, String operatorId, RelabelRequests.Create request) {
int total = 0;
int notified = 0;
Map<String, Integer> packagesByWarehouse = new LinkedHashMap<>();
for (RelabelRequests.Item item : request.items()) {
if (item.notifiedPackageCount() > item.totalPackageCount()) {
throw badRequest("Notified relocation packages cannot exceed total packages for SKU " + item.skuCode());
}
total = Math.addExact(total, item.totalPackageCount());
notified = Math.addExact(notified, item.notifiedPackageCount());
String warehouseId = destination(item, request.overseasWarehouseId());
packagesByWarehouse.merge(warehouseId, item.totalPackageCount(), Math::addExact);
}
String requestHash = hash(request);
RelabelApplication replay = applications.findKey(customerId, request.idempotencyKey().trim());
@@ -71,40 +76,20 @@ public class RelabelApplicationService implements WalletResultHandler {
}
LocalDateTime now = LocalDateTime.now();
long applicationId = IdWorker.getId();
String relabelNo = orderNoGenerator.next("RELABEL", request.overseasWarehouseId(), applicationId);
LastlegOrder order = new LastlegOrder();
order.setId(IdWorker.getId());
order.setOrderNo(orderNoGenerator.next("RELABEL", request.overseasWarehouseId(), order.getId()));
order.setCustomerId(customerId);
order.setOrderType("RELABEL_APPLICATION");
order.setInboundCode(relabelNo);
order.setSourceRegion("CUSTOMER");
order.setSourceInboundId(relabelNo);
order.setSourceOrderNo(request.customerOrderNo().trim());
order.setOverseasWarehouseId(request.overseasWarehouseId().trim());
order.setPalletCount(0);
order.setPackageCount(total);
order.setPickedPackageCount(0);
order.setShippedPackageCount(0);
order.setCurrentStatus("PENDING_ASSIGNMENT");
order.setSubOrderStatus("RELABEL:" + PENDING_PROCESSING);
order.setRelabelOrderNo(relabelNo);
order.setRelabelStatus(PENDING_PROCESSING);
order.setCustomerRemark(trim(request.remark()));
order.setSourceEventId("CUSTOMER_RELABEL:" + relabelNo);
order.setSourceLoadingPlanId("RELABEL_APPLICATION");
order.setSourcePayloadHash("sha256:" + requestHash);
order.setCreatedAt(now);
order.setUpdatedAt(now);
orders.insert(order);
String primaryWarehouseId = packagesByWarehouse.keySet().iterator().next();
String relabelNo = orderNoGenerator.next("RELABEL", primaryWarehouseId, applicationId);
List<LastlegOrder> tailOrders = packagesByWarehouse.entrySet().stream()
.map(entry -> insertTailOrder(customerId, request, requestHash, relabelNo,
entry.getKey(), entry.getValue(), now))
.toList();
RelabelApplication application = new RelabelApplication();
application.setId(applicationId);
application.setCustomerId(customerId);
application.setRelabelNo(relabelNo);
application.setLastlegOrderId(order.getId());
application.setLastlegOrderId(tailOrders.get(0).getId());
application.setCustomerOrderNo(request.customerOrderNo().trim());
application.setOverseasWarehouseId(request.overseasWarehouseId().trim());
application.setOverseasWarehouseId(primaryWarehouseId);
application.setLabelDeadline(request.labelDeadline());
application.setContactName(request.contactName().trim());
application.setContactPhone(request.contactPhone().trim());
@@ -128,10 +113,12 @@ public class RelabelApplicationService implements WalletResultHandler {
application.setWalletCommandEventId(walletEventId);
applications.updateById(application);
for (RelabelRequests.Item source : request.items()) insertItem(customerId, applicationId, source, now);
for (RelabelRequests.Item source : request.items()) {
insertItem(customerId, applicationId, source, request.overseasWarehouseId(), now);
}
insertHistory(customerId, applicationId, null, PENDING_PROCESSING, operatorId, "Relabel application submitted", now);
insertOrderHistory(customerId, order.getId(), null, order.getCurrentStatus(),
"Relabel application submitted", now);
tailOrders.forEach(order -> insertOrderHistory(customerId, order.getId(), null,
order.getCurrentStatus(), "Relabel application submitted", now));
return detail(customerId, relabelNo);
}
@@ -145,7 +132,9 @@ public class RelabelApplicationService implements WalletResultHandler {
.like(StringUtils.hasText(query.relabelNo()), RelabelApplication::getRelabelNo, query.relabelNo())
.like(StringUtils.hasText(query.customerOrderNo()), RelabelApplication::getCustomerOrderNo, query.customerOrderNo())
.eq(StringUtils.hasText(query.status()), RelabelApplication::getStatus, query.status())
.eq(StringUtils.hasText(query.overseasWarehouseId()), RelabelApplication::getOverseasWarehouseId, query.overseasWarehouseId())
.apply(StringUtils.hasText(query.overseasWarehouseId()),
"EXISTS (SELECT 1 FROM ll_relabel_item rw WHERE rw.application_id=ll_relabel_application.id AND rw.customer_id=ll_relabel_application.customer_id AND rw.destination_warehouse_id={0})",
query.overseasWarehouseId())
.ge(query.createdFrom() != null, RelabelApplication::getCreatedAt, query.createdFrom())
.le(query.createdTo() != null, RelabelApplication::getCreatedAt, query.createdTo())
.apply(StringUtils.hasText(query.skuCode()),
@@ -166,10 +155,13 @@ public class RelabelApplicationService implements WalletResultHandler {
.eq(RelabelStatusHistory::getCustomerId, recordCustomerId)
.eq(RelabelStatusHistory::getApplicationId, application.getId())
.orderByAsc(RelabelStatusHistory::getCreatedAt));
List<LastlegOrder> tailOrders = tailOrders(application);
return new RelabelResponses.Detail(summary(application, detailItems), application.getRemark(),
detailItems.stream().map(this::item).toList(), statusHistory.stream().map(value ->
new RelabelResponses.History(value.getFromStatus(), value.getToStatus(), value.getOperatorId(),
value.getRemark(), value.getCreatedAt())).toList());
value.getRemark(), value.getCreatedAt())).toList(), tailOrders.stream().map(value ->
new RelabelResponses.TailOrder(value.getId(), value.getOrderNo(), value.getOverseasWarehouseId(),
Objects.requireNonNullElse(value.getPackageCount(), 0), value.getCurrentStatus())).toList());
}
@Transactional
@@ -201,30 +193,32 @@ public class RelabelApplicationService implements WalletResultHandler {
"换标取消,释放冻结", 2L);
application.setWalletStatus("RELEASE_PENDING"); applications.updateById(application);
}
LastlegOrder order = orders.selectById(application.getLastlegOrderId());
if (order == null || !customerId.equals(order.getCustomerId())) throw conflict("Linked tail-leg order is missing");
String orderBefore = order.getCurrentStatus();
String mainStatus = STATUSES.contains(orderBefore) ? "PENDING_ASSIGNMENT" : orderBefore;
// A return-relabel tail order joins the normal inbound queue after labels are finished.
String orderStatus = switch (next) {
case "COMPLETED" -> "RETURN_RELABEL".equals(order.getOrderType())
? "PENDING_ASSIGNMENT" : "COMPLETED";
case "REJECTED", "CANCELLED" -> "CANCELLED";
default -> mainStatus;
};
order.setCurrentStatus(orderStatus);
order.setSubOrderStatus("RELABEL:" + next);
order.setRelabelStatus(next);
order.setUpdatedAt(now);
orders.updateById(order);
insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now);
if (!Objects.equals(orderBefore, orderStatus)) {
insertOrderHistory(customerId, order.getId(), orderBefore, orderStatus, request.remark(), now);
List<LastlegOrder> linkedOrders = tailOrders(application);
if (linkedOrders.isEmpty()) throw conflict("Linked tail-leg order is missing");
for (LastlegOrder order : linkedOrders) {
String orderBefore = order.getCurrentStatus();
String mainStatus = STATUSES.contains(orderBefore) ? "PENDING_ASSIGNMENT" : orderBefore;
String orderStatus = switch (next) {
case "COMPLETED" -> "RETURN_RELABEL".equals(order.getOrderType())
? "PENDING_ASSIGNMENT" : "COMPLETED";
case "REJECTED", "CANCELLED" -> "CANCELLED";
default -> mainStatus;
};
order.setCurrentStatus(orderStatus);
order.setSubOrderStatus("RELABEL:" + next);
order.setRelabelStatus(next);
order.setUpdatedAt(now);
orders.updateById(order);
if (!Objects.equals(orderBefore, orderStatus)) {
insertOrderHistory(customerId, order.getId(), orderBefore, orderStatus, request.remark(), now);
}
}
insertHistory(customerId, application.getId(), before, next, operatorId, request.remark(), now);
return detail(customerId, relabelNo);
}
private void insertItem(String customerId, long applicationId, RelabelRequests.Item source, LocalDateTime now) {
private void insertItem(String customerId, long applicationId, RelabelRequests.Item source,
String defaultWarehouseId, LocalDateTime now) {
RelabelItem item = new RelabelItem();
item.setId(IdWorker.getId()); item.setApplicationId(applicationId); item.setCustomerId(customerId);
item.setSkuCode(source.skuCode().trim()); item.setProductImageFileId(trim(source.productImageFileId()));
@@ -232,16 +226,21 @@ public class RelabelApplicationService implements WalletResultHandler {
item.setProductNameZh(source.productNameZh().trim()); item.setTotalPackageCount(source.totalPackageCount());
item.setNotifiedPackageCount(source.notifiedPackageCount()); item.setShipmentNo(source.shipmentNo().trim());
item.setTrackingCode(trim(source.trackingCode())); item.setNewSkuCode(source.newSkuCode().trim());
item.setLabelRequirement(trim(source.labelRequirement())); item.setRelabelFileId(trim(source.relabelFileId()));
item.setLabelRequirement(trim(source.labelRequirement())); item.setRemark(trim(source.remark())); item.setRelabelFileId(trim(source.relabelFileId()));
item.setRelabelFileName(trim(source.relabelFileName())); item.setRelabelFileUrl(trim(source.relabelFileUrl()));
item.setCartonMark(source.cartonMark().trim()); item.setFbaCartonMark(source.fbaCartonMark().trim());
item.setDestinationWarehouseId(destination(source, defaultWarehouseId));
item.setCreatedAt(now); items.insert(item);
}
private RelabelResponses.Summary summary(RelabelApplication value, List<RelabelItem> detailItems) {
LastlegOrder order = orders.selectById(value.getLastlegOrderId());
String skuCodes = detailItems.stream().map(RelabelItem::getSkuCode).filter(Objects::nonNull).distinct().collect(Collectors.joining(","));
String warehouses = detailItems.stream().map(RelabelItem::getDestinationWarehouseId)
.filter(StringUtils::hasText).distinct().collect(Collectors.joining(","));
return new RelabelResponses.Summary(value.getId(), value.getRelabelNo(), value.getLastlegOrderId(),
order == null ? null : order.getOrderNo(), value.getCustomerOrderNo(), value.getOverseasWarehouseId(),
order == null ? null : order.getOrderNo(), value.getCustomerOrderNo(),
StringUtils.hasText(warehouses) ? warehouses : value.getOverseasWarehouseId(),
value.getLabelDeadline(), value.getContactName(), value.getContactPhone(), value.getStatus(),
Objects.requireNonNullElse(value.getItemCount(), detailItems.size()),
Objects.requireNonNullElse(value.getTotalPackageCount(), 0),
@@ -276,8 +275,47 @@ public class RelabelApplicationService implements WalletResultHandler {
return new RelabelResponses.Item(value.getId(), value.getSkuCode(), value.getProductImageFileId(),
value.getProductImageFileName(), value.getProductImageUrl(), value.getProductNameZh(),
value.getTotalPackageCount(), value.getNotifiedPackageCount(), value.getShipmentNo(),
value.getTrackingCode(), value.getNewSkuCode(), value.getLabelRequirement(), value.getRelabelFileId(),
value.getRelabelFileName(), value.getRelabelFileUrl());
value.getTrackingCode(), value.getNewSkuCode(), value.getLabelRequirement(), value.getRemark(), value.getRelabelFileId(),
value.getRelabelFileName(), value.getRelabelFileUrl(), value.getCartonMark(), value.getFbaCartonMark(),
value.getDestinationWarehouseId());
}
private LastlegOrder insertTailOrder(String customerId, RelabelRequests.Create request, String requestHash,
String relabelNo, String warehouseId, int packageCount,
LocalDateTime now) {
LastlegOrder order = new LastlegOrder();
order.setId(IdWorker.getId());
order.setOrderNo(orderNoGenerator.next("RELABEL", warehouseId, order.getId()));
order.setCustomerId(customerId); order.setOrderType("RELABEL_APPLICATION");
order.setInboundCode(relabelNo); order.setSourceRegion("CUSTOMER");
order.setSourceInboundId(relabelNo + ":" + warehouseId);
order.setSourceOrderNo(request.customerOrderNo().trim()); order.setOverseasWarehouseId(warehouseId);
order.setPalletCount(0); order.setPackageCount(packageCount); order.setPickedPackageCount(0);
order.setShippedPackageCount(0); order.setCurrentStatus("PENDING_ASSIGNMENT");
order.setSubOrderStatus("RELABEL:" + PENDING_PROCESSING); order.setRelabelOrderNo(relabelNo);
order.setRelabelStatus(PENDING_PROCESSING); order.setCustomerRemark(trim(request.remark()));
order.setSourceEventId("CUSTOMER_RELABEL:" + relabelNo + ":" + warehouseId);
order.setSourceLoadingPlanId("RELABEL_APPLICATION"); order.setSourcePayloadHash("sha256:" + requestHash);
order.setCreatedAt(now); order.setUpdatedAt(now); orders.insert(order);
return order;
}
private List<LastlegOrder> tailOrders(RelabelApplication application) {
List<LastlegOrder> linked = orders.selectList(new LambdaQueryWrapper<LastlegOrder>()
.eq(LastlegOrder::getCustomerId, application.getCustomerId())
.eq(LastlegOrder::getRelabelOrderNo, application.getRelabelNo())
.orderByAsc(LastlegOrder::getId));
if (!linked.isEmpty()) return linked;
LastlegOrder legacy = orders.selectById(application.getLastlegOrderId());
return legacy == null ? List.of() : List.of(legacy);
}
private static String destination(RelabelRequests.Item item, String fallback) {
String value = StringUtils.hasText(item.destinationWarehouseId())
? item.destinationWarehouseId().trim()
: trim(fallback);
if (!StringUtils.hasText(value)) throw badRequest("Destination warehouse is required for every carton");
return value;
}
private List<RelabelItem> itemList(Long applicationId) {