This commit is contained in:
2026-08-20 18:02:38 +08:00
parent b55b049e77
commit 1cd8b6a473
210 changed files with 6974 additions and 825 deletions
@@ -21,5 +21,8 @@ class OverseasProcurementInventoryBoundaryTest {
assertFalse(source.contains("stockIn("));
assertFalse(source.contains("setAvailablePackages"));
assertFalse(source.contains("availablePackages"));
assertFalse(source.contains("addOutbound"));
assertFalse(source.contains("OverseasProcurementOutbound"));
assertFalse(source.contains("outboundQuantity"));
}
}
@@ -0,0 +1,42 @@
package com.baiyue.lastleg.procurement;
import org.junit.jupiter.api.Test;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class OverseasProcurementOpenApiContractTest {
@Test
void procurementContractDocumentsCandidatesClearSemanticsAndExcludesOutbound() throws Exception {
String yaml;
try (var input = getClass().getResourceAsStream("/openapi/overseas-business-v1.yaml")) {
yaml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
}
assertTrue(yaml.contains("/customer-options:"));
assertTrue(yaml.contains("/warehouse-options:"));
assertTrue(yaml.contains("显式 null 或空串表示清空"));
assertTrue(yaml.contains("platform:overseas-business:purchase:list"));
assertTrue(yaml.contains("platform:overseas-business:purchase:edit"));
assertFalse(yaml.contains("/outbounds"));
assertFalse(yaml.contains("采购出库单"));
}
@Test
void contractContainsPlatformTruckWritesAndIndependentLooseboardDomain() throws Exception {
String yaml;
try (var input = getClass().getResourceAsStream("/openapi/overseas-business-v1.yaml")) {
yaml = new String(input.readAllBytes(), StandardCharsets.UTF_8);
}
assertTrue(yaml.contains("platform:overseas-business:truck:create"));
assertTrue(yaml.contains("platform:overseas-business:truck:edit"));
assertTrue(yaml.contains("/api/platform/v1/last-leg/local-looseboard-orders:"));
assertTrue(yaml.contains("platform:overseas-business:looseboard:list"));
assertTrue(yaml.contains("platform:overseas-business:looseboard:create"));
assertTrue(yaml.contains("platform:overseas-business:looseboard:edit"));
assertTrue(yaml.contains("platform:overseas-business:looseboard:status"));
assertFalse(yaml.contains("/api/platform/v1/last-leg/outbound"));
assertFalse(yaml.contains("/api/platform/v1/last-leg/delivery"));
}
}
@@ -8,14 +8,16 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class OverseasProcurementSchemaContractTest {
@Test
void baselineContainsOrderOutboundAndReviewHistoryTables() throws Exception {
void baselineContainsOrderAndOperationHistoryButNoProcurementOutbound() throws Exception {
String sql;
try (var input = getClass().getResourceAsStream("/db/lastleg.sql")) {
sql = new String(input.readAllBytes(), StandardCharsets.UTF_8);
}
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_overseas_procurement_order"));
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_overseas_procurement_outbound"));
assertTrue(sql.contains("CREATE TABLE IF NOT EXISTS ll_overseas_procurement_status_history"));
assertTrue(sql.contains("UNIQUE KEY uk_ll_overseas_procurement_outbound_no(customer_id,order_id,outbound_no)"));
assertTrue(sql.contains("operation_type VARCHAR(32) NOT NULL"));
assertTrue(sql.contains("change_detail JSON NULL"));
org.junit.jupiter.api.Assertions.assertFalse(sql.contains("CREATE TABLE IF NOT EXISTS ll_overseas_procurement_outbound"));
org.junit.jupiter.api.Assertions.assertFalse(sql.contains("outbound_quantity BIGINT"));
}
}
@@ -62,6 +62,15 @@ class OverseasProcurementControllerTest {
verify(service).review("100", "HC1", "7", request);
}
@Test
void platformUpdatePassesAuditedOperator() {
var request = new OverseasProcurementRequests.Update(0L, null, null, null, null,
null, null, null, null, null, null, null, null, null);
platform.update(user(0L, "PLATFORM", Set.of(OverseasProcurementService.MANAGE_PERMISSION)),
"100", "HC1", request);
verify(service).update("100", "HC1", "7", request);
}
private AuthenticatedUser user(long tenantId, String appCode, Set<String> permissions) {
return new AuthenticatedUser(7L, tenantId, tenantId, 8L, "operator", appCode, "WEB", permissions);
}
@@ -3,8 +3,9 @@ package com.baiyue.lastleg.procurement.service;
import com.baiyue.lastleg.procurement.dto.OverseasProcurementRequests;
import com.baiyue.lastleg.procurement.entity.OverseasProcurementOrder;
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementOrderMapper;
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementOutboundMapper;
import com.baiyue.lastleg.procurement.mapper.OverseasProcurementStatusHistoryMapper;
import com.baiyue.lastleg.masterdata.quote.service.PartnerWarehouseDirectory;
import com.baiyue.lastleg.masterdata.quote.dto.OverseasQuoteResponses;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -23,21 +24,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class)
class OverseasProcurementServiceTest {
@Mock OverseasProcurementOrderMapper orders;
@Mock OverseasProcurementOutboundMapper outbounds;
@Mock OverseasProcurementStatusHistoryMapper histories;
@Mock PartnerWarehouseDirectory warehouseDirectory;
private OverseasProcurementService service;
@BeforeEach
void setUp() {
service = new OverseasProcurementService();
ReflectionTestUtils.setField(service, "orders", orders);
ReflectionTestUtils.setField(service, "outbounds", outbounds);
ReflectionTestUtils.setField(service, "histories", histories);
ReflectionTestUtils.setField(service, "warehouseDirectory", warehouseDirectory);
ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper());
}
@@ -49,7 +52,6 @@ class OverseasProcurementServiceTest {
return 1;
});
when(orders.findNo(anyString(), anyString())).thenAnswer(ignored -> saved.get());
when(outbounds.listOrder(anyString(), any())).thenReturn(List.of());
when(histories.listOrder(anyString(), any())).thenReturn(List.of());
var detail = service.create("100", "7", new OverseasProcurementRequests.Create(
@@ -69,21 +71,75 @@ class OverseasProcurementServiceTest {
100L, null, null, null, null, null, null, null, null);
ResponseStatusException error = assertThrows(ResponseStatusException.class,
() -> service.update("100", "HC1", request));
() -> service.update("100", "HC1", "7", request));
assertEquals(409, error.getStatusCode().value());
}
@Test
void outboundCannotExceedDerivedInventory() {
OverseasProcurementOrder order = order(OverseasProcurementService.APPROVED, 100, 80);
void explicitNullClearsOptionalFieldsAndWritesOperationHistory() {
OverseasProcurementOrder order = order(OverseasProcurementService.APPROVED, 100, 0);
order.setProductUrl("https://example.test/old");
when(orders.lockNo("100", "HC1")).thenReturn(order);
when(orders.addOutbound(order.getId(), "100", 30)).thenReturn(0);
var request = new OverseasProcurementRequests.AddOutbound(30L, "OUT-1", null, null,
LocalDateTime.now(), null);
when(orders.findNo("100", "HC1")).thenReturn(order);
when(histories.listOrder("100", 1L)).thenReturn(List.of());
var request = new OverseasProcurementRequests.Update(0L, null, null,
new ObjectMapper().nullNode(), new ObjectMapper().nullNode(), null,
null, null, null, null, null, null, null, null);
ResponseStatusException error = assertThrows(ResponseStatusException.class,
() -> service.addOutbound("100", "HC1", "7", request));
assertEquals(409, error.getStatusCode().value());
service.update("100", "HC1", "7", request);
assertEquals(null, order.getProductUrl());
verify(histories).insert(org.mockito.ArgumentMatchers.<com.baiyue.lastleg.procurement.entity.OverseasProcurementStatusHistory>argThat(value ->
"UPDATE".equals(value.getOperationType()) && value.getChangeDetail().contains("productUrl")));
}
@Test
void omittedOptionalFieldKeepsExistingValue() {
OverseasProcurementOrder order = order(OverseasProcurementService.APPROVED, 100, 0);
order.setProductUrl("https://example.test/keep");
when(orders.lockNo("100", "HC1")).thenReturn(order);
when(orders.findNo("100", "HC1")).thenReturn(order);
when(histories.listOrder("100", 1L)).thenReturn(List.of());
var request = new OverseasProcurementRequests.Update(0L, null, null, null, null,
null, null, null, null, null, null, null, null, null);
service.update("100", "HC1", "7", request);
assertEquals("https://example.test/keep", order.getProductUrl());
verify(orders, never()).updateById(org.mockito.ArgumentMatchers.<OverseasProcurementOrder>any());
}
@Test
void warehouseSelectionFreezesCanonicalCandidateName() {
OverseasProcurementOrder order = order(OverseasProcurementService.APPROVED, 100, 0);
when(orders.lockNo("100", "HC1")).thenReturn(order);
when(orders.findNo("100", "HC1")).thenReturn(order);
when(histories.listOrder("100", 1L)).thenReturn(List.of());
when(warehouseDirectory.required(10L)).thenReturn(
new OverseasQuoteResponses.PartnerWarehouseOption("10", "LAX-01", "洛杉矶采购仓", "US"));
ObjectMapper mapper = new ObjectMapper();
var request = new OverseasProcurementRequests.Update(0L, null, null, null, null,
null, mapper.getNodeFactory().textNode("10"), mapper.getNodeFactory().textNode("伪造名称"),
null, null, null, null, null, null);
service.update("100", "HC1", "7", request);
assertEquals("10", order.getPurchaseWarehouseId());
assertEquals("洛杉矶采购仓", order.getPurchaseWarehouseName());
}
@Test
void customerOptionsAreDeduplicatedSnapshotsFromProcurementOrders() {
OverseasProcurementOrder option = new OverseasProcurementOrder();
option.setCustomerId("100");
option.setCustomerName("Acme");
when(orders.customerOptions("ac", 20)).thenReturn(List.of(option));
var values = service.customerOptions(" ac ", 20);
assertEquals(1, values.size());
assertEquals("100", values.get(0).customerId());
assertEquals("Acme", values.get(0).customerName());
}
private OverseasProcurementOrder order(String status, long inbound, long outbound) {
@@ -93,7 +149,6 @@ class OverseasProcurementServiceTest {
order.setOrderNo("HC1");
order.setStatus(status);
order.setInboundQuantity(inbound);
order.setOutboundQuantity(outbound);
order.setPurchaseQuantity(500L);
order.setVersion(0L);
order.setCreatedAt(LocalDateTime.now());