优化
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
package com.baiyue.lastleg.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_event_outbox")
|
||||
public class LastlegEventOutbox {
|
||||
@TableId(type = IdType.INPUT)
|
||||
private Long id;
|
||||
private String customerId;
|
||||
private String aggregateType;
|
||||
private String aggregateId;
|
||||
private String eventType;
|
||||
private Integer eventVersion;
|
||||
private String eventKey;
|
||||
private String payloadJson;
|
||||
private String status;
|
||||
private Integer retryCount;
|
||||
private LocalDateTime nextRetryAt;
|
||||
private LocalDateTime publishedAt;
|
||||
private String lastError;
|
||||
private LocalDateTime createdAt;
|
||||
}
|
||||
@@ -56,6 +56,7 @@ public class LastlegOrder {
|
||||
private String warehouseRemark;
|
||||
private String sourceEventId;
|
||||
private String sourceLoadingPlanId;
|
||||
private String sourceContainerBookingId;
|
||||
private Long sourceLoadingPlanVersion;
|
||||
private Long sourceHandoverVersion;
|
||||
private String sourcePayloadHash;
|
||||
|
||||
+9
@@ -105,6 +105,15 @@ public class InboundPdaController {
|
||||
return ApiResponse.success(service.complete(scope(user, customerId), planNo));
|
||||
}
|
||||
|
||||
@PostMapping("/unload-photos")
|
||||
public ApiResponse<PdaResponses.Task> uploadUnloadPhotos(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@PathVariable String planNo,
|
||||
@RequestParam(required = false) String customerId,
|
||||
@Valid @RequestBody PdaRequests.UnloadPhotos request) {
|
||||
requireOperate(user);
|
||||
return ApiResponse.success(service.uploadUnloadPhotos(scope(user, customerId), planNo, request));
|
||||
}
|
||||
|
||||
private String scope(AuthenticatedUser user, String requestedCustomer) {
|
||||
if (user == null || user.tenantId() == null) throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
|
||||
if ("PLATFORM".equals(user.appCode()) && user.tenantId() == 0L) {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.baiyue.lastleg.inboundplan.pda.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class PdaRequests {
|
||||
private PdaRequests() {}
|
||||
|
||||
@@ -22,4 +25,7 @@ public final class PdaRequests {
|
||||
|
||||
public record RemoveCarton(@NotBlank String cartonMark, @NotBlank String idempotencyKey,
|
||||
String operatorId, String operatorName) {}
|
||||
|
||||
public record UnloadPhotos(@NotEmpty List<@Positive Long> fileIds, @NotBlank String idempotencyKey,
|
||||
String operatorId, String operatorName) {}
|
||||
}
|
||||
|
||||
+3
-1
@@ -8,7 +8,9 @@ public final class PdaResponses {
|
||||
private PdaResponses() {}
|
||||
|
||||
public record Task(long taskId, String taskNo, String planNo, String status,
|
||||
LocalDateTime pushedAt, LocalDateTime startedAt, LocalDateTime completedAt) {}
|
||||
LocalDateTime pushedAt, LocalDateTime startedAt, LocalDateTime completedAt,
|
||||
List<Long> unloadPhotoFileIds, LocalDateTime unloadPhotoUploadedAt,
|
||||
LocalDateTime estimatedEmptyReturnAt) {}
|
||||
|
||||
public record Operation(String result, long allocationId, int requiredPackageCount,
|
||||
int countedPackageCount, int pickedPackageCount,
|
||||
|
||||
+3
@@ -21,6 +21,9 @@ public class InboundPdaTask {
|
||||
private LocalDateTime pushedAt;
|
||||
private LocalDateTime startedAt;
|
||||
private LocalDateTime completedAt;
|
||||
private String unloadPhotoFileIds;
|
||||
private LocalDateTime unloadPhotoUploadedAt;
|
||||
private LocalDateTime estimatedEmptyReturnAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
private Integer version;
|
||||
|
||||
+39
-1
@@ -17,6 +17,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -44,6 +45,7 @@ public class InboundPdaWorkflowService {
|
||||
@Resource private PdaWorkflowRepository repository;
|
||||
@Resource private WarehouseResourceGateway warehouse;
|
||||
@Resource private InboundStockReceiptService stockReceipts;
|
||||
@Resource private InboundUnloadCompletedOutboxService unloadCompletedOutbox;
|
||||
@Resource private ObjectMapper sourceObjectMapper;
|
||||
|
||||
private ObjectMapper objectMapper;
|
||||
@@ -372,6 +374,11 @@ public class InboundPdaWorkflowService {
|
||||
InboundPdaTask task = repository.lockTask(customerId, plan.getId());
|
||||
if (task == null) throw conflict("Push the inbound plan to PDA before operating");
|
||||
if ("COMPLETED".equals(task.getStatus())) return task(task, planNo);
|
||||
List<Long> unloadPhotoFileIds = unloadPhotoFileIds(task);
|
||||
if (unloadPhotoFileIds.isEmpty() || task.getUnloadPhotoUploadedAt() == null
|
||||
|| task.getEstimatedEmptyReturnAt() == null) {
|
||||
throw conflict("Upload unload photos before completing the PDA inbound task");
|
||||
}
|
||||
Map<Long, Integer> pickedByLine = new HashMap<>();
|
||||
List<InboundPlanPalletAllocation> allocations = repository.allocations(plan.getId());
|
||||
for (InboundPlanPalletAllocation allocation : allocations) {
|
||||
@@ -402,9 +409,30 @@ public class InboundPdaWorkflowService {
|
||||
plan.setStatus("COMPLETED");
|
||||
plan.setUpdatedAt(now);
|
||||
repository.update(plan);
|
||||
unloadCompletedOutbox.publish(plan, task, unloadPhotoFileIds);
|
||||
return task(task, planNo);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public PdaResponses.Task uploadUnloadPhotos(String customerId, String planNo, PdaRequests.UnloadPhotos request) {
|
||||
InboundPlan plan = requirePlan(customerId, planNo);
|
||||
InboundPdaOperation replay = replay(customerId, "UPLOAD_UNLOAD_PHOTOS", request.idempotencyKey(), request);
|
||||
if (replay != null) return read(replay, PdaResponses.Task.class);
|
||||
InboundPdaTask task = requireActiveTask(customerId, plan);
|
||||
List<Long> fileIds = request.fileIds().stream().distinct().sorted().toList();
|
||||
if (fileIds.size() != request.fileIds().size()) throw conflict("Unload photo file IDs cannot be duplicated");
|
||||
LocalDateTime uploadedAt = LocalDateTime.now();
|
||||
task.setUnloadPhotoFileIds(write(fileIds));
|
||||
task.setUnloadPhotoUploadedAt(uploadedAt);
|
||||
task.setEstimatedEmptyReturnAt(uploadedAt.plusDays(2));
|
||||
task.setUpdatedAt(uploadedAt);
|
||||
task.setVersion(value(task.getVersion()) + 1);
|
||||
repository.update(task);
|
||||
PdaResponses.Task result = task(task, planNo);
|
||||
saveOperation(customerId, task.getId(), null, "UPLOAD_UNLOAD_PHOTOS", request.idempotencyKey(), request, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void moveSourceAllocation(Long planId, InboundPlanPalletAllocation source, Long targetPalletId,
|
||||
int packageCount, BigDecimal movedVolume) {
|
||||
source.setPickedPackageCount(value(source.getPickedPackageCount()) - packageCount);
|
||||
@@ -582,7 +610,17 @@ public class InboundPdaWorkflowService {
|
||||
|
||||
private PdaResponses.Task task(InboundPdaTask task, String planNo) {
|
||||
return new PdaResponses.Task(task.getId(), task.getTaskNo(), planNo, task.getStatus(),
|
||||
task.getPushedAt(), task.getStartedAt(), task.getCompletedAt());
|
||||
task.getPushedAt(), task.getStartedAt(), task.getCompletedAt(), unloadPhotoFileIds(task),
|
||||
task.getUnloadPhotoUploadedAt(), task.getEstimatedEmptyReturnAt());
|
||||
}
|
||||
|
||||
private List<Long> unloadPhotoFileIds(InboundPdaTask task) {
|
||||
if (task.getUnloadPhotoFileIds() == null || task.getUnloadPhotoFileIds().isBlank()) return List.of();
|
||||
try {
|
||||
return objectMapper.readValue(task.getUnloadPhotoFileIds(), new TypeReference<List<Long>>() {});
|
||||
} catch (JsonProcessingException exception) {
|
||||
throw new IllegalStateException("Cannot read unload photo file IDs", exception);
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal perPackageVolume(InboundPlanLine line) {
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
package com.baiyue.lastleg.inboundplan.pda.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
|
||||
import com.baiyue.common.event.EventEnvelope;
|
||||
import com.baiyue.common.event.EventIdentifiers;
|
||||
import com.baiyue.common.event.EventPayloadCanonicalizer;
|
||||
import com.baiyue.common.event.EventPayloadHash;
|
||||
import com.baiyue.common.event.EventTypes;
|
||||
import com.baiyue.common.event.OverseasContainerUnloadedV1;
|
||||
import com.baiyue.common.event.RegionCode;
|
||||
import com.baiyue.lastleg.entity.LastlegEventOutbox;
|
||||
import com.baiyue.lastleg.entity.LastlegOrder;
|
||||
import com.baiyue.lastleg.inboundplan.entity.InboundPlan;
|
||||
import com.baiyue.lastleg.inboundplan.pda.entity.InboundPdaTask;
|
||||
import com.baiyue.lastleg.mapper.LastlegEventOutboxMapper;
|
||||
import com.baiyue.lastleg.mapper.LastlegOrderMapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
|
||||
/** Writes the overseas-to-CN handover to the local transactional outbox. */
|
||||
@Service
|
||||
public class InboundUnloadCompletedOutboxService {
|
||||
@Resource private LastlegEventOutboxMapper mapper;
|
||||
@Resource private LastlegOrderMapper orders;
|
||||
@Resource private ObjectMapper objectMapper;
|
||||
|
||||
public void publish(InboundPlan plan, InboundPdaTask task, List<Long> photoFileIds) {
|
||||
LastlegOrder sourceOrder = orders.selectById(plan.getSourceOrderId());
|
||||
if (sourceOrder == null || sourceOrder.getSourceContainerBookingId() == null
|
||||
|| sourceOrder.getSourceContainerBookingId().isBlank()) {
|
||||
throw new IllegalStateException("Inbound plan has no source container booking ID");
|
||||
}
|
||||
Instant photoUploadedAt = task.getUnloadPhotoUploadedAt().toInstant(ZoneOffset.UTC);
|
||||
var payload = new OverseasContainerUnloadedV1(sourceOrder.getSourceContainerBookingId(),
|
||||
plan.getContainerNo(), photoUploadedAt, photoUploadedAt, null);
|
||||
JsonNode payloadNode = objectMapper.valueToTree(payload);
|
||||
long aggregateVersion = Math.max(1, task.getVersion() == null ? 1 : task.getVersion());
|
||||
String eventKey = EventTypes.OVERSEAS_CONTAINER_UNLOADED + ':' + task.getId();
|
||||
String traceId = EventIdentifiers.newUuid();
|
||||
EventEnvelope envelope = new EventEnvelope(EventIdentifiers.newUuid(), EventTypes.OVERSEAS_CONTAINER_UNLOADED,
|
||||
1, Instant.now(), RegionCode.OVERSEAS, RegionCode.CN, task.getCustomerId(), "CONTAINER_BOOKING",
|
||||
sourceOrder.getSourceContainerBookingId(), aggregateVersion, 1L, traceId, eventKey, null, payloadNode,
|
||||
EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(payloadNode))).requireValid();
|
||||
|
||||
LastlegEventOutbox event = new LastlegEventOutbox();
|
||||
event.setId(IdWorker.getId()); event.setCustomerId(task.getCustomerId());
|
||||
event.setAggregateType(envelope.aggregateType()); event.setAggregateId(envelope.aggregateId());
|
||||
event.setEventType(envelope.eventType()); event.setEventVersion(envelope.eventVersion());
|
||||
event.setEventKey(eventKey); event.setPayloadJson(json(envelope)); event.setStatus("PENDING");
|
||||
event.setRetryCount(0); event.setCreatedAt(LocalDateTime.now(ZoneOffset.UTC));
|
||||
mapper.insert(event);
|
||||
}
|
||||
|
||||
private String json(Object value) {
|
||||
try { return objectMapper.writeValueAsString(value); }
|
||||
catch (JsonProcessingException exception) {
|
||||
throw new IllegalStateException("Cannot serialize unload-completed event", exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.baiyue.lastleg.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.lastleg.entity.LastlegEventOutbox;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
@Mapper
|
||||
public interface LastlegEventOutboxMapper extends BaseMapper<LastlegEventOutbox> {
|
||||
}
|
||||
+2
-1
@@ -120,7 +120,8 @@ public class LoadingCompletedConsumer {
|
||||
order.setVolumeWeightKg(source.items().stream().map(FirstLegLoadingPlanCompletedV1.Item::dimensionalWeightKg)
|
||||
.filter(Objects::nonNull).reduce(BigDecimal.ZERO,BigDecimal::add));
|
||||
order.setCurrentStatus("PENDING_ASSIGNMENT"); order.setSourceEventId(event.eventId());
|
||||
order.setSourceLoadingPlanId(payload.loadingPlanId()); order.setSourceLoadingPlanVersion(event.aggregateVersion());
|
||||
order.setSourceLoadingPlanId(payload.loadingPlanId()); order.setSourceContainerBookingId(payload.containerBookingId());
|
||||
order.setSourceLoadingPlanVersion(event.aggregateVersion());
|
||||
order.setSourceHandoverVersion(event.handoverVersion()); order.setSourcePayloadHash(orderHash);
|
||||
order.setCreatedAt(LocalDateTime.now()); order.setUpdatedAt(LocalDateTime.now());
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user