优化
This commit is contained in:
@@ -558,6 +558,26 @@ paths:
|
||||
requestId: {type: string, nullable: true}
|
||||
traceId: {type: string, nullable: true}
|
||||
'403': {description: 无查看权限}
|
||||
/api/platform/v1/first-leg/inbounds/{inboundId}/measurement:
|
||||
get:
|
||||
summary: 查询入库测量详情
|
||||
description: >-
|
||||
需要 platform:firstleg:inbound:measure:view 权限。尺寸测量图片只随 items 返回,
|
||||
由稳定的 item.id(measurementItemId)与 intelligentImageFileId 关联;称重图片只随
|
||||
weightResults 返回,由稳定的 weightResult.id 与 fileId 关联,并携带确认状态及服务端确认的
|
||||
grossWeightKg/netWeightKg。两类证据不得合并为无明细归属的统一图片列表。
|
||||
actualGrossWeightKg 是服务端权威的货物总重量;存在已确认称重结果时为所有已确认
|
||||
netWeightKg 之和,消费者不得根据图片或 OCR 值自行重算。
|
||||
parameters:
|
||||
- {name: tenantId, in: query, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
- {name: inboundId, in: path, required: true, schema: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
responses:
|
||||
'200':
|
||||
description: 成功或业务失败,按响应体 code 判断
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/InboundMeasurementDetailEnvelope'}
|
||||
'403': {description: 无入库测量查看权限}
|
||||
/api/platform/v1/first-leg/inbounds/{inboundId}/measurement/carton-scans:
|
||||
post:
|
||||
summary: 平台模拟单箱设备扫描
|
||||
@@ -1204,6 +1224,96 @@ components:
|
||||
confirmedBy: {type: string, nullable: true}
|
||||
confirmedAt: {type: string, format: date-time, nullable: true}
|
||||
version: {type: integer, minimum: 0}
|
||||
InboundMeasurementItem:
|
||||
type: object
|
||||
description: >-
|
||||
单条尺寸测量明细。id 是稳定 measurementItemId;intelligentImageFileId 仅表示该明细的
|
||||
尺寸测量原图,不得与 weightResults.fileId 混用。
|
||||
required: [id]
|
||||
properties:
|
||||
id: {type: string, pattern: '^[1-9][0-9]*$', description: 稳定 measurementItemId}
|
||||
inboundDetailId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
itemNo: {type: string, nullable: true}
|
||||
boxSpecification: {type: string, nullable: true}
|
||||
packageCount: {type: integer, nullable: true}
|
||||
lengthCm: {type: number, format: double, nullable: true}
|
||||
widthCm: {type: number, format: double, nullable: true}
|
||||
heightCm: {type: number, format: double, nullable: true}
|
||||
girthCm: {type: number, format: double, nullable: true}
|
||||
unitWeightKg: {type: number, format: double, nullable: true}
|
||||
unitVolumeCbm: {type: number, format: double, nullable: true}
|
||||
totalWeightKg: {type: number, format: double, nullable: true}
|
||||
totalVolumeCbm: {type: number, format: double, nullable: true}
|
||||
volumeWeightKg: {type: number, format: double, nullable: true}
|
||||
intelligentImageFileId:
|
||||
type: string
|
||||
pattern: '^[1-9][0-9]*$'
|
||||
nullable: true
|
||||
description: 当前尺寸测量明细的 File 文件 ID;图片类型固定为 DIMENSION
|
||||
overLimitWarning: {type: string, nullable: true}
|
||||
outerLengthCm: {type: number, format: double, nullable: true}
|
||||
outerWidthCm: {type: number, format: double, nullable: true}
|
||||
outerHeightCm: {type: number, format: double, nullable: true}
|
||||
remark: {type: string, nullable: true}
|
||||
boxCode: {type: string, nullable: true}
|
||||
scannedShipmentId: {type: string, nullable: true}
|
||||
confirmedPackageCount: {type: integer, nullable: true}
|
||||
machineNo: {type: string, nullable: true}
|
||||
deviceMeasuredAt: {type: string, format: date-time, nullable: true}
|
||||
updatedAt: {type: string, format: date-time, nullable: true}
|
||||
InboundMeasurementDetail:
|
||||
type: object
|
||||
description: >-
|
||||
入库测量权威详情。尺寸证据天然归属 items,称重证据天然归属 weightResults,响应不提供统一 images。
|
||||
required: [inboundId, inboundVersion, items, weightResults]
|
||||
properties:
|
||||
inboundId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
inboundCode: {type: string, nullable: true}
|
||||
companyName: {type: string, nullable: true}
|
||||
channelCode: {type: string, nullable: true}
|
||||
channelName: {type: string, nullable: true}
|
||||
processStatus: {type: string, nullable: true}
|
||||
measureReviewStatus: {type: string, nullable: true}
|
||||
recommendedPalletType: {type: string, nullable: true}
|
||||
reviewReason: {type: string, nullable: true}
|
||||
reviewerId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
reviewerName: {type: string, nullable: true}
|
||||
measurementId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
sourceType: {type: string, nullable: true}
|
||||
measurementStatus: {type: string, nullable: true}
|
||||
inboundVersion: {type: integer, minimum: 0}
|
||||
version: {type: integer, minimum: 0, nullable: true}
|
||||
originalDocumentNo: {type: string, nullable: true}
|
||||
originalGrossWeightKg: {type: number, format: double, nullable: true}
|
||||
originalVolumeCbm: {type: number, format: double, nullable: true}
|
||||
originalPackageCount: {type: integer, nullable: true}
|
||||
actualPackageCount: {type: integer, nullable: true}
|
||||
actualGrossWeightKg:
|
||||
type: number
|
||||
format: double
|
||||
nullable: true
|
||||
multipleOf: 0.001
|
||||
description: 服务端权威货物总重量 KG;存在已确认称重结果时为所有 CONFIRMED netWeightKg 之和
|
||||
actualVolumeCbm: {type: number, format: double, nullable: true}
|
||||
volumeWeightKg: {type: number, format: double, nullable: true}
|
||||
actualGoodsName: {type: string, nullable: true}
|
||||
items:
|
||||
type: array
|
||||
description: DIMENSION 尺寸测量明细及其图片证据
|
||||
items: {$ref: '#/components/schemas/InboundMeasurementItem'}
|
||||
weightResults:
|
||||
type: array
|
||||
description: WEIGHT 称重结果及其图片、确认状态和服务端重量事实
|
||||
items: {$ref: '#/components/schemas/InboundWeightResult'}
|
||||
InboundMeasurementDetailEnvelope:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code: {type: string, example: '200'}
|
||||
message: {type: string}
|
||||
data: {$ref: '#/components/schemas/InboundMeasurementDetail'}
|
||||
requestId: {type: string, nullable: true}
|
||||
traceId: {type: string, nullable: true}
|
||||
ConfirmWeightResultRequest:
|
||||
type: object
|
||||
required: [version]
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
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.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class InboundMeasurementEvidenceOpenApiContractTest {
|
||||
|
||||
@Test
|
||||
void measurementDetailSeparatesDimensionAndWeightEvidenceByStableRecord() throws Exception {
|
||||
String source = new ClassPathResource("openapi/firstleg-v1.yaml")
|
||||
.getContentAsString(StandardCharsets.UTF_8);
|
||||
Map<String, Object> document = stringMap(new Yaml().load(source));
|
||||
Map<String, Object> paths = stringMap(document.get("paths"));
|
||||
Map<String, Object> detailPath = stringMap(paths.get(
|
||||
"/api/platform/v1/first-leg/inbounds/{inboundId}/measurement"));
|
||||
assertThat(detailPath).containsKey("get");
|
||||
|
||||
Map<String, Object> schemas = stringMap(stringMap(document.get("components")).get("schemas"));
|
||||
Map<String, Object> detailProperties = properties(schemas, "InboundMeasurementDetail");
|
||||
assertThat(detailProperties).containsKeys("actualGrossWeightKg", "items", "weightResults");
|
||||
assertThat(stringMap(detailProperties.get("items")))
|
||||
.containsEntry("description", "DIMENSION 尺寸测量明细及其图片证据");
|
||||
assertThat(stringMap(detailProperties.get("weightResults")))
|
||||
.containsEntry("description", "WEIGHT 称重结果及其图片、确认状态和服务端重量事实");
|
||||
assertThat(detailProperties).doesNotContainKey("images");
|
||||
|
||||
Map<String, Object> itemProperties = properties(schemas, "InboundMeasurementItem");
|
||||
assertThat(itemProperties).containsKeys("id", "intelligentImageFileId");
|
||||
Map<String, Object> weightProperties = properties(schemas, "InboundWeightResult");
|
||||
assertThat(weightProperties).containsKeys(
|
||||
"id", "fileId", "recognitionStatus", "grossWeightKg", "netWeightKg", "confirmedAt");
|
||||
}
|
||||
|
||||
private static Map<String, Object> properties(Map<String, Object> schemas, String schemaName) {
|
||||
return stringMap(stringMap(schemas.get(schemaName)).get("properties"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Map<String, Object> stringMap(Object value) {
|
||||
return (Map<String, Object>) value;
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -136,6 +137,45 @@ class InboundMeasurementServiceTest {
|
||||
verify(repository, never()).updateInbound(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailKeepsDimensionAndWeightEvidenceOnTheirStableBusinessRecords() {
|
||||
InboundOrderEntity inbound = inbound("MEASURE_PENDING");
|
||||
inbound.setActualGrossWeightKg(new BigDecimal("11.500"));
|
||||
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
|
||||
measurement.setId(30L);
|
||||
measurement.setVersion(4);
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setId(40L);
|
||||
item.setTenantId(1L);
|
||||
item.setMeasurementId(30L);
|
||||
item.setSourceImageFileId(140L);
|
||||
InboundWeightResultEntity weight = weight(50L, "30.000", "KG", "CONFIRMED", 2);
|
||||
weight.setGrossWeightKg(new BigDecimal("30.000"));
|
||||
weight.setPalletTareWeightKg(new BigDecimal("18.500"));
|
||||
weight.setNetWeightKg(new BigDecimal("11.500"));
|
||||
weight.setConfirmedAt(LocalDateTime.of(2026, 8, 17, 10, 30));
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(repository.findLatestMeasurement(1L, 10L)).thenReturn(measurement);
|
||||
when(repository.listItems(1L, 30L)).thenReturn(List.of(item));
|
||||
when(repository.listWeights(1L, 10L)).thenReturn(List.of(weight));
|
||||
|
||||
InboundMeasurementResponses.Detail result = service.detail(1L, 10L);
|
||||
|
||||
assertThat(result.actualGrossWeightKg()).isEqualByComparingTo("11.500");
|
||||
assertThat(result.items()).singleElement().satisfies(dimension -> {
|
||||
assertThat(dimension.id()).isEqualTo(40L);
|
||||
assertThat(dimension.intelligentImageFileId()).isEqualTo(140L);
|
||||
});
|
||||
assertThat(result.weightResults()).singleElement().satisfies(confirmedWeight -> {
|
||||
assertThat(confirmedWeight.id()).isEqualTo(50L);
|
||||
assertThat(confirmedWeight.fileId()).isEqualTo(150L);
|
||||
assertThat(confirmedWeight.recognitionStatus()).isEqualTo("CONFIRMED");
|
||||
assertThat(confirmedWeight.grossWeightKg()).isEqualByComparingTo("30.000");
|
||||
assertThat(confirmedWeight.netWeightKg()).isEqualByComparingTo("11.500");
|
||||
assertThat(confirmedWeight.confirmedAt()).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void creatingWeightResultNormalizesProviderConfidenceAtBusinessBoundary() {
|
||||
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
|
||||
|
||||
Reference in New Issue
Block a user