Merge branch 'test' into test-02huogui
# Conflicts: # deploy/sql/头程数据库统一增量-MySQL8.sql
This commit is contained in:
+10
-4
@@ -4,6 +4,7 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -27,10 +28,15 @@ class MerchantControllerIdentityContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void merchantWarehouseDefaultsToPlatformResourceScope() throws Exception {
|
||||
var value = MerchantWarehouseController.class.getDeclaredField("publicTenantId")
|
||||
.getAnnotation(org.springframework.beans.factory.annotation.Value.class);
|
||||
assertThat(value.value()).isEqualTo("${baiyue.warehouse.public-tenant-id:0}");
|
||||
void merchantControllersUseFixedPlatformResourceScope() throws Exception {
|
||||
for (Class<?> controller : Set.of(MerchantWarehouseController.class, MerchantPartnerController.class)) {
|
||||
var value = controller.getDeclaredField("PUBLIC_TENANT_ID");
|
||||
value.setAccessible(true);
|
||||
assertThat(Modifier.isStatic(value.getModifiers())).isTrue();
|
||||
assertThat(Modifier.isFinal(value.getModifiers())).isTrue();
|
||||
assertThat(value.getLong(null)).isEqualTo(0L);
|
||||
assertThat(value.getAnnotation(org.springframework.beans.factory.annotation.Value.class)).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthenticatedUser user(String appCode, Long tenantId) {
|
||||
|
||||
+21
@@ -49,6 +49,8 @@ class PlatformMasterDataControllerContractTest {
|
||||
assertWarehouseMethod("create", PostMapping.class, "platform:warehouse:create");
|
||||
assertWarehouseMethod("update", PutMapping.class, "platform:warehouse:edit");
|
||||
assertWarehouseMethod("status", PatchMapping.class, "platform:warehouse:status");
|
||||
assertWarehouseMethod("importTemplate", GetMapping.class, "platform:warehouse:create");
|
||||
assertWarehouseMethod("importWarehouses", PostMapping.class, "platform:warehouse:create");
|
||||
|
||||
Method page = java.util.Arrays.stream(PlatformWarehouseController.class.getDeclaredMethods())
|
||||
.filter(candidate -> candidate.getName().equals("page"))
|
||||
@@ -56,6 +58,8 @@ class PlatformMasterDataControllerContractTest {
|
||||
.orElseThrow();
|
||||
assertThat(page.getParameterTypes()[1]).isEqualTo(Long.class);
|
||||
assertThat(page.getParameters()[1].getAnnotation(RequestParam.class).required()).isFalse();
|
||||
assertThat(page.getGenericReturnType().getTypeName())
|
||||
.contains("WarehouseSummaryResponse");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -110,6 +114,23 @@ class PlatformMasterDataControllerContractTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void upsertWarehouseImportRequiresEditPermission() throws Exception {
|
||||
var guard = PlatformWarehouseController.class.getDeclaredMethod("requireImportPermission",
|
||||
AuthenticatedUser.class, com.baiyue.warehouse.dto.WarehouseImportDtos.ImportMode.class);
|
||||
guard.setAccessible(true);
|
||||
var createOnlyUser = new AuthenticatedUser(1L, 0L, 0L, 1L,
|
||||
"admin", "PLATFORM", "WEB", java.util.Set.of("platform:warehouse:create"));
|
||||
var editUser = new AuthenticatedUser(1L, 0L, 0L, 1L,
|
||||
"admin", "PLATFORM", "WEB", java.util.Set.of("platform:warehouse:create", "platform:warehouse:edit"));
|
||||
|
||||
guard.invoke(null, createOnlyUser, com.baiyue.warehouse.dto.WarehouseImportDtos.ImportMode.CREATE_ONLY);
|
||||
guard.invoke(null, editUser, com.baiyue.warehouse.dto.WarehouseImportDtos.ImportMode.UPSERT);
|
||||
assertThatThrownBy(() -> guard.invoke(null, createOnlyUser,
|
||||
com.baiyue.warehouse.dto.WarehouseImportDtos.ImportMode.UPSERT))
|
||||
.hasCauseInstanceOf(BizException.class);
|
||||
}
|
||||
|
||||
private static void assertContract(Class<?> controller, String authority, String path) throws Exception {
|
||||
RequestMapping mapping = controller.getAnnotation(RequestMapping.class);
|
||||
assertThat(mapping.value()).containsExactly(path);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.baiyue.warehouse.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.mapper.ServiceCapabilityMapper;
|
||||
import com.baiyue.warehouse.mapper.WarehouseAddressMapper;
|
||||
import com.baiyue.warehouse.mapper.WarehouseMapper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SpringJUnitConfig(WarehouseRepository.class)
|
||||
class WarehouseRepositoryTest {
|
||||
@MockBean private WarehouseMapper warehouses;
|
||||
@MockBean private WarehouseAddressMapper addresses;
|
||||
@MockBean private ServiceCapabilityMapper capabilities;
|
||||
@Resource private WarehouseRepository repository;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "warehouse-test"), WarehouseEntity.class);
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "warehouse-address-test"), WarehouseAddressEntity.class);
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "warehouse-capability-test"), ServiceCapabilityEntity.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pageSummariesSearchesAddressKeywordAndHydratesPrimaryAddress() {
|
||||
WarehouseAddressEntity matchedAddress = new WarehouseAddressEntity();
|
||||
matchedAddress.setWarehouseId(7L);
|
||||
WarehouseAddressEntity primaryAddress = new WarehouseAddressEntity();
|
||||
primaryAddress.setId(70L);
|
||||
primaryAddress.setWarehouseId(7L);
|
||||
primaryAddress.setCountryCode("US");
|
||||
primaryAddress.setStateProvince("CA");
|
||||
primaryAddress.setCity("Los Angeles");
|
||||
primaryAddress.setAddressLine1("123 Logistics Ave");
|
||||
when(addresses.selectList(any())).thenReturn(List.of(matchedAddress), List.of(primaryAddress));
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setId(7L);
|
||||
warehouse.setTenantId(0L);
|
||||
warehouse.setWarehouseCode("FBA-LAX9");
|
||||
warehouse.setWarehouseName("LAX9");
|
||||
warehouse.setWarehouseType("FBA");
|
||||
warehouse.setCountryCode("US");
|
||||
warehouse.setTimezone("UTC-8");
|
||||
warehouse.setStatus("ACTIVE");
|
||||
when(warehouses.selectPage(any(), any())).thenAnswer(invocation -> {
|
||||
Page<WarehouseEntity> page = invocation.getArgument(0);
|
||||
page.setRecords(List.of(warehouse));
|
||||
page.setTotal(1);
|
||||
return page;
|
||||
});
|
||||
WarehouseRequests.QueryRequest query = new WarehouseRequests.QueryRequest(1, 20, "Los Angeles",
|
||||
null, null, "FBA", "ACTIVE");
|
||||
|
||||
var result = repository.pageSummaries(0L, query);
|
||||
|
||||
assertThat(result.total()).isEqualTo(1);
|
||||
assertThat(result.records()).singleElement().satisfies(item -> {
|
||||
assertThat(item.warehouseCode()).isEqualTo("FBA-LAX9");
|
||||
assertThat(item.primaryAddress().city()).isEqualTo("Los Angeles");
|
||||
assertThat(item.primaryAddress().displayAddress()).contains("123 Logistics Ave");
|
||||
});
|
||||
verify(addresses, org.mockito.Mockito.times(2)).selectList(any());
|
||||
verify(warehouses).selectPage(any(), any());
|
||||
}
|
||||
}
|
||||
+3
@@ -43,6 +43,9 @@ class WarehouseSchemaContractTest {
|
||||
@Test
|
||||
void shouldHandleCase04() throws IOException {
|
||||
String sql = read("phase2-pallet-location-schema.sql");
|
||||
assertColumns(sql, "wh_pallet", "tenant_id", "warehouse_id", "pallet_code", "pallet_type",
|
||||
"capacity_cbm", "weight_kg", "color", "status", "remark", "deleted", "version");
|
||||
assertThat(sql).contains("uk_pallet_tenant_code (tenant_id, pallet_code)");
|
||||
assertColumns(sql, "wh_location", "tenant_id", "warehouse_id", "zone_code", "zone_name",
|
||||
"location_code", "location_type", "max_weight_kg", "shared_overflow", "status", "remark", "deleted", "version");
|
||||
assertColumns(sql, "wh_location_reservation", "idempotency_key", "business_id", "pallet_id",
|
||||
|
||||
+17
@@ -4,6 +4,7 @@ import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.warehouse.dto.PalletRequests;
|
||||
import com.baiyue.warehouse.entity.PalletEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.repository.PalletRepository;
|
||||
import com.baiyue.warehouse.repository.WarehouseRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -16,6 +17,8 @@ import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
@SpringJUnitConfig(PalletApplicationService.class)
|
||||
class PalletApplicationServiceTest {
|
||||
@MockBean private PalletRepository repository;
|
||||
@@ -46,6 +49,20 @@ class PalletApplicationServiceTest {
|
||||
verify(repository).page(null, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsOverseasWarehouseForCreate() {
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setWarehouseType("OVERSEAS");
|
||||
warehouse.setStatus("ACTIVE");
|
||||
when(warehouseRepository.findOwned(0, 20)).thenReturn(warehouse);
|
||||
PalletRequests.SaveRequest request = new PalletRequests.SaveRequest(
|
||||
20L, "木托盘", new BigDecimal("1.2"), new BigDecimal("8"), "黄色", null);
|
||||
|
||||
assertThatThrownBy(() -> service.create(0, request))
|
||||
.isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("仅支持国内仓");
|
||||
}
|
||||
|
||||
private static PalletEntity pallet(String status) {
|
||||
PalletEntity entity = new PalletEntity();
|
||||
entity.setId(30L);
|
||||
|
||||
+71
-1
@@ -2,10 +2,13 @@ package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseRepository;
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
@@ -17,6 +20,15 @@ import static org.mockito.Mockito.*;
|
||||
class WarehouseApplicationServiceTest {
|
||||
@MockBean private WarehouseRepository repository;
|
||||
@Resource private WarehouseApplicationService service;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
doAnswer(invocation -> {
|
||||
WarehouseEntity warehouse = invocation.getArgument(0);
|
||||
warehouse.setId(1L);
|
||||
return null;
|
||||
}).when(repository).insertWarehouse(any(WarehouseEntity.class));
|
||||
}
|
||||
@Test
|
||||
void shouldHandleCase01() {
|
||||
WarehouseEntity warehouse=new WarehouseEntity(); warehouse.setId(1L); warehouse.setTenantId(10001L); warehouse.setStatus("ACTIVE");
|
||||
@@ -40,10 +52,68 @@ class WarehouseApplicationServiceTest {
|
||||
}
|
||||
@Test
|
||||
void pagesAllPlatformWarehousesWithoutCustomerFilter() {
|
||||
WarehouseRequests.QueryRequest query = new WarehouseRequests.QueryRequest(1, 200, null, null, null, null);
|
||||
WarehouseRequests.QueryRequest query = new WarehouseRequests.QueryRequest(1, 200, null, null, null, null, null);
|
||||
when(repository.page(null, query)).thenReturn(new PageResult<>(java.util.List.of(), 1, 200, 0, 0));
|
||||
|
||||
assertThat(service.page(null, query).records()).isEmpty();
|
||||
verify(repository).page(null, query);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsDomesticWarehouseWithPlatformDefaults() {
|
||||
WarehouseRequests.SaveRequest request = new WarehouseRequests.SaveRequest(null, "CN-SZX-01", "深圳作业仓",
|
||||
null, null, null, "张三", "13800138000", null, null, null,
|
||||
new WarehouseRequests.AddressRequest(null, null, "广东省", "深圳市", "南山区",
|
||||
"518000", "前海路 1 号", null, null, null),
|
||||
java.util.List.of(new WarehouseRequests.CapabilityRequest("RECEIVING", "收货", true, null, null)));
|
||||
|
||||
service.create(request);
|
||||
|
||||
var warehouse = org.mockito.ArgumentCaptor.forClass(WarehouseEntity.class);
|
||||
var address = org.mockito.ArgumentCaptor.forClass(WarehouseAddressEntity.class);
|
||||
var capability = org.mockito.ArgumentCaptor.forClass(ServiceCapabilityEntity.class);
|
||||
verify(repository).insertWarehouse(warehouse.capture());
|
||||
verify(repository).insertAddress(address.capture());
|
||||
verify(repository).insertCapability(capability.capture());
|
||||
assertThat(warehouse.getValue().getTenantId()).isZero();
|
||||
assertThat(warehouse.getValue().getWarehouseType()).isEqualTo("DOMESTIC");
|
||||
assertThat(warehouse.getValue().getCountryCode()).isEqualTo("CN");
|
||||
assertThat(warehouse.getValue().getTimezone()).isEqualTo("UTC+8");
|
||||
assertThat(warehouse.getValue().getStatus()).isEqualTo("ACTIVE");
|
||||
assertThat(address.getValue().getAddressType()).isEqualTo("WAREHOUSE");
|
||||
assertThat(address.getValue().getCountryCode()).isEqualTo("CN");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fbaWarehouseUsesConfirmedStateTimezoneAndDoesNotCreateCapabilities() {
|
||||
WarehouseRequests.SaveRequest request = new WarehouseRequests.SaveRequest(null, "FBA-LAX9", "LAX9",
|
||||
"FBA", "US", null, null, null, null, null, null,
|
||||
new WarehouseRequests.AddressRequest(null, "US", "CA", "Redlands", null,
|
||||
"92374", "Amazon Fulfillment Center", null, null, null),
|
||||
java.util.List.of(new WarehouseRequests.CapabilityRequest("RECEIVING", "收货", true, null, null)));
|
||||
|
||||
service.create(request);
|
||||
|
||||
var warehouse = org.mockito.ArgumentCaptor.forClass(WarehouseEntity.class);
|
||||
verify(repository).insertWarehouse(warehouse.capture());
|
||||
verify(repository, never()).insertCapability(any());
|
||||
assertThat(warehouse.getValue().getTenantId()).isZero();
|
||||
assertThat(warehouse.getValue().getWarehouseType()).isEqualTo("FBA");
|
||||
assertThat(warehouse.getValue().getTimezone()).isEqualTo("UTC-8");
|
||||
assertThat(warehouse.getValue().getStatus()).isEqualTo("ACTIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fbaWarehouseInMultiTimezoneStateRequiresExplicitTimezone() {
|
||||
WarehouseRequests.SaveRequest request = new WarehouseRequests.SaveRequest(null, "FBA-DFW", "DFW",
|
||||
"FBA", "US", null, null, null, null, null, null,
|
||||
new WarehouseRequests.AddressRequest(null, "US", "TX", "Dallas", null,
|
||||
"75201", "Amazon", null, null, null),
|
||||
null);
|
||||
|
||||
assertThatThrownBy(() -> service.create(request)).isInstanceOf(BizException.class)
|
||||
.extracting(exception -> ((BizException) exception).getResultCode())
|
||||
.isEqualTo(ResultCode.VALIDATION_FAILED);
|
||||
verify(repository, never()).insertWarehouse(any());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.warehouse.dto.WarehouseImportDtos.ImportMode;
|
||||
import com.baiyue.warehouse.dto.WarehouseImportDtos.WarehouseImportResult;
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseRepository;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@DisplayName("仓库批量导入")
|
||||
@SpringJUnitConfig(WarehouseImportService.class)
|
||||
class WarehouseImportServiceTest {
|
||||
@MockBean private WarehouseRepository repository;
|
||||
@MockBean private WarehouseApplicationService warehouseService;
|
||||
@Resource private WarehouseImportService service;
|
||||
|
||||
@Test
|
||||
@DisplayName("默认 CREATE_ONLY 只新增新仓库并返回预检分类")
|
||||
void shouldCreateOnlyNewWarehousesByDefault() throws Exception {
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"CN-SZX-01", "深圳作业仓", "", "", "广东省", "深圳市", "南山区", "前海路 1 号", "518000", "", "", "张三", "13800138000", "", ""},
|
||||
{"FBA-LAX9", "", "FBA", "US", "CA", "Redlands", "", "Amazon FC", "92374", "", "", "", "", "", ""}
|
||||
});
|
||||
|
||||
WarehouseImportResult result = service.importFile(0L, "warehouses.xlsx", file, null);
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(result.committed()).isTrue();
|
||||
assertThat(result.mode()).isEqualTo(ImportMode.CREATE_ONLY);
|
||||
assertThat(result.createdRows()).isEqualTo(2);
|
||||
assertThat(result.previewRows()).extracting("action").containsExactly("CREATE", "CREATE");
|
||||
var captor = org.mockito.ArgumentCaptor.forClass(WarehouseRequests.SaveRequest.class);
|
||||
verify(warehouseService, org.mockito.Mockito.times(2)).create(captor.capture());
|
||||
assertThat(captor.getAllValues().get(0).tenantId()).isZero();
|
||||
assertThat(captor.getAllValues().get(1).warehouseType()).isEqualTo("FBA");
|
||||
assertThat(captor.getAllValues().get(1).warehouseName()).isEqualTo("FBA-LAX9");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("任一行错误时整批零写入")
|
||||
void shouldRejectWholeBatchWhenAnyRowInvalid() throws Exception {
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"FBA-DFW", "DFW", "FBA", "US", "TX", "Dallas", "", "Amazon FC", "75201", "", "", "", "", "", ""},
|
||||
{"CN-SZX-01", "深圳作业仓", "", "", "广东省", "深圳市", "", "前海路 1 号", "", "", "", "", "", "", ""}
|
||||
});
|
||||
|
||||
WarehouseImportResult result = service.importFile(0L, "warehouses.xlsx", file, null);
|
||||
|
||||
assertThat(result.success()).isFalse();
|
||||
assertThat(result.committed()).isFalse();
|
||||
assertThat(result.createdRows()).isZero();
|
||||
assertThat(result.errors()).anyMatch(error -> error.row() == 4 && error.field().equals("时区"));
|
||||
verify(warehouseService, never()).create(any());
|
||||
verify(warehouseService, never()).update(org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("UPSERT 更新现有仓库时保留联系人、备注并不重启停用数据")
|
||||
void shouldPreserveContactRemarkAndInactiveStatusOnUpsert() throws Exception {
|
||||
WarehouseEntity existing = new WarehouseEntity();
|
||||
existing.setId(9L);
|
||||
existing.setTenantId(0L);
|
||||
existing.setWarehouseCode("CN-SZX-01");
|
||||
existing.setWarehouseName("旧名称");
|
||||
existing.setWarehouseType("DOMESTIC");
|
||||
existing.setCountryCode("CN");
|
||||
existing.setTimezone("UTC+8");
|
||||
existing.setContactName("旧联系人");
|
||||
existing.setContactPhone("旧电话");
|
||||
existing.setContactEmail("old@example.com");
|
||||
existing.setStatus("INACTIVE");
|
||||
existing.setRemark("旧备注");
|
||||
WarehouseAddressEntity address = new WarehouseAddressEntity();
|
||||
address.setCountryCode("CN");
|
||||
address.setStateProvince("广东省");
|
||||
address.setCity("深圳市");
|
||||
address.setAddressLine1("旧地址");
|
||||
when(repository.findByCode(0L, "CN-SZX-01")).thenReturn(existing);
|
||||
when(repository.findAddresses(0L, 9L)).thenReturn(java.util.List.of(address));
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"CN-SZX-01", "深圳作业仓", "", "", "广东省", "深圳市", "", "新地址", "", "", "ACTIVE", "新联系人", "新电话", "new@example.com", "新备注"}
|
||||
});
|
||||
|
||||
WarehouseImportResult result = service.importFile(0L, "warehouses.xlsx", file, ImportMode.UPSERT);
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(result.committed()).isTrue();
|
||||
assertThat(result.updatedRows()).isEqualTo(1);
|
||||
var captor = org.mockito.ArgumentCaptor.forClass(WarehouseRequests.SaveRequest.class);
|
||||
verify(warehouseService).update(eq(0L), eq(9L), captor.capture());
|
||||
assertThat(captor.getValue().contactName()).isEqualTo("旧联系人");
|
||||
assertThat(captor.getValue().remark()).isEqualTo("旧备注");
|
||||
assertThat(captor.getValue().status()).isEqualTo("INACTIVE");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("dryRun 只返回预检分类和计数,不写入仓库")
|
||||
void shouldOnlyPreviewWhenDryRun() throws Exception {
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"CN-SZX-01", "深圳作业仓", "", "", "广东省", "深圳市", "南山区", "前海路 1 号", "518000", "", "", "张三", "13800138000", "", ""},
|
||||
{"FBA-LAX9", "LAX9", "FBA", "US", "CA", "Redlands", "", "Amazon FC", "92374", "", "", "", "", "", ""}
|
||||
});
|
||||
|
||||
WarehouseImportResult result = service.importFile(0L, "warehouses.xlsx", file, null, true);
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(result.committed()).isFalse();
|
||||
assertThat(result.createdRows()).isEqualTo(2);
|
||||
assertThat(result.previewRows()).extracting("action").containsExactly("CREATE", "CREATE");
|
||||
verify(warehouseService, never()).create(any());
|
||||
verify(warehouseService, never()).update(org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("CREATE_ONLY 遇到现有仓库时不更新并按无变化统计")
|
||||
void shouldSkipExistingWarehouseInCreateOnlyMode() throws Exception {
|
||||
WarehouseEntity existing = new WarehouseEntity();
|
||||
existing.setId(9L);
|
||||
existing.setTenantId(0L);
|
||||
existing.setWarehouseCode("CN-SZX-01");
|
||||
existing.setWarehouseName("旧名称");
|
||||
existing.setWarehouseType("DOMESTIC");
|
||||
existing.setCountryCode("CN");
|
||||
existing.setTimezone("UTC+8");
|
||||
existing.setStatus("ACTIVE");
|
||||
WarehouseAddressEntity address = new WarehouseAddressEntity();
|
||||
address.setCountryCode("CN");
|
||||
address.setStateProvince("广东省");
|
||||
address.setCity("深圳市");
|
||||
address.setAddressLine1("旧地址");
|
||||
when(repository.findByCode(0L, "CN-SZX-01")).thenReturn(existing);
|
||||
when(repository.findAddresses(0L, 9L)).thenReturn(java.util.List.of(address));
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"CN-SZX-01", "深圳作业仓", "", "", "广东省", "深圳市", "", "新地址", "", "", "", "", "", "", ""}
|
||||
});
|
||||
|
||||
WarehouseImportResult result = service.importFile(0L, "warehouses.xlsx", file, ImportMode.CREATE_ONLY);
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
assertThat(result.committed()).isTrue();
|
||||
assertThat(result.createdRows()).isZero();
|
||||
assertThat(result.updatedRows()).isZero();
|
||||
assertThat(result.unchangedRows()).isEqualTo(1);
|
||||
assertThat(result.previewRows()).extracting("action").containsExactly("UNCHANGED");
|
||||
assertThat(result.previewRows()).extracting("reason").containsExactly("仅新增模式,现有仓库不修改");
|
||||
verify(warehouseService, never()).create(any());
|
||||
verify(warehouseService, never()).update(org.mockito.ArgumentMatchers.anyLong(), org.mockito.ArgumentMatchers.anyLong(), any());
|
||||
}
|
||||
|
||||
private static byte[] withRows(byte[] template, String[][] values) throws Exception {
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(template));
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
for (int rowIndex = 0; rowIndex < values.length; rowIndex++) {
|
||||
Row row = workbook.getSheetAt(0).createRow(rowIndex + 3);
|
||||
for (int column = 0; column < values[rowIndex].length; column++) {
|
||||
row.createCell(column).setCellValue(values[rowIndex][column]);
|
||||
}
|
||||
}
|
||||
workbook.write(output);
|
||||
return output.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
-- 运行前请先在同一连接中执行 warehouse/src/main/resources/db/warehouse-public-master-data.sql。
|
||||
USE baiyue_warehouse;
|
||||
|
||||
SELECT COUNT(*) AS fixed_platform_warehouse_count
|
||||
FROM wh_warehouse
|
||||
WHERE tenant_id = 0
|
||||
AND warehouse_code IN ('CN-YIWU-01', 'CN-DONGGUAN-01', 'US-LAX-01', 'US-NYC-01')
|
||||
AND warehouse_type IN ('DOMESTIC', 'OVERSEAS')
|
||||
AND deleted = 0;
|
||||
|
||||
SELECT COUNT(*) AS fixed_platform_address_count
|
||||
FROM wh_warehouse_address address
|
||||
JOIN wh_warehouse warehouse
|
||||
ON warehouse.id = address.warehouse_id
|
||||
AND warehouse.tenant_id = address.tenant_id
|
||||
WHERE warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_code IN ('CN-YIWU-01', 'CN-DONGGUAN-01', 'US-LAX-01', 'US-NYC-01')
|
||||
AND address.address_type = 'OPERATING'
|
||||
AND address.deleted = 0
|
||||
AND warehouse.deleted = 0;
|
||||
|
||||
SELECT COUNT(*) AS fba_public_warehouse_count
|
||||
FROM wh_warehouse
|
||||
WHERE tenant_id = 0
|
||||
AND warehouse_type = 'FBA'
|
||||
AND country_code = 'US'
|
||||
AND deleted = 0;
|
||||
|
||||
SELECT COUNT(*) AS fba_public_address_count
|
||||
FROM wh_warehouse_address address
|
||||
JOIN wh_warehouse warehouse
|
||||
ON warehouse.id = address.warehouse_id
|
||||
AND warehouse.tenant_id = address.tenant_id
|
||||
WHERE warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_type = 'FBA'
|
||||
AND address.address_type = 'OPERATING'
|
||||
AND address.country_code = 'US'
|
||||
AND address.deleted = 0
|
||||
AND warehouse.deleted = 0;
|
||||
|
||||
SELECT COUNT(*) AS fba_capability_violations
|
||||
FROM wh_service_capability capability
|
||||
JOIN wh_warehouse warehouse
|
||||
ON warehouse.id = capability.warehouse_id
|
||||
AND warehouse.tenant_id = capability.tenant_id
|
||||
WHERE warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_type = 'FBA'
|
||||
AND capability.deleted = 0;
|
||||
@@ -0,0 +1,116 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const currentDir = dirname(fileURLToPath(import.meta.url))
|
||||
const warehouseRoot = resolve(currentDir, '..', '..', '..')
|
||||
const sqlPath = resolve(warehouseRoot, 'src/main/resources/db/warehouse-public-master-data.sql')
|
||||
const acceptedPath = resolve(warehouseRoot, 'src/main/resources/db/master-data/fba-public-addresses.accepted.csv')
|
||||
const rejectedPath = resolve(warehouseRoot, 'src/main/resources/db/master-data/fba-public-addresses.rejected.csv')
|
||||
const fixedWarehousePath = resolve(warehouseRoot, 'src/main/resources/db/master-data/platform-fixed-warehouses.csv')
|
||||
|
||||
function parseCsv(text) {
|
||||
const rows = []
|
||||
let field = ''
|
||||
let row = []
|
||||
let quoted = false
|
||||
for (let index = 0; index < text.length; index += 1) {
|
||||
const char = text[index]
|
||||
if (quoted) {
|
||||
if (char === '"' && text[index + 1] === '"') {
|
||||
field += '"'
|
||||
index += 1
|
||||
} else if (char === '"') {
|
||||
quoted = false
|
||||
} else {
|
||||
field += char
|
||||
}
|
||||
} else if (char === '"') {
|
||||
quoted = true
|
||||
} else if (char === ',') {
|
||||
row.push(field)
|
||||
field = ''
|
||||
} else if (char === '\n') {
|
||||
row.push(field.replace(/\r$/, ''))
|
||||
rows.push(row)
|
||||
row = []
|
||||
field = ''
|
||||
} else {
|
||||
field += char
|
||||
}
|
||||
}
|
||||
if (field.length > 0 || row.length > 0) {
|
||||
row.push(field)
|
||||
rows.push(row)
|
||||
}
|
||||
const [headers, ...data] = rows.filter((item) => item.some((value) => value !== ''))
|
||||
return data.map((values) => Object.fromEntries(headers.map((header, index) => [header, values[index] ?? ''])))
|
||||
}
|
||||
|
||||
const sql = readFileSync(sqlPath, 'utf8')
|
||||
const accepted = parseCsv(readFileSync(acceptedPath, 'utf8'))
|
||||
const rejected = parseCsv(readFileSync(rejectedPath, 'utf8'))
|
||||
const fixedWarehouses = parseCsv(readFileSync(fixedWarehousePath, 'utf8'))
|
||||
|
||||
assert.equal(accepted.length, 252)
|
||||
assert.equal(rejected.length, 54)
|
||||
assert.equal(fixedWarehouses.length, 4)
|
||||
|
||||
const codes = new Set()
|
||||
const warehouseIds = new Set()
|
||||
const addressIds = new Set()
|
||||
for (const row of accepted) {
|
||||
assert.match(row.warehouse_code, /^[A-Z0-9]{3,5}$/)
|
||||
assert.equal(row.warehouse_code, row.warehouse_code.trim().toUpperCase())
|
||||
assert.ok(row.city.trim())
|
||||
assert.ok(row.state_province.trim())
|
||||
assert.ok(row.address_line1.trim())
|
||||
assert.ok(row.postal_code.trim())
|
||||
assert.ok(row.timezone.trim())
|
||||
assert.equal(row.country_code, 'US')
|
||||
assert.ok(!codes.has(row.warehouse_code), `duplicate code ${row.warehouse_code}`)
|
||||
assert.ok(!warehouseIds.has(row.warehouse_id_seed), `duplicate warehouse id ${row.warehouse_id_seed}`)
|
||||
assert.ok(!addressIds.has(row.address_id_seed), `duplicate address id ${row.address_id_seed}`)
|
||||
codes.add(row.warehouse_code)
|
||||
warehouseIds.add(row.warehouse_id_seed)
|
||||
addressIds.add(row.address_id_seed)
|
||||
}
|
||||
|
||||
assert.ok(rejected.some((row) => row.reject_reason.includes('non_fba_business_node')))
|
||||
assert.equal(rejected.filter((row) => row.reject_reason.includes('timezone_pending_confirmation')).length, 1)
|
||||
|
||||
assert.deepEqual(fixedWarehouses.map((row) => row.warehouse_code), [
|
||||
'CN-YIWU-01',
|
||||
'CN-DONGGUAN-01',
|
||||
'US-LAX-01',
|
||||
'US-NYC-01',
|
||||
])
|
||||
assert.deepEqual(fixedWarehouses.map((row) => row.timezone), [
|
||||
'UTC+8',
|
||||
'UTC+8',
|
||||
'UTC-8',
|
||||
'UTC-5',
|
||||
])
|
||||
assert.ok(fixedWarehouses.every((row) => row.address_line1.includes('待维护') || row.address_line1.includes('pending maintenance')))
|
||||
|
||||
assert.match(sql, /CREATE TEMPORARY TABLE tmp_wh_fixed_platform_warehouse/)
|
||||
assert.match(sql, /'CN-YIWU-01','义乌仓','DOMESTIC','CN','UTC\+8'/)
|
||||
assert.match(sql, /'CN-DONGGUAN-01','东莞仓','DOMESTIC','CN','UTC\+8'/)
|
||||
assert.match(sql, /'US-LAX-01','洛杉矶仓','OVERSEAS','US','UTC-8'/)
|
||||
assert.match(sql, /'US-NYC-01','纽约仓','OVERSEAS','US','UTC-5'/)
|
||||
assert.match(sql, /固定平台仓地址种子ID与既有数据冲突/)
|
||||
assert.match(sql, /固定平台仓地址父子解析失败/)
|
||||
assert.match(sql, /FROM tmp_wh_fixed_platform_warehouse source[\s\S]+ON DUPLICATE KEY UPDATE\s+tenant_id = VALUES\(tenant_id\);/)
|
||||
|
||||
assert.match(sql, /CREATE TEMPORARY TABLE tmp_wh_fba_public_master_data/)
|
||||
assert.match(sql, /JOIN wh_warehouse warehouse\s+ON warehouse\.tenant_id = 0\s+AND warehouse\.warehouse_code = source\.warehouse_code/s)
|
||||
assert.match(sql, /ON DUPLICATE KEY UPDATE\s+warehouse_type = VALUES\(warehouse_type\),\s+country_code = VALUES\(country_code\),\s+timezone = VALUES\(timezone\),\s+updated_at = CURRENT_TIMESTAMP\(3\);/s)
|
||||
assert.doesNotMatch(sql, /status\s*=\s*VALUES\(status\)/)
|
||||
assert.doesNotMatch(sql, /contact_name\s*=\s*VALUES\(contact_name\)/)
|
||||
assert.doesNotMatch(sql, /contact_phone\s*=\s*VALUES\(contact_phone\)/)
|
||||
assert.doesNotMatch(sql, /contact_email\s*=\s*VALUES\(contact_email\)/)
|
||||
assert.doesNotMatch(sql, /remark\s*=\s*VALUES\(remark\)/)
|
||||
assert.doesNotMatch(sql, /INSERT INTO wh_service_capability/i)
|
||||
assert.match(sql, /FBA公共主数据地址种子ID与既有数据冲突/)
|
||||
assert.match(sql, /FBA公共地址不得创建平台作业能力/)
|
||||
Reference in New Issue
Block a user