2026-08-19 16:24:02 +08:00
package com.baiyue.lastleg.controller ;
import com.baiyue.common.core.api.ApiResponse ;
import com.baiyue.common.core.api.PageResult ;
import com.baiyue.common.event.EventEnvelope ;
import com.baiyue.common.security.AuthenticatedUser ;
import com.baiyue.lastleg.dto.LastlegOrderDetailResponse ;
import com.baiyue.lastleg.dto.LastlegOrderRequests ;
import com.baiyue.lastleg.dto.LastlegOrderSummary ;
import com.baiyue.lastleg.security.InternalServiceTokenGuard ;
2026-08-20 18:02:38 +08:00
import com.baiyue.lastleg.security.OverseasPermissions ;
2026-08-19 16:24:02 +08:00
import com.baiyue.lastleg.service.LastlegOrderService ;
import com.baiyue.lastleg.service.LoadingCompletedConsumer ;
2026-08-25 10:31:04 +08:00
import com.baiyue.lastleg.shipping.service.ContainerShippingNodeConsumer ;
import com.baiyue.common.event.EventTypes ;
2026-08-19 16:24:02 +08:00
import io.swagger.v3.oas.annotations.Operation ;
import io.swagger.v3.oas.annotations.tags.Tag ;
import jakarta.validation.Valid ;
import jakarta.annotation.Resource ;
import org.springframework.http.HttpHeaders ;
import org.springframework.http.HttpStatus ;
import org.springframework.http.MediaType ;
import org.springframework.http.ResponseEntity ;
import org.springframework.security.core.annotation.AuthenticationPrincipal ;
2026-08-20 18:02:38 +08:00
import org.springframework.security.access.prepost.PreAuthorize ;
2026-08-19 16:24:02 +08:00
import org.springframework.web.bind.annotation.GetMapping ;
import org.springframework.web.bind.annotation.PathVariable ;
import org.springframework.web.bind.annotation.PostMapping ;
import org.springframework.web.bind.annotation.PutMapping ;
import org.springframework.web.bind.annotation.RequestBody ;
import org.springframework.web.bind.annotation.RequestHeader ;
import org.springframework.web.bind.annotation.RequestParam ;
import org.springframework.web.bind.annotation.RestController ;
import org.springframework.web.server.ResponseStatusException ;
import java.nio.charset.StandardCharsets ;
@RestController
@Tag ( name = "Tail-leg orders" )
public class LastlegOrderController {
@Resource private LoadingCompletedConsumer consumer ;
2026-08-25 10:31:04 +08:00
@Resource private ContainerShippingNodeConsumer shippingNodeConsumer ;
2026-08-19 16:24:02 +08:00
@Resource private LastlegOrderService orderService ;
@Resource private InternalServiceTokenGuard tokenGuard ;
@PostMapping ( "/internal/lastleg/events" )
2026-08-25 10:31:04 +08:00
@Operation ( summary = "Receive a first-leg loading or actual shipping-node event" ,
description = "Accepts loading-completed V1/V2 and FirstLegContainerShippingNodeRecorded V1 through the internal token boundary; only monotonic actual PICKUP_READY-or-later facts open overseas PDA receiving." )
2026-08-19 16:24:02 +08:00
public ApiResponse < Integer > receive (
@RequestHeader ( value = "X-Internal-Service-Token" , required = false ) String token ,
@RequestBody EventEnvelope event ) {
tokenGuard . verify ( token );
2026-08-25 10:31:04 +08:00
int consumed = EventTypes . FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED . equals ( event . eventType ())
? shippingNodeConsumer . consume ( event ) : consumer . consume ( event );
return ApiResponse . success ( consumed );
2026-08-19 16:24:02 +08:00
}
@GetMapping ( "/api/platform/v1/last-leg/orders" )
2026-08-20 18:02:38 +08:00
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_LIST + "')" )
@Operation ( summary = "Page tail-leg order rows" , description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_LIST )
2026-08-19 16:24:02 +08:00
public ApiResponse < PageResult < LastlegOrderSummary >> list (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@Valid LastlegOrderRequests . Query query ) {
2026-08-21 10:43:25 +08:00
return ApiResponse . success ( orderService . page ( platformScope ( user , customerId ), query ));
2026-08-19 16:24:02 +08:00
}
@GetMapping ( "/api/platform/v1/last-leg/orders/{id}" )
2026-08-20 18:02:38 +08:00
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_LIST + "')" )
@Operation ( summary = "Get one inbound-code-level tail-leg order" , description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_LIST )
2026-08-19 16:24:02 +08:00
public ApiResponse < LastlegOrderDetailResponse > detail (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@PathVariable long id ) {
2026-08-21 10:43:25 +08:00
return ApiResponse . success ( orderService . detail ( platformScope ( user , customerId ), id ));
2026-08-19 16:24:02 +08:00
}
@GetMapping ( value = "/api/platform/v1/last-leg/orders/export" , produces = "text/csv" )
2026-08-20 18:02:38 +08:00
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_EXPORT + "')" )
@Operation ( summary = "Export filtered tail-leg orders as UTF-8 CSV" , description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_EXPORT )
2026-08-19 16:24:02 +08:00
public ResponseEntity < byte []> export (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@Valid LastlegOrderRequests . Query query ) {
2026-08-21 10:43:25 +08:00
byte [] body = orderService . exportCsv ( platformScope ( user , customerId ), query );
2026-08-19 16:24:02 +08:00
return ResponseEntity . ok ()
. contentType ( new MediaType ( "text" , "csv" , StandardCharsets . UTF_8 ))
. header ( HttpHeaders . CONTENT_DISPOSITION , "attachment; filename=lastleg-orders.csv" )
. body ( body );
}
@PutMapping ( "/api/platform/v1/last-leg/orders/{id}/destination" )
2026-08-20 18:02:38 +08:00
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_EDIT + "')" )
@Operation ( summary = "Transfer one order to a destination warehouse" , description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_EDIT )
2026-08-19 16:24:02 +08:00
public ApiResponse < Integer > transferOne (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@PathVariable long id ,
@Valid @RequestBody LastlegOrderRequests . SingleTransferDestination request ) {
2026-08-21 10:43:25 +08:00
return ApiResponse . success ( orderService . transferDestination ( platformScope ( user , customerId ), request . withOrderId ( id )));
2026-08-19 16:24:02 +08:00
}
@PutMapping ( "/api/platform/v1/last-leg/orders/destination" )
2026-08-20 18:02:38 +08:00
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_EDIT + "')" )
@Operation ( summary = "Transfer multiple orders to a destination warehouse" , description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_EDIT )
2026-08-19 16:24:02 +08:00
public ApiResponse < Integer > transferBatch (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@Valid @RequestBody LastlegOrderRequests . TransferDestination request ) {
2026-08-21 10:43:25 +08:00
return ApiResponse . success ( orderService . transferDestination ( platformScope ( user , customerId ), request ));
2026-08-19 16:24:02 +08:00
}
2026-08-21 10:43:25 +08:00
private String platformScope ( AuthenticatedUser user , String requestedCustomer ) {
2026-08-19 16:24:02 +08:00
if ( user == null || user . tenantId () == null ) {
throw new ResponseStatusException ( HttpStatus . UNAUTHORIZED );
}
2026-08-21 10:43:25 +08:00
if ( ! "PLATFORM" . equals ( user . appCode ()) || user . tenantId () != 0L ) {
throw new ResponseStatusException ( HttpStatus . FORBIDDEN , "Platform identity is required" );
2026-08-19 16:24:02 +08:00
}
2026-08-21 10:43:25 +08:00
return requestedCustomer == null || requestedCustomer . isBlank () || "0" . equals ( requestedCustomer )
? null : requestedCustomer . trim ();
2026-08-19 16:24:02 +08:00
}
}