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; import com.baiyue.lastleg.security.OverseasPermissions; import com.baiyue.lastleg.service.LastlegOrderService; import com.baiyue.lastleg.service.LoadingCompletedConsumer; import com.baiyue.lastleg.shipping.service.ContainerShippingNodeConsumer; import com.baiyue.common.event.EventTypes; 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; import org.springframework.security.access.prepost.PreAuthorize; 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; @Resource private ContainerShippingNodeConsumer shippingNodeConsumer; @Resource private LastlegOrderService orderService; @Resource private InternalServiceTokenGuard tokenGuard; @PostMapping("/internal/lastleg/events") @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.") public ApiResponse receive( @RequestHeader(value = "X-Internal-Service-Token", required = false) String token, @RequestBody EventEnvelope event) { tokenGuard.verify(token); int consumed = EventTypes.FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED.equals(event.eventType()) ? shippingNodeConsumer.consume(event) : consumer.consume(event); return ApiResponse.success(consumed); } @GetMapping("/api/platform/v1/last-leg/orders") @PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_LIST + "')") @Operation(summary = "Page tail-leg order rows", description = "Required authority: " + OverseasPermissions.LAST_MILE_ORDER_LIST) public ApiResponse> list( @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @Valid LastlegOrderRequests.Query query) { return ApiResponse.success(orderService.page(platformScope(user, customerId), query)); } @GetMapping("/api/platform/v1/last-leg/orders/{id}") @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) public ApiResponse detail( @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @PathVariable long id) { return ApiResponse.success(orderService.detail(platformScope(user, customerId), id)); } @GetMapping(value = "/api/platform/v1/last-leg/orders/export", produces = "text/csv") @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) public ResponseEntity export( @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @Valid LastlegOrderRequests.Query query) { byte[] body = orderService.exportCsv(platformScope(user, customerId), query); 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") @PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_EDIT + "')") @Operation(summary = "Transfer one order to a destination warehouse", description = "Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EDIT) public ApiResponse transferOne( @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @PathVariable long id, @Valid @RequestBody LastlegOrderRequests.SingleTransferDestination request) { return ApiResponse.success(orderService.transferDestination(platformScope(user, customerId), request.withOrderId(id))); } @PutMapping("/api/platform/v1/last-leg/orders/destination") @PreAuthorize("hasAuthority('" + OverseasPermissions.LAST_MILE_ORDER_EDIT + "')") @Operation(summary = "Transfer multiple orders to a destination warehouse", description = "Required authority: " + OverseasPermissions.LAST_MILE_ORDER_EDIT) public ApiResponse transferBatch( @AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId, @Valid @RequestBody LastlegOrderRequests.TransferDestination request) { return ApiResponse.success(orderService.transferDestination(platformScope(user, customerId), request)); } private String platformScope(AuthenticatedUser user, String requestedCustomer) { if (user == null || user.tenantId() == null) { throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); } if (!"PLATFORM".equals(user.appCode()) || user.tenantId() != 0L) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Platform identity is required"); } return requestedCustomer == null || requestedCustomer.isBlank() || "0".equals(requestedCustomer) ? null : requestedCustomer.trim(); } }