优化
This commit is contained in:
@@ -64,6 +64,58 @@ paths:
|
|||||||
schema: {$ref: '#/components/schemas/OrderApprovedInboundCommand'}
|
schema: {$ref: '#/components/schemas/OrderApprovedInboundCommand'}
|
||||||
responses:
|
responses:
|
||||||
'200': {description: 成功或业务失败;相同客户和订单重放返回已有入库单 ID}
|
'200': {description: 成功或业务失败;相同客户和订单重放返回已有入库单 ID}
|
||||||
|
/internal/firstleg/v1/inbounds/unified-orders/query:
|
||||||
|
post:
|
||||||
|
summary: 按统一订单条件分页查询履约摘要(Order 内部)
|
||||||
|
description: >-
|
||||||
|
mainStatus 接受六态产品状态;历史 WAIT_SPLIT 仅作为兼容筛选值,按 INBOUNDED 范围查询。
|
||||||
|
所有响应 mainStatus 均归并为六态产品状态,不返回 WAIT_SPLIT。
|
||||||
|
security:
|
||||||
|
- internalServiceToken: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedInboundQuery'}
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 成功或业务失败,按响应体 code 判断
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedInboundPageEnvelope'}
|
||||||
|
/internal/firstleg/v1/inbounds/unified-orders/summaries:
|
||||||
|
post:
|
||||||
|
summary: 按订单 ID 批量查询统一履约摘要(Order 内部)
|
||||||
|
security:
|
||||||
|
- internalServiceToken: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedInboundBatchRequest'}
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 成功或业务失败,按响应体 code 判断
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedInboundListEnvelope'}
|
||||||
|
/internal/firstleg/v1/inbounds/unified-orders/detail:
|
||||||
|
post:
|
||||||
|
summary: 按订单 ID 查询统一履约详情(Order 内部)
|
||||||
|
description: 时间线保留 Firstleg 内部 fromStatus/toStatus;summary.mainStatus 始终使用六态产品状态。
|
||||||
|
security:
|
||||||
|
- internalServiceToken: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedInboundDetailRequest'}
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: 成功或业务失败;找不到对应入库单时 data 为 null
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedInboundDetailEnvelope'}
|
||||||
/api/platform/v1/first-leg/tasks:
|
/api/platform/v1/first-leg/tasks:
|
||||||
get:
|
get:
|
||||||
summary: 分页查询履约任务
|
summary: 分页查询履约任务
|
||||||
@@ -528,6 +580,198 @@ components:
|
|||||||
required: true
|
required: true
|
||||||
schema: {type: string, pattern: '^[1-9][0-9]*$'}
|
schema: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||||
schemas:
|
schemas:
|
||||||
|
UnifiedMainStatus:
|
||||||
|
type: string
|
||||||
|
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, LOADING, LOADED, DELIVERED]
|
||||||
|
description: 产品主状态;历史内部状态 WAIT_SPLIT 在响应中统一归并为 INBOUNDED
|
||||||
|
UnifiedInboundQuery:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
tenantId: {type: integer, format: int64, nullable: true}
|
||||||
|
keyword: {type: string, maxLength: 64, nullable: true}
|
||||||
|
orderNo: {type: string, maxLength: 64, nullable: true}
|
||||||
|
mainStatus:
|
||||||
|
type: string
|
||||||
|
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, LOADING, LOADED, DELIVERED, WAIT_SPLIT]
|
||||||
|
nullable: true
|
||||||
|
description: WAIT_SPLIT 仅为历史兼容筛选值,等价于 INBOUNDED
|
||||||
|
customerId: {type: integer, format: int64, nullable: true}
|
||||||
|
receivingWarehouseId: {type: integer, format: int64, nullable: true}
|
||||||
|
destinationWarehouseId: {type: integer, format: int64, nullable: true}
|
||||||
|
inboundCode: {type: string, maxLength: 64, nullable: true}
|
||||||
|
internalOrderStatus: {type: string, maxLength: 32, nullable: true}
|
||||||
|
expectedArrivalDate: {type: string, format: date, nullable: true}
|
||||||
|
feeReviewStatus: {type: string, maxLength: 32, nullable: true}
|
||||||
|
channelName: {type: string, maxLength: 128, nullable: true}
|
||||||
|
containerNo: {type: string, maxLength: 64, nullable: true}
|
||||||
|
cabinetNo: {type: string, maxLength: 64, nullable: true}
|
||||||
|
sailingDate: {type: string, format: date, nullable: true}
|
||||||
|
loaded: {type: boolean, nullable: true}
|
||||||
|
fullContainer: {type: boolean, nullable: true}
|
||||||
|
measurementReviewStatus: {type: string, maxLength: 32, nullable: true}
|
||||||
|
candidateOrderIds: {type: array, nullable: true, items: {type: integer, format: int64}}
|
||||||
|
createdFrom: {type: string, format: date-time, nullable: true}
|
||||||
|
createdTo: {type: string, format: date-time, nullable: true}
|
||||||
|
page: {type: integer, format: int64, minimum: 1, default: 1}
|
||||||
|
size: {type: integer, format: int64, minimum: 1, maximum: 200, default: 20}
|
||||||
|
UnifiedInboundBatchRequest:
|
||||||
|
type: object
|
||||||
|
required: [orderIds]
|
||||||
|
properties:
|
||||||
|
tenantId: {type: integer, format: int64, nullable: true}
|
||||||
|
orderIds:
|
||||||
|
type: array
|
||||||
|
minItems: 1
|
||||||
|
maxItems: 200
|
||||||
|
items: {type: integer, format: int64, minimum: 1}
|
||||||
|
UnifiedInboundDetailRequest:
|
||||||
|
type: object
|
||||||
|
required: [tenantId, orderId]
|
||||||
|
properties:
|
||||||
|
tenantId: {type: integer, format: int64, minimum: 1}
|
||||||
|
orderId: {type: integer, format: int64, minimum: 1}
|
||||||
|
UnifiedInboundPageEnvelope:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code: {type: string, example: '200'}
|
||||||
|
message: {type: string}
|
||||||
|
data:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
records: {type: array, items: {$ref: '#/components/schemas/UnifiedInboundSummary'}}
|
||||||
|
page: {type: integer, format: int64, minimum: 1}
|
||||||
|
size: {type: integer, format: int64, minimum: 1}
|
||||||
|
total: {type: integer, format: int64, minimum: 0}
|
||||||
|
pages: {type: integer, format: int64, minimum: 0}
|
||||||
|
UnifiedInboundListEnvelope:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code: {type: string, example: '200'}
|
||||||
|
message: {type: string}
|
||||||
|
data: {type: array, items: {$ref: '#/components/schemas/UnifiedInboundSummary'}}
|
||||||
|
UnifiedInboundDetailEnvelope:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code: {type: string, example: '200'}
|
||||||
|
message: {type: string}
|
||||||
|
data:
|
||||||
|
allOf: [{$ref: '#/components/schemas/UnifiedInboundDetail'}]
|
||||||
|
nullable: true
|
||||||
|
UnifiedInboundDetail:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
summary: {$ref: '#/components/schemas/UnifiedInboundSummary'}
|
||||||
|
timeline:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
source: {type: string}
|
||||||
|
eventType: {type: string}
|
||||||
|
fromStatus: {type: string, nullable: true}
|
||||||
|
toStatus: {type: string, nullable: true}
|
||||||
|
action: {type: string, nullable: true}
|
||||||
|
remark: {type: string, nullable: true}
|
||||||
|
operatorId: {type: integer, format: int64, nullable: true}
|
||||||
|
operatorName: {type: string, nullable: true}
|
||||||
|
occurredAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedInboundSummary:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
inbound: {$ref: '#/components/schemas/UnifiedInboundOrderSummary'}
|
||||||
|
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
|
||||||
|
nextAction: {type: string, nullable: true}
|
||||||
|
preload: {$ref: '#/components/schemas/UnifiedPreloadSummary'}
|
||||||
|
loading: {$ref: '#/components/schemas/UnifiedLoadingSummary'}
|
||||||
|
delivery: {$ref: '#/components/schemas/UnifiedDeliverySummary'}
|
||||||
|
UnifiedInboundOrderSummary:
|
||||||
|
type: object
|
||||||
|
description: InboundOrderSummary 的内部统一订单投影
|
||||||
|
properties:
|
||||||
|
id: {type: integer, format: int64}
|
||||||
|
tenantId: {type: integer, format: int64}
|
||||||
|
orderId: {type: integer, format: int64, nullable: true}
|
||||||
|
orderNo: {type: string, nullable: true}
|
||||||
|
inboundCode: {type: string, nullable: true}
|
||||||
|
processStatus: {type: string, nullable: true, description: Firstleg 内部状态,历史数据可能为 WAIT_SPLIT}
|
||||||
|
orderStatusSnapshot: {type: string, nullable: true}
|
||||||
|
measureReviewStatus: {type: string, nullable: true}
|
||||||
|
feeStatus: {type: string, nullable: true}
|
||||||
|
feeReviewStatus: {type: string, nullable: true}
|
||||||
|
notificationStatus: {type: string, nullable: true}
|
||||||
|
version: {type: integer, nullable: true}
|
||||||
|
expectedArrivalAt: {type: string, format: date-time, nullable: true}
|
||||||
|
customerId: {type: integer, format: int64, nullable: true}
|
||||||
|
customerName: {type: string, nullable: true}
|
||||||
|
companyId: {type: integer, format: int64, nullable: true}
|
||||||
|
companyName: {type: string, nullable: true}
|
||||||
|
warehouseId: {type: integer, format: int64, nullable: true}
|
||||||
|
warehouseName: {type: string, nullable: true}
|
||||||
|
deliveryWarehouseId: {type: integer, format: int64, nullable: true}
|
||||||
|
deliveryWarehouseName: {type: string, nullable: true}
|
||||||
|
businessType: {type: string, nullable: true}
|
||||||
|
channelCode: {type: string, nullable: true}
|
||||||
|
channelName: {type: string, nullable: true}
|
||||||
|
taxMode: {type: string, nullable: true}
|
||||||
|
pricingTaxMode: {type: string, nullable: true}
|
||||||
|
containerMode: {type: string, nullable: true}
|
||||||
|
customsInspection: {type: boolean, nullable: true}
|
||||||
|
destinationType: {type: string, nullable: true}
|
||||||
|
destinationWarehouseId: {type: integer, format: int64, nullable: true}
|
||||||
|
destinationCode: {type: string, nullable: true}
|
||||||
|
destinationName: {type: string, nullable: true}
|
||||||
|
holdFlag: {type: boolean, nullable: true}
|
||||||
|
currentHoldReason: {type: string, nullable: true}
|
||||||
|
totalPackageCount: {type: integer, nullable: true}
|
||||||
|
locationCodes: {type: string, nullable: true}
|
||||||
|
actualGrossWeightKg: {type: number, nullable: true}
|
||||||
|
actualNetWeightKg: {type: number, nullable: true}
|
||||||
|
actualVolumeCbm: {type: number, nullable: true}
|
||||||
|
volumeWeightKg: {type: number, nullable: true}
|
||||||
|
chargeableWeightKg: {type: number, nullable: true}
|
||||||
|
chargeableVolumeCbm: {type: number, nullable: true}
|
||||||
|
palletCount: {type: integer, nullable: true}
|
||||||
|
measureCompletedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
inboundedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
expressCompanyCode: {type: string, nullable: true}
|
||||||
|
expressCompanyName: {type: string, nullable: true}
|
||||||
|
loadingStatus: {type: string, nullable: true}
|
||||||
|
containerNo: {type: string, nullable: true}
|
||||||
|
cabinetNo: {type: string, nullable: true}
|
||||||
|
vesselName: {type: string, nullable: true}
|
||||||
|
voyageNo: {type: string, nullable: true}
|
||||||
|
sailingAt: {type: string, format: date-time, nullable: true}
|
||||||
|
portArrivalAt: {type: string, format: date-time, nullable: true}
|
||||||
|
expressTrackingNo: {type: string, nullable: true}
|
||||||
|
createdAt: {type: string, format: date-time, nullable: true}
|
||||||
|
updatedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedPreloadSummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
status: {type: string, nullable: true}
|
||||||
|
version: {type: integer, nullable: true}
|
||||||
|
itemCount: {type: integer, nullable: true}
|
||||||
|
updatedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedLoadingSummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
status: {type: string, nullable: true}
|
||||||
|
loadingVersion: {type: integer, nullable: true, description: 当前为 pickingVersion 兼容别名}
|
||||||
|
pickingVersion: {type: integer, nullable: true}
|
||||||
|
expectedCount: {type: integer, nullable: true}
|
||||||
|
pickedCount: {type: integer, nullable: true}
|
||||||
|
evidenceCount: {type: integer, nullable: true}
|
||||||
|
sealNo: {type: string, nullable: true}
|
||||||
|
updatedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedDeliverySummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
status: {type: string, nullable: true}
|
||||||
|
completedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
trackingNo: {type: string, nullable: true}
|
||||||
MojiaMeasurementUploadEnvelope:
|
MojiaMeasurementUploadEnvelope:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
+3
-2
@@ -41,6 +41,8 @@ class InboundOrderControllerContractTest {
|
|||||||
String taskList = openapi.substring(
|
String taskList = openapi.substring(
|
||||||
openapi.indexOf("/api/platform/v1/first-leg/tasks:"),
|
openapi.indexOf("/api/platform/v1/first-leg/tasks:"),
|
||||||
openapi.indexOf("/api/platform/v1/first-leg/tasks/{taskId}:"));
|
openapi.indexOf("/api/platform/v1/first-leg/tasks/{taskId}:"));
|
||||||
|
assertThat(inboundList)
|
||||||
|
.doesNotContain("WAIT_SPLIT");
|
||||||
assertThat(openapi)
|
assertThat(openapi)
|
||||||
.contains("/api/platform/inbounds:")
|
.contains("/api/platform/inbounds:")
|
||||||
.contains("/api/platform/inbounds/{inboundId}:")
|
.contains("/api/platform/inbounds/{inboundId}:")
|
||||||
@@ -53,8 +55,7 @@ class InboundOrderControllerContractTest {
|
|||||||
.contains("tenantId: {type: string, pattern: '^[1-9][0-9]*$', description:")
|
.contains("tenantId: {type: string, pattern: '^[1-9][0-9]*$', description:")
|
||||||
.contains("[WAIT_MEASURE, WAIT_REVIEW, WAIT_FEE_SEND, INBOUNDED]")
|
.contains("[WAIT_MEASURE, WAIT_REVIEW, WAIT_FEE_SEND, INBOUNDED]")
|
||||||
.contains("platform:firstleg:inbound:list")
|
.contains("platform:firstleg:inbound:list")
|
||||||
.doesNotContain("WAIT_CUSTOMER_CONFIRM")
|
.doesNotContain("WAIT_CUSTOMER_CONFIRM");
|
||||||
.doesNotContain("WAIT_SPLIT");
|
|
||||||
assertThat(inboundList).contains("#/components/schemas/InboundOrderPageEnvelope");
|
assertThat(inboundList).contains("#/components/schemas/InboundOrderPageEnvelope");
|
||||||
assertThat(taskList).doesNotContain("InboundOrderPageEnvelope");
|
assertThat(taskList).doesNotContain("InboundOrderPageEnvelope");
|
||||||
}
|
}
|
||||||
|
|||||||
+56
@@ -0,0 +1,56 @@
|
|||||||
|
package com.baiyue.firstleg.controller;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class UnifiedInboundOpenApiContractTest {
|
||||||
|
@Test
|
||||||
|
void documentsInternalUnifiedOrderEndpointsAndLegacyWaitSplitMapping() throws Exception {
|
||||||
|
String yaml = new ClassPathResource("openapi/firstleg-v1.yaml")
|
||||||
|
.getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
Map<String, Object> document = stringMap(new Yaml().load(yaml));
|
||||||
|
Map<String, Object> paths = stringMap(document.get("paths"));
|
||||||
|
assertThat(paths).containsKeys(
|
||||||
|
"/internal/firstleg/v1/inbounds/unified-orders/query",
|
||||||
|
"/internal/firstleg/v1/inbounds/unified-orders/summaries",
|
||||||
|
"/internal/firstleg/v1/inbounds/unified-orders/detail");
|
||||||
|
|
||||||
|
Map<String, Object> schemas = stringMap(stringMap(document.get("components")).get("schemas"));
|
||||||
|
Map<String, Object> responseStatus = stringMap(schemas.get("UnifiedMainStatus"));
|
||||||
|
assertThat(stringList(responseStatus.get("enum"))).containsExactly(
|
||||||
|
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
|
||||||
|
assertThat(responseStatus.get("description").toString()).contains("WAIT_SPLIT", "INBOUNDED");
|
||||||
|
|
||||||
|
Map<String, Object> query = stringMap(schemas.get("UnifiedInboundQuery"));
|
||||||
|
Map<String, Object> queryStatus = stringMap(stringMap(query.get("properties")).get("mainStatus"));
|
||||||
|
assertThat(stringList(queryStatus.get("enum"))).contains("WAIT_SPLIT");
|
||||||
|
assertThat(queryStatus.get("description").toString()).contains("INBOUNDED");
|
||||||
|
|
||||||
|
Map<String, Object> inbound = stringMap(schemas.get("UnifiedInboundOrderSummary"));
|
||||||
|
assertThat(stringMap(inbound.get("properties"))).containsKeys(
|
||||||
|
"id", "tenantId", "orderId", "inboundCode", "processStatus", "version",
|
||||||
|
"customerId", "warehouseId", "deliveryWarehouseId", "totalPackageCount",
|
||||||
|
"businessType", "channelCode", "pricingTaxMode", "containerMode", "destinationType",
|
||||||
|
"holdFlag", "actualGrossWeightKg", "actualNetWeightKg", "actualVolumeCbm",
|
||||||
|
"volumeWeightKg", "chargeableWeightKg", "chargeableVolumeCbm", "palletCount",
|
||||||
|
"inboundedAt", "expressCompanyCode", "expressCompanyName", "loadingStatus", "containerNo",
|
||||||
|
"cabinetNo", "sailingAt", "portArrivalAt", "createdAt", "updatedAt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Map<String, Object> stringMap(Object value) {
|
||||||
|
return (Map<String, Object>) value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static List<String> stringList(Object value) {
|
||||||
|
return (List<String>) value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -43,7 +43,11 @@ paths:
|
|||||||
- $ref: '#/components/parameters/UnifiedPage'
|
- $ref: '#/components/parameters/UnifiedPage'
|
||||||
- $ref: '#/components/parameters/UnifiedSize'
|
- $ref: '#/components/parameters/UnifiedSize'
|
||||||
responses:
|
responses:
|
||||||
'200': {description: 成功或业务失败,响应体 code 为字符串 200 时成功}
|
'200':
|
||||||
|
description: 成功或业务失败,响应体 code 为字符串 200 时成功
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedOrderPageEnvelope'}
|
||||||
'403': {description: 需要平台身份和 platform:order:list 权限}
|
'403': {description: 需要平台身份和 platform:order:list 权限}
|
||||||
/api/platform/v1/orders/{orderId}:
|
/api/platform/v1/orders/{orderId}:
|
||||||
get:
|
get:
|
||||||
@@ -53,7 +57,11 @@ paths:
|
|||||||
- $ref: '#/components/parameters/UnifiedOrderId'
|
- $ref: '#/components/parameters/UnifiedOrderId'
|
||||||
- $ref: '#/components/parameters/UnifiedTenantId'
|
- $ref: '#/components/parameters/UnifiedTenantId'
|
||||||
responses:
|
responses:
|
||||||
'200': {description: 成功或业务失败,按响应体 code 判断}
|
'200':
|
||||||
|
description: 成功或业务失败,按响应体 code 判断
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedOrderDetailEnvelope'}
|
||||||
'403': {description: 需要平台身份和 platform:order:list 权限}
|
'403': {description: 需要平台身份和 platform:order:list 权限}
|
||||||
/api/platform/v1/orders/{orderId}/internal-notes:
|
/api/platform/v1/orders/{orderId}/internal-notes:
|
||||||
post:
|
post:
|
||||||
@@ -105,7 +113,11 @@ paths:
|
|||||||
- $ref: '#/components/parameters/UnifiedPage'
|
- $ref: '#/components/parameters/UnifiedPage'
|
||||||
- $ref: '#/components/parameters/UnifiedSize'
|
- $ref: '#/components/parameters/UnifiedSize'
|
||||||
responses:
|
responses:
|
||||||
'200': {description: 成功或业务失败,按响应体 code 判断}
|
'200':
|
||||||
|
description: 成功或业务失败,按响应体 code 判断
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: {$ref: '#/components/schemas/UnifiedOrderPageEnvelope'}
|
||||||
'403': {description: 需要 merchant:order:list 权限}
|
'403': {description: 需要 merchant:order:list 权限}
|
||||||
/api/merchant/v1/orders/{orderId}:
|
/api/merchant/v1/orders/{orderId}:
|
||||||
get:
|
get:
|
||||||
@@ -118,7 +130,7 @@ paths:
|
|||||||
description: 成功或业务失败,按响应体 code 判断
|
description: 成功或业务失败,按响应体 code 判断
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema: {$ref: '#/components/schemas/FirstLegOrderSummary'}
|
schema: {$ref: '#/components/schemas/UnifiedOrderDetailEnvelope'}
|
||||||
'403': {description: 需要 merchant:order:view 权限}
|
'403': {description: 需要 merchant:order:view 权限}
|
||||||
/api/merchant/v1/orders/first-leg:
|
/api/merchant/v1/orders/first-leg:
|
||||||
get:
|
get:
|
||||||
@@ -295,6 +307,146 @@ components:
|
|||||||
scheme: bearer
|
scheme: bearer
|
||||||
bearerFormat: JWT
|
bearerFormat: JWT
|
||||||
schemas:
|
schemas:
|
||||||
|
UnifiedMainStatus:
|
||||||
|
type: string
|
||||||
|
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, LOADING, LOADED, DELIVERED]
|
||||||
|
description: >-
|
||||||
|
统一订单产品主状态。历史内部状态 WAIT_SPLIT 仅作为查询入参兼容,所有列表、详情和时间线响应均归并为 INBOUNDED。
|
||||||
|
UnifiedOrderPageEnvelope:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code: {type: string, example: '200'}
|
||||||
|
message: {type: string}
|
||||||
|
data: {$ref: '#/components/schemas/UnifiedOrderPage'}
|
||||||
|
UnifiedOrderPage:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
records: {type: array, items: {$ref: '#/components/schemas/UnifiedOrderRow'}}
|
||||||
|
page: {type: integer, format: int64, minimum: 1}
|
||||||
|
size: {type: integer, format: int64, minimum: 1}
|
||||||
|
total: {type: integer, format: int64, minimum: 0}
|
||||||
|
pages: {type: integer, format: int64, minimum: 0}
|
||||||
|
UnifiedOrderDetailEnvelope:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code: {type: string, example: '200'}
|
||||||
|
message: {type: string}
|
||||||
|
data: {$ref: '#/components/schemas/UnifiedOrderDetail'}
|
||||||
|
UnifiedOrderDetail:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
summary: {$ref: '#/components/schemas/UnifiedOrderRow'}
|
||||||
|
order:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
order: {type: object, description: Order 服务订单主实体}
|
||||||
|
cargoes: {type: array, items: {type: object}}
|
||||||
|
parties: {type: array, items: {type: object}}
|
||||||
|
internalNotes: {type: array, items: {type: object}, description: 客户详情始终为空数组}
|
||||||
|
statusHistory: {type: array, items: {type: object}}
|
||||||
|
timeline: {type: array, items: {$ref: '#/components/schemas/UnifiedOrderTimelineEvent'}}
|
||||||
|
UnifiedOrderRow:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
orderId: {type: integer, format: int64}
|
||||||
|
tenantId: {type: integer, format: int64}
|
||||||
|
orderNo: {type: string}
|
||||||
|
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
|
||||||
|
nextAction: {type: string, nullable: true}
|
||||||
|
relation: {$ref: '#/components/schemas/UnifiedOrderRelation'}
|
||||||
|
internalOrderStatus: {type: string, enum: [DRAFT, SUBMITTED, APPROVED, ACCEPTED, REJECTED, CANCELLED, PROCESSING, COMPLETED], nullable: true}
|
||||||
|
customerId: {type: integer, format: int64, nullable: true}
|
||||||
|
customerName: {type: string, nullable: true}
|
||||||
|
channelName: {type: string, nullable: true}
|
||||||
|
receivingWarehouseId: {type: integer, format: int64, nullable: true}
|
||||||
|
receivingWarehouseName: {type: string, nullable: true}
|
||||||
|
destinationWarehouseId: {type: integer, format: int64, nullable: true}
|
||||||
|
destinationWarehouseName: {type: string, nullable: true}
|
||||||
|
inboundId: {type: integer, format: int64, nullable: true}
|
||||||
|
inboundVersion: {type: integer, nullable: true, description: C04 loading/complete 的 expectedVersion}
|
||||||
|
inboundCode: {type: string, nullable: true}
|
||||||
|
expectedShipDate: {type: string, format: date, nullable: true}
|
||||||
|
totalPackageCount: {type: integer, nullable: true}
|
||||||
|
recommendedPalletType: {type: string, nullable: true}
|
||||||
|
locationCodes: {type: string, nullable: true}
|
||||||
|
actualGrossWeightKg: {type: number, nullable: true}
|
||||||
|
actualVolumeCbm: {type: number, nullable: true}
|
||||||
|
measurement: {$ref: '#/components/schemas/UnifiedMeasurementSummary'}
|
||||||
|
preload: {$ref: '#/components/schemas/UnifiedPreloadSummary'}
|
||||||
|
loading: {$ref: '#/components/schemas/UnifiedLoadingSummary'}
|
||||||
|
delivery: {$ref: '#/components/schemas/UnifiedDeliverySummary'}
|
||||||
|
transport: {$ref: '#/components/schemas/UnifiedTransportSummary'}
|
||||||
|
customerRemark: {type: string, nullable: true}
|
||||||
|
latestInternalNote: {type: string, nullable: true, description: 客户详情不返回平台内部备注}
|
||||||
|
createdAt: {type: string, format: date-time, nullable: true}
|
||||||
|
updatedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedOrderRelation:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
role: {type: string, enum: [PARENT, CHILD]}
|
||||||
|
rootOrderId: {type: integer, format: int64, nullable: true}
|
||||||
|
parentOrderId: {type: integer, format: int64, nullable: true}
|
||||||
|
splitSequence: {type: integer, nullable: true}
|
||||||
|
childCount: {type: integer, nullable: true}
|
||||||
|
UnifiedMeasurementSummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
reviewStatus: {type: string, nullable: true}
|
||||||
|
actualGrossWeightKg: {type: number, nullable: true}
|
||||||
|
actualVolumeCbm: {type: number, nullable: true}
|
||||||
|
chargeableWeightKg: {type: number, nullable: true}
|
||||||
|
completedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedPreloadSummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
status: {type: string, nullable: true}
|
||||||
|
version: {type: integer, nullable: true}
|
||||||
|
itemCount: {type: integer, nullable: true}
|
||||||
|
updatedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedLoadingSummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
status: {type: string, nullable: true}
|
||||||
|
loadingVersion: {type: integer, nullable: true, description: 当前为 pickingVersion 兼容别名}
|
||||||
|
pickingVersion: {type: integer, nullable: true}
|
||||||
|
expectedCount: {type: integer, nullable: true}
|
||||||
|
pickedCount: {type: integer, nullable: true}
|
||||||
|
evidenceCount: {type: integer, nullable: true}
|
||||||
|
sealNo: {type: string, nullable: true}
|
||||||
|
updatedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedDeliverySummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
status: {type: string, nullable: true}
|
||||||
|
completedAt: {type: string, format: date-time, nullable: true}
|
||||||
|
trackingNo: {type: string, nullable: true}
|
||||||
|
UnifiedTransportSummary:
|
||||||
|
type: object
|
||||||
|
nullable: true
|
||||||
|
properties:
|
||||||
|
loadingStatus: {type: string, nullable: true}
|
||||||
|
containerNo: {type: string, nullable: true}
|
||||||
|
cabinetNo: {type: string, nullable: true}
|
||||||
|
vesselName: {type: string, nullable: true}
|
||||||
|
voyageNo: {type: string, nullable: true}
|
||||||
|
sailingAt: {type: string, format: date-time, nullable: true}
|
||||||
|
portArrivalAt: {type: string, format: date-time, nullable: true}
|
||||||
|
UnifiedOrderTimelineEvent:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
source: {type: string}
|
||||||
|
eventType: {type: string}
|
||||||
|
mainStatus: {$ref: '#/components/schemas/UnifiedMainStatus'}
|
||||||
|
action: {type: string, nullable: true}
|
||||||
|
remark: {type: string, nullable: true}
|
||||||
|
operatorId: {type: integer, format: int64, nullable: true}
|
||||||
|
operatorName: {type: string, nullable: true}
|
||||||
|
occurredAt: {type: string, format: date-time, nullable: true}
|
||||||
FulfillmentSplitQuantityLine:
|
FulfillmentSplitQuantityLine:
|
||||||
type: object
|
type: object
|
||||||
required: [sourceDetailId, packageCount]
|
required: [sourceDetailId, packageCount]
|
||||||
@@ -459,9 +611,10 @@ components:
|
|||||||
name: mainStatus
|
name: mainStatus
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
|
description: 历史值 WAIT_SPLIT 仍可作为兼容筛选传入,但响应 mainStatus 始终归并为 INBOUNDED
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, LOADING, LOADED, DELIVERED]
|
enum: [PENDING_REVIEW, WAIT_MEASURE, INBOUNDED, LOADING, LOADED, DELIVERED, WAIT_SPLIT]
|
||||||
UnifiedInboundCode:
|
UnifiedInboundCode:
|
||||||
{name: inboundCode, in: query, required: false, description: 客户提交后生成的入仓码,待审核订单同样可查询, schema: {type: string, maxLength: 40}}
|
{name: inboundCode, in: query, required: false, description: 客户提交后生成的入仓码,待审核订单同样可查询, schema: {type: string, maxLength: 40}}
|
||||||
UnifiedInternalOrderStatus:
|
UnifiedInternalOrderStatus:
|
||||||
|
|||||||
@@ -39,7 +39,24 @@ class UnifiedOrderApiContractTest {
|
|||||||
Map<String, Object> mainStatus = stringMap(parameters.get("UnifiedMainStatus"));
|
Map<String, Object> mainStatus = stringMap(parameters.get("UnifiedMainStatus"));
|
||||||
Map<String, Object> schema = stringMap(mainStatus.get("schema"));
|
Map<String, Object> schema = stringMap(mainStatus.get("schema"));
|
||||||
assertThat(stringList(schema.get("enum"))).containsExactly(
|
assertThat(stringList(schema.get("enum"))).containsExactly(
|
||||||
|
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED", "WAIT_SPLIT");
|
||||||
|
assertThat(mainStatus.get("description").toString())
|
||||||
|
.contains("WAIT_SPLIT", "INBOUNDED");
|
||||||
|
|
||||||
|
Map<String, Object> schemas = stringMap(components.get("schemas"));
|
||||||
|
Map<String, Object> responseStatus = stringMap(schemas.get("UnifiedMainStatus"));
|
||||||
|
assertThat(stringList(responseStatus.get("enum"))).containsExactly(
|
||||||
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
|
"PENDING_REVIEW", "WAIT_MEASURE", "INBOUNDED", "LOADING", "LOADED", "DELIVERED");
|
||||||
|
assertThat(responseStatus.get("description").toString()).contains("WAIT_SPLIT", "INBOUNDED");
|
||||||
|
|
||||||
|
Map<String, Object> row = stringMap(schemas.get("UnifiedOrderRow"));
|
||||||
|
assertThat(stringMap(row.get("properties"))).containsKeys(
|
||||||
|
"orderId", "mainStatus", "inboundId", "inboundVersion", "measurement", "preload",
|
||||||
|
"loading", "delivery", "transport", "customerRemark", "latestInternalNote");
|
||||||
|
assertThat(responseSchema(paths, "/api/platform/v1/orders", "get"))
|
||||||
|
.containsEntry("$ref", "#/components/schemas/UnifiedOrderPageEnvelope");
|
||||||
|
assertThat(responseSchema(paths, "/api/merchant/v1/orders/{orderId}", "get"))
|
||||||
|
.containsEntry("$ref", "#/components/schemas/UnifiedOrderDetailEnvelope");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -61,4 +78,11 @@ class UnifiedOrderApiContractTest {
|
|||||||
private static List<String> stringList(Object value) {
|
private static List<String> stringList(Object value) {
|
||||||
return (List<String>) value;
|
return (List<String>) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Map<String, Object> responseSchema(Map<String, Object> paths, String path, String method) {
|
||||||
|
Map<String, Object> operation = stringMap(stringMap(paths.get(path)).get(method));
|
||||||
|
Map<String, Object> response = stringMap(stringMap(operation.get("responses")).get("200"));
|
||||||
|
Map<String, Object> content = stringMap(response.get("content"));
|
||||||
|
return stringMap(stringMap(content.get("application/json")).get("schema"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user