fix(lastleg): expose authoritative outbound type to PDA

This commit is contained in:
2026-09-30 13:33:53 +08:00
parent 7f3c0690cd
commit bd73c7a326
4 changed files with 53 additions and 15 deletions
@@ -75,7 +75,8 @@ public final class DeliveryResponses {
Long palletId, Long warehousePalletId, String palletNo,
int packageCount, BigDecimal grossWeightKg,
BigDecimal volumeCbm) {}
public record PdaTask(Long id, String taskNo, String status, String outboundNo, LocalDateTime pushedAt, LocalDateTime completedAt) {}
public record PdaTask(Long id, String taskNo, String status, String outboundNo, String outboundType,
LocalDateTime pushedAt, LocalDateTime completedAt) {}
public record PdaTaskResolution(String workflow, String taskNo, String outboundNo, String dispatchNo) {}
public record PdaPickPosition(Long inventoryPositionId, String palletNo, String cartonMark,
String skuCode, String inboundCode, long availablePackages,
@@ -1114,7 +1114,7 @@ public class DeliveryTransferService {
&& Objects.equals(item.getInboundCode(), balance.getInboundCode());
}
private DeliveryResponses.PdaTask taskResult(OutboundOrder outbound, OutboundPdaTask task) { return new DeliveryResponses.PdaTask(task.getId(), task.getTaskNo(), task.getStatus(), outbound.getOutboundNo(), task.getPushedAt(), task.getCompletedAt()); }
private DeliveryResponses.PdaTask taskResult(OutboundOrder outbound, OutboundPdaTask task) { return new DeliveryResponses.PdaTask(task.getId(), task.getTaskNo(), task.getStatus(), outbound.getOutboundNo(), outbound.getOutboundType(), task.getPushedAt(), task.getCompletedAt()); }
private DeliveryResponses.PdaTaskResolution outboundResolution(OutboundOrder outbound, OutboundPdaTask task) {
return new DeliveryResponses.PdaTaskResolution("OUTBOUND", task.getTaskNo(), outbound.getOutboundNo(),
outbound.getDispatchNo());
@@ -45,7 +45,15 @@ paths:
summary: 查询Token绑定仓的一件代发出库任务
description: 一件代发复用统一出库任务模型;不接收customerId或warehouseId,派送不进入PDA。
parameters: [{ name: status, in: query, required: false, schema: { type: string } }]
responses: { '200': { description: 出库任务列表 } }
responses:
'200':
description: 出库任务列表;每项包含服务端出库类型 outboundType
content:
application/json:
schema:
type: object
properties:
data: { type: array, items: { $ref: '#/components/schemas/PdaOutboundTask' } }
/api/pda/v1/last-leg/outbound-tasks/resolve/{businessNo}:
get:
summary: 解析出库作业扫码业务号
@@ -68,7 +76,7 @@ paths:
summary: 扫码拣取一件代发箱件
parameters: [{ name: taskNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/OutboundPick' } } } }
responses: { '200': { description: 更新后的统一出库任务 }, '409': { description: 库存、数量或幂等冲突 } }
responses: { '200': { description: 更新后的统一出库任务, content: { application/json: { schema: { $ref: '#/components/schemas/PdaOutboundTaskResponse' } } } }, '409': { description: 库存、数量或幂等冲突 } }
/api/pda/v1/last-leg/outbound-tasks/{taskNo}/pallets/{palletNo}/pick:
post:
summary: 扫描一件代发托盘并原子拣取整托剩余箱件
@@ -77,14 +85,14 @@ paths:
- { name: taskNo, in: path, required: true, schema: { type: string } }
- { name: palletNo, in: path, required: true, schema: { type: string } }
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/IdempotentAction' } } } }
responses: { '200': { description: 整托拣货后的任务 }, '404': { description: 当前任务没有该托盘 }, '409': { description: 非一件代发、库存不足或幂等冲突 } }
responses: { '200': { description: 整托拣货后的任务, content: { application/json: { schema: { $ref: '#/components/schemas/PdaOutboundTaskResponse' } } } }, '404': { description: 当前任务没有该托盘 }, '409': { description: 非一件代发、库存不足或幂等冲突 } }
/api/pda/v1/last-leg/outbound-tasks/{taskNo}/complete:
post:
summary: 完成一件代发出库任务
description: 全部托盘及箱件精确拣完后完成;同一完成幂等键返回既有结果,其他键明确冲突。
parameters: [{ name: taskNo, in: path, required: true, schema: { type: string } }]
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/IdempotentAction' } } } }
responses: { '200': { description: COMPLETED任务 }, '409': { description: 未全部拣货或幂等冲突 } }
responses: { '200': { description: COMPLETED任务, content: { application/json: { schema: { $ref: '#/components/schemas/PdaOutboundTaskResponse' } } } }, '409': { description: 未全部拣货或幂等冲突 } }
/api/pda/v1/last-leg/stocktakes:
get:
summary: 查询Token绑定仓盘点任务
@@ -868,19 +876,27 @@ components:
maxItems: 500
description: 本次实际扫描的箱唛。一件代发指定箱唛任务分批拣货时必须提交本次子集,数量与 packages 一致;换标任务仍须提交全部指定箱唛。
items: { type: string, minLength: 1, maxLength: 255 }
PdaOutboundTaskResponse:
type: object
properties:
code: { type: string }
data: { $ref: '#/components/schemas/PdaOutboundTask' }
PdaOutboundTask:
type: object
required: [taskNo, status, outboundNo, outboundType]
properties:
id: { type: string, description: 内部任务键,不作为用户展示编号 }
taskNo: { type: string }
status: { type: string }
outboundNo: { type: string }
outboundType: { type: string, enum: [ONE_CLICK_DROPSHIP, RETURN_OUTBOUND, RELABEL_OUTBOUND, WAREHOUSE_TRANSFER_OUTBOUND, DISPATCH_DELIVERY], description: 正式出库单类型;列表、详情及写操作结果共用此响应 }
pushedAt: { type: string, format: date-time, nullable: true }
completedAt: { type: string, format: date-time, nullable: true }
PdaOutboundTaskDetail:
type: object
required: [task, items]
properties:
task:
type: object
required: [taskNo, status, outboundNo]
properties:
taskNo: { type: string }
status: { type: string }
outboundNo: { type: string }
pushedAt: { type: string, format: date-time, nullable: true }
completedAt: { type: string, format: date-time, nullable: true }
task: { $ref: '#/components/schemas/PdaOutboundTask' }
items:
type: array
items:
@@ -66,6 +66,27 @@ import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class DeliveryTransferServiceTest {
@org.junit.jupiter.params.ParameterizedTest
@org.junit.jupiter.params.provider.ValueSource(strings = {"ONE_CLICK_DROPSHIP", "RETURN_OUTBOUND", "RELABEL_OUTBOUND", "WAREHOUSE_TRANSFER_OUTBOUND", "DISPATCH_DELIVERY"})
void pdaListAndDetailExposeAuthoritativeOutboundType(String outboundType) {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "type-key");
outbound.setOverseasWarehouseId("WH-1");
outbound.setOutboundType(outboundType);
OutboundPdaTask task = new OutboundPdaTask();
task.setId(701L); task.setCustomerId("TENANT-A"); task.setOutboundOrderId(301L);
task.setTaskNo("PDA-701"); task.setStatus("PENDING");
when(pdaTasks.listByWarehouse("WH-1", null)).thenReturn(List.of(task));
when(pdaTasks.findByNoAndWarehouse("PDA-701", "WH-1")).thenReturn(task);
when(outboundOrders.selectById(301L)).thenReturn(outbound);
when(outboundPallets.findByOutbound("TENANT-A", 301L)).thenReturn(List.of());
assertEquals(outboundType, service.pdaTasks("WH-1", null).get(0).outboundType());
var detail = service.pdaTaskDetail("WH-1", "PDA-701");
assertEquals(outboundType, detail.task().outboundType());
assertEquals(outboundType, new com.fasterxml.jackson.databind.ObjectMapper()
.valueToTree(detail).path("task").path("outboundType").asText());
}
@Mock DispatchOrderMapper dispatchOrders;
@Mock DispatchItemMapper dispatchItems;
@Mock OutboundOrderMapper outboundOrders;