feat(orders): expose customer-owned loaded transport facts

This commit is contained in:
2026-10-05 01:06:11 +08:00
parent 5ad3fec449
commit b000b056d1
15 changed files with 286 additions and 9 deletions
@@ -0,0 +1,17 @@
package com.baiyue.firstleg.dto;
import java.time.LocalDateTime;
import java.util.List;
/** Customer-safe, read-only transport facts for one actually loaded inbound/container fragment. */
public record CustomerTransportFragment(String inboundCode, String cabinetNo, String containerNo,
List<Node> nodes) {
public record Node(String standardNode, Integer nodeSequence, String providerCode,
LocalDateTime estimatedAt, LocalDateTime actualAt,
String locationCode, String locationName) {}
public record Row(Long inboundId, Long containerBookingId, String inboundCode, String cabinetNo,
String containerNo, String standardNode, Integer nodeSequence, String providerCode,
LocalDateTime estimatedAt, LocalDateTime actualAt,
String locationCode, String locationName) {}
}
@@ -6,7 +6,8 @@ import java.util.List;
/** Detail payload for the product-level order detail. */
public record UnifiedInboundDetail(
UnifiedInboundSummary summary,
List<TimelineEvent> timeline
List<TimelineEvent> timeline,
List<CustomerTransportFragment> transportFragments
) {
public record TimelineEvent(String source, String eventType, String fromStatus, String toStatus,
String action, String remark, Long operatorId, String operatorName,
@@ -10,6 +10,29 @@ import java.util.List;
/** 统一订单按当前有效装柜明细批量读取柜次船期。 */
@Mapper
public interface UnifiedInboundTransportMapper {
@Select("""
SELECT owned.inbound_id, owned.container_booking_id, owned.inbound_code,
b.cabinet_no, b.container_no, n.standard_node, n.node_sequence, n.provider_code,
n.estimated_at, n.actual_at, n.location_code, n.location_name
FROM (
SELECT DISTINCT o.id AS inbound_id, p.container_booking_id, o.inbound_code
FROM fl_inbound_order o
JOIN fl_loading_plan_item i ON i.inbound_id=o.id AND i.account_id=o.tenant_id
JOIN fl_loading_plan p ON p.id=i.loading_plan_id AND p.plan_status='COMPLETED'
JOIN fl_loading_pick_task t ON t.loading_plan_id=p.id AND t.task_status='COMPLETED'
JOIN fl_loading_pick_item picked ON picked.pick_task_id=t.id
AND picked.loading_plan_item_id=i.id
WHERE o.tenant_id=#{tenantId} AND o.order_id=#{orderId} AND o.deleted=0
AND i.deleted=0 AND picked.item_status IN ('PICKED','PARTIAL')
AND picked.picked_package_count>0
) owned
JOIN fl_container_booking b ON b.id=owned.container_booking_id
LEFT JOIN fl_carrier_schedule_node_fact n ON n.container_booking_id=owned.container_booking_id
ORDER BY owned.inbound_id, owned.container_booking_id, n.node_sequence, n.id
""")
List<com.baiyue.firstleg.dto.CustomerTransportFragment.Row> findCustomerTransportNodes(
@Param("tenantId") long tenantId, @Param("orderId") long orderId);
@Select("""
<script>
SELECT DISTINCT i.inbound_id, i.account_id, p.container_booking_id,
@@ -86,6 +86,24 @@ public class UnifiedInboundReadRepository {
return findTransports(Map.of(inboundId, tenantId)).get(inboundId);
}
public List<com.baiyue.firstleg.dto.CustomerTransportFragment> findCustomerTransportFragments(
long tenantId, long orderId) {
var grouped = new java.util.LinkedHashMap<String,
List<com.baiyue.firstleg.dto.CustomerTransportFragment.Row>>();
transports.findCustomerTransportNodes(tenantId, orderId).forEach(row ->
grouped.computeIfAbsent(row.inboundId() + ":" + row.containerBookingId(),
ignored -> new java.util.ArrayList<>()).add(row));
return grouped.values().stream().map(rows -> {
var first = rows.get(0);
var nodes = rows.stream().filter(row -> row.standardNode() != null)
.map(row -> new com.baiyue.firstleg.dto.CustomerTransportFragment.Node(
row.standardNode(), row.nodeSequence(), row.providerCode(), row.estimatedAt(),
row.actualAt(), row.locationCode(), row.locationName())).toList();
return new com.baiyue.firstleg.dto.CustomerTransportFragment(first.inboundCode(),
first.cabinetNo(), first.containerNo(), nodes);
}).toList();
}
public Map<Long, CurrentContainerTransport> findTransports(Map<Long, Long> tenantByInbound) {
if (tenantByInbound.isEmpty()) return Map.of();
Map<Long, List<CurrentContainerTransport>> grouped = transports
@@ -83,8 +83,11 @@ public class UnifiedInboundQueryService {
? operations.findTransport(tenantId, inbound.getId()) : null;
boolean customerFeeConfirmationRequired = pricing.customerFeeConfirmationRequiredMap(
tenantId, Map.of(inbound.getId(), inbound.getTenantId())).getOrDefault(inbound.getId(), false);
boolean hasTransportScope = includePhaseThree || inbounds.findByOrderIds(tenantId, List.of(orderId))
.stream().anyMatch(item -> requiresPhaseThreeSummary(mainStatus(item.getProcessStatus())));
return new UnifiedInboundDetail(summary(inbound, detail, preload, pick, evidence, deliveryAt,
transport, null, null, customerFeeConfirmationRequired), timeline);
transport, null, null, customerFeeConfirmationRequired), timeline,
hasTransportScope ? operations.findCustomerTransportFragments(tenantId, orderId) : List.of());
}
private List<UnifiedInboundSummary> summaries(List<InboundOrderEntity> records) {