优化
This commit is contained in:
+9
@@ -4,6 +4,7 @@ import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.WarehouseDetailResponse;
|
||||
import com.baiyue.warehouse.dto.OverseasPartnerWarehouseResponse;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@@ -11,6 +12,7 @@ import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/internal/warehouses")
|
||||
@@ -39,6 +41,13 @@ public class InternalWarehouseController {
|
||||
return ApiResponse.success(service.activeDetail(PLATFORM_TENANT_ID, id));
|
||||
}
|
||||
|
||||
@GetMapping("/overseas-partners")
|
||||
public ApiResponse<List<OverseasPartnerWarehouseResponse>> overseasPartners(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token) {
|
||||
verifyToken(token);
|
||||
return ApiResponse.success(service.activeOverseasPartners());
|
||||
}
|
||||
|
||||
private void verifyToken(String token) {
|
||||
byte[] supplied = token == null ? new byte[0] : token.getBytes(StandardCharsets.UTF_8);
|
||||
if (!MessageDigest.isEqual(expected, supplied)) {
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.baiyue.warehouse.dto;
|
||||
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(name = "OverseasPartnerWarehouseResponse", description = "Active overseas partner warehouse option / 启用的海外合作仓候选")
|
||||
public record OverseasPartnerWarehouseResponse(
|
||||
@Schema(description = "Platform public warehouse ID / 平台公共仓库 ID") Long id,
|
||||
@Schema(description = "Warehouse code / 仓库编码") String warehouseCode,
|
||||
@Schema(description = "Warehouse name / 仓库名称") String warehouseName,
|
||||
@Schema(description = "ISO two-letter country code / 国家二字码") String countryCode
|
||||
) {
|
||||
public static OverseasPartnerWarehouseResponse of(WarehouseEntity warehouse) {
|
||||
return new OverseasPartnerWarehouseResponse(warehouse.getId(), warehouse.getWarehouseCode(),
|
||||
warehouse.getWarehouseName(), warehouse.getCountryCode());
|
||||
}
|
||||
}
|
||||
@@ -29,8 +29,8 @@ public final class WarehouseRequests {
|
||||
@NotBlank @Size(max = 50) String warehouseCode,
|
||||
@Schema(description = "Warehouse name / 仓库名称", example = "Los Angeles Main Warehouse / 洛杉矶主仓", maxLength = 120, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 120) String warehouseName,
|
||||
@Schema(description = "Warehouse type; defaults to DOMESTIC / 仓库类型,默认 DOMESTIC", example = "FBA", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS"}, defaultValue = "DOMESTIC")
|
||||
@Pattern(regexp = "DOMESTIC|FBA|OVERSEAS") String warehouseType,
|
||||
@Schema(description = "Warehouse type; defaults to DOMESTIC / 仓库类型,默认 DOMESTIC", example = "OVERSEAS_PARTNER", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS", "OVERSEAS_PARTNER"}, defaultValue = "DOMESTIC")
|
||||
@Pattern(regexp = "DOMESTIC|FBA|OVERSEAS|OVERSEAS_PARTNER") String warehouseType,
|
||||
@Schema(description = "ISO 3166-1 alpha-2 country code; DOMESTIC defaults to CN / ISO 两位国家代码,DOMESTIC 默认 CN", example = "US", pattern = "[A-Z]{2}")
|
||||
@Pattern(regexp = "[A-Z]{2}") String countryCode,
|
||||
@Schema(description = "UTC offset; DOMESTIC defaults to UTC+8 / UTC 偏移量,DOMESTIC 默认 UTC+8", example = "UTC-8")
|
||||
@@ -104,8 +104,8 @@ public final class WarehouseRequests {
|
||||
String countryCode,
|
||||
@Schema(description = "Enabled capability code filter / 已启用能力编码筛选", example = "STORAGE")
|
||||
String capabilityCode,
|
||||
@Schema(description = "Warehouse type filter / 仓库类型筛选", example = "DOMESTIC", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS"})
|
||||
@Pattern(regexp = "DOMESTIC|FBA|OVERSEAS") String warehouseType,
|
||||
@Schema(description = "Warehouse type filter / 仓库类型筛选", example = "OVERSEAS_PARTNER", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS", "OVERSEAS_PARTNER"})
|
||||
@Pattern(regexp = "DOMESTIC|FBA|OVERSEAS|OVERSEAS_PARTNER") String warehouseType,
|
||||
@Schema(description = "Status filter / 状态筛选", example = "ACTIVE", allowableValues = {"ACTIVE", "INACTIVE"})
|
||||
@Pattern(regexp = "ACTIVE|INACTIVE") String status
|
||||
) {
|
||||
|
||||
@@ -13,7 +13,7 @@ public record WarehouseSummaryResponse(
|
||||
@Schema(description = "Platform public scope ID, fixed to 0 / 平台公共作用域,固定为 0") Long tenantId,
|
||||
@Schema(description = "Warehouse code / 仓库编码") String warehouseCode,
|
||||
@Schema(description = "Warehouse name / 仓库名称") String warehouseName,
|
||||
@Schema(description = "Warehouse type / 仓库类型", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS"}) String warehouseType,
|
||||
@Schema(description = "Warehouse type / 仓库类型", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS", "OVERSEAS_PARTNER"}) String warehouseType,
|
||||
@Schema(description = "ISO two-letter country code / 国家二字码") String countryCode,
|
||||
@Schema(description = "UTC offset / UTC 偏移量") String timezone,
|
||||
@Schema(description = "Contact name / 联系人") String contactName,
|
||||
|
||||
@@ -27,7 +27,8 @@ public class WarehouseEntity extends BaseEntity {
|
||||
private String warehouseCode;
|
||||
@Schema(description = "Warehouse name / 仓库名称", example = "Los Angeles Main Warehouse / 洛杉矶主仓")
|
||||
private String warehouseName;
|
||||
@Schema(description = "Warehouse type / 仓库类型", example = "OVERSEAS")
|
||||
@Schema(description = "Warehouse type / 仓库类型", example = "OVERSEAS_PARTNER",
|
||||
allowableValues = {"DOMESTIC", "FBA", "OVERSEAS", "OVERSEAS_PARTNER"})
|
||||
private String warehouseType;
|
||||
@Schema(description = "ISO two-letter country code / ISO 两位国家代码", example = "US")
|
||||
private String countryCode;
|
||||
|
||||
@@ -59,6 +59,15 @@ public class WarehouseRepository {
|
||||
.eq(WarehouseEntity::getStatus, "ACTIVE"));
|
||||
}
|
||||
|
||||
public List<WarehouseEntity> findActiveByType(long tenantId, String warehouseType) {
|
||||
return warehouses.selectList(new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
.eq(WarehouseEntity::getWarehouseType, warehouseType)
|
||||
.eq(WarehouseEntity::getStatus, "ACTIVE")
|
||||
.orderByAsc(WarehouseEntity::getWarehouseName)
|
||||
.orderByAsc(WarehouseEntity::getId));
|
||||
}
|
||||
|
||||
public boolean existsCode(long tenantId, String code, Long excludedId) {
|
||||
return warehouses.selectCount(new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.WarehouseDetailResponse;
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import com.baiyue.warehouse.dto.OverseasPartnerWarehouseResponse;
|
||||
import com.baiyue.warehouse.dto.WarehouseSummaryResponse;
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
@@ -86,6 +87,14 @@ public class WarehouseApplicationService {
|
||||
return repository.activePage(tenantId, query);
|
||||
}
|
||||
|
||||
public List<OverseasPartnerWarehouseResponse> activeOverseasPartners() {
|
||||
return repository.findActiveByType(WarehouseSaveNormalizer.PUBLIC_TENANT_ID,
|
||||
WarehouseSaveNormalizer.TYPE_OVERSEAS_PARTNER)
|
||||
.stream()
|
||||
.map(OverseasPartnerWarehouseResponse::of)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public WarehouseDetailResponse activeDetail(long tenantId, long id) {
|
||||
WarehouseEntity warehouse = repository.findOwned(tenantId, id);
|
||||
if (warehouse == null) {
|
||||
|
||||
@@ -56,7 +56,7 @@ public class WarehouseImportService {
|
||||
Row instruction = sheet.createRow(1);
|
||||
instruction.createCell(0).setCellValue("说明");
|
||||
instruction.createCell(1).setCellValue(
|
||||
"默认仅新增;仓库类型默认 DOMESTIC,状态默认 ACTIVE;DOMESTIC 默认 CN/UTC+8;FBA 名称可不填并默认使用编码,多时区州必须显式填写时区;更新不覆盖联系人和备注");
|
||||
"默认仅新增;仓库类型默认 DOMESTIC,可填 OVERSEAS_PARTNER 表示海外合作仓;状态默认 ACTIVE;DOMESTIC 默认 CN/UTC+8;FBA 名称可不填并默认使用编码,多时区州必须显式填写时区;更新不覆盖联系人和备注");
|
||||
Row header = sheet.createRow(2);
|
||||
for (int i = 0; i < HEADERS.length; i++) {
|
||||
header.createCell(i).setCellValue(HEADERS[i]);
|
||||
@@ -202,8 +202,8 @@ public class WarehouseImportService {
|
||||
} else {
|
||||
optionalLength(row, errors, "仓库名称", row.name(), 120);
|
||||
}
|
||||
if (!row.type().isBlank() && !row.type().matches("(?i)DOMESTIC|FBA|OVERSEAS")) {
|
||||
errors.add(new WarehouseImportError(row.row(), "仓库类型", "只能填写 DOMESTIC、FBA 或 OVERSEAS"));
|
||||
if (!row.type().isBlank() && !row.type().matches("(?i)DOMESTIC|FBA|OVERSEAS|OVERSEAS_PARTNER")) {
|
||||
errors.add(new WarehouseImportError(row.row(), "仓库类型", "只能填写 DOMESTIC、FBA、OVERSEAS 或 OVERSEAS_PARTNER"));
|
||||
}
|
||||
if (!row.countryCode().isBlank() && !row.countryCode().matches("(?i)[A-Z]{2}")) {
|
||||
errors.add(new WarehouseImportError(row.row(), "国家二字码", "必须是两个英文字母"));
|
||||
|
||||
@@ -15,6 +15,8 @@ final class WarehouseSaveNormalizer {
|
||||
static final long PUBLIC_TENANT_ID = 0L;
|
||||
static final String TYPE_DOMESTIC = "DOMESTIC";
|
||||
static final String TYPE_FBA = "FBA";
|
||||
static final String TYPE_OVERSEAS = "OVERSEAS";
|
||||
static final String TYPE_OVERSEAS_PARTNER = "OVERSEAS_PARTNER";
|
||||
static final String STATUS_ACTIVE = "ACTIVE";
|
||||
static final String DEFAULT_ADDRESS_TYPE = "WAREHOUSE";
|
||||
static final String DEFAULT_DOMESTIC_COUNTRY = "CN";
|
||||
@@ -68,8 +70,9 @@ final class WarehouseSaveNormalizer {
|
||||
|
||||
static NormalizedSaveRequest normalize(WarehouseRequests.SaveRequest request, long tenantId) {
|
||||
String warehouseType = upperOrDefault(request.warehouseType(), TYPE_DOMESTIC);
|
||||
if (!TYPE_DOMESTIC.equals(warehouseType) && !TYPE_FBA.equals(warehouseType) && !"OVERSEAS".equals(warehouseType)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "Warehouse type must be DOMESTIC, FBA or OVERSEAS / 仓库类型只能为 DOMESTIC、FBA 或 OVERSEAS");
|
||||
if (!Set.of(TYPE_DOMESTIC, TYPE_FBA, TYPE_OVERSEAS, TYPE_OVERSEAS_PARTNER).contains(warehouseType)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"Warehouse type must be DOMESTIC, FBA, OVERSEAS or OVERSEAS_PARTNER / 仓库类型只能为 DOMESTIC、FBA、OVERSEAS 或 OVERSEAS_PARTNER");
|
||||
}
|
||||
String status = upperOrDefault(request.status(), STATUS_ACTIVE);
|
||||
if (!STATUS_ACTIVE.equals(status) && !"INACTIVE".equals(status)) {
|
||||
|
||||
@@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS wh_warehouse (
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共仓库固定使用 tenant_id=0',
|
||||
warehouse_code VARCHAR(50) NOT NULL COMMENT '平台内唯一仓库编码',
|
||||
warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称',
|
||||
warehouse_type VARCHAR(32) NOT NULL DEFAULT 'DOMESTIC' COMMENT '公共仓库类型:DOMESTIC、FBA、OVERSEAS',
|
||||
warehouse_type VARCHAR(32) NOT NULL DEFAULT 'DOMESTIC' COMMENT '公共仓库类型:DOMESTIC国内仓、FBA亚马逊仓、OVERSEAS普通海外仓、OVERSEAS_PARTNER海外合作仓',
|
||||
country_code CHAR(2) NOT NULL COMMENT '所在国家二字码',
|
||||
timezone VARCHAR(64) NOT NULL COMMENT '仓库所在地UTC偏移量',
|
||||
contact_name VARCHAR(64) DEFAULT NULL COMMENT '仓库联系人',
|
||||
|
||||
+12
@@ -3,6 +3,7 @@ package com.baiyue.warehouse.controller;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.WarehouseDetailResponse;
|
||||
import com.baiyue.warehouse.dto.OverseasPartnerWarehouseResponse;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -71,4 +72,15 @@ class InternalWarehouseControllerTest {
|
||||
|
||||
verifyNoInteractions(service);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listsActiveOverseasPartnerOptionsForInternalCallers() {
|
||||
var option = new OverseasPartnerWarehouseResponse(200002L, "US-PARTNER-LAX", "洛杉矶合作仓", "US");
|
||||
when(service.activeOverseasPartners()).thenReturn(List.of(option));
|
||||
|
||||
var response = controller.overseasPartners(INTERNAL_TOKEN);
|
||||
|
||||
assertThat(response.data()).containsExactly(option);
|
||||
verify(service).activeOverseasPartners();
|
||||
}
|
||||
}
|
||||
|
||||
+33
@@ -116,4 +116,37 @@ class WarehouseApplicationServiceTest {
|
||||
.isEqualTo(ResultCode.VALIDATION_FAILED);
|
||||
verify(repository, never()).insertWarehouse(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void createsOverseasPartnerWarehouseAsPlatformPublicMasterData() {
|
||||
WarehouseRequests.SaveRequest request = new WarehouseRequests.SaveRequest(null, "US-PARTNER-LAX", "洛杉矶合作仓",
|
||||
"OVERSEAS_PARTNER", "US", "UTC-8", "Alex", "+1-213-555-0100", null, null, null,
|
||||
new WarehouseRequests.AddressRequest(null, "US", "CA", "Los Angeles", null,
|
||||
"90001", "123 Partner Ave", null, null, null),
|
||||
java.util.List.of(new WarehouseRequests.CapabilityRequest("STORAGE", "仓储", true, null, null)));
|
||||
|
||||
service.create(request);
|
||||
|
||||
var warehouse = org.mockito.ArgumentCaptor.forClass(WarehouseEntity.class);
|
||||
verify(repository).insertWarehouse(warehouse.capture());
|
||||
assertThat(warehouse.getValue().getTenantId()).isZero();
|
||||
assertThat(warehouse.getValue().getWarehouseType()).isEqualTo("OVERSEAS_PARTNER");
|
||||
assertThat(warehouse.getValue().getCountryCode()).isEqualTo("US");
|
||||
assertThat(warehouse.getValue().getTimezone()).isEqualTo("UTC-8");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesOnlyActiveOverseasPartnerWarehousesToInternalConsumers() {
|
||||
WarehouseEntity partner = new WarehouseEntity();
|
||||
partner.setId(2L);
|
||||
partner.setWarehouseCode("US-PARTNER-LAX");
|
||||
partner.setWarehouseName("洛杉矶合作仓");
|
||||
partner.setCountryCode("US");
|
||||
when(repository.findActiveByType(0L, "OVERSEAS_PARTNER")).thenReturn(java.util.List.of(partner));
|
||||
|
||||
assertThat(service.activeOverseasPartners())
|
||||
.extracting("id", "warehouseCode", "warehouseName", "countryCode")
|
||||
.containsExactly(tuple(2L, "US-PARTNER-LAX", "洛杉矶合作仓", "US"));
|
||||
verify(repository).findActiveByType(0L, "OVERSEAS_PARTNER");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,22 @@ class WarehouseImportServiceTest {
|
||||
assertThat(captor.getAllValues().get(1).warehouseName()).isEqualTo("FBA-LAX9");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("海外合作仓类型可通过批量导入进入公共仓库主数据")
|
||||
void shouldImportOverseasPartnerWarehouse() throws Exception {
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"US-PARTNER-LAX", "洛杉矶合作仓", "OVERSEAS_PARTNER", "US", "CA", "Los Angeles", "", "123 Partner Ave", "90001", "UTC-8", "", "", "", "", ""}
|
||||
});
|
||||
|
||||
WarehouseImportResult result = service.importFile(0L, "warehouses.xlsx", file, null);
|
||||
|
||||
assertThat(result.success()).isTrue();
|
||||
var captor = org.mockito.ArgumentCaptor.forClass(WarehouseRequests.SaveRequest.class);
|
||||
verify(warehouseService).create(captor.capture());
|
||||
assertThat(captor.getValue().tenantId()).isZero();
|
||||
assertThat(captor.getValue().warehouseType()).isEqualTo("OVERSEAS_PARTNER");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("任一行错误时整批零写入")
|
||||
void shouldRejectWholeBatchWhenAnyRowInvalid() throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user