This commit is contained in:
2026-09-01 11:09:14 +08:00
191 changed files with 6486 additions and 403 deletions
@@ -56,5 +56,15 @@ public final class OverseasProcurementRequests {
JsonNode overseasCarrier,
JsonNode overseasTrackingNo,
JsonNode estimatedArrivalAt,
JsonNode remark) {}
JsonNode remark,
JsonNode outboundDetails) {
public Update(Long version, String purchaseCategory, String productName, JsonNode productSpecification,
JsonNode productUrl, Long inboundQuantity, JsonNode purchaseWarehouseId, JsonNode purchaseWarehouseName,
JsonNode domesticCarrier, JsonNode domesticTrackingNo, JsonNode overseasCarrier, JsonNode overseasTrackingNo,
JsonNode estimatedArrivalAt, JsonNode remark) {
this(version, purchaseCategory, productName, productSpecification, productUrl, inboundQuantity,
purchaseWarehouseId, purchaseWarehouseName, domesticCarrier, domesticTrackingNo, overseasCarrier,
overseasTrackingNo, estimatedArrivalAt, remark, null);
}
}
}
@@ -7,12 +7,14 @@ import java.util.List;
public final class OverseasProcurementResponses {
private OverseasProcurementResponses() {}
public record OutboundDetail(String id, long quantity, String carrier, String trackingNo, LocalDateTime shippedAt, String remark) {}
public record Summary(
Long id, String orderNo, String customerId, String customerName,
String purchaseCategory, String productName, String productSpecification,
long purchaseQuantity, BigDecimal budgetUnitPrice, String currency,
String purchaseWarehouseId, String purchaseWarehouseName,
long inboundQuantity, long inventoryQuantity,
long inboundQuantity, long outboundQuantity, long inventoryQuantity,
String status, long version, LocalDateTime createdAt, LocalDateTime updatedAt) {}
public record CustomerOption(String customerId, String customerName) {}
@@ -32,5 +34,5 @@ public final class OverseasProcurementResponses {
String overseasCarrier, String overseasTrackingNo,
LocalDateTime estimatedArrivalAt, String remark,
String createdBy, String reviewedBy, String reviewRemark, LocalDateTime reviewedAt,
List<History> histories) {}
List<History> histories, List<OutboundDetail> outboundDetails) {}
}
@@ -30,6 +30,7 @@ public class OverseasProcurementOrder {
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String purchaseWarehouseId;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String purchaseWarehouseName;
private Long inboundQuantity;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String outboundDetails;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String domesticCarrier;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String domesticTrackingNo;
@TableField(updateStrategy = FieldStrategy.ALWAYS) private String overseasCarrier;
@@ -37,6 +37,7 @@ import java.util.Set;
@Service
public class OverseasProcurementService {
@Resource private com.baiyue.lastleg.service.LastlegOrderNoGenerator orderNoGenerator;
public static final String REVIEW_PERMISSION = OverseasPermissions.PURCHASE_REVIEW;
public static final String MANAGE_PERMISSION = OverseasPermissions.PURCHASE_EDIT;
public static final String PENDING_REVIEW = "PENDING_REVIEW";
@@ -62,7 +63,7 @@ public class OverseasProcurementService {
order.setId(IdWorker.getId());
order.setCustomerId(customerId);
order.setCustomerName(trim(request.customerName()));
order.setOrderNo("HC" + now.format(DateTimeFormatter.BASIC_ISO_DATE) + order.getId());
order.setOrderNo(orderNoGenerator.next("PROCUREMENT", request.receivingAddress(), order.getId()));
order.setPurchaseCategory(request.purchaseCategory().trim());
order.setProductName(request.productName().trim());
order.setProductSpecification(trim(request.productSpecification()));
@@ -147,7 +148,7 @@ public class OverseasProcurementService {
new OverseasProcurementResponses.History(value.getOperationType(), value.getFromStatus(),
value.getToStatus(), value.getOperatorId(), value.getIdempotencyKey(),
value.getChangeDetail(), value.getRemark(),
value.getCreatedAt())).toList());
value.getCreatedAt())).toList(), outboundDetails(order));
}
@Transactional
@@ -204,6 +205,7 @@ public class OverseasProcurementService {
patchText(request.overseasTrackingNo(), 128, "overseasTrackingNo", order::setOverseasTrackingNo);
patchDateTime(request.estimatedArrivalAt(), order::setEstimatedArrivalAt);
patchText(request.remark(), 1000, "remark", order::setRemark);
if (request.outboundDetails() != null) order.setOutboundDetails(normalizeOutbound(request.outboundDetails(), order.getInboundQuantity()));
Map<String, Object> after = editableSnapshot(order);
if (before.equals(after)) return detail(customerId, orderNo);
LocalDateTime now = LocalDateTime.now();
@@ -229,11 +231,16 @@ public class OverseasProcurementService {
private OverseasProcurementResponses.Summary summary(OverseasProcurementOrder order) {
long inbound = value(order.getInboundQuantity());
long outbound = outboundDetails(order).stream().mapToLong(OverseasProcurementResponses.OutboundDetail::quantity).sum();
String displayStatus = order.getStatus();
if (APPROVED.equals(order.getStatus()) && inbound > 0) {
displayStatus = outbound == 0 ? "PENDING_OUTBOUND" : outbound < inbound ? "PARTIALLY_OUTBOUND" : "OUTBOUNDED";
}
return new OverseasProcurementResponses.Summary(order.getId(), order.getOrderNo(), order.getCustomerId(),
order.getCustomerName(), order.getPurchaseCategory(), order.getProductName(),
order.getProductSpecification(), value(order.getPurchaseQuantity()), order.getBudgetUnitPrice(),
order.getCurrency(), order.getPurchaseWarehouseId(), order.getPurchaseWarehouseName(), inbound, inbound,
order.getStatus(), value(order.getVersion()), order.getCreatedAt(), order.getUpdatedAt());
order.getCurrency(), order.getPurchaseWarehouseId(), order.getPurchaseWarehouseName(), inbound, outbound, Math.max(0, inbound - outbound),
displayStatus, value(order.getVersion()), order.getCreatedAt(), order.getUpdatedAt());
}
private void history(OverseasProcurementOrder order, String operationType, String from, String to,
@@ -335,9 +342,26 @@ public class OverseasProcurementService {
result.put("overseasTrackingNo", order.getOverseasTrackingNo());
result.put("estimatedArrivalAt", order.getEstimatedArrivalAt());
result.put("remark", order.getRemark());
result.put("outboundDetails", order.getOutboundDetails());
return result;
}
private List<OverseasProcurementResponses.OutboundDetail> outboundDetails(OverseasProcurementOrder order) {
if (!StringUtils.hasText(order.getOutboundDetails())) return List.of();
try { return objectMapper.readValue(order.getOutboundDetails(), objectMapper.getTypeFactory().constructCollectionType(List.class, OverseasProcurementResponses.OutboundDetail.class)); }
catch (JsonProcessingException e) { return List.of(); }
}
private String normalizeOutbound(JsonNode node, Long inbound) {
if (!node.isArray()) throw badRequest("outboundDetails must be an array");
long total = 0;
for (JsonNode item : node) {
if (!item.isObject() || item.path("quantity").asLong(-1) < 0) throw badRequest("outboundDetails quantity must be non-negative");
total += item.path("quantity").asLong();
}
if (total > value(inbound)) throw badRequest("outbound quantity cannot exceed inbound quantity");
try { return objectMapper.writeValueAsString(node); } catch (JsonProcessingException e) { throw badRequest("Invalid outboundDetails"); }
}
private String changeDetail(Map<String, Object> before, Map<String, Object> after) {
Map<String, Object> changes = new LinkedHashMap<>();
before.forEach((field, oldValue) -> {