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
@@ -36,7 +36,11 @@ public final class OverseasProcurementRequests {
String productName, String status, String purchaseWarehouseId,
LocalDateTime createdFrom, LocalDateTime createdTo) {}
public record Review(@NotBlank String decision, @Size(max = 1000) String remark) {}
public record Review(
@NotNull @Min(0) Long version,
@NotBlank String decision,
@NotBlank @Size(max = 255) String idempotencyKey,
@Size(max = 1000) String remark) {}
public record Update(
@NotNull @Min(0) Long version,
@@ -22,7 +22,7 @@ public final class OverseasProcurementResponses {
public record History(
String operationType, String fromStatus, String toStatus, String operatorId,
String changeDetail, String remark,
String idempotencyKey, String changeDetail, String remark,
LocalDateTime createdAt) {}
public record Detail(
@@ -42,6 +42,8 @@ public class OverseasProcurementOrder {
private String createdBy;
private String reviewedBy;
private String reviewRemark;
private String reviewIdempotencyKey;
private String reviewRequestHash;
private LocalDateTime reviewedAt;
private Long version;
private LocalDateTime createdAt;
@@ -17,6 +17,7 @@ public class OverseasProcurementStatusHistory {
private String fromStatus;
private String toStatus;
private String operatorId;
private String idempotencyKey;
private String changeDetail;
private String remark;
private LocalDateTime createdAt;
@@ -89,7 +89,8 @@ public class OverseasProcurementService {
if (concurrent != null) return replay(customerId, concurrent, requestHash);
throw exception;
}
history(order, "CREATE", null, PENDING_REVIEW, operatorId, null, request.remark(), now);
history(order, "CREATE", null, PENDING_REVIEW, operatorId,
key, null, request.remark(), now);
return detail(customerId, order.getOrderNo());
}
@@ -144,7 +145,8 @@ public class OverseasProcurementService {
order.getCreatedBy(), order.getReviewedBy(), order.getReviewRemark(), order.getReviewedAt(),
histories.listOrder(customerId, order.getId()).stream().map(value ->
new OverseasProcurementResponses.History(value.getOperationType(), value.getFromStatus(),
value.getToStatus(), value.getOperatorId(), value.getChangeDetail(), value.getRemark(),
value.getToStatus(), value.getOperatorId(), value.getIdempotencyKey(),
value.getChangeDetail(), value.getRemark(),
value.getCreatedAt())).toList());
}
@@ -157,16 +159,27 @@ public class OverseasProcurementService {
default -> throw badRequest("Review decision must be APPROVE or REJECT");
};
OverseasProcurementOrder order = lock(customerId, orderNo);
if (next.equals(order.getStatus())) return detail(customerId, orderNo);
String key = request.idempotencyKey().trim();
String requestHash = hash(request);
if (key.equals(order.getReviewIdempotencyKey())) {
if (!requestHash.equals(order.getReviewRequestHash())) {
throw conflict("Review idempotency key was reused with a different payload");
}
return detail(customerId, orderNo);
}
if (!request.version().equals(order.getVersion())) throw conflict("Order was changed; refresh and retry");
if (!PENDING_REVIEW.equals(order.getStatus())) throw conflict("Overseas procurement order was already reviewed");
LocalDateTime now = LocalDateTime.now();
order.setStatus(next);
order.setReviewedBy(operatorId);
order.setReviewRemark(trim(request.remark()));
order.setReviewIdempotencyKey(key);
order.setReviewRequestHash(requestHash);
order.setReviewedAt(now);
touch(order, now);
orders.updateById(order);
history(order, "REVIEW", PENDING_REVIEW, next, operatorId, null, request.remark(), now);
history(order, "REVIEW", PENDING_REVIEW, next, operatorId,
key, null, request.remark(), now);
return detail(customerId, orderNo);
}
@@ -197,7 +210,7 @@ public class OverseasProcurementService {
touch(order, now);
orders.updateById(order);
history(order, "UPDATE", order.getStatus(), order.getStatus(), operatorId,
changeDetail(before, after), null, now);
null, changeDetail(before, after), null, now);
return detail(customerId, orderNo);
}
@@ -224,7 +237,8 @@ public class OverseasProcurementService {
}
private void history(OverseasProcurementOrder order, String operationType, String from, String to,
String operator, String changeDetail, String remark, LocalDateTime now) {
String operator, String idempotencyKey, String changeDetail,
String remark, LocalDateTime now) {
OverseasProcurementStatusHistory history = new OverseasProcurementStatusHistory();
history.setId(IdWorker.getId());
history.setOrderId(order.getId());
@@ -233,6 +247,7 @@ public class OverseasProcurementService {
history.setFromStatus(from);
history.setToStatus(to);
history.setOperatorId(operator);
history.setIdempotencyKey(trim(idempotencyKey));
history.setChangeDetail(changeDetail);
history.setRemark(trim(remark));
history.setCreatedAt(now);