This commit is contained in:
2026-10-09 17:36:24 +08:00
parent 74bf0e73c5
commit 261cdc87cb
27 changed files with 636 additions and 45 deletions
@@ -0,0 +1,25 @@
package com.baiyue.lastleg.controller;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.lastleg.dto.CustomerOrderProgress;
import com.baiyue.lastleg.security.InternalServiceTokenGuard;
import com.baiyue.lastleg.service.CustomerOrderProgressService;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/internal/lastleg/v1/customer-order-progress")
public class InternalCustomerOrderProgressController {
private final InternalServiceTokenGuard guard;
private final CustomerOrderProgressService service;
public InternalCustomerOrderProgressController(InternalServiceTokenGuard guard, CustomerOrderProgressService service) {
this.guard = guard; this.service = service;
}
@PostMapping
public ApiResponse<CustomerOrderProgress.Response> read(
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
@Valid @RequestBody CustomerOrderProgress.Request request) {
guard.verify(token);
return ApiResponse.success(service.read(request.customerId()));
}
}
@@ -0,0 +1,13 @@
package com.baiyue.lastleg.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.List;
public final class CustomerOrderProgress {
private CustomerOrderProgress() {}
public record Request(@NotNull @Positive Long customerId) {}
public record Source(String orderId, long packages, long scheduledPackages, long deliveringPackages) {}
public record Progress(long orderId, String stage, boolean partial) {}
public record Response(List<Progress> progress) {}
}
@@ -0,0 +1,68 @@
package com.baiyue.lastleg.mapper;
import com.baiyue.lastleg.dto.CustomerOrderProgress;
import org.apache.ibatis.annotations.*;
import java.util.List;
/** Batch read of every actual source, never the representative MIN(order_id) on a shared pallet. */
@Mapper
public interface CustomerOrderProgressMapper {
@Select("""
WITH scheduled AS (
SELECT line.order_id,SUM(a.planned_package_count) AS packages
FROM ll_dispatch_order d
JOIN ll_dispatch_item i ON i.dispatch_order_id=d.id AND i.customer_id=d.customer_id
JOIN ll_inbound_plan_pallet_allocation a ON a.pallet_id=i.plan_pallet_id
JOIN ll_inbound_plan_line line ON line.id=a.line_id AND line.customer_id=d.customer_id
WHERE d.customer_id=#{customerId} AND d.status IN ('PENDING_DEPARTURE','PICKING','READY_TO_DEPART')
AND i.warehouse_pallet_id IS NULL
GROUP BY line.order_id
UNION ALL
SELECT b.inbound_order_id AS order_id,SUM(p.available_packages+COALESCE(pk.packages,0)) AS packages
FROM ll_dispatch_order d
JOIN ll_dispatch_item i ON i.dispatch_order_id=d.id AND i.customer_id=d.customer_id
JOIN ll_inventory_position p ON p.customer_id=d.customer_id AND p.pallet_id=i.warehouse_pallet_id
AND p.overseas_warehouse_id=i.overseas_warehouse_id
JOIN ll_inventory_balance b ON b.id=p.balance_id AND b.customer_id=d.customer_id
LEFT JOIN (SELECT dispatch_item_id,inventory_position_id,SUM(packages) AS packages
FROM ll_dispatch_pda_pick WHERE customer_id=#{customerId}
GROUP BY dispatch_item_id,inventory_position_id) pk
ON pk.dispatch_item_id=i.id AND pk.inventory_position_id=p.id
WHERE d.customer_id=#{customerId} AND d.status IN ('PENDING_DEPARTURE','PICKING','READY_TO_DEPART')
AND (i.plan_pallet_id IS NULL OR EXISTS (
SELECT 1 FROM ll_inbound_plan_pallet_allocation a
JOIN ll_inbound_plan_line line ON line.id=a.line_id AND line.customer_id=d.customer_id
WHERE a.pallet_id=i.plan_pallet_id AND line.order_id=b.inbound_order_id))
GROUP BY b.inbound_order_id
), departed AS (
SELECT a.order_id,SUM(a.packages) AS packages
FROM ll_dispatch_pda_pick_order a
JOIN ll_dispatch_pda_pick p ON p.id=a.pick_id AND p.customer_id=a.customer_id
JOIN ll_dispatch_pda_task t ON t.id=p.task_id AND t.customer_id=a.customer_id
JOIN ll_dispatch_order ds ON ds.id=t.dispatch_order_id AND ds.customer_id=a.customer_id
JOIN ll_dispatch_item i ON i.id=p.dispatch_item_id AND i.dispatch_order_id=ds.id AND i.customer_id=a.customer_id
WHERE a.customer_id=#{customerId} AND ds.status='DEPARTED' AND ds.depart_at IS NOT NULL
AND EXISTS (
SELECT 1 FROM ll_delivery_order d
JOIN ll_outbound_order outb ON outb.id=d.outbound_order_id AND outb.customer_id=d.customer_id
WHERE d.customer_id=a.customer_id AND d.dispatch_order_id=ds.id
AND outb.status='OUTBOUNDED' AND d.status IN ('WAIT_DELIVERY','DELIVERING')
AND d.delivery_group_key=CASE
WHEN i.destination_type='PRIVATE' AND NULLIF(TRIM(i.delivery_address),'') IS NOT NULL
THEN CONCAT('PRIVATE:',TRIM(i.delivery_address),':',TRIM(COALESCE(i.postal_code,'')))
WHEN COALESCE(NULLIF(TRIM(i.destination_warehouse_code),''),NULLIF(TRIM(i.fba_warehouse_code),'')) IS NOT NULL
THEN CONCAT('CODE:',UPPER(COALESCE(NULLIF(TRIM(i.destination_warehouse_code),''),NULLIF(TRIM(i.fba_warehouse_code),''))))
WHEN NULLIF(TRIM(i.destination_warehouse_id),'') IS NOT NULL THEN CONCAT('ID:',TRIM(i.destination_warehouse_id))
ELSE NULL END)
GROUP BY a.order_id
)
SELECT o.source_order_id AS order_id,COALESCE(o.package_count,0) AS packages,
COALESCE((SELECT SUM(s.packages) FROM scheduled s WHERE s.order_id=o.id),0) AS scheduled_packages,
COALESCE((SELECT SUM(d.packages) FROM departed d WHERE d.order_id=o.id),0) AS delivering_packages
FROM ll_order o WHERE o.customer_id=#{customerId} AND o.source_region='FIRSTLEG'
AND o.source_order_id IS NOT NULL AND o.source_inbound_id IS NOT NULL
AND o.order_type NOT IN ('RELABEL_APPLICATION','WAREHOUSE_TRANSFER')
ORDER BY o.id
""")
List<CustomerOrderProgress.Source> sources(@Param("customerId") String customerId);
}
@@ -0,0 +1,31 @@
package com.baiyue.lastleg.service;
import com.baiyue.lastleg.dto.CustomerOrderProgress;
import com.baiyue.lastleg.mapper.CustomerOrderProgressMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class CustomerOrderProgressService {
private final CustomerOrderProgressMapper mapper;
public CustomerOrderProgressService(CustomerOrderProgressMapper mapper) { this.mapper = mapper; }
@Transactional(readOnly=true)
public CustomerOrderProgress.Response read(long customerId) {
return new CustomerOrderProgress.Response(project(mapper.sources(Long.toString(customerId))));
}
static List<CustomerOrderProgress.Progress> project(List<CustomerOrderProgress.Source> sources) {
return sources.stream().collect(Collectors.groupingBy(CustomerOrderProgress.Source::orderId))
.entrySet().stream().map(entry -> {
long total = entry.getValue().stream().mapToLong(CustomerOrderProgress.Source::packages).sum();
long delivering = entry.getValue().stream().mapToLong(CustomerOrderProgress.Source::deliveringPackages).sum();
long scheduled = entry.getValue().stream().mapToLong(CustomerOrderProgress.Source::scheduledPackages).sum();
String stage = delivering > 0 ? "delivering" : scheduled > 0 ? "scheduled" : "none";
long covered = delivering > 0 ? delivering : scheduled;
// Equality is deliberate: duplicate or incomplete attribution must not claim full coverage.
boolean partial = total <= 0 || covered != total || entry.getValue().stream().anyMatch(s -> s.packages() <= 0);
return new CustomerOrderProgress.Progress(Long.parseLong(entry.getKey()), stage, partial);
}).filter(p -> !"none".equals(p.stage())).sorted(java.util.Comparator.comparingLong(CustomerOrderProgress.Progress::orderId)).toList();
}
}