This commit is contained in:
2026-08-20 15:25:40 +08:00
parent b55b049e77
commit cd34cd426e
14 changed files with 170 additions and 49 deletions
@@ -13,15 +13,15 @@ import java.util.Map;
* 尾程预留服务状态接口,用于验证尾程服务边界及基础可用性。
*/
@Tag(
name = "Last-leg service status / 尾程服务状态",
description = "Last-leg reserved service availability API. / 尾程预留服务的可用性接口。"
name = "尾程服务状态",
description = "尾程服务可用性接口"
)
@RestController
@RequestMapping("/api/platform/v1/last-leg")
public class LastlegHealthController {
@Operation(
summary = "Get service status / 查询服务状态",
description = "Returns the logical status of the reserved lastleg-service boundary. / 返回预留 lastleg-service 边界的逻辑运行状态。"
summary = "查询服务状态",
description = "返回尾程服务边界的逻辑运行状态"
)
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
@@ -32,14 +32,15 @@ import org.springframework.web.server.ResponseStatusException;
import java.nio.charset.StandardCharsets;
@RestController
@Tag(name = "Tail-leg orders")
@Tag(name = "尾程订单")
public class LastlegOrderController {
@Resource private LoadingCompletedConsumer consumer;
@Resource private LastlegOrderService orderService;
@Resource private InternalServiceTokenGuard tokenGuard;
@PostMapping("/internal/lastleg/events")
@Operation(summary = "Receive a first-leg loading-completed event")
/** 接收头程装柜完成事件并创建尾程订单。 */
@Operation(summary = "接收头程装柜完成事件")
public ApiResponse<Integer> receive(
@RequestHeader(value = "X-Internal-Service-Token", required = false) String token,
@RequestBody EventEnvelope event) {
@@ -48,7 +49,8 @@ public class LastlegOrderController {
}
@GetMapping("/api/platform/v1/last-leg/orders")
@Operation(summary = "Page tail-leg order rows")
/** 分页查询尾程订单列表。 */
@Operation(summary = "分页查询尾程订单")
public ApiResponse<PageResult<LastlegOrderSummary>> list(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -57,7 +59,8 @@ public class LastlegOrderController {
}
@GetMapping("/api/platform/v1/last-leg/orders/{id}")
@Operation(summary = "Get one inbound-code-level tail-leg order")
/** 查询指定入仓单维度的尾程订单详情。 */
@Operation(summary = "查询尾程订单详情")
public ApiResponse<LastlegOrderDetailResponse> detail(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -66,7 +69,8 @@ public class LastlegOrderController {
}
@GetMapping(value = "/api/platform/v1/last-leg/orders/export", produces = "text/csv")
@Operation(summary = "Export filtered tail-leg orders as UTF-8 CSV")
/** 按筛选条件导出尾程订单 CSV 文件。 */
@Operation(summary = "导出尾程订单")
public ResponseEntity<byte[]> export(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -79,7 +83,8 @@ public class LastlegOrderController {
}
@PutMapping("/api/platform/v1/last-leg/orders/{id}/destination")
@Operation(summary = "Transfer one order to a destination warehouse")
/** 将单个尾程订单转移到目标仓库。 */
@Operation(summary = "单个订单转移仓库")
public ApiResponse<Integer> transferOne(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -89,7 +94,8 @@ public class LastlegOrderController {
}
@PutMapping("/api/platform/v1/last-leg/orders/destination")
@Operation(summary = "Transfer multiple orders to a destination warehouse")
/** 将多个尾程订单批量转移到目标仓库。 */
@Operation(summary = "批量订单转移仓库")
public ApiResponse<Integer> transferBatch(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -17,12 +17,13 @@ import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/platform/v1/last-leg")
@Tag(name = "Outbound and delivery")
@Tag(name = "出库与派送")
public class DeliveryTransferController {
@Resource private DeliveryTransferService service;
@PostMapping("/dispatches/{dispatchNo}/transfer-delivery")
@Operation(summary = "Create one pending outbound order and one pending delivery order")
/** 创建一个待处理出库单和一个待派送单。 */
@Operation(summary = "创建出库与派送单")
public ApiResponse<DeliveryResponses.TransferResult> transfer(
@AuthenticationPrincipal AuthenticatedUser user, @PathVariable String dispatchNo,
@RequestParam(required = false) String customerId,
@@ -30,28 +31,36 @@ public class DeliveryTransferController {
return ApiResponse.success(service.transfer(scope(user, customerId), dispatchNo, operator(user), request));
}
/** 分页查询出库单。 */
@GetMapping("/outbound-orders")
@Operation(summary = "分页查询出库单")
public ApiResponse<PageResult<DeliveryResponses.OutboundRow>> outboundPage(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId,
@Valid DeliveryRequests.Query query) {
return ApiResponse.success(service.outboundPage(scope(user, customerId), query));
}
/** 查询出库单详情及托盘信息。 */
@GetMapping("/outbound-orders/{outboundNo}")
@Operation(summary = "查询出库单详情")
public ApiResponse<DeliveryResponses.OutboundDetail> outboundDetail(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId,
@PathVariable String outboundNo, @Valid DeliveryRequests.PalletQuery query) {
return ApiResponse.success(service.outboundDetail(scope(user, customerId), outboundNo, query));
}
/** 分页查询派送单。 */
@GetMapping("/delivery-orders")
@Operation(summary = "分页查询派送单")
public ApiResponse<PageResult<DeliveryResponses.DeliveryRow>> deliveryPage(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId,
@Valid DeliveryRequests.Query query) {
return ApiResponse.success(service.deliveryPage(scope(user, customerId), query));
}
/** 查询派送单详情及托盘信息。 */
@GetMapping("/delivery-orders/{deliveryNo}")
@Operation(summary = "查询派送单详情")
public ApiResponse<DeliveryResponses.OutboundDetail> deliveryDetail(
@AuthenticationPrincipal AuthenticatedUser user, @RequestParam(required = false) String customerId,
@PathVariable String deliveryNo, @Valid DeliveryRequests.PalletQuery query) {
@@ -32,13 +32,14 @@ import java.util.List;
@RestController
@RequestMapping("/api/platform/v1/last-leg/dispatches")
@Tag(name = "Tail-leg vehicle dispatch")
@Tag(name = "尾程派车")
public class DispatchController {
@Resource private DispatchService service;
@Resource private DispatchPdaWorkflowService pdaWorkflow;
@GetMapping("/candidates")
@Operation(summary = "List unlocked planned or received pallets for intelligent pre-dispatch")
/** 查询可用于智能预派车的未锁定托盘。 */
@Operation(summary = "查询可派车托盘")
public ApiResponse<List<DispatchPalletCandidate>> candidates(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -49,7 +50,8 @@ public class DispatchController {
}
@PostMapping("/pre-arrange")
@Operation(summary = "Create a pending-departure dispatch and lock all selected pallets")
/** 创建待出发派车单并锁定选中的托盘。 */
@Operation(summary = "创建预派车单")
public ApiResponse<DispatchResponse> preArrange(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -60,7 +62,8 @@ public class DispatchController {
}
@GetMapping
@Operation(summary = "Page dispatch orders")
/** 分页查询派车单。 */
@Operation(summary = "分页查询派车单")
public ApiResponse<PageResult<DispatchResponse>> page(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -69,7 +72,8 @@ public class DispatchController {
}
@GetMapping("/{dispatchNo}")
@Operation(summary = "Get dispatch details and all pallet destinations")
/** 查询派车单详情及托盘目的地。 */
@Operation(summary = "查询派车单详情")
public ApiResponse<DispatchResponse> detail(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -78,7 +82,8 @@ public class DispatchController {
}
@PutMapping("/{dispatchNo}")
@Operation(summary = "Edit vehicle, driver, appointment, route and departure data")
/** 编辑车辆、司机、预约、路线和出发信息。 */
@Operation(summary = "编辑派车单")
public ApiResponse<DispatchResponse> update(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -88,7 +93,8 @@ public class DispatchController {
}
@DeleteMapping("/{dispatchNo}")
@Operation(summary = "Cancel a pending dispatch and release its pallet locks")
/** 取消待处理派车单并释放托盘锁定。 */
@Operation(summary = "取消派车单")
public ApiResponse<Void> cancel(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -98,7 +104,8 @@ public class DispatchController {
}
@PostMapping("/{dispatchNo}/depart")
@Operation(summary = "Ship all PDA-picked pallets and mark the dispatch departed")
/** 出库 PDA 已拣货托盘并将派车单标记为已出发。 */
@Operation(summary = "PDA 出库并完成派车")
public ApiResponse<DispatchPdaResponses.Departed> depart(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -5,7 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "Tail-leg order row at inbound-code granularity")
@Schema(description = "按入仓单编码粒度展示的尾程订单列表行")
public record LastlegOrderSummary(
Long id,
String orderNo,
@@ -20,20 +20,22 @@ import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/platform/v1/last-leg/inbound-plans")
@Tag(name = "Tail-leg inbound plans")
@Tag(name = "尾程入仓计划")
public class InboundPlanController {
@Resource private InboundPlanService service;
@Resource private InboundFbaAppointmentService appointmentService;
@PostMapping("/convert")
@Operation(summary = "Convert one inbound order to one inbound plan")
/** 将入仓单转换为入仓计划。 */
@Operation(summary = "入仓单转入仓计划")
public ApiResponse<InboundPlanResponse> convert(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody ConvertInboundPlanRequest request) {
return ApiResponse.success(service.convert(scope(user, request.customerId()), request.orderId()));
}
@GetMapping("/{planNo}")
@Operation(summary = "Get an inbound plan with lines, pallets and allocations")
/** 查询入仓计划及其明细、托盘和分配信息。 */
@Operation(summary = "查询入仓计划详情")
public ApiResponse<InboundPlanResponse> detail(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String planNo,
@RequestParam(required = false) String customerId) {
@@ -41,7 +43,8 @@ public class InboundPlanController {
}
@PostMapping("/{planNo}/pallets/supplement")
@Operation(summary = "Add one printable supplemental pallet for a plan destination warehouse")
/** 为计划目标仓新增一个可打印的补充托盘。 */
@Operation(summary = "新增补充托盘")
public ApiResponse<InboundPlanResponse.Pallet> supplementPallet(@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String planNo,
@Valid @RequestBody SupplementPalletRequest request) {
@@ -49,7 +52,8 @@ public class InboundPlanController {
}
@PostMapping("/fba-appointments/batch")
@Operation(summary = "Create one FBA appointment for selected inbound lines")
/** 为选中的入仓明细创建 FBA 预约。 */
@Operation(summary = "创建 FBA 预约")
public ApiResponse<FbaAppointmentResponse> batchFbaAppointment(
@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody BatchFbaAppointmentRequest request) {
@@ -59,7 +63,8 @@ public class InboundPlanController {
}
@GetMapping("/fba-appointments/{appointmentNo}")
@Operation(summary = "Get an FBA appointment and its selected inbound lines")
/** 查询 FBA 预约及其选中的入仓明细。 */
@Operation(summary = "查询 FBA 预约详情")
public ApiResponse<FbaAppointmentResponse> fbaAppointmentDetail(
@AuthenticationPrincipal AuthenticatedUser user,
@PathVariable String appointmentNo,
@@ -39,10 +39,11 @@ public class InboundUnloadCompletedOutboxService {
throw new IllegalStateException("Inbound plan has no source container booking ID");
}
Instant photoUploadedAt = task.getUnloadPhotoUploadedAt().toInstant(ZoneOffset.UTC);
var payload = new OverseasContainerUnloadedV1(sourceOrder.getSourceContainerBookingId(),
plan.getContainerNo(), photoUploadedAt, photoUploadedAt, null);
var payload = new OverseasContainerUnloadedV1(plan.getContainerNo(), photoUploadedAt,
photoUploadedAt, null);
JsonNode payloadNode = objectMapper.valueToTree(payload);
long aggregateVersion = Math.max(1, task.getVersion() == null ? 1 : task.getVersion());
// Multiple inbound plans can share one container; upload epoch keeps their container events ordered.
long aggregateVersion = photoUploadedAt.toEpochMilli();
String eventKey = EventTypes.OVERSEAS_CONTAINER_UNLOADED + ':' + task.getId();
String traceId = EventIdentifiers.newUuid();
EventEnvelope envelope = new EventEnvelope(EventIdentifiers.newUuid(), EventTypes.OVERSEAS_CONTAINER_UNLOADED,
@@ -19,14 +19,15 @@ import org.springframework.web.server.ResponseStatusException;
@RestController
@RequestMapping("/api/platform/v1/last-leg/inventory")
@Tag(name = "Tail-leg inbound-order package inventory commands")
@Tag(name = "尾程入仓单库存操作")
public class InventoryCommandController {
public static final String MANAGE_PERMISSION = "lastleg:inventory:manage";
public static final String ADJUST_PERMISSION = "lastleg:inventory:adjust";
@Resource private InventoryCommandService service;
@PostMapping("/hold")
@Operation(summary = "Move available inbound-order packages to hold")
/** 将可用入仓单箱数转为冻结。 */
@Operation(summary = "冻结入仓单库存")
public ApiResponse<InventoryCommandResponse> hold(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody InventoryCommandRequests.Move request) {
requirePermission(user, MANAGE_PERMISSION);
@@ -34,7 +35,8 @@ public class InventoryCommandController {
}
@PostMapping("/release")
@Operation(summary = "Release held inbound-order packages")
/** 释放已冻结的入仓单箱数。 */
@Operation(summary = "释放入仓单库存")
public ApiResponse<InventoryCommandResponse> release(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody InventoryCommandRequests.Move request) {
requirePermission(user, MANAGE_PERMISSION);
@@ -42,7 +44,8 @@ public class InventoryCommandController {
}
@PostMapping("/allocate")
@Operation(summary = "Allocate available inbound-order packages to an outbound order")
/** 将可用入仓单箱数分配给出库单。 */
@Operation(summary = "分配出库库存")
public ApiResponse<InventoryCommandResponse> allocate(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody InventoryCommandRequests.Move request) {
requirePermission(user, MANAGE_PERMISSION);
@@ -50,7 +53,8 @@ public class InventoryCommandController {
}
@PostMapping("/unallocate")
@Operation(summary = "Cancel an outbound allocation")
/** 取消出库库存分配。 */
@Operation(summary = "取消出库分配")
public ApiResponse<InventoryCommandResponse> unallocate(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody InventoryCommandRequests.Move request) {
requirePermission(user, MANAGE_PERMISSION);
@@ -58,7 +62,8 @@ public class InventoryCommandController {
}
@PostMapping("/pick")
@Operation(summary = "Confirm picked inbound-order packages")
/** 确认入仓单箱数已拣货。 */
@Operation(summary = "确认拣货")
public ApiResponse<InventoryCommandResponse> pick(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody InventoryCommandRequests.Move request) {
requirePermission(user, MANAGE_PERMISSION);
@@ -66,7 +71,8 @@ public class InventoryCommandController {
}
@PostMapping("/ship")
@Operation(summary = "Ship picked inbound-order packages and report whether the pallet is empty")
/** 出库已拣货箱数并返回托盘是否为空。 */
@Operation(summary = "出库拣货库存")
public ApiResponse<InventoryCommandResponse> ship(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody InventoryCommandRequests.Move request) {
requirePermission(user, MANAGE_PERMISSION);
@@ -74,7 +80,8 @@ public class InventoryCommandController {
}
@PostMapping("/adjust")
@Operation(summary = "Adjust inbound-order package inventory after a physical count")
/** 根据实物盘点结果调整入仓单库存。 */
@Operation(summary = "盘点调整库存")
public ApiResponse<InventoryCommandResponse> adjust(@AuthenticationPrincipal AuthenticatedUser user,
@Valid @RequestBody InventoryCommandRequests.Adjustment request) {
requirePermission(user, ADJUST_PERMISSION);
@@ -28,14 +28,15 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
@RestController
@Tag(name = "Inventory queries")
@Tag(name = "库存查询")
public class InventoryQueryController {
private static final String BASE_PATH = "/api/platform/v1/last-leg/inventory";
@Resource private InventoryQueryService service;
@GetMapping(BASE_PATH + "/summary")
@Operation(summary = "Get inbound-order package inventory totals")
/** 查询入仓单库存汇总。 */
@Operation(summary = "查询库存汇总")
public ApiResponse<InventoryDashboardResponse> summary(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -44,7 +45,8 @@ public class InventoryQueryController {
}
@GetMapping(BASE_PATH + "/balances")
@Operation(summary = "Page inventory by customer, warehouse and inbound order")
/** 按客户、仓库和入仓单分页查询库存。 */
@Operation(summary = "分页查询库存余额")
public ApiResponse<PageResult<InventoryBalanceView>> balances(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -53,7 +55,8 @@ public class InventoryQueryController {
}
@GetMapping(BASE_PATH + "/balances/{balanceId}/positions")
@Operation(summary = "List pallet and batch positions for one inbound-order balance")
/** 查询库存余额对应的托盘和批次位置。 */
@Operation(summary = "查询库存位置")
public ApiResponse<List<InventoryPositionView>> positions(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -63,7 +66,8 @@ public class InventoryQueryController {
}
@GetMapping(BASE_PATH + "/ledger")
@Operation(summary = "Page append-only inventory movements")
/** 分页查询只增不改的库存流水。 */
@Operation(summary = "分页查询库存流水")
public ApiResponse<PageResult<InventoryLedgerView>> ledgers(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -72,7 +76,8 @@ public class InventoryQueryController {
}
@GetMapping(value = BASE_PATH + "/balances/export", produces = "text/csv")
@Operation(summary = "Export filtered inbound-order inventory as UTF-8 CSV")
/** 导出筛选后的入仓单库存 CSV 文件。 */
@Operation(summary = "导出库存余额")
public ResponseEntity<byte[]> exportBalances(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,
@@ -81,7 +86,8 @@ public class InventoryQueryController {
}
@GetMapping(value = BASE_PATH + "/ledger/export", produces = "text/csv")
@Operation(summary = "Export filtered inventory movements as UTF-8 CSV")
/** 导出筛选后的库存流水 CSV 文件。 */
@Operation(summary = "导出库存流水")
public ResponseEntity<byte[]> exportLedgers(
@AuthenticationPrincipal AuthenticatedUser user,
@RequestParam(required = false) String customerId,