Files
baiyue-firstleg/firstleg/src/main/java/com/baiyue/firstleg/service/UnifiedInboundQueryService.java
T

236 lines
13 KiB
Java
Raw Normal View History

2026-08-06 13:56:20 +08:00
package com.baiyue.firstleg.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baiyue.common.core.api.PageResult;
import com.baiyue.firstleg.dto.InboundOrderDetail;
import com.baiyue.firstleg.dto.UnifiedInboundDetail;
import com.baiyue.firstleg.dto.UnifiedInboundQuery;
import com.baiyue.firstleg.dto.UnifiedInboundSummary;
import com.baiyue.firstleg.entity.InboundOrderEntity;
2026-08-18 11:32:55 +08:00
import com.baiyue.firstleg.dto.CurrentContainerTransport;
import com.baiyue.firstleg.loading.entity.CabinetLoadingEvidenceEntity;
2026-08-06 13:56:20 +08:00
import com.baiyue.firstleg.repository.InboundOrderRepository;
import com.baiyue.firstleg.repository.UnifiedInboundReadRepository;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
2026-08-07 19:40:42 +08:00
/** Read-only adapter from internal inbound states to the product main statuses. */
2026-08-06 13:56:20 +08:00
@Service
public class UnifiedInboundQueryService {
private static final Set<String> MAIN_STATUSES = Set.of(
2026-08-13 14:37:51 +08:00
"PENDING_REVIEW", "WAIT_MEASURE", "WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED");
2026-08-06 15:43:35 +08:00
private static final Set<String> REVIEW_STATUSES = Set.of("UNRECORDED", "PENDING", "APPROVED", "REJECTED");
2026-08-06 13:56:20 +08:00
@Resource private InboundOrderRepository inbounds;
@Resource private UnifiedInboundReadRepository operations;
@Resource private ObjectMapper json;
public PageResult<UnifiedInboundSummary> page(UnifiedInboundQuery query) {
validate(query);
2026-08-06 15:32:11 +08:00
if ("PENDING_REVIEW".equals(query.mainStatus())
|| query.candidateOrderIds() != null && query.candidateOrderIds().isEmpty()) {
2026-08-06 13:56:20 +08:00
return new PageResult<>(List.of(), query.page(), query.size(), 0, 0);
}
Page<InboundOrderEntity> source = inbounds.pageUnifiedEntities(query, internalStatuses(query.mainStatus()));
List<UnifiedInboundSummary> records = summaries(source.getRecords());
return new PageResult<>(records, source.getCurrent(), source.getSize(), source.getTotal(), source.getPages());
}
public List<UnifiedInboundSummary> summaries(Long tenantId, List<Long> orderIds) {
Map<Long, UnifiedInboundSummary> byOrder = new LinkedHashMap<>();
for (UnifiedInboundSummary item : summaries(inbounds.findByOrderIds(tenantId, orderIds)))
byOrder.putIfAbsent(item.inbound().orderId(), item);
return orderIds.stream().distinct().map(byOrder::get).filter(java.util.Objects::nonNull).toList();
}
public UnifiedInboundDetail detail(long tenantId, long orderId) {
InboundOrderEntity inbound = inbounds.findByOrderId(tenantId, orderId);
if (inbound == null) return null;
2026-08-24 14:42:38 +08:00
applyConfirmedPalletCount(inbound, inbounds.confirmedPalletCountsMap(tenantId,
Map.of(inbound.getId(), inbound.getTenantId())));
2026-08-06 13:56:20 +08:00
InboundOrderDetail detail = inbounds.detail(tenantId, inbound);
String mainStatus = mainStatus(inbound.getProcessStatus());
boolean includePhaseThree = requiresPhaseThreeSummary(mainStatus);
2026-08-06 13:56:20 +08:00
List<UnifiedInboundDetail.TimelineEvent> timeline = detail.statusHistory().stream()
.map(item -> new UnifiedInboundDetail.TimelineEvent(
"FIRSTLEG", "STATUS", item.fromStatus(), item.toStatus(), item.action(), item.reason(),
item.operatorId(), item.operatorName(), item.occurredAt()))
.toList();
// Phase-three tables are optional during the phase-two measurement workflow.
com.baiyue.firstleg.split.entity.PreloadPlanEntity preload = null;
com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity pick = null;
List<CabinetLoadingEvidenceEntity> evidence = List.of();
LocalDateTime deliveryAt = null;
if (includePhaseThree) {
preload = operations.findPreload(tenantId, inbound.getId());
pick = operations.findPick(tenantId, inbound.getId());
evidence = operations.findEvidence(tenantId, inbound.getId());
deliveryAt = deliveryAt(detail.statusHistory());
}
2026-08-18 11:32:55 +08:00
CurrentContainerTransport transport = includePhaseThree
? operations.findTransport(tenantId, inbound.getId()) : null;
2026-08-17 19:51:54 +08:00
return new UnifiedInboundDetail(summary(inbound, detail, preload, pick, evidence, deliveryAt,
transport, null, null), timeline);
2026-08-06 13:56:20 +08:00
}
private List<UnifiedInboundSummary> summaries(List<InboundOrderEntity> records) {
2026-08-08 12:01:45 +08:00
Map<Long, Long> phaseThreeOwners = records.stream()
.filter(inbound -> requiresPhaseThreeSummary(mainStatus(inbound.getProcessStatus())))
.collect(Collectors.toMap(
InboundOrderEntity::getId, InboundOrderEntity::getTenantId, (left, right) -> left));
var preloads = operations.findPreloads(phaseThreeOwners);
var picks = operations.findPicks(phaseThreeOwners);
var evidences = operations.findEvidence(phaseThreeOwners);
var deliveries = operations.findDeliveryHistory(phaseThreeOwners);
var transports = operations.findTransports(phaseThreeOwners);
2026-08-11 20:00:31 +08:00
Map<Long, Long> owners = records.stream().collect(Collectors.toMap(
InboundOrderEntity::getId, InboundOrderEntity::getTenantId, (left, right) -> left));
2026-08-24 14:42:38 +08:00
var confirmedPalletCounts = inbounds.confirmedPalletCountsMap(null, owners);
records.forEach(inbound -> applyConfirmedPalletCount(inbound, confirmedPalletCounts));
2026-08-11 20:00:31 +08:00
var locationCodes = inbounds.locationCodesMap(null, owners);
2026-08-17 19:51:54 +08:00
var pickups = inbounds.pickupMap(null, owners);
2026-08-06 13:56:20 +08:00
return records.stream().map(inbound -> summary(inbound, null, preloads.get(inbound.getId()),
picks.get(inbound.getId()), evidences.getOrDefault(inbound.getId(), List.of()),
deliveries.get(inbound.getId()) == null ? null : deliveries.get(inbound.getId()).getOccurredAt(),
2026-08-17 19:51:54 +08:00
transports.get(inbound.getId()), locationCodes.get(inbound.getId()), pickups.get(inbound.getId()))).toList();
2026-08-06 13:56:20 +08:00
}
2026-08-24 14:42:38 +08:00
private static void applyConfirmedPalletCount(InboundOrderEntity inbound,
Map<Long, Integer> confirmedPalletCounts) {
if ((inbound.getPalletCount() == null || inbound.getPalletCount() <= 0)
&& confirmedPalletCounts.getOrDefault(inbound.getId(), 0) > 0) {
inbound.setPalletCount(confirmedPalletCounts.get(inbound.getId()));
}
}
2026-08-06 13:56:20 +08:00
private UnifiedInboundSummary summary(InboundOrderEntity inbound, InboundOrderDetail detail,
com.baiyue.firstleg.split.entity.PreloadPlanEntity preload,
2026-08-08 11:33:18 +08:00
com.baiyue.firstleg.loading.entity.InboundLoadingPickTaskEntity pick,
List<CabinetLoadingEvidenceEntity> evidence, LocalDateTime deliveryAt,
2026-08-18 11:32:55 +08:00
CurrentContainerTransport transport, String locationCodes,
2026-08-17 19:51:54 +08:00
com.baiyue.firstleg.entity.InboundPickupSnapshotEntity pickup) {
2026-08-06 13:56:20 +08:00
String mainStatus = mainStatus(inbound.getProcessStatus());
List<CabinetLoadingEvidenceEntity> activeEvidence = evidence.stream()
2026-08-10 11:28:57 +08:00
.filter(item -> item.getStatus() == null || "ACTIVE".equals(item.getStatus())).toList();
String sealNo = activeEvidence.stream().filter(item -> "SEAL".equals(item.getEvidenceType()))
.map(CabinetLoadingEvidenceEntity::getSealNo).filter(StringUtils::hasText).findFirst().orElse(null);
2026-08-06 13:56:20 +08:00
var preloadSummary = preload == null ? null : new UnifiedInboundSummary.PreloadSummary(
preload.getStatus(), preload.getVersion(), itemCount(preload.getItemsJson()), preload.getUpdatedAt());
2026-08-10 11:28:57 +08:00
var loadingSummary = pick == null && activeEvidence.isEmpty() ? null : new UnifiedInboundSummary.LoadingSummary(
2026-08-06 13:56:20 +08:00
pick == null ? null : pick.getStatus(), pick == null ? null : pick.getVersion(),
pick == null ? null : pick.getVersion(), pick == null ? null : pick.getExpectedCount(),
2026-08-10 11:28:57 +08:00
pick == null ? null : pick.getPickedCount(), activeEvidence.size(), sealNo,
pick == null ? activeEvidence.stream().map(CabinetLoadingEvidenceEntity::getUpdatedAt)
2026-08-06 13:56:20 +08:00
.filter(java.util.Objects::nonNull).max(LocalDateTime::compareTo).orElse(null) : pick.getUpdatedAt());
var deliverySummary = "LOADED".equals(mainStatus) || "DELIVERED".equals(mainStatus)
? new UnifiedInboundSummary.DeliverySummary(
"DELIVERED".equals(mainStatus) ? "COMPLETED" : "PENDING", deliveryAt,
inbound.getExpressTrackingNo()) : null;
2026-08-18 11:32:55 +08:00
var baseSummary = detail == null ? null : detail.summary();
var inboundSummary = com.baiyue.firstleg.dto.InboundOrderSummary.from(inbound, transport,
pickup == null && baseSummary != null && baseSummary.requestedPickupAt() != null
? pickupSnapshot(baseSummary.requestedPickupAt()) : pickup,
locationCodes == null && baseSummary != null ? baseSummary.locationCodes() : locationCodes);
2026-08-06 13:56:20 +08:00
return new UnifiedInboundSummary(inboundSummary, mainStatus,
nextAction(mainStatus, inbound.getProcessStatus(), preload == null ? null : preload.getStatus()),
preloadSummary, loadingSummary, deliverySummary);
}
2026-08-18 11:32:55 +08:00
private com.baiyue.firstleg.entity.InboundPickupSnapshotEntity pickupSnapshot(LocalDateTime pickupAt) {
var snapshot = new com.baiyue.firstleg.entity.InboundPickupSnapshotEntity();
snapshot.setPickupAt(pickupAt);
return snapshot;
}
2026-08-06 13:56:20 +08:00
private LocalDateTime deliveryAt(List<InboundOrderDetail.StatusHistorySummary> history) {
return history.stream()
.filter(item -> "COMPLETE_DELIVERY".equals(item.action()) || "DELIVERED".equals(item.toStatus()))
.map(InboundOrderDetail.StatusHistorySummary::occurredAt)
.filter(java.util.Objects::nonNull).max(LocalDateTime::compareTo).orElse(null);
}
static String mainStatus(String status) {
if (status == null) return null;
return switch (status) {
2026-08-13 09:49:55 +08:00
case "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING" -> "WAIT_MEASURE";
2026-08-13 14:37:51 +08:00
case "WAIT_SPLIT" -> "WAIT_SPLIT";
case "LOADING", "LOADED", "DELIVERED" -> status;
2026-08-06 13:56:20 +08:00
default -> null;
};
}
static String nextAction(String mainStatus, String processStatus, String preloadStatus) {
if (mainStatus == null) return null;
return switch (mainStatus) {
case "PENDING_REVIEW" -> "APPROVE_ORDER";
case "WAIT_MEASURE" -> "MEASURE_PENDING".equals(processStatus) ? "CONFIRM_MEASUREMENT" : null;
2026-08-13 14:37:51 +08:00
case "WAIT_SPLIT" -> !"WAIT_SPLIT".equals(processStatus) ? null
2026-08-06 13:56:20 +08:00
: "CONFIRMED".equals(preloadStatus) ? null
: preloadStatus == null ? "SMART_SPLIT" : "CONFIRM_PRELOAD";
case "LOADING" -> "COMPLETE_LOADING";
2026-08-10 12:40:47 +08:00
case "LOADED" -> null;
2026-08-06 13:56:20 +08:00
case "DELIVERED" -> null;
default -> null;
};
}
2026-08-08 12:01:45 +08:00
static boolean requiresPhaseThreeSummary(String mainStatus) {
return mainStatus != null && Set.of("LOADING", "LOADED", "DELIVERED").contains(mainStatus);
}
2026-08-06 13:56:20 +08:00
private List<String> internalStatuses(String mainStatus) {
if (!StringUtils.hasText(mainStatus)) return List.of();
return switch (mainStatus) {
2026-08-13 09:49:55 +08:00
case "WAIT_MEASURE" -> List.of("WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",
"WAIT_PRICING", "FEE_CONFIRMING", "INBOUNDING");
case "WAIT_SPLIT" -> List.of("WAIT_SPLIT");
2026-08-07 19:40:42 +08:00
case "LOADING", "LOADED", "DELIVERED" -> List.of(mainStatus);
2026-08-06 13:56:20 +08:00
default -> List.of("__NO_MATCH__");
};
}
private int itemCount(String itemsJson) {
if (!StringUtils.hasText(itemsJson)) return 0;
try {
JsonNode node = json.readTree(itemsJson);
return node.isArray() ? node.size() : 0;
} catch (Exception ignored) {
return 0;
}
}
private void validate(UnifiedInboundQuery query) {
2026-08-09 20:35:11 +08:00
if (StringUtils.hasText(query.mainStatus())
&& !MAIN_STATUSES.contains(query.mainStatus())
2026-08-13 09:49:55 +08:00
) {
2026-08-06 13:56:20 +08:00
throw new com.baiyue.common.core.exception.BizException(
com.baiyue.common.core.api.ResultCode.BAD_REQUEST, "统一订单主状态不合法");
}
if (query.createdFrom() != null && query.createdTo() != null
&& query.createdFrom().isAfter(query.createdTo())) {
throw new com.baiyue.common.core.exception.BizException(
com.baiyue.common.core.api.ResultCode.BAD_REQUEST, "创建时间起点不能晚于终点");
}
2026-08-06 15:43:35 +08:00
if (StringUtils.hasText(query.feeReviewStatus()) && !REVIEW_STATUSES.contains(query.feeReviewStatus())) {
throw new com.baiyue.common.core.exception.BizException(
com.baiyue.common.core.api.ResultCode.BAD_REQUEST, "费用审核状态不合法");
}
if (StringUtils.hasText(query.measurementReviewStatus())
&& !REVIEW_STATUSES.contains(query.measurementReviewStatus())) {
throw new com.baiyue.common.core.exception.BizException(
com.baiyue.common.core.api.ResultCode.BAD_REQUEST, "测量审核状态不合法");
}
2026-08-06 13:56:20 +08:00
}
}