feat: gate overseas receiving and harden phase4 workflows

This commit is contained in:
2026-08-25 10:31:04 +08:00
parent cb78bc6b41
commit 52b9fc382f
93 changed files with 2779 additions and 201 deletions
@@ -289,8 +289,7 @@ public class UsTruckOrderService {
pallet.setUpdatedAt(now);
pallets.updateById(pallet);
}
int allocated = (int) rows.stream().filter(row -> "ALLOCATED".equals(row.getStatus())).count()
+ selected.size();
int allocated = (int) rows.stream().filter(row -> "ALLOCATED".equals(row.getStatus())).count();
int accepted = value(order.getReceivedPalletCount());
if (accepted < 1 || allocated > accepted) throw conflict("Allocated pallet count exceeds accepted pallets");
String from = order.getStatus();
@@ -330,6 +329,69 @@ public class UsTruckOrderService {
return command(customerId, orderNo, operatorId, "COMPLETE", IN_TRANSIT, COMPLETED, request);
}
private UsTruckOrderResponses.Detail command(String customerId, String orderNo, String operatorId,
String operationType, String expectedStatus, String nextStatus,
UsTruckOrderRequests.Command request) {
UsTruckOrder order = lock(customerId, orderNo);
UsTruckOrderResponses.Detail replay = replayOperation(order, operationType,
request.idempotencyKey(), request);
if (replay != null) return replay;
requireVersion(order, request.version());
if (!expectedStatus.equals(order.getStatus())) {
throw conflict("Truck order must be " + expectedStatus + " before " + operationType);
}
if (ALLOCATED.equals(expectedStatus)
&& value(order.getAllocatedPalletCount()) != value(order.getReceivedPalletCount())) {
throw conflict("All accepted pallets must be allocated before dispatch preparation");
}
return transition(order, operatorId, operationType, request.idempotencyKey(), request,
expectedStatus, nextStatus, request.remark());
}
private UsTruckOrderResponses.Detail transition(UsTruckOrder order, String operatorId,
String operationType, String idempotencyKey, Object request,
String from, String to, String remark) {
LocalDateTime now = LocalDateTime.now();
order.setStatus(to);
touch(order);
orders.updateById(order);
recordOperation(order, operationType, idempotencyKey, request, operatorId, now);
history(order, operationType, from, to, operatorId, idempotencyKey, remark, now);
return detail(order.getCustomerId(), order.getOrderNo());
}
private UsTruckOrderResponses.Detail replayOperation(UsTruckOrder order, String operationType,
String idempotencyKey, Object request) {
String key = idempotencyKey.trim();
UsTruckOrderOperation existing = operations.findKey(order.getCustomerId(), order.getId(), key);
if (existing == null) return null;
if (!operationType.equals(existing.getOperationType()) || !hash(request).equals(existing.getRequestHash())) {
throw conflict("Idempotency key was reused with a different truck operation payload");
}
return detail(order.getCustomerId(), order.getOrderNo());
}
private void recordOperation(UsTruckOrder order, String operationType, String idempotencyKey,
Object request, String operatorId, LocalDateTime now) {
UsTruckOrderOperation operation = new UsTruckOrderOperation();
operation.setId(IdWorker.getId());
operation.setOrderId(order.getId());
operation.setCustomerId(order.getCustomerId());
operation.setOperationType(operationType);
operation.setIdempotencyKey(idempotencyKey.trim());
operation.setRequestHash(hash(request));
operation.setResultVersion(order.getVersion());
operation.setCreatedBy(operatorId);
operation.setCreatedAt(now);
operations.insert(operation);
}
private void requireVersion(UsTruckOrder order, Long requestedVersion) {
if (!requestedVersion.equals(value(order.getVersion()))) {
throw conflict("Order was changed; refresh and retry");
}
}
private UsTruckOrder lock(String customerId, String orderNo) { UsTruckOrder o = orders.lockNo(customerId, orderNo); if (o == null) throw notFound(); return o; }
private UsTruckOrderResponses.Detail replay(String customerId, UsTruckOrder order, String hash) { if (!hash.equals(order.getRequestHash())) throw conflict("Idempotency key was reused with different truck data"); return detail(customerId, order.getOrderNo()); }
private UsTruckOrderResponses.Detail detail(UsTruckOrder o, List<UsTruckOrderStatusHistory> hs) {
@@ -338,15 +400,23 @@ public class UsTruckOrderService {
.toList();
return new UsTruckOrderResponses.Detail(summary(o), o.getOriginAddress(), o.getDestinationAddress(),
o.getRemark(), o.getCreatedBy(), palletDetails,
hs.stream().map(h -> new UsTruckOrderResponses.History(h.getFromStatus(), h.getToStatus(),
h.getOperatorId(), h.getRemark(), h.getCreatedAt())).toList());
hs.stream().map(h -> new UsTruckOrderResponses.History(h.getOperationType(),
h.getFromStatus(), h.getToStatus(), h.getOperatorId(), h.getIdempotencyKey(),
h.getRemark(), h.getCreatedAt())).toList());
}
private UsTruckOrderResponses.Pallet pallet(UsTruckOrderPallet p) {
return new UsTruckOrderResponses.Pallet(p.getId(), p.getPalletNo(), value(p.getSequenceNo()),
p.getSource(), p.getStatus(), p.getWarehousePalletId(), p.getPackageCount(), p.getReceivedAt());
}
private UsTruckOrderResponses.Summary summary(UsTruckOrder o) { return new UsTruckOrderResponses.Summary(o.getId(), o.getCustomerId(), o.getOrderNo(), o.getTransportType(), o.getOrigin(), o.getDestination(), o.getContainerType(), value(o.getPalletCount()), value(o.getPackageCount()), value(o.getReceivedPalletCount()), value(o.getAllocatedPalletCount()), value(o.getShippedPalletCount()), o.getTotalWeightKg(), o.getTotalVolumeCbm(), o.getBudgetAmount(), o.getCurrency(), o.getContactName(), o.getContactInfo(), o.getRequestedDate(), o.getStatus(), value(o.getVersion()), o.getCreatedAt(), o.getUpdatedAt()); }
private void history(UsTruckOrder o, String from, String to, String operator, String remark, LocalDateTime at) { UsTruckOrderStatusHistory h = new UsTruckOrderStatusHistory(); h.setId(IdWorker.getId()); h.setOrderId(o.getId()); h.setCustomerId(o.getCustomerId()); h.setFromStatus(from); h.setToStatus(to); h.setOperatorId(operator); h.setRemark(trim(remark)); h.setCreatedAt(at); histories.insert(h); }
private UsTruckOrderResponses.Summary summary(UsTruckOrder o) { return new UsTruckOrderResponses.Summary(o.getId(), o.getCustomerId(), o.getOrderNo(), o.getTransportType(), o.getOrigin(), o.getDestination(), o.getContainerType(), value(o.getPalletCount()), value(o.getPackageCount()), value(o.getReceivedPalletCount()), value(o.getAllocatedPalletCount()), o.getTotalWeightKg(), o.getTotalVolumeCbm(), o.getBudgetAmount(), o.getCurrency(), o.getContactName(), o.getContactInfo(), o.getRequestedDate(), o.getStatus(), value(o.getVersion()), o.getCreatedAt(), o.getUpdatedAt()); }
private void history(UsTruckOrder o, String operationType, String from, String to, String operator,
String idempotencyKey, String remark, LocalDateTime at) {
UsTruckOrderStatusHistory h = new UsTruckOrderStatusHistory();
h.setId(IdWorker.getId()); h.setOrderId(o.getId()); h.setCustomerId(o.getCustomerId());
h.setOperationType(operationType); h.setFromStatus(from); h.setToStatus(to);
h.setOperatorId(operator); h.setIdempotencyKey(trim(idempotencyKey)); h.setRemark(trim(remark));
h.setCreatedAt(at); histories.insert(h);
}
private void createPlannedPallets(UsTruckOrder order) {
int existing = pallets.listOrder(order.getCustomerId(), order.getId()).size();
LocalDateTime now = LocalDateTime.now();