优化
This commit is contained in:
+69
@@ -75,6 +75,15 @@ public class PlatformUsTruckOrderController {
|
||||
return ApiResponse.success(service.changeStatus(requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/review")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
public ApiResponse<UsTruckOrderResponses.Detail> review(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam String customerId, @PathVariable String orderNo,
|
||||
@Valid @RequestBody UsTruckOrderRequests.Review request) {
|
||||
requirePermission(user, OverseasPermissions.TRUCK_STATUS);
|
||||
return ApiResponse.success(service.review(requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/receive-pallets")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
@Operation(summary = "Receive pallets for a local truck order", description = "Required authority: " + OverseasPermissions.TRUCK_STATUS)
|
||||
@@ -85,6 +94,66 @@ public class PlatformUsTruckOrderController {
|
||||
return ApiResponse.success(service.receivePallets(requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/review-pallet-overage")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
public ApiResponse<UsTruckOrderResponses.Detail> reviewPalletOverage(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody UsTruckOrderRequests.ReviewPalletOverage request) {
|
||||
requirePermission(user, OverseasPermissions.TRUCK_STATUS);
|
||||
return ApiResponse.success(service.reviewPalletOverage(
|
||||
requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/allocate-pallets")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
public ApiResponse<UsTruckOrderResponses.Detail> allocatePallets(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody UsTruckOrderRequests.AllocatePallets request) {
|
||||
requirePermission(user, OverseasPermissions.TRUCK_STATUS);
|
||||
return ApiResponse.success(service.allocatePallets(
|
||||
requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/prepare-dispatch")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
public ApiResponse<UsTruckOrderResponses.Detail> prepareDispatch(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody UsTruckOrderRequests.Command request) {
|
||||
requirePermission(user, OverseasPermissions.TRUCK_STATUS);
|
||||
return ApiResponse.success(service.prepareDispatch(
|
||||
requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/dispatch")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
public ApiResponse<UsTruckOrderResponses.Detail> dispatch(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody UsTruckOrderRequests.Command request) {
|
||||
requirePermission(user, OverseasPermissions.TRUCK_STATUS);
|
||||
return ApiResponse.success(service.dispatch(
|
||||
requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/start-transit")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
public ApiResponse<UsTruckOrderResponses.Detail> startTransit(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody UsTruckOrderRequests.Command request) {
|
||||
requirePermission(user, OverseasPermissions.TRUCK_STATUS);
|
||||
return ApiResponse.success(service.startTransit(
|
||||
requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
@PostMapping("/{orderNo}/complete")
|
||||
@PreAuthorize("hasAuthority('" + OverseasPermissions.TRUCK_STATUS + "')")
|
||||
public ApiResponse<UsTruckOrderResponses.Detail> complete(
|
||||
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam String customerId,
|
||||
@PathVariable String orderNo, @Valid @RequestBody UsTruckOrderRequests.Command request) {
|
||||
requirePermission(user, OverseasPermissions.TRUCK_STATUS);
|
||||
return ApiResponse.success(service.complete(
|
||||
requiredCustomer(customerId), orderNo, operator(user), request));
|
||||
}
|
||||
|
||||
private void requirePlatform(AuthenticatedUser user) {
|
||||
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
if (!"PLATFORM".equals(user.appCode()) || user.tenantId() != 0L) {
|
||||
|
||||
+28
-1
@@ -4,7 +4,10 @@ import jakarta.validation.constraints.DecimalMin;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
@@ -71,7 +74,31 @@ public final class UsTruckOrderRequests {
|
||||
|
||||
public record ChangeStatus(
|
||||
@NotNull @Min(0) Long version,
|
||||
@NotBlank @Size(max = 32) String status,
|
||||
@NotBlank @Pattern(regexp = "CANCELLED") String status,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record Review(
|
||||
@NotNull @Min(0) Long version,
|
||||
@NotBlank @Pattern(regexp = "APPROVE|REJECT") String decision,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record ReviewPalletOverage(
|
||||
@NotNull @Min(0) Long version,
|
||||
@NotBlank @Pattern(regexp = "APPROVE|REJECT") String decision,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record AllocatePallets(
|
||||
@NotNull @Min(0) Long version,
|
||||
@NotEmpty @Size(max = 500) List<@NotNull @Positive Long> palletIds,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record Command(
|
||||
@NotNull @Min(0) Long version,
|
||||
@NotBlank @Size(max = 255) String idempotencyKey,
|
||||
@Size(max = 1000) String remark) {}
|
||||
|
||||
public record ReceivePallets(
|
||||
|
||||
+3
-2
@@ -11,13 +11,14 @@ public final class UsTruckOrderResponses {
|
||||
public record Summary(
|
||||
Long id, String customerId, String orderNo, String transportType,
|
||||
String origin, String destination, String containerType, int palletCount, int packageCount,
|
||||
int receivedPalletCount, int allocatedPalletCount, int shippedPalletCount,
|
||||
int receivedPalletCount, int allocatedPalletCount,
|
||||
BigDecimal totalWeightKg, BigDecimal totalVolumeCbm, BigDecimal budgetAmount,
|
||||
String currency, String contactName, String contactInfo, LocalDate requestedDate,
|
||||
String status, long version, LocalDateTime createdAt, LocalDateTime updatedAt) {}
|
||||
|
||||
public record History(
|
||||
String fromStatus, String toStatus, String operatorId, String remark, LocalDateTime createdAt) {}
|
||||
String operationType, String fromStatus, String toStatus, String operatorId,
|
||||
String idempotencyKey, String remark, LocalDateTime createdAt) {}
|
||||
|
||||
public record Pallet(Long id, String palletNo, int sequenceNo, String source, String status,
|
||||
Long warehousePalletId, Integer packageCount, LocalDateTime receivedAt) {}
|
||||
|
||||
@@ -25,7 +25,6 @@ public class UsTruckOrder {
|
||||
private Integer packageCount;
|
||||
private Integer receivedPalletCount;
|
||||
private Integer allocatedPalletCount;
|
||||
private Integer shippedPalletCount;
|
||||
private BigDecimal totalWeightKg;
|
||||
private BigDecimal totalVolumeCbm;
|
||||
private BigDecimal budgetAmount;
|
||||
@@ -37,8 +36,6 @@ public class UsTruckOrder {
|
||||
private String remark;
|
||||
private String idempotencyKey;
|
||||
private String requestHash;
|
||||
private String receiptIdempotencyKey;
|
||||
private String receiptRequestHash;
|
||||
private String createdBy;
|
||||
private Long version;
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.baiyue.lastleg.trucking.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@TableName("ll_us_truck_order_operation")
|
||||
public class UsTruckOrderOperation {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private Long orderId;
|
||||
private String customerId;
|
||||
private String operationType;
|
||||
private String idempotencyKey;
|
||||
private String requestHash;
|
||||
private Long resultVersion;
|
||||
private String createdBy;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
+2
@@ -13,9 +13,11 @@ public class UsTruckOrderStatusHistory {
|
||||
@TableId(type = IdType.INPUT) private Long id;
|
||||
private Long orderId;
|
||||
private String customerId;
|
||||
private String operationType;
|
||||
private String fromStatus;
|
||||
private String toStatus;
|
||||
private String operatorId;
|
||||
private String idempotencyKey;
|
||||
private String remark;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.baiyue.lastleg.trucking.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.trucking.entity.UsTruckOrderOperation;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
@Mapper
|
||||
public interface UsTruckOrderOperationMapper extends BaseMapper<UsTruckOrderOperation> {
|
||||
@Select("SELECT * FROM ll_us_truck_order_operation WHERE customer_id=#{customerId} AND order_id=#{orderId} AND idempotency_key=#{key} LIMIT 1")
|
||||
UsTruckOrderOperation findKey(@Param("customerId") String customerId,
|
||||
@Param("orderId") Long orderId,
|
||||
@Param("key") String key);
|
||||
}
|
||||
+136
-38
@@ -7,9 +7,11 @@ import com.baiyue.common.core.api.PageResult;
|
||||
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;
|
||||
@@ -30,6 +32,7 @@ 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;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
@@ -55,20 +58,12 @@ public class UsTruckOrderService {
|
||||
private static final Set<String> STATUSES = Set.of(PENDING_REVIEW, WAITING_INBOUND, RECEIVING,
|
||||
WAITING_CONSOLIDATION, PALLET_OVERAGE_REVIEW, PARTIALLY_ALLOCATED, ALLOCATED,
|
||||
PENDING_DISPATCH, DISPATCHED, IN_TRANSIT, COMPLETED, REJECTED, CANCELLED);
|
||||
private static final java.util.Map<String, Set<String>> TRANSITIONS = java.util.Map.ofEntries(
|
||||
java.util.Map.entry(PENDING_REVIEW, Set.of(WAITING_INBOUND, REJECTED, CANCELLED)),
|
||||
java.util.Map.entry(WAITING_INBOUND, Set.of(RECEIVING, CANCELLED)),
|
||||
java.util.Map.entry(RECEIVING, Set.of(WAITING_CONSOLIDATION, PALLET_OVERAGE_REVIEW, CANCELLED)),
|
||||
java.util.Map.entry(PALLET_OVERAGE_REVIEW, Set.of(WAITING_CONSOLIDATION, CANCELLED)),
|
||||
java.util.Map.entry(WAITING_CONSOLIDATION, Set.of(PARTIALLY_ALLOCATED, ALLOCATED, CANCELLED)),
|
||||
java.util.Map.entry(PARTIALLY_ALLOCATED, Set.of(ALLOCATED, CANCELLED)),
|
||||
java.util.Map.entry(ALLOCATED, Set.of(PENDING_DISPATCH, CANCELLED)),
|
||||
java.util.Map.entry(PENDING_DISPATCH, Set.of(DISPATCHED, CANCELLED)),
|
||||
java.util.Map.entry(DISPATCHED, Set.of(IN_TRANSIT)),
|
||||
java.util.Map.entry(IN_TRANSIT, Set.of(COMPLETED)));
|
||||
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");
|
||||
|
||||
@Resource private UsTruckOrderMapper orders;
|
||||
@Resource private UsTruckOrderOperationMapper operations;
|
||||
@Resource private UsTruckOrderPalletMapper pallets;
|
||||
@Resource private UsTruckOrderStatusHistoryMapper histories;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
@@ -90,7 +85,7 @@ public class UsTruckOrderService {
|
||||
order.setDestination(trim(request.destination())); order.setDestinationAddress(trim(request.destinationAddress()));
|
||||
order.setContainerType(StringUtils.hasText(request.containerType()) ? norm(request.containerType()) : null);
|
||||
order.setPalletCount(request.palletCount()); order.setPackageCount(value(request.packageCount()));
|
||||
order.setReceivedPalletCount(0); order.setAllocatedPalletCount(0); order.setShippedPalletCount(0);
|
||||
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());
|
||||
@@ -103,7 +98,7 @@ public class UsTruckOrderService {
|
||||
if (concurrent != null) return replay(customerId, concurrent, hash);
|
||||
throw e;
|
||||
}
|
||||
history(order, null, PENDING_REVIEW, operatorId, request.remark(), now);
|
||||
history(order, "CREATE", null, PENDING_REVIEW, operatorId, key, request.remark(), now);
|
||||
return detail(customerId, order.getOrderNo());
|
||||
}
|
||||
|
||||
@@ -150,7 +145,7 @@ public class UsTruckOrderService {
|
||||
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);
|
||||
history(order, order.getStatus(), order.getStatus(), operatorId, "UPDATE", LocalDateTime.now());
|
||||
history(order, "UPDATE", order.getStatus(), order.getStatus(), operatorId, null, "UPDATE", LocalDateTime.now());
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
|
||||
@@ -158,34 +153,37 @@ public class UsTruckOrderService {
|
||||
public UsTruckOrderResponses.Detail changeStatus(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.ChangeStatus request) {
|
||||
UsTruckOrder order = lock(customerId, orderNo);
|
||||
if (!request.version().equals(value(order.getVersion()))) throw conflict("Order was changed; refresh and retry");
|
||||
String next = norm(request.status()); if (!STATUSES.contains(next)) throw badRequest("Unsupported truck order status");
|
||||
String from = order.getStatus();
|
||||
if (!next.equals(from)) {
|
||||
if (!TRANSITIONS.getOrDefault(from, Set.of()).contains(next)) {
|
||||
throw conflict("Unsupported truck order transition: " + from + " -> " + next);
|
||||
}
|
||||
order.setStatus(next);
|
||||
if (PENDING_REVIEW.equals(from) && WAITING_INBOUND.equals(next)) createPlannedPallets(order);
|
||||
touch(order); orders.updateById(order);
|
||||
history(order, from, next, operatorId, request.remark(), LocalDateTime.now());
|
||||
UsTruckOrderResponses.Detail replay = replayOperation(order, "CANCEL", request.idempotencyKey(), request);
|
||||
if (replay != null) return replay;
|
||||
requireVersion(order, request.version());
|
||||
if (!CANCELLED.equals(norm(request.status())) || !CANCELLABLE.contains(order.getStatus())) {
|
||||
throw conflict("Only cancellable truck orders can use the generic status endpoint");
|
||||
}
|
||||
return detail(customerId, orderNo);
|
||||
return transition(order, operatorId, "CANCEL", request.idempotencyKey(), request,
|
||||
order.getStatus(), CANCELLED, request.remark());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail review(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.Review request) {
|
||||
UsTruckOrder order = lock(customerId, orderNo);
|
||||
UsTruckOrderResponses.Detail replay = replayOperation(order, "REVIEW", request.idempotencyKey(), request);
|
||||
if (replay != null) return replay;
|
||||
requireVersion(order, request.version());
|
||||
if (!PENDING_REVIEW.equals(order.getStatus())) throw conflict("Only pending-review truck orders can be reviewed");
|
||||
String next = "APPROVE".equals(norm(request.decision())) ? WAITING_INBOUND : REJECTED;
|
||||
if (WAITING_INBOUND.equals(next)) createPlannedPallets(order);
|
||||
return transition(order, operatorId, "REVIEW", request.idempotencyKey(), request,
|
||||
PENDING_REVIEW, next, request.remark());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UsTruckOrderResponses.Detail receivePallets(String customerId, String orderNo, String operatorId,
|
||||
UsTruckOrderRequests.ReceivePallets request) {
|
||||
UsTruckOrder order = lock(customerId, orderNo);
|
||||
String key = request.idempotencyKey().trim();
|
||||
String requestHash = hash(request);
|
||||
if (key.equals(order.getReceiptIdempotencyKey())) {
|
||||
if (!requestHash.equals(order.getReceiptRequestHash())) {
|
||||
throw conflict("Receipt idempotency key was reused with different pallet data");
|
||||
}
|
||||
return detail(customerId, orderNo);
|
||||
}
|
||||
if (!request.version().equals(value(order.getVersion()))) throw conflict("Order was changed; refresh and retry");
|
||||
UsTruckOrderResponses.Detail replay = replayOperation(order, "RECEIVE_PALLETS", request.idempotencyKey(), request);
|
||||
if (replay != null) return replay;
|
||||
requireVersion(order, request.version());
|
||||
if (!Set.of(WAITING_INBOUND, RECEIVING).contains(order.getStatus())) {
|
||||
throw conflict("Only waiting or receiving orders can receive pallets");
|
||||
}
|
||||
@@ -220,18 +218,118 @@ public class UsTruckOrderService {
|
||||
}
|
||||
}
|
||||
order.setReceivedPalletCount(actual);
|
||||
order.setReceiptIdempotencyKey(key);
|
||||
order.setReceiptRequestHash(requestHash);
|
||||
String from = order.getStatus();
|
||||
String next = actual > planned ? PALLET_OVERAGE_REVIEW
|
||||
: actual == planned ? WAITING_CONSOLIDATION : RECEIVING;
|
||||
order.setStatus(next);
|
||||
touch(order);
|
||||
orders.updateById(order);
|
||||
history(order, from, next, operatorId, request.remark(), now);
|
||||
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) {
|
||||
UsTruckOrder order = lock(customerId, orderNo);
|
||||
UsTruckOrderResponses.Detail replay = replayOperation(order, "REVIEW_PALLET_OVERAGE",
|
||||
request.idempotencyKey(), request);
|
||||
if (replay != null) return replay;
|
||||
requireVersion(order, request.version());
|
||||
if (!PALLET_OVERAGE_REVIEW.equals(order.getStatus())) {
|
||||
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());
|
||||
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);
|
||||
}
|
||||
if (approve) {
|
||||
accepted = value(order.getReceivedPalletCount());
|
||||
order.setPalletCount(accepted);
|
||||
} else {
|
||||
order.setReceivedPalletCount(accepted);
|
||||
}
|
||||
order.setStatus(WAITING_CONSOLIDATION);
|
||||
touch(order);
|
||||
orders.updateById(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) {
|
||||
UsTruckOrder order = lock(customerId, orderNo);
|
||||
UsTruckOrderResponses.Detail replay = replayOperation(order, "ALLOCATE_PALLETS",
|
||||
request.idempotencyKey(), request);
|
||||
if (replay != null) return replay;
|
||||
requireVersion(order, request.version());
|
||||
if (!Set.of(WAITING_CONSOLIDATION, PARTIALLY_ALLOCATED).contains(order.getStatus())) {
|
||||
throw conflict("Only waiting-consolidation or partially-allocated orders can allocate pallets");
|
||||
}
|
||||
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> 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");
|
||||
}
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (UsTruckOrderPallet pallet : selected) {
|
||||
pallet.setStatus("ALLOCATED");
|
||||
pallet.setUpdatedAt(now);
|
||||
pallets.updateById(pallet);
|
||||
}
|
||||
int allocated = (int) rows.stream().filter(row -> "ALLOCATED".equals(row.getStatus())).count()
|
||||
+ selected.size();
|
||||
int accepted = value(order.getReceivedPalletCount());
|
||||
if (accepted < 1 || allocated > accepted) throw conflict("Allocated pallet count exceeds accepted pallets");
|
||||
String from = order.getStatus();
|
||||
String next = allocated == accepted ? ALLOCATED : PARTIALLY_ALLOCATED;
|
||||
order.setAllocatedPalletCount(allocated);
|
||||
order.setStatus(next);
|
||||
touch(order);
|
||||
orders.updateById(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) {
|
||||
return command(customerId, orderNo, operatorId, "PREPARE_DISPATCH", ALLOCATED,
|
||||
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 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) {
|
||||
|
||||
Reference in New Issue
Block a user