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 ;
2026-08-26 11:05:26 +08:00
import com.baiyue.lastleg.entity.LastlegOrderFee ;
2026-08-19 16:24:02 +08:00
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 ;
2026-08-28 16:30:02 +08:00
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody ;
2026-08-19 16:24:02 +08:00
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 14:46:06 +08:00
int consumed ;
if ( EventTypes . FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED . equals ( event . eventType ())) {
consumed = shippingNodeConsumer . consume ( event );
} else if ( EventTypes . FIRST_LEG_LOADING_PLAN_COMPLETED . equals ( event . eventType ())) {
consumed = consumer . consume ( event );
} else {
throw new ResponseStatusException ( HttpStatus . BAD_REQUEST , "Lastleg 不消费该事件类型: " + event . eventType ());
}
2026-08-25 10:31:04 +08:00
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
}
2026-08-26 11:05:26 +08:00
@PostMapping ( "/api/platform/v1/last-leg/orders/{id}/fees" )
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_EDIT + "')" )
@Operation ( summary = "Add a fee to one tail-leg order" , description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_EDIT )
public ApiResponse < LastlegOrderFee > createFee (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@PathVariable long id ,
@Valid @RequestBody LastlegOrderRequests . CreateFee request ) {
return ApiResponse . success ( orderService . createFee ( platformScope ( user , customerId ), id , request , operatorName ( user )));
}
2026-08-28 14:20:48 +08:00
@PutMapping ( "/api/platform/v1/last-leg/orders/{id}/detained" )
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_EDIT + "')" )
@Operation ( summary = "Set tail-leg order hold status" , description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_EDIT )
public ApiResponse < Void > setDetained (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@PathVariable long id ,
@Valid @RequestBody LastlegOrderRequests . SetDetained request ) {
orderService . setDetained ( platformScope ( user , customerId ), id , request , operatorName ( user ));
return ApiResponse . success ( null );
}
@PutMapping ( "/api/platform/v1/last-leg/orders/{id}/pallet-merge-required" )
@PreAuthorize ( "hasAuthority('" + OverseasPermissions . LAST_MILE_ORDER_EDIT + "')" )
@Operation ( summary = "Set the PDA pallet-merge reminder for one tail-leg order" ,
description = "Required authority: " + OverseasPermissions . LAST_MILE_ORDER_EDIT )
public ApiResponse < Void > setPalletMergeRequired (
@AuthenticationPrincipal AuthenticatedUser user ,
@RequestParam ( required = false ) String customerId ,
@PathVariable long id ,
@Valid @RequestBody LastlegOrderRequests . SetPalletMergeRequired request ) {
orderService . setPalletMergeRequired ( platformScope ( user , customerId ), id , request , operatorName ( user ));
return ApiResponse . success ( null );
}
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-28 16:30:02 +08:00
public ResponseEntity < StreamingResponseBody > export (
2026-08-19 16:24:02 +08:00
@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-28 16:30:02 +08:00
StreamingResponseBody stream = output -> { output . write ( body ); output . flush (); };
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" )
2026-08-28 16:30:02 +08:00
. body ( stream );
2026-08-19 16:24:02 +08:00
}
@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
}
2026-08-26 11:05:26 +08:00
private String operatorName ( AuthenticatedUser user ) {
return user . username () == null || user . username (). isBlank () ? String . valueOf ( user . userId ()) : user . username ();
}
2026-08-19 16:24:02 +08:00
}