This commit is contained in:
2026-09-11 17:20:56 +08:00
parent c8e8245853
commit 458a4cfa50
8 changed files with 207 additions and 1 deletions
@@ -0,0 +1,16 @@
package com.baiyue.firstleg.dashboard;
import java.math.BigDecimal;
import java.time.LocalDateTime;
public final class DashboardMetricsDtos {
private DashboardMetricsDtos() {}
public record OperationalMetrics(
long inTransitContainerCount,
BigDecimal inTransitTeu,
long unknownContainerTypeCount,
long pendingCustomsOrderCount,
LocalDateTime asOf
) {}
}
@@ -0,0 +1,52 @@
package com.baiyue.firstleg.dashboard;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Mapper
public interface DashboardMetricsMapper {
@Select("""
SELECT transit.container_count inTransitContainerCount,
transit.total_teu inTransitTeu,
transit.unknown_container_type_count unknownContainerTypeCount,
customs.pending_customs_order_count pendingCustomsOrderCount,
CURRENT_TIMESTAMP(3) asOf
FROM (
SELECT COUNT(*) container_count,
COALESCE(SUM(container.teu), 0) total_teu,
COALESCE(SUM(CASE WHEN container.teu IS NULL THEN 1 ELSE 0 END), 0) unknown_container_type_count
FROM (
SELECT DISTINCT booking.id,
CASE
WHEN UPPER(TRIM(booking.container_type)) LIKE '20%' THEN 1.0
WHEN UPPER(TRIM(booking.container_type)) LIKE '40%' THEN 2.0
WHEN UPPER(TRIM(booking.container_type)) LIKE '45%' THEN 2.25
ELSE NULL
END teu
FROM fl_container_booking booking
JOIN fl_ocean_booking ocean ON ocean.id = booking.ocean_booking_id
WHERE booking.actual_sailing_at IS NOT NULL
AND booking.actual_warehouse_arrival_at IS NULL
AND booking.record_status = 'ACTIVE'
AND ocean.status <> 'CANCELLED'
) container
) transit
CROSS JOIN (
SELECT COUNT(DISTINCT inbound.order_id) pending_customs_order_count
FROM fl_customs_container customs_container
JOIN fl_loading_plan_item item
ON item.loading_plan_id = customs_container.loading_plan_id AND item.deleted = 0
JOIN fl_inbound_order inbound
ON inbound.id = item.inbound_id AND inbound.tenant_id = item.account_id AND inbound.deleted = 0
WHERE customs_container.status <> 'COMPLETED'
) customs
""")
MetricsProjection read();
record MetricsProjection(Long inTransitContainerCount, BigDecimal inTransitTeu,
Long unknownContainerTypeCount, Long pendingCustomsOrderCount,
LocalDateTime asOf) {}
}
@@ -0,0 +1,24 @@
package com.baiyue.firstleg.dashboard;
import com.baiyue.firstleg.dashboard.DashboardMetricsDtos.OperationalMetrics;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class DashboardMetricsService {
@Resource private DashboardMetricsMapper mapper;
public OperationalMetrics read() {
var row = mapper.read();
if (row == null) return new OperationalMetrics(0, BigDecimal.ZERO, 0, 0, null);
return new OperationalMetrics(value(row.inTransitContainerCount()),
row.inTransitTeu() == null ? BigDecimal.ZERO : row.inTransitTeu(),
value(row.unknownContainerTypeCount()), value(row.pendingCustomsOrderCount()), row.asOf());
}
private static long value(Long value) {
return value == null ? 0 : value;
}
}
@@ -0,0 +1,25 @@
package com.baiyue.firstleg.dashboard;
import com.baiyue.common.core.api.ApiResponse;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dashboard.DashboardMetricsDtos.OperationalMetrics;
import com.baiyue.firstleg.service.FirstlegScopeGuard;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/platform/v1/first-leg/dashboard")
public class PlatformDashboardMetricsController {
@Resource private DashboardMetricsService service;
@GetMapping("/operational-metrics")
@PreAuthorize("hasAuthority('platform:firstleg:container:list') and hasAuthority('platform:firstleg:customs:list')")
public ApiResponse<OperationalMetrics> metrics(@AuthenticationPrincipal AuthenticatedUser user) {
FirstlegScopeGuard.platform(user);
return ApiResponse.success(service.read());
}
}