优化
This commit is contained in:
@@ -55,6 +55,8 @@ paths:
|
||||
parameters: [{$ref: '#/components/parameters/CaseId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
|
||||
post:
|
||||
summary: Submit to customs agent and freeze approved file versions in a batch snapshot
|
||||
description: customsAgentId must identify an ACTIVE Warehouse supplier in category CUSTOMS; the persisted agent name is resolved server-side and free text cannot bypass supplier validation.
|
||||
requestBody: {required: true, content: {application/json: {schema: {$ref: '#/components/schemas/SubmitBatchRequest'}}}}
|
||||
responses: {'200': {description: Submission batch}}
|
||||
/api/platform/v1/customs/submission-batches/{batchId}/receipts:
|
||||
parameters:
|
||||
@@ -91,3 +93,13 @@ components:
|
||||
in: path
|
||||
required: true
|
||||
schema: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
schemas:
|
||||
SubmitBatchRequest:
|
||||
type: object
|
||||
required: [customsAgentId, expectedVersion]
|
||||
properties:
|
||||
customsAgentId: {type: integer, format: int64, minimum: 1, description: ACTIVE Warehouse supplier ID validated as CUSTOMS.}
|
||||
customsAgentName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint; persisted name always comes from Warehouse.}
|
||||
externalReference: {type: string, maxLength: 128, nullable: true}
|
||||
note: {type: string, maxLength: 500, nullable: true}
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
|
||||
@@ -52,6 +52,8 @@ components:
|
||||
channelCode: {type: string, maxLength: 64, description: Selected from GET /api/platform/v1/ocean-bookings/channels; server validates it against the current ACTIVE price book and freezes channelName.}
|
||||
warehouseId: {type: integer, format: int64, minimum: 1}
|
||||
warehouseName: {type: string, maxLength: 128}
|
||||
bookingAgentId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional ACTIVE Warehouse supplier ID validated as BOOKING with INTERNATIONAL scope.}
|
||||
bookingAgentName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint only; the persisted snapshot always comes from Warehouse and cannot be supplied without bookingAgentId.}
|
||||
serviceTerm: {type: string, maxLength: 32, description: Required free-text Service Term snapshot, for example CY-CY.}
|
||||
freightTerm: {type: string, maxLength: 32, description: Required free-text Freight Term snapshot, for example NINGBO.}
|
||||
billOfLadingNo: {type: string, maxLength: 128, description: Required offline-confirmed bill of lading snapshot.}
|
||||
|
||||
@@ -160,6 +160,8 @@ components:
|
||||
oceanBookingId: {type: integer, format: int64, minimum: 1}
|
||||
containerBookingId: {type: integer, format: int64, minimum: 1}
|
||||
loadingPlanId: {type: integer, format: int64, minimum: 1}
|
||||
carrierId: {type: integer, format: int64, minimum: 1, nullable: true, description: Optional ACTIVE Warehouse supplier ID validated as TRUCKING.}
|
||||
carrierName: {type: string, maxLength: 128, nullable: true, description: Optional consistency hint only; the persisted snapshot comes from Warehouse and cannot be supplied without carrierId.}
|
||||
plannedLoadingAt: {type: string, format: date-time}
|
||||
expectedVersion: {type: integer, minimum: 0}
|
||||
DetailStableReferences:
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.baiyue.firstleg.client;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class WarehouseSupplierClientContractTest {
|
||||
@Test
|
||||
void usesWarehouseInternalSupplierEndpointAndToken() throws Exception {
|
||||
FeignClient annotation = WarehouseSupplierClient.class.getAnnotation(FeignClient.class);
|
||||
assertThat(annotation.path()).isEqualTo("/internal/warehouse/v1/suppliers");
|
||||
Method method = WarehouseSupplierClient.class.getDeclaredMethod("get", String.class, long.class,
|
||||
boolean.class, String.class, String.class);
|
||||
assertThat(method.getAnnotation(GetMapping.class).value()).containsExactly("/{supplierId}");
|
||||
assertThat(method.getParameterAnnotations()[0][0]).isInstanceOf(RequestHeader.class);
|
||||
assertThat(((RequestHeader) method.getParameterAnnotations()[0][0]).value())
|
||||
.isEqualTo("X-Internal-Service-Token");
|
||||
assertThat(method.getParameterAnnotations()[3][0]).isInstanceOf(RequestParam.class);
|
||||
assertThat(method.getParameterAnnotations()[4][0]).isInstanceOf(RequestParam.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void openApiDocumentsServerResolvedSupplierSnapshots() throws Exception {
|
||||
String ocean = Files.readString(Path.of("src/main/resources/openapi/ocean-bookings-v1.yaml"), StandardCharsets.UTF_8);
|
||||
String truck = Files.readString(Path.of("src/main/resources/openapi/truck-appointments-v1.yaml"), StandardCharsets.UTF_8);
|
||||
String customs = Files.readString(Path.of("src/main/resources/openapi/customs-v1.yaml"), StandardCharsets.UTF_8);
|
||||
assertThat(ocean).contains("bookingAgentId:", "BOOKING", "INTERNATIONAL", "persisted snapshot always comes from Warehouse");
|
||||
assertThat(truck).contains("carrierId:", "TRUCKING", "persisted snapshot comes from Warehouse");
|
||||
assertThat(customs).contains("customsAgentId", "CUSTOMS", "free text cannot bypass supplier validation");
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.baiyue.firstleg.customs.client.CustomsFileClient;
|
||||
import com.baiyue.firstleg.customs.dto.CustomsDtos.*;
|
||||
import com.baiyue.firstleg.customs.entity.*;
|
||||
import com.baiyue.firstleg.customs.repository.CustomsRepository;
|
||||
import com.baiyue.firstleg.service.WarehouseSupplierSnapshotService;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -24,6 +25,7 @@ import static org.mockito.Mockito.*;
|
||||
class CustomsServiceTest {
|
||||
@Mock CustomsRepository repository;
|
||||
@Mock CustomsFileClient files;
|
||||
@Mock WarehouseSupplierSnapshotService suppliers;
|
||||
@InjectMocks CustomsService service;
|
||||
|
||||
@BeforeEach
|
||||
@@ -31,6 +33,8 @@ class CustomsServiceTest {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
|
||||
ReflectionTestUtils.setField(service, "internalToken", "token");
|
||||
when(suppliers.customs(anyLong(), anyString()))
|
||||
.thenReturn(new com.baiyue.firstleg.client.WarehouseSupplierClient.Snapshot(88L, "线下报关代理", "CUSTOMS", null, "ACTIVE"));
|
||||
when(repository.completeOperation(anyLong(), anyString(), anyLong())).thenReturn(1);
|
||||
}
|
||||
|
||||
@@ -84,7 +88,7 @@ class CustomsServiceTest {
|
||||
when(repository.transition(41L, 4, "RECEIPT_PENDING", 7L, "admin")).thenReturn(1);
|
||||
|
||||
CustomsSubmissionBatchEntity result = service.submitBatch(41L, 7L, "admin", "batch-1",
|
||||
new SubmitBatchRequest("线下报关代理", "EXT-1", "提交", 4));
|
||||
new SubmitBatchRequest(88L, "线下报关代理", "EXT-1", "提交", 4));
|
||||
|
||||
assertThat(result.getFileSnapshotJson()).contains("documentVersion").contains("INVOICE");
|
||||
verify(repository).freeze(51L);
|
||||
|
||||
@@ -38,10 +38,13 @@ class TruckAppointmentServiceTest {
|
||||
@Mock OceanBookingRepository oceanBookings;
|
||||
@Mock SmartLoadingRepository loadingPlans;
|
||||
@Mock ContainerRepository containers;
|
||||
@Mock WarehouseSupplierSnapshotService suppliers;
|
||||
@Spy ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build();
|
||||
@InjectMocks TruckAppointmentService service;
|
||||
|
||||
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
|
||||
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this);
|
||||
when(suppliers.trucking(any(), any())).thenReturn(null);
|
||||
}
|
||||
|
||||
@Test void detailResolvesStableNamesAndIdentifiersFromLinkedResources() {
|
||||
TruckAppointmentEntity appointment = new TruckAppointmentEntity();
|
||||
|
||||
Reference in New Issue
Block a user