feat(trucking): refine truck demand contacts vehicle and service times
This commit is contained in:
+3
-3
@@ -73,11 +73,11 @@ public class FleetPoolOrderBridge {
|
||||
loose.insert(order); // No placeholder ll_order: incoming owns the one physical tail order.
|
||||
} else {
|
||||
var order=new UsTruckOrder();order.setId(row.getBusinessOrderId());order.setCustomerId(in.customerId());order.setOrderNo(row.getBusinessOrderNo());
|
||||
order.setTransportType(transport);order.setOrigin(in.warehouseName());order.setDestination(in.destinationLabel());
|
||||
order.setContainerType(null); // Pool inquiry does not collect a container specification.
|
||||
order.setOrigin(in.warehouseName());order.setDestination(in.destinationLabel());
|
||||
order.setPalletCount(in.expectedPalletCount());order.setPackageCount(in.packageCount());order.setReceivedPalletCount(0);order.setAllocatedPalletCount(0);
|
||||
order.setTotalWeightKg(in.weightKg());order.setTotalVolumeCbm(in.volumeCbm());order.setCurrency(in.currency());
|
||||
order.setContactName(in.contactName());order.setContactInfo(in.contactPhone());order.setRequestedDate(in.plannedDepartureAt().toLocalDate());
|
||||
// 来源未提供的车型、目的联系人与送货时间保持未知。
|
||||
order.setOriginContactName(in.contactName());order.setOriginContactPhone(in.contactPhone());order.setPickupAt(in.plannedDepartureAt());
|
||||
order.setStatus(row.getFulfillmentStage());order.setRemark(in.remark());order.setIdempotencyKey("FLEET_POOL:"+in.bookingNo());
|
||||
order.setRequestHash(hash);order.setCreatedBy(operator);order.setVersion(0L);order.setCreatedAt(now);order.setUpdatedAt(now);
|
||||
trucks.insert(order); // No simulated ll_us_truck_order_pallet rows.
|
||||
|
||||
+30
-32
@@ -11,75 +11,73 @@ import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public final class UsTruckOrderRequests {
|
||||
/** 仅提供请求模型。 */
|
||||
private UsTruckOrderRequests() {}
|
||||
|
||||
/** 客户或平台提交独立本土卡车需求,时间按填写值保存。 */
|
||||
public record Create(
|
||||
@Size(max = 32) String transportType,
|
||||
@NotBlank @Pattern(regexp = "PALLET_15|PALLET_30") String vehicleType,
|
||||
@NotBlank @Size(max = 255) String origin,
|
||||
@Size(max = 500) String originAddress,
|
||||
@NotBlank @Size(max = 255) String destination,
|
||||
@Size(max = 500) String destinationAddress,
|
||||
@Size(max = 32) String containerType,
|
||||
@NotNull @Min(1) Integer palletCount,
|
||||
@Min(0) Integer packageCount,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal totalWeightKg,
|
||||
@NotNull @DecimalMin("0.00") BigDecimal totalVolumeCbm,
|
||||
@DecimalMin("0.00") BigDecimal budgetAmount,
|
||||
@Size(min = 3, max = 3) String currency,
|
||||
@NotBlank @Size(max = 128) String contactName,
|
||||
@NotBlank @Size(max = 128) String contactInfo,
|
||||
@NotNull LocalDate requestedDate,
|
||||
@NotBlank @Size(max = 128) String originContactName,
|
||||
@NotBlank @Size(max = 128) String originContactPhone,
|
||||
@NotBlank @Size(max = 128) String destinationContactName,
|
||||
@NotBlank @Size(max = 128) String destinationContactPhone,
|
||||
@NotNull LocalDateTime pickupAt,
|
||||
@NotNull LocalDateTime deliveryAt,
|
||||
@Size(max = 1000) String remark,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey) {
|
||||
/** Backward-compatible constructor for clients still sending legacy transport fields. */
|
||||
public Create(String transportType, String origin, String originAddress, String destination,
|
||||
String destinationAddress, String containerType, Integer packageCount,
|
||||
BigDecimal totalWeightKg, BigDecimal totalVolumeCbm, BigDecimal budgetAmount,
|
||||
String currency, String contactName, String contactInfo, LocalDate requestedDate,
|
||||
String remark, String idempotencyKey) {
|
||||
this(transportType, origin, originAddress, destination, destinationAddress, containerType,
|
||||
packageCount == null || packageCount < 1 ? 1 : packageCount, packageCount,
|
||||
totalWeightKg, totalVolumeCbm, budgetAmount, currency, contactName, contactInfo,
|
||||
requestedDate, remark, idempotencyKey);
|
||||
}
|
||||
}
|
||||
@NotBlank @Size(max = 255) String idempotencyKey) {}
|
||||
|
||||
public record Query(
|
||||
@Min(1) long page, @Min(1) @Max(200) long size,
|
||||
String orderNo, String routeKeyword, String status, String transportType,
|
||||
String customerId, LocalDate requestedFrom, LocalDate requestedTo) {}
|
||||
String orderNo, String routeKeyword, String status, String vehicleType,
|
||||
String customerId, @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime pickupFrom,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime pickupTo) {}
|
||||
|
||||
public record MerchantQuery(
|
||||
@Min(1) long page, @Min(1) @Max(200) long size,
|
||||
String orderNo, String routeKeyword, String status, String transportType,
|
||||
LocalDate requestedFrom, LocalDate requestedTo) {
|
||||
String orderNo, String routeKeyword, String status, String vehicleType,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime pickupFrom,
|
||||
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime pickupTo) {
|
||||
/** 客户范围由认证身份决定,查询模型不接收客户标识。 */
|
||||
public Query scoped() {
|
||||
return new Query(page, size, orderNo, routeKeyword, status, transportType,
|
||||
null, requestedFrom, requestedTo);
|
||||
return new Query(page, size, orderNo, routeKeyword, status, vehicleType,
|
||||
null, pickupFrom, pickupTo);
|
||||
}
|
||||
}
|
||||
|
||||
/** 待审核订单可更新需求;两端联系信息和时间须完整填写。 */
|
||||
public record Update(
|
||||
@NotNull @Min(0) Long version,
|
||||
@Size(max = 32) String transportType,
|
||||
@NotBlank @Pattern(regexp = "PALLET_15|PALLET_30") String vehicleType,
|
||||
@Size(max = 255) String origin,
|
||||
@Size(max = 500) String originAddress,
|
||||
@Size(max = 255) String destination,
|
||||
@Size(max = 500) String destinationAddress,
|
||||
@Size(max = 32) String containerType,
|
||||
@Min(1) Integer palletCount,
|
||||
@Min(0) Integer packageCount,
|
||||
@DecimalMin("0.00") BigDecimal totalWeightKg,
|
||||
@DecimalMin("0.00") BigDecimal totalVolumeCbm,
|
||||
@DecimalMin("0.00") BigDecimal budgetAmount,
|
||||
@Size(min = 3, max = 3) String currency,
|
||||
@Size(max = 128) String contactName,
|
||||
@Size(max = 128) String contactInfo,
|
||||
LocalDate requestedDate,
|
||||
@NotBlank @Size(max = 128) String originContactName,
|
||||
@NotBlank @Size(max = 128) String originContactPhone,
|
||||
@NotBlank @Size(max = 128) String destinationContactName,
|
||||
@NotBlank @Size(max = 128) String destinationContactPhone,
|
||||
@NotNull LocalDateTime pickupAt,
|
||||
@NotNull LocalDateTime deliveryAt,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record ChangeStatus(
|
||||
|
||||
+6
-5
@@ -1,20 +1,21 @@
|
||||
package com.baiyue.lastleg.trucking.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public final class UsTruckOrderResponses {
|
||||
/** 仅提供响应模型。 */
|
||||
private UsTruckOrderResponses() {}
|
||||
|
||||
/** Pool orders retain null containerType; forecast physical pallets do not imply a container type. */
|
||||
/** 独立订单展示客户填写的车型和两端信息;拼车来源未知字段保持为空。 */
|
||||
public record Summary(
|
||||
Long id, String customerId, String orderNo, String transportType,
|
||||
String origin, String destination, String containerType, int palletCount, int packageCount,
|
||||
Long id, String customerId, String orderNo, String vehicleType,
|
||||
String origin, String destination, int palletCount, int packageCount,
|
||||
int receivedPalletCount, int allocatedPalletCount,
|
||||
BigDecimal totalWeightKg, BigDecimal totalVolumeCbm, BigDecimal budgetAmount,
|
||||
String currency, String contactName, String contactInfo, LocalDate requestedDate,
|
||||
String currency, String originContactName, String originContactPhone,
|
||||
String destinationContactName, String destinationContactPhone, LocalDateTime pickupAt, LocalDateTime deliveryAt,
|
||||
String status, long version, LocalDateTime createdAt, LocalDateTime updatedAt, com.baiyue.lastleg.fleet.business.FleetPoolOrderModels.Arrangement fleetArrangement) {}
|
||||
|
||||
public record History(
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@@ -15,12 +14,11 @@ public class UsTruckOrder {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private String customerId;
|
||||
private String orderNo;
|
||||
private String transportType;
|
||||
private String vehicleType;
|
||||
private String origin;
|
||||
private String originAddress;
|
||||
private String destination;
|
||||
private String destinationAddress;
|
||||
private String containerType;
|
||||
private Integer palletCount;
|
||||
private Integer packageCount;
|
||||
private Integer receivedPalletCount;
|
||||
@@ -29,9 +27,12 @@ public class UsTruckOrder {
|
||||
private BigDecimal totalVolumeCbm;
|
||||
private BigDecimal budgetAmount;
|
||||
private String currency;
|
||||
private String contactName;
|
||||
private String contactInfo;
|
||||
private LocalDate requestedDate;
|
||||
private String originContactName;
|
||||
private String originContactPhone;
|
||||
private String destinationContactName;
|
||||
private String destinationContactPhone;
|
||||
private LocalDateTime pickupAt;
|
||||
private LocalDateTime deliveryAt;
|
||||
private String status;
|
||||
private String remark;
|
||||
private String idempotencyKey;
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.baiyue.lastleg.trucking.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.lastleg.trucking.dto.UsTruckOrderRequests;
|
||||
import com.baiyue.lastleg.trucking.entity.*;
|
||||
import com.baiyue.lastleg.trucking.mapper.*;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
import java.util.List;
|
||||
|
||||
/** 卡车订单持久化边界;事务和状态决策由服务负责。 */
|
||||
@Repository
|
||||
public class UsTruckOrderRepository {
|
||||
private final UsTruckOrderMapper orders;
|
||||
private final UsTruckOrderPalletMapper pallets;
|
||||
private final UsTruckOrderStatusHistoryMapper histories;
|
||||
private final UsTruckOrderOperationMapper operations;
|
||||
|
||||
/** 绑定本服务的订单、托盘和不可变历史 Mapper。 */
|
||||
public UsTruckOrderRepository(UsTruckOrderMapper orders, UsTruckOrderPalletMapper pallets,
|
||||
UsTruckOrderStatusHistoryMapper histories, UsTruckOrderOperationMapper operations) {
|
||||
this.orders = orders; this.pallets = pallets; this.histories = histories; this.operations = operations;
|
||||
}
|
||||
|
||||
/** 按客户、路线、状态、车型和取货时间组合分页条件。 */
|
||||
public Page<UsTruckOrder> page(String customerId, UsTruckOrderRequests.Query query) {
|
||||
LambdaQueryWrapper<UsTruckOrder> w = new LambdaQueryWrapper<UsTruckOrder>()
|
||||
.eq(StringUtils.hasText(customerId), UsTruckOrder::getCustomerId, customerId)
|
||||
.like(StringUtils.hasText(query.orderNo()), UsTruckOrder::getOrderNo, query.orderNo())
|
||||
.and(StringUtils.hasText(query.routeKeyword()), x -> x.like(UsTruckOrder::getOrigin, query.routeKeyword())
|
||||
.or().like(UsTruckOrder::getDestination, query.routeKeyword()))
|
||||
.eq(StringUtils.hasText(query.status()), UsTruckOrder::getStatus,
|
||||
StringUtils.hasText(query.status()) ? query.status().trim().toUpperCase() : null)
|
||||
.eq(StringUtils.hasText(query.vehicleType()), UsTruckOrder::getVehicleType,
|
||||
StringUtils.hasText(query.vehicleType()) ? query.vehicleType() : null)
|
||||
.eq(StringUtils.hasText(query.customerId()), UsTruckOrder::getCustomerId, query.customerId())
|
||||
.ge(query.pickupFrom() != null, UsTruckOrder::getPickupAt, query.pickupFrom())
|
||||
.le(query.pickupTo() != null, UsTruckOrder::getPickupAt, query.pickupTo())
|
||||
.orderByDesc(UsTruckOrder::getCreatedAt).orderByDesc(UsTruckOrder::getId);
|
||||
return orders.selectPage(Page.of(query.page(), query.size()), w);
|
||||
}
|
||||
/** 按客户和订单号读取。 */
|
||||
public UsTruckOrder findNo(String customerId, String orderNo) { return orders.findNo(customerId, orderNo); }
|
||||
/** 在当前事务中按客户和订单号加行锁。 */
|
||||
public UsTruckOrder lockNo(String customerId, String orderNo) { return orders.lockNo(customerId, orderNo); }
|
||||
/** 按客户和创建幂等键读取。 */
|
||||
public UsTruckOrder findKey(String customerId, String key) { return orders.findKey(customerId, key); }
|
||||
/** 保存新订单。 */
|
||||
public void insertOrder(UsTruckOrder order) { orders.insert(order); }
|
||||
/** 保存已锁定订单的当前状态。 */
|
||||
public void updateOrder(UsTruckOrder order) { orders.updateById(order); }
|
||||
/** 读取本客户订单的托盘。 */
|
||||
public List<UsTruckOrderPallet> pallets(String customerId, Long orderId) { return pallets.listOrder(customerId, orderId); }
|
||||
/** 保存实际托盘记录。 */
|
||||
public void insertPallet(UsTruckOrderPallet row) { pallets.insert(row); }
|
||||
/** 更新实际托盘处理状态。 */
|
||||
public void updatePallet(UsTruckOrderPallet row) { pallets.updateById(row); }
|
||||
/** 读取本客户订单的不可变历史。 */
|
||||
public List<UsTruckOrderStatusHistory> histories(String customerId, Long orderId) { return histories.listOrder(customerId, orderId); }
|
||||
/** 保存订单历史。 */
|
||||
public void insertHistory(UsTruckOrderStatusHistory row) { histories.insert(row); }
|
||||
/** 按客户、订单和幂等键读取操作结果。 */
|
||||
public UsTruckOrderOperation findOperation(String customerId, Long orderId, String key) { return operations.findKey(customerId, orderId, key); }
|
||||
/** 保存不可变操作摘要。 */
|
||||
public void insertOperation(UsTruckOrderOperation row) { operations.insert(row); }
|
||||
}
|
||||
+106
-74
@@ -1,20 +1,14 @@
|
||||
package com.baiyue.lastleg.trucking.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.core.container.ContainerSpecificationCatalog;
|
||||
import com.baiyue.lastleg.trucking.dto.UsTruckOrderRequests;
|
||||
import com.baiyue.lastleg.trucking.dto.UsTruckOrderResponses;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrder;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrderOperation;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrderPallet;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrderStatusHistory;
|
||||
import com.baiyue.lastleg.trucking.mapper.UsTruckOrderMapper;
|
||||
import com.baiyue.lastleg.trucking.mapper.UsTruckOrderOperationMapper;
|
||||
import com.baiyue.lastleg.trucking.mapper.UsTruckOrderPalletMapper;
|
||||
import com.baiyue.lastleg.trucking.mapper.UsTruckOrderStatusHistoryMapper;
|
||||
import com.baiyue.lastleg.security.OverseasPermissions;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -26,12 +20,9 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HexFormat;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -44,8 +35,6 @@ public class UsTruckOrderService {
|
||||
@Resource private com.baiyue.lastleg.service.LastlegOrderNoGenerator orderNoGenerator;
|
||||
public static final String MANAGE_PERMISSION = OverseasPermissions.TRUCK_STATUS;
|
||||
public static final String PENDING_REVIEW = "PENDING_REVIEW";
|
||||
/** Kept for source compatibility; new orders use PENDING_REVIEW. */
|
||||
@Deprecated public static final String PENDING_CONFIRMATION = PENDING_REVIEW;
|
||||
public static final String WAITING_INBOUND = "WAITING_INBOUND";
|
||||
public static final String RECEIVING = "RECEIVING";
|
||||
public static final String WAITING_CONSOLIDATION = "WAITING_CONSOLIDATION";
|
||||
@@ -63,43 +52,41 @@ public class UsTruckOrderService {
|
||||
PENDING_DISPATCH, DISPATCHED, IN_TRANSIT, COMPLETED, REJECTED, CANCELLED);
|
||||
private static final Set<String> CANCELLABLE = Set.of(PENDING_REVIEW, WAITING_INBOUND, RECEIVING,
|
||||
PALLET_OVERAGE_REVIEW, WAITING_CONSOLIDATION, PARTIALLY_ALLOCATED, ALLOCATED, PENDING_DISPATCH);
|
||||
private static final Set<String> TRANSPORT_TYPES = Set.of("PORT_PICKUP", "WAREHOUSE_TO_WAREHOUSE", "WAREHOUSE_TO_DOOR");
|
||||
private static final Set<String> VEHICLE_TYPES = Set.of("PALLET_15", "PALLET_30");
|
||||
|
||||
@Resource private UsTruckOrderMapper orders;
|
||||
@Resource private UsTruckOrderOperationMapper operations;
|
||||
@Resource private UsTruckOrderPalletMapper pallets;
|
||||
@Resource private UsTruckOrderStatusHistoryMapper histories;
|
||||
@Resource private com.baiyue.lastleg.trucking.repository.UsTruckOrderRepository repository;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
|
||||
/** 创建卡车需求,校验两端资料并保留创建幂等结果。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail create(String customerId, String operatorId, UsTruckOrderRequests.Create request) {
|
||||
String key = request.idempotencyKey().trim();
|
||||
String hash = hash(request);
|
||||
UsTruckOrder replay = orders.findKey(customerId, key);
|
||||
UsTruckOrder replay = repository.findKey(customerId, key);
|
||||
if (replay != null) return replay(customerId, replay, hash);
|
||||
if (StringUtils.hasText(request.transportType())) validateTransport(request.transportType());
|
||||
validateRequirements(request.vehicleType(), request.originContactName(), request.originContactPhone(),
|
||||
request.destinationContactName(), request.destinationContactPhone(), request.pickupAt(), request.deliveryAt());
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
UsTruckOrder order = new UsTruckOrder();
|
||||
order.setId(IdWorker.getId());
|
||||
order.setCustomerId(customerId);
|
||||
order.setOrderNo(orderNoGenerator.next("TRUCK", request.origin(), order.getId()));
|
||||
order.setTransportType(StringUtils.hasText(request.transportType()) ? norm(request.transportType()) : null);
|
||||
order.setVehicleType(request.vehicleType());
|
||||
order.setOrigin(trim(request.origin())); order.setOriginAddress(trim(request.originAddress()));
|
||||
order.setDestination(trim(request.destination())); order.setDestinationAddress(trim(request.destinationAddress()));
|
||||
order.setContainerType(StringUtils.hasText(request.containerType())
|
||||
? ContainerSpecificationCatalog.require(request.containerType()).containerType()
|
||||
: ContainerSpecificationCatalog.DEFAULT_CONTAINER_TYPE);
|
||||
order.setPalletCount(request.palletCount()); order.setPackageCount(value(request.packageCount()));
|
||||
// 车型只表达用车需求;实际托数由收货记录,不能按车型虚构货量。
|
||||
order.setPalletCount(0); order.setPackageCount(value(request.packageCount()));
|
||||
order.setReceivedPalletCount(0); order.setAllocatedPalletCount(0);
|
||||
order.setTotalWeightKg(request.totalWeightKg()); order.setTotalVolumeCbm(request.totalVolumeCbm());
|
||||
order.setBudgetAmount(request.budgetAmount()); order.setCurrency(defaultCurrency(request.currency()));
|
||||
order.setContactName(request.contactName().trim()); order.setContactInfo(request.contactInfo().trim());
|
||||
order.setRequestedDate(request.requestedDate()); order.setStatus(PENDING_REVIEW);
|
||||
order.setOriginContactName(request.originContactName().trim()); order.setOriginContactPhone(request.originContactPhone().trim());
|
||||
order.setDestinationContactName(request.destinationContactName().trim()); order.setDestinationContactPhone(request.destinationContactPhone().trim());
|
||||
order.setPickupAt(request.pickupAt()); order.setDeliveryAt(request.deliveryAt()); order.setStatus(PENDING_REVIEW);
|
||||
order.setRemark(trim(request.remark())); order.setIdempotencyKey(key); order.setRequestHash(hash);
|
||||
order.setCreatedBy(operatorId); order.setVersion(0L); order.setCreatedAt(now); order.setUpdatedAt(now);
|
||||
try { orders.insert(order); }
|
||||
try { repository.insertOrder(order); }
|
||||
catch (DuplicateKeyException e) {
|
||||
UsTruckOrder concurrent = orders.findKey(customerId, key);
|
||||
UsTruckOrder concurrent = repository.findKey(customerId, key);
|
||||
if (concurrent != null) return replay(customerId, concurrent, hash);
|
||||
throw e;
|
||||
}
|
||||
@@ -107,57 +94,48 @@ public class UsTruckOrderService {
|
||||
return detail(customerId, order.getOrderNo());
|
||||
}
|
||||
|
||||
/** 按客户范围分页读取订单并批量关联承运安排。 */
|
||||
@Transactional(readOnly = true, isolation = org.springframework.transaction.annotation.Isolation.REPEATABLE_READ)
|
||||
public PageResult<UsTruckOrderResponses.Summary> page(String customerId, UsTruckOrderRequests.Query query) {
|
||||
validateQuery(query);
|
||||
LambdaQueryWrapper<UsTruckOrder> w = new LambdaQueryWrapper<UsTruckOrder>()
|
||||
.eq(StringUtils.hasText(customerId), UsTruckOrder::getCustomerId, customerId)
|
||||
.like(StringUtils.hasText(query.orderNo()), UsTruckOrder::getOrderNo, query.orderNo())
|
||||
.and(StringUtils.hasText(query.routeKeyword()), x -> x.like(UsTruckOrder::getOrigin, query.routeKeyword())
|
||||
.or().like(UsTruckOrder::getDestination, query.routeKeyword()))
|
||||
.eq(StringUtils.hasText(query.status()), UsTruckOrder::getStatus,
|
||||
StringUtils.hasText(query.status()) ? norm(query.status()) : null)
|
||||
.eq(StringUtils.hasText(query.transportType()), UsTruckOrder::getTransportType,
|
||||
StringUtils.hasText(query.transportType()) ? norm(query.transportType()) : null)
|
||||
.eq(StringUtils.hasText(query.customerId()), UsTruckOrder::getCustomerId, query.customerId())
|
||||
.ge(query.requestedFrom() != null, UsTruckOrder::getRequestedDate, query.requestedFrom())
|
||||
.le(query.requestedTo() != null, UsTruckOrder::getRequestedDate, query.requestedTo())
|
||||
.orderByDesc(UsTruckOrder::getCreatedAt).orderByDesc(UsTruckOrder::getId);
|
||||
Page<UsTruckOrder> result = orders.selectPage(Page.of(query.page(), query.size()), w);
|
||||
Page<UsTruckOrder> result = repository.page(customerId, query);
|
||||
var arrangements = fleetPool.arrangements(result.getRecords().stream().map(this::poolKey).toList());
|
||||
return new PageResult<>(result.getRecords().stream().map(o -> summary(o, arrangements.get(poolKey(o)))).toList(), result.getCurrent(),
|
||||
result.getSize(), result.getTotal(), result.getPages());
|
||||
}
|
||||
|
||||
/** 读取本客户的订单、实际托盘及操作历史。 */
|
||||
@Transactional(readOnly = true, isolation = org.springframework.transaction.annotation.Isolation.REPEATABLE_READ)
|
||||
public UsTruckOrderResponses.Detail detail(String customerId, String orderNo) {
|
||||
UsTruckOrder order = orders.findNo(customerId, orderNo);
|
||||
UsTruckOrder order = repository.findNo(customerId, orderNo);
|
||||
if (order == null) throw notFound();
|
||||
return detail(order, histories.listOrder(customerId, order.getId()));
|
||||
return detail(order, repository.histories(customerId, order.getId()));
|
||||
}
|
||||
|
||||
/** 按版本更新待审核订单的车型、联系人和取送时间。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail update(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.Update request) {
|
||||
UsTruckOrder order = lock(customerId, orderNo);
|
||||
if (!request.version().equals(value(order.getVersion()))) throw conflict("Order was changed; refresh and retry");
|
||||
if (!PENDING_REVIEW.equals(order.getStatus())) throw conflict("Only pending review orders can be edited");
|
||||
if (request.transportType() != null) { validateTransport(request.transportType()); order.setTransportType(norm(request.transportType())); }
|
||||
validateRequirements(request.vehicleType(), request.originContactName(), request.originContactPhone(),
|
||||
request.destinationContactName(), request.destinationContactPhone(), request.pickupAt(), request.deliveryAt());
|
||||
order.setVehicleType(request.vehicleType());
|
||||
setText(request.origin(), order::setOrigin); setPresent(request.originAddress(), order::setOriginAddress);
|
||||
setText(request.destination(), order::setDestination); setPresent(request.destinationAddress(), order::setDestinationAddress);
|
||||
setText(request.containerType(), v -> order.setContainerType(
|
||||
ContainerSpecificationCatalog.require(v).containerType()));
|
||||
if (request.palletCount() != null) order.setPalletCount(request.palletCount());
|
||||
if (request.packageCount() != null) order.setPackageCount(request.packageCount());
|
||||
if (request.totalWeightKg() != null) order.setTotalWeightKg(request.totalWeightKg()); if (request.totalVolumeCbm() != null) order.setTotalVolumeCbm(request.totalVolumeCbm());
|
||||
if (request.budgetAmount() != null) order.setBudgetAmount(request.budgetAmount()); if (request.currency() != null) order.setCurrency(defaultCurrency(request.currency()));
|
||||
setText(request.contactName(), order::setContactName); setText(request.contactInfo(), order::setContactInfo);
|
||||
if (request.requestedDate() != null) order.setRequestedDate(request.requestedDate()); setPresent(request.remark(), order::setRemark);
|
||||
touch(order); orders.updateById(order);
|
||||
order.setOriginContactName(request.originContactName().trim()); order.setOriginContactPhone(request.originContactPhone().trim());
|
||||
order.setDestinationContactName(request.destinationContactName().trim()); order.setDestinationContactPhone(request.destinationContactPhone().trim());
|
||||
order.setPickupAt(request.pickupAt()); order.setDeliveryAt(request.deliveryAt()); setPresent(request.remark(), order::setRemark);
|
||||
touch(order); repository.updateOrder(order);
|
||||
history(order, "UPDATE", order.getStatus(), order.getStatus(), operatorId, null, "UPDATE", LocalDateTime.now());
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
/** 按版本幂等取消允许取消的订单。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail changeStatus(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.ChangeStatus request) {
|
||||
@@ -172,6 +150,7 @@ public class UsTruckOrderService {
|
||||
order.getStatus(), CANCELLED, request.remark());
|
||||
}
|
||||
|
||||
/** 审核订单,仅对真实已知预报建立待收托盘。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail review(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.Review request) {
|
||||
@@ -186,6 +165,7 @@ public class UsTruckOrderService {
|
||||
PENDING_REVIEW, next, request.remark());
|
||||
}
|
||||
|
||||
/** 记录实际收货,首次实收确定独立订单托数。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail receivePallets(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.ReceivePallets request) {
|
||||
@@ -198,9 +178,12 @@ public class UsTruckOrderService {
|
||||
}
|
||||
|
||||
createPlannedPallets(order);
|
||||
List<UsTruckOrderPallet> current = pallets.listOrder(customerId, order.getId());
|
||||
int planned = value(order.getPalletCount());
|
||||
List<UsTruckOrderPallet> current = repository.pallets(customerId, order.getId());
|
||||
int actual = request.actualPalletCount();
|
||||
// 独立下单没有预报托数,首次实收形成确认基准,后续超量仍需审核。
|
||||
boolean firstReceipt = value(order.getPalletCount()) == 0;
|
||||
int planned = firstReceipt ? actual : value(order.getPalletCount());
|
||||
order.setPalletCount(planned);
|
||||
List<Integer> packageCounts = request.palletPackageCounts() == null ? List.of()
|
||||
: request.palletPackageCounts();
|
||||
if (!packageCounts.isEmpty() && packageCounts.size() != actual) {
|
||||
@@ -213,7 +196,7 @@ public class UsTruckOrderService {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (int sequence = 1; sequence <= actual; sequence++) {
|
||||
UsTruckOrderPallet pallet = sequence <= current.size() ? current.get(sequence - 1)
|
||||
: newPallet(order, sequence, "PDA_OVERAGE", now);
|
||||
: newPallet(order, sequence, firstReceipt ? "PDA_RECEIPT" : "PDA_OVERAGE", now);
|
||||
pallet.setStatus(sequence > planned ? "OVERAGE_PENDING_REVIEW" : "RECEIVED");
|
||||
if (!packageCounts.isEmpty()) pallet.setPackageCount(packageCounts.get(sequence - 1));
|
||||
else if (actual == 1) pallet.setPackageCount(request.packageCount());
|
||||
@@ -221,9 +204,9 @@ public class UsTruckOrderService {
|
||||
pallet.setUpdatedAt(now);
|
||||
if (pallet.getId() == null) {
|
||||
pallet.setId(IdWorker.getId());
|
||||
pallets.insert(pallet);
|
||||
repository.insertPallet(pallet);
|
||||
} else {
|
||||
pallets.updateById(pallet);
|
||||
repository.updatePallet(pallet);
|
||||
}
|
||||
}
|
||||
order.setReceivedPalletCount(actual);
|
||||
@@ -232,12 +215,13 @@ public class UsTruckOrderService {
|
||||
: actual == planned ? WAITING_CONSOLIDATION : RECEIVING;
|
||||
order.setStatus(next);
|
||||
touch(order);
|
||||
orders.updateById(order);
|
||||
repository.updateOrder(order);
|
||||
recordOperation(order, "RECEIVE_PALLETS", request.idempotencyKey(), request, operatorId, now);
|
||||
history(order, "RECEIVE_PALLETS", from, next, operatorId, request.idempotencyKey(), request.remark(), now);
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
/** 审核后续实收超出已确认托数的部分。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail reviewPalletOverage(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.ReviewPalletOverage request) {
|
||||
@@ -250,14 +234,14 @@ public class UsTruckOrderService {
|
||||
throw conflict("Only pallet-overage-review orders can be reviewed");
|
||||
}
|
||||
boolean approve = "APPROVE".equals(norm(request.decision()));
|
||||
List<UsTruckOrderPallet> rows = pallets.listOrder(customerId, order.getId());
|
||||
List<UsTruckOrderPallet> rows = repository.pallets(customerId, order.getId());
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
int accepted = value(order.getPalletCount());
|
||||
for (UsTruckOrderPallet pallet : rows) {
|
||||
if (!"OVERAGE_PENDING_REVIEW".equals(pallet.getStatus())) continue;
|
||||
pallet.setStatus(approve ? "RECEIVED" : "OVERAGE_REJECTED");
|
||||
pallet.setUpdatedAt(now);
|
||||
pallets.updateById(pallet);
|
||||
repository.updatePallet(pallet);
|
||||
}
|
||||
if (approve) {
|
||||
accepted = value(order.getReceivedPalletCount());
|
||||
@@ -267,13 +251,14 @@ public class UsTruckOrderService {
|
||||
}
|
||||
order.setStatus(WAITING_CONSOLIDATION);
|
||||
touch(order);
|
||||
orders.updateById(order);
|
||||
repository.updateOrder(order);
|
||||
recordOperation(order, "REVIEW_PALLET_OVERAGE", request.idempotencyKey(), request, operatorId, now);
|
||||
history(order, "REVIEW_PALLET_OVERAGE", PALLET_OVERAGE_REVIEW, WAITING_CONSOLIDATION,
|
||||
operatorId, request.idempotencyKey(), request.remark(), now);
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
/** 分配已实收且尚未分配的订单托盘。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail allocatePallets(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.AllocatePallets request) {
|
||||
@@ -287,7 +272,7 @@ public class UsTruckOrderService {
|
||||
}
|
||||
Set<Long> requested = new HashSet<>(request.palletIds());
|
||||
if (requested.size() != request.palletIds().size()) throw badRequest("palletIds must be unique");
|
||||
List<UsTruckOrderPallet> rows = pallets.listOrder(customerId, order.getId());
|
||||
List<UsTruckOrderPallet> rows = repository.pallets(customerId, order.getId());
|
||||
List<UsTruckOrderPallet> selected = rows.stream().filter(row -> requested.contains(row.getId())).toList();
|
||||
if (selected.size() != requested.size() || selected.stream().anyMatch(row -> !"RECEIVED".equals(row.getStatus()))) {
|
||||
throw conflict("Selected pallets must belong to the order and be received but unallocated");
|
||||
@@ -296,7 +281,7 @@ public class UsTruckOrderService {
|
||||
for (UsTruckOrderPallet pallet : selected) {
|
||||
pallet.setStatus("ALLOCATED");
|
||||
pallet.setUpdatedAt(now);
|
||||
pallets.updateById(pallet);
|
||||
repository.updatePallet(pallet);
|
||||
}
|
||||
int allocated = (int) rows.stream().filter(row -> "ALLOCATED".equals(row.getStatus())).count();
|
||||
int accepted = value(order.getReceivedPalletCount());
|
||||
@@ -306,13 +291,14 @@ public class UsTruckOrderService {
|
||||
order.setAllocatedPalletCount(allocated);
|
||||
order.setStatus(next);
|
||||
touch(order);
|
||||
orders.updateById(order);
|
||||
repository.updateOrder(order);
|
||||
recordOperation(order, "ALLOCATE_PALLETS", request.idempotencyKey(), request, operatorId, now);
|
||||
history(order, "ALLOCATE_PALLETS", from, next, operatorId,
|
||||
request.idempotencyKey(), request.remark(), now);
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
/** 将全部分配完成的订单推进到待派车。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail prepareDispatch(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.Command request) {
|
||||
@@ -320,24 +306,28 @@ public class UsTruckOrderService {
|
||||
PENDING_DISPATCH, request);
|
||||
}
|
||||
|
||||
/** 确认订单已派车并记录操作历史。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail dispatch(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.Command request) {
|
||||
return command(customerId, orderNo, operatorId, "DISPATCH", PENDING_DISPATCH, DISPATCHED, request);
|
||||
}
|
||||
|
||||
/** 确认订单进入运输阶段。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail startTransit(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.Command request) {
|
||||
return command(customerId, orderNo, operatorId, "START_TRANSIT", DISPATCHED, IN_TRANSIT, request);
|
||||
}
|
||||
|
||||
/** 确认运输完成并保留幂等结果。 */
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail complete(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.Command request) {
|
||||
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) {
|
||||
@@ -357,22 +347,24 @@ public class UsTruckOrderService {
|
||||
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);
|
||||
repository.updateOrder(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);
|
||||
UsTruckOrderOperation existing = repository.findOperation(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");
|
||||
@@ -380,6 +372,7 @@ public class UsTruckOrderService {
|
||||
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();
|
||||
@@ -392,19 +385,23 @@ public class UsTruckOrderService {
|
||||
operation.setResultVersion(order.getVersion());
|
||||
operation.setCreatedBy(operatorId);
|
||||
operation.setCreatedAt(now);
|
||||
operations.insert(operation);
|
||||
repository.insertOperation(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(); fleetPool.requireStandalone(customerId, "US_TRUCK", o.getId()); return o; }
|
||||
/** 按客户范围锁单并校验独立订单资格。 */
|
||||
private UsTruckOrder lock(String customerId, String orderNo) { UsTruckOrder o = repository.lockNo(customerId, orderNo); if (o == null) throw notFound(); fleetPool.requireStandalone(customerId, "US_TRUCK", o.getId()); 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) {
|
||||
List<UsTruckOrderResponses.Pallet> palletDetails = pallets.listOrder(o.getCustomerId(), o.getId()).stream()
|
||||
List<UsTruckOrderResponses.Pallet> palletDetails = repository.pallets(o.getCustomerId(), o.getId()).stream()
|
||||
.map(this::pallet)
|
||||
.toList();
|
||||
return new UsTruckOrderResponses.Detail(summary(o), o.getOriginAddress(), o.getDestinationAddress(),
|
||||
@@ -413,30 +410,37 @@ public class UsTruckOrderService {
|
||||
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 com.baiyue.lastleg.fleet.business.FleetPoolOrderModels.OrderKey poolKey(UsTruckOrder o) { return new com.baiyue.lastleg.fleet.business.FleetPoolOrderModels.OrderKey(o.getCustomerId(), "US_TRUCK", o.getId()); }
|
||||
/** 转换需求字段与承运安排摘要。 */
|
||||
private UsTruckOrderResponses.Summary summary(UsTruckOrder o) { return summary(o, fleetPool.arrangement(poolKey(o))); }
|
||||
private UsTruckOrderResponses.Summary summary(UsTruckOrder o, com.baiyue.lastleg.fleet.business.FleetPoolOrderModels.Arrangement arrangement) { 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(), arrangement); }
|
||||
/** 转换需求字段与承运安排摘要。 */
|
||||
private UsTruckOrderResponses.Summary summary(UsTruckOrder o, com.baiyue.lastleg.fleet.business.FleetPoolOrderModels.Arrangement arrangement) { return new UsTruckOrderResponses.Summary(o.getId(), o.getCustomerId(), o.getOrderNo(), o.getVehicleType(), o.getOrigin(), o.getDestination(), value(o.getPalletCount()), value(o.getPackageCount()), value(o.getReceivedPalletCount()), value(o.getAllocatedPalletCount()), o.getTotalWeightKg(), o.getTotalVolumeCbm(), o.getBudgetAmount(), o.getCurrency(), o.getOriginContactName(), o.getOriginContactPhone(), o.getDestinationContactName(), o.getDestinationContactPhone(), o.getPickupAt(), o.getDeliveryAt(), o.getStatus(), value(o.getVersion()), o.getCreatedAt(), o.getUpdatedAt(), arrangement); }
|
||||
/** 保留订单状态变更和操作人证据。 */
|
||||
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);
|
||||
h.setCreatedAt(at); repository.insertHistory(h);
|
||||
}
|
||||
/** 仅为已知的预报数量建立待收托盘。 */
|
||||
private void createPlannedPallets(UsTruckOrder order) {
|
||||
int existing = pallets.listOrder(order.getCustomerId(), order.getId()).size();
|
||||
int existing = repository.pallets(order.getCustomerId(), order.getId()).size();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (int sequence = existing + 1; sequence <= value(order.getPalletCount()); sequence++) {
|
||||
UsTruckOrderPallet pallet = newPallet(order, sequence, "ORDER_PLAN", now);
|
||||
pallet.setId(IdWorker.getId());
|
||||
pallets.insert(pallet);
|
||||
repository.insertPallet(pallet);
|
||||
}
|
||||
}
|
||||
/** 建立指定序号的订单托盘记录。 */
|
||||
private UsTruckOrderPallet newPallet(UsTruckOrder order, int sequence, String source, LocalDateTime now) {
|
||||
UsTruckOrderPallet pallet = new UsTruckOrderPallet();
|
||||
pallet.setOrderId(order.getId()); pallet.setCustomerId(order.getCustomerId());
|
||||
@@ -445,17 +449,45 @@ public class UsTruckOrderService {
|
||||
pallet.setCreatedAt(now); pallet.setUpdatedAt(now);
|
||||
return pallet;
|
||||
}
|
||||
private void validateQuery(UsTruckOrderRequests.Query q) { if (StringUtils.hasText(q.status()) && !STATUSES.contains(norm(q.status()))) throw badRequest("Unsupported truck order status"); if (StringUtils.hasText(q.transportType())) validateTransport(q.transportType()); if (q.requestedFrom() != null && q.requestedTo() != null && q.requestedFrom().isAfter(q.requestedTo())) throw badRequest("requestedFrom must not be after requestedTo"); }
|
||||
private void validateTransport(String v) { if (!TRANSPORT_TYPES.contains(norm(v))) throw badRequest("Unsupported transport type"); }
|
||||
/** 校验状态、车型及取货时间筛选区间。 */
|
||||
private void validateQuery(UsTruckOrderRequests.Query q) {
|
||||
if (StringUtils.hasText(q.status()) && !STATUSES.contains(norm(q.status()))) throw badRequest("Unsupported truck order status");
|
||||
if (StringUtils.hasText(q.vehicleType()) && !VEHICLE_TYPES.contains(q.vehicleType())) throw badRequest("请选择有效的车辆类型");
|
||||
if (q.pickupFrom() != null && q.pickupTo() != null && q.pickupFrom().isAfter(q.pickupTo())) throw badRequest("取货时间起点不能晚于终点");
|
||||
}
|
||||
|
||||
/** 校验两端必填资料和先取后送的时间顺序;不进行时区转换。 */
|
||||
private void validateRequirements(String vehicleType, String originName, String originPhone,
|
||||
String destinationName, String destinationPhone,
|
||||
LocalDateTime pickupAt, LocalDateTime deliveryAt) {
|
||||
if (vehicleType == null || !VEHICLE_TYPES.contains(vehicleType)) throw badRequest("请选择有效的车辆类型");
|
||||
if (!StringUtils.hasText(originName) || !StringUtils.hasText(originPhone)
|
||||
|| !StringUtils.hasText(destinationName) || !StringUtils.hasText(destinationPhone)) throw badRequest("请完整填写起运地和目的地联系人及联系电话");
|
||||
if (pickupAt == null || deliveryAt == null) throw badRequest("请填写取货时间和送货时间");
|
||||
if (deliveryAt.isBefore(pickupAt)) throw badRequest("送货时间不能早于取货时间");
|
||||
}
|
||||
/** 计算请求摘要以校验幂等重放。 */
|
||||
private String hash(Object v) { try { return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(objectMapper.writeValueAsBytes(v))); } catch (JsonProcessingException | NoSuchAlgorithmException e) { throw new IllegalStateException("Cannot hash truck request", e); } }
|
||||
/** 推进订单版本和更新时间。 */
|
||||
private static void touch(UsTruckOrder o) { o.setVersion(value(o.getVersion()) + 1); o.setUpdatedAt(LocalDateTime.now()); }
|
||||
/** 仅更新非空业务文字。 */
|
||||
private static void setText(String v, Consumer<String> c) { if (StringUtils.hasText(v)) c.accept(v.trim()); }
|
||||
/** 更新请求明确携带的可选文字。 */
|
||||
private static void setPresent(String v, Consumer<String> c) { if (v != null) c.accept(trim(v)); }
|
||||
/** 统一处理可选文字空值。 */
|
||||
private static String trim(String v) { return v == null || v.isBlank() ? null : v.trim(); }
|
||||
/** 统一状态与币种编码大小写。 */
|
||||
private static String norm(String v) { return v.trim().toUpperCase(); }
|
||||
/** 未指定币种时使用美元。 */
|
||||
private static String defaultCurrency(String v) { return StringUtils.hasText(v) ? norm(v) : "USD"; }
|
||||
private static int value(Integer v) { return v == null ? 0 : v; } private static long value(Long v) { return v == null ? 0 : v; }
|
||||
/** 将计数空值转换为零。 */
|
||||
private static int value(Integer v) { return v == null ? 0 : v; }
|
||||
/** 将计数空值转换为零。 */
|
||||
private static long value(Long v) { return v == null ? 0 : v; }
|
||||
/** 构造请求校验错误。 */
|
||||
private static ResponseStatusException badRequest(String m) { return new ResponseStatusException(HttpStatus.BAD_REQUEST, m); }
|
||||
/** 构造状态或并发冲突错误。 */
|
||||
private static ResponseStatusException conflict(String m) { return new ResponseStatusException(HttpStatus.CONFLICT, m); }
|
||||
/** 构造客户范围内订单不存在错误。 */
|
||||
private static ResponseStatusException notFound() { return new ResponseStatusException(HttpStatus.NOT_FOUND, "US truck order not found"); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user