fix(order): paginate merchant workbench with scoped queue counts

This commit is contained in:
2026-10-03 14:57:51 +08:00
parent 7d9ee18196
commit 780d37e1d2
22 changed files with 705 additions and 6 deletions
@@ -43,6 +43,7 @@ public class InternalInboundOrderController {
private InboundCartonLabelService cartonLabels;
@Resource
private UnifiedInboundQueryService unifiedQueryService;
@Resource private com.baiyue.firstleg.service.MerchantWorkbenchFilterService workbenchFilters;
@Resource
private OrderDeclarationSyncService declarationSync;
@Resource
@@ -148,6 +149,14 @@ public class InternalInboundOrderController {
return ApiResponse.success(remarksSync.sync(command));
}
@PostMapping("/unified-orders/workbench-filters")
public ApiResponse<com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Response> workbenchFilters(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@Valid @RequestBody com.baiyue.firstleg.dto.MerchantWorkbenchFilters.Request request) {
requireToken(token);
return ApiResponse.success(workbenchFilters.read(request));
}
@PostMapping("/unified-orders/query")
public ApiResponse<PageResult<UnifiedInboundSummary>> unifiedPage(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@@ -0,0 +1,17 @@
package com.baiyue.firstleg.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Pattern;
import java.util.List;
/** Customer-scoped filter identifiers; never a replacement order list or cross-schema query. */
public final class MerchantWorkbenchFilters {
private MerchantWorkbenchFilters() {}
public record Request(@NotNull @Positive Long tenantId,
@Pattern(regexp = "WAIT_MEASURE|WAIT_PALLETIZE|WAIT_SPLIT|LOADING|LOADED|DELIVERED") String mainStatus) {}
public record Response(List<Long> matchingStatusOrderIds, List<Long> feeOrderIds,
List<Long> customsOrderIds) {}
}
@@ -0,0 +1,66 @@
package com.baiyue.firstleg.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface MerchantWorkbenchFilterMapper {
@Select("""
<script>
SELECT DISTINCT order_id FROM fl_inbound_order
WHERE tenant_id=#{tenantId} AND deleted=0 AND order_id IS NOT NULL
AND process_status IN
<foreach collection="statuses" item="status" open="(" separator="," close=")">#{status}</foreach>
ORDER BY order_id
</script>
""")
List<Long> byProcessStatuses(@Param("tenantId") long tenantId, @Param("statuses") List<String> statuses);
@Select("""
SELECT DISTINCT i.order_id
FROM fl_inbound_order i
JOIN fl_inbound_fee f ON f.tenant_id=i.tenant_id AND f.inbound_id=i.id
WHERE i.tenant_id=#{tenantId} AND i.deleted=0 AND i.order_id IS NOT NULL
AND f.fee_status='SENT' AND f.customer_confirmed_at IS NULL
AND NOT EXISTS (SELECT 1 FROM fl_inbound_fee newer
WHERE newer.tenant_id=f.tenant_id AND newer.inbound_id=f.inbound_id
AND (newer.fee_version>f.fee_version OR (newer.fee_version=f.fee_version AND newer.id>f.id)))
ORDER BY i.order_id
""")
List<Long> pendingFees(@Param("tenantId") long tenantId);
@Select("""
SELECT DISTINCT i.order_id
FROM fl_inbound_order i
JOIN fl_loading_plan p ON p.id=(
SELECT latest.id FROM fl_loading_plan latest
JOIN fl_loading_plan_item li ON li.loading_plan_id=latest.id AND li.deleted=0
JOIN fl_inbound_order own ON own.id=li.inbound_id AND own.tenant_id=li.account_id AND own.deleted=0
WHERE latest.plan_status='COMPLETED' AND own.tenant_id=i.tenant_id
AND own.order_id=i.order_id AND own.tax_mode='TAX_REFUND'
ORDER BY COALESCE(latest.confirmed_at,latest.updated_at) DESC,latest.id DESC LIMIT 1
)
JOIN fl_container_booking b ON b.id=p.container_booking_id AND b.record_status='ACTIVE'
LEFT JOIN fl_customs_container c ON c.loading_plan_id=p.id
WHERE i.tenant_id=#{tenantId} AND i.deleted=0 AND i.tax_mode='TAX_REFUND'
AND i.process_status IN ('LOADED','DELIVERED')
AND (c.id IS NULL OR c.status='WAITING_CUSTOMER_DOCUMENTS')
AND EXISTS (
SELECT 1 FROM fl_loading_plan_item item
JOIN fl_inbound_order member ON member.id=item.inbound_id
AND member.tenant_id=item.account_id AND member.deleted=0
WHERE item.loading_plan_id=p.id AND item.deleted=0
AND member.tenant_id=i.tenant_id AND member.tax_mode='TAX_REFUND'
AND NOT EXISTS (
SELECT 1 FROM fl_customs_customer_document_inbound scope
JOIN fl_customs_customer_document d ON d.id=scope.document_id
AND d.customs_container_id=c.id AND d.tenant_id=i.tenant_id AND d.status='ACTIVE'
WHERE scope.inbound_id=member.id AND scope.tenant_id=i.tenant_id
)
)
ORDER BY i.order_id
""")
List<Long> pendingCustoms(@Param("tenantId") long tenantId);
}
@@ -0,0 +1,19 @@
package com.baiyue.firstleg.service;
import com.baiyue.firstleg.dto.MerchantWorkbenchFilters;
import com.baiyue.firstleg.mapper.MerchantWorkbenchFilterMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class MerchantWorkbenchFilterService {
@Resource private MerchantWorkbenchFilterMapper mapper;
@Resource private UnifiedInboundQueryService unified;
public MerchantWorkbenchFilters.Response read(MerchantWorkbenchFilters.Request request) {
return new MerchantWorkbenchFilters.Response(
request.mainStatus() == null ? null : mapper.byProcessStatuses(request.tenantId(),
unified.internalStatuses(request.mainStatus())),
mapper.pendingFees(request.tenantId()), mapper.pendingCustoms(request.tenantId()));
}
}
@@ -199,7 +199,7 @@ public class UnifiedInboundQueryService {
return mainStatus != null && Set.of("LOADING", "LOADED", "DELIVERED").contains(mainStatus);
}
private List<String> internalStatuses(String mainStatus) {
List<String> internalStatuses(String mainStatus) {
if (!StringUtils.hasText(mainStatus)) return List.of();
return switch (mainStatus) {
case "WAIT_MEASURE" -> List.of("WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED",