Merge remote-tracking branch 'origin/dev' into test
# Conflicts: # warehouse/src/main/java/com/baiyue/warehouse/dto/WarehouseRequests.java # warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseImportService.java # warehouse/src/main/java/com/baiyue/warehouse/service/WarehouseSaveNormalizer.java # warehouse/src/main/resources/db/warehouse.sql
This commit is contained in:
+5
-1
@@ -2,8 +2,9 @@
|
||||
|
||||
## 职责与边界
|
||||
|
||||
- 负责国内收货仓、地址/能力、往来单位、快递公司、平台公共供应商、国内整柜库存/出入库,以及托盘主数据、库位预占和占用。
|
||||
- 负责国内收货仓及现有平台仓库主数据、地址/能力、往来单位、快递公司、平台公共供应商、国内整柜库存/出入库,以及托盘主数据、库位预占和占用。四期只在本模块扩展“海外仓合作仓”仓库类型及其公共主数据契约。
|
||||
- 入库单、测量和入库状态归 `firstleg/`;订单只通过内部接口校验并保存快照。
|
||||
- 四期海外接柜、卸货、海外入库、换标、产品、箱号、库存和库内作业属于海外区域服务,不得落入本国内 Warehouse Service;范围先读 `../../docs/四期/README.md`。
|
||||
- 公共资源统一 `tenant_id=0`,不得绑定客户或要求客户筛选。
|
||||
|
||||
## 模块入口
|
||||
@@ -11,6 +12,8 @@
|
||||
| 目标 | 入口 |
|
||||
|---|---|
|
||||
| 仓库/地址/能力 | `controller/PlatformWarehouseController.java`、`MerchantWarehouseController.java`、`service/WarehouseApplicationService.java` |
|
||||
| 公共海外收货仓内部目录 | `controller/InternalWarehouseController.java` 的 `/internal/warehouses/shared-overseas`、`/shared-overseas/{warehouseId}`,响应 `dto/SharedOverseasWarehouseResponse.java`;只返回平台公共 `OVERSEAS + ACTIVE + RECEIVING` 仓,供海外资源服务按 warehouseId 引用 |
|
||||
| 四期海外仓合作仓类型 | 沿用仓库/地址/能力入口;同步核对仓库 DTO、OpenAPI、`src/main/resources/db/`、统一 SQL 及 Admin `modules/warehouses/`,并与普通海外仓语义明确区分 |
|
||||
| 往来单位/快递 | `controller/PlatformPartnerController.java`、`PlatformExpressCompanyController.java`、`service/PlatformPartnerQueryService.java`、`PartnerImportService.java` |
|
||||
| 托盘 | `controller/PlatformPalletController.java`、`service/PalletApplicationService.java` |
|
||||
| 库位与分配 | `controller/PlatformLocationController.java`、`InternalPalletLocationController.java`、`service/PalletLocationAllocationService.java` |
|
||||
@@ -24,6 +27,7 @@
|
||||
## 修改约束
|
||||
|
||||
- 内部仓储接口使用 `X-Internal-Service-Token`,不加入 Gateway 公网路由;资源停用/不存在遵循统一 body code。
|
||||
- 仓库 ID、类型、状态、地址和服务能力在本模块唯一维护;海外服务不得复制仓库主档。共享海外收货仓目录只接受内部服务令牌,列表和单项必须使用相同的 `tenant_id=0 + OVERSEAS + ACTIVE + RECEIVING` 判定。
|
||||
- 导入先全量校验再原子写入;调整 DTO、类型或容量规则时同步 Order/Firstleg 客户端和契约测试。
|
||||
|
||||
## 三期托盘、库位与 A0 规则
|
||||
|
||||
+25
@@ -4,6 +4,8 @@ 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.dto.SharedOverseasWarehouseResponse;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
@@ -11,6 +13,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 +42,28 @@ 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());
|
||||
}
|
||||
|
||||
@GetMapping("/shared-overseas")
|
||||
public ApiResponse<List<SharedOverseasWarehouseResponse>> sharedOverseasWarehouses(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token) {
|
||||
verifyToken(token);
|
||||
return ApiResponse.success(service.activeSharedOverseasWarehouses());
|
||||
}
|
||||
|
||||
@GetMapping("/shared-overseas/{warehouseId}")
|
||||
public ApiResponse<SharedOverseasWarehouseResponse> sharedOverseasWarehouse(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
@PathVariable long warehouseId) {
|
||||
verifyToken(token);
|
||||
return ApiResponse.success(service.activeSharedOverseasWarehouse(warehouseId));
|
||||
}
|
||||
|
||||
private void verifyToken(String token) {
|
||||
byte[] supplied = token == null ? new byte[0] : token.getBytes(StandardCharsets.UTF_8);
|
||||
if (!MessageDigest.isEqual(expected, supplied)) {
|
||||
|
||||
@@ -71,6 +71,9 @@ public final class MasterDataQueries {
|
||||
@NotBlank @Size(max = 128) String partnerName,
|
||||
@Schema(description = "单值单位类型", allowableValues = {"CARRIER", "SHIPPER", "CONSIGNEE"})
|
||||
@Pattern(regexp = "^(CARRIER|SHIPPER|CONSIGNEE)$") String partnerType,
|
||||
@Schema(description = "船司标准编码;包含 CARRIER 类型时必填,其他类型必须为空",
|
||||
allowableValues = {"MATSON", "SML", "ZIM", "COSCO", "EMC", "HEDE", "OTHER"})
|
||||
@Pattern(regexp = "^(MATSON|SML|ZIM|COSCO|EMC|HEDE|OTHER)$") String carrierCode,
|
||||
@Schema(description = "联系人", example = "张三")
|
||||
@NotBlank @Size(max = 64) String contactName,
|
||||
@Schema(description = "联系电话", example = "13800138000")
|
||||
|
||||
+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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.warehouse.dto;
|
||||
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
@Schema(name = "SharedOverseasWarehouseResponse",
|
||||
description = "Domestic-authoritative active shared overseas receiving warehouse")
|
||||
public record SharedOverseasWarehouseResponse(
|
||||
Long warehouseId,
|
||||
String warehouseCode,
|
||||
String warehouseName,
|
||||
String countryCode,
|
||||
String status
|
||||
) {
|
||||
public static SharedOverseasWarehouseResponse of(WarehouseEntity warehouse) {
|
||||
return new SharedOverseasWarehouseResponse(warehouse.getId(), warehouse.getWarehouseCode(),
|
||||
warehouse.getWarehouseName(), warehouse.getCountryCode(), warehouse.getStatus());
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ 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", "OVERSEAS_PARTNER"}, defaultValue = "DOMESTIC")
|
||||
@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,
|
||||
@@ -104,7 +104,7 @@ 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", "OVERSEAS_PARTNER"})
|
||||
@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
|
||||
|
||||
@@ -28,6 +28,9 @@ public class PartnerEntity extends BaseEntity {
|
||||
private String partnerName;
|
||||
@Schema(description = "类型", example = "CARRIER", allowableValues = {"CARRIER", "SHIPPER", "CONSIGNEE"})
|
||||
private String partnerType;
|
||||
@Schema(description = "船司标准编码;船公司必填,其他类型必须为空", example = "MATSON",
|
||||
allowableValues = {"MATSON", "SML", "ZIM", "COSCO", "EMC", "HEDE", "OTHER"})
|
||||
private String carrierCode;
|
||||
@TableField(exist = false)
|
||||
@Schema(description = "单位类型集合", allowableValues = {"CARRIER", "SHIPPER", "CONSIGNEE"})
|
||||
private List<String> types;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -35,10 +35,12 @@ public class PartnerImportService {
|
||||
private static final String TEMPLATE_VERSION = "1";
|
||||
private static final Set<String> ALLOWED_TYPES = Set.of(
|
||||
"CARRIER", "SHIPPER", "CONSIGNEE");
|
||||
private static final Set<String> ALLOWED_CARRIER_CODES = Set.of(
|
||||
"MATSON", "SML", "ZIM", "COSCO", "EMC", "HEDE", "OTHER");
|
||||
private static final Pattern EMAIL = Pattern.compile("^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$");
|
||||
private static final String[] HEADERS = {
|
||||
"单位编码*", "单位名称*", "单位类型*", "联系人*", "联系电话*", "联系邮箱",
|
||||
"国家二字码*", "省/州", "城市*", "区/县", "详细地址*", "邮政编码", "状态*", "备注"
|
||||
"船司标准编码", "国家二字码*", "省/州", "城市*", "区/县", "详细地址*", "邮政编码", "状态*", "备注"
|
||||
};
|
||||
|
||||
@Resource
|
||||
@@ -107,9 +109,9 @@ public class PartnerImportService {
|
||||
ImportRow item = new ImportRow(index + 1,
|
||||
value(row, 0, formatter), value(row, 1, formatter), parseTypes(value(row, 2, formatter)),
|
||||
value(row, 3, formatter), value(row, 4, formatter), value(row, 5, formatter),
|
||||
value(row, 6, formatter).toUpperCase(Locale.ROOT), value(row, 7, formatter),
|
||||
value(row, 8, formatter), value(row, 9, formatter), value(row, 10, formatter),
|
||||
value(row, 11, formatter), value(row, 12, formatter).toUpperCase(Locale.ROOT), value(row, 13, formatter));
|
||||
value(row, 6, formatter).toUpperCase(Locale.ROOT), value(row, 7, formatter).toUpperCase(Locale.ROOT),
|
||||
value(row, 8, formatter), value(row, 9, formatter), value(row, 10, formatter), value(row, 11, formatter),
|
||||
value(row, 12, formatter), value(row, 13, formatter).toUpperCase(Locale.ROOT), value(row, 14, formatter));
|
||||
validate(tenantId, item, rows, errors);
|
||||
rows.add(item);
|
||||
}
|
||||
@@ -134,6 +136,11 @@ public class PartnerImportService {
|
||||
if (row.types.isEmpty()) errors.add(error(row, "单位类型", "至少填写一个类型"));
|
||||
row.types.stream().filter(type -> !ALLOWED_TYPES.contains(type))
|
||||
.forEach(type -> errors.add(error(row, "单位类型", "不支持类型 " + type)));
|
||||
if (row.types.contains("CARRIER") && !ALLOWED_CARRIER_CODES.contains(row.carrierCode)) {
|
||||
errors.add(error(row, "船司标准编码", "船公司必须选择 MATSON、SML、ZIM、COSCO、EMC、HEDE 或 OTHER"));
|
||||
} else if (!row.types.contains("CARRIER") && !row.carrierCode.isBlank()) {
|
||||
errors.add(error(row, "船司标准编码", "非船公司必须留空"));
|
||||
}
|
||||
required(row, errors, "联系人", row.contactName, 64);
|
||||
required(row, errors, "联系电话", row.contactPhone, 32);
|
||||
if (row.email.length() > 128 || (!row.email.isBlank() && !EMAIL.matcher(row.email).matches())) {
|
||||
@@ -162,7 +169,8 @@ public class PartnerImportService {
|
||||
entity.setPartnerCode(row.code);
|
||||
entity.setPartnerName(row.name);
|
||||
entity.setTypes(row.types);
|
||||
entity.setPartnerType(row.types.get(0));
|
||||
entity.setPartnerType(row.types.contains("CARRIER") ? "CARRIER" : row.types.get(0));
|
||||
entity.setCarrierCode(nullable(row.carrierCode));
|
||||
entity.setContactName(row.contactName);
|
||||
entity.setContactPhone(row.contactPhone);
|
||||
entity.setContactEmail(nullable(row.email));
|
||||
@@ -216,6 +224,6 @@ public class PartnerImportService {
|
||||
}
|
||||
|
||||
private record ImportRow(int row, String code, String name, List<String> types, String contactName,
|
||||
String contactPhone, String email, String countryCode, String province, String city,
|
||||
String contactPhone, String email, String carrierCode, String countryCode, String province, String city,
|
||||
String district, String address, String postalCode, String status, String remark) { }
|
||||
}
|
||||
|
||||
+24
-1
@@ -24,6 +24,8 @@ public class PlatformPartnerQueryService {
|
||||
private static final long PUBLIC_TENANT_ID = 0L;
|
||||
private static final Set<String> ALLOWED_QUERY_TYPES = Set.of(
|
||||
"CARRIER", "SHIPPER", "CONSIGNEE");
|
||||
private static final Set<String> ALLOWED_CARRIER_CODES = Set.of(
|
||||
"MATSON", "SML", "ZIM", "COSCO", "EMC", "HEDE", "OTHER");
|
||||
@Resource
|
||||
private PartnerRepository repository;
|
||||
|
||||
@@ -121,10 +123,12 @@ public class PlatformPartnerQueryService {
|
||||
|
||||
private static void copy(PartnerEntity entity, PartnerSaveRequest request) {
|
||||
List<String> types = normalizeTypes(request.types(), request.partnerType());
|
||||
String carrierCode = normalizeCarrierCode(types, request.carrierCode());
|
||||
entity.setPartnerCode(request.partnerCode());
|
||||
entity.setPartnerName(request.partnerName());
|
||||
entity.setTypes(types);
|
||||
entity.setPartnerType(legacyType(types));
|
||||
entity.setCarrierCode(carrierCode);
|
||||
entity.setContactName(request.contactName());
|
||||
entity.setContactPhone(request.contactPhone());
|
||||
entity.setContactEmail(request.contactEmail());
|
||||
@@ -154,6 +158,25 @@ public class PlatformPartnerQueryService {
|
||||
}
|
||||
|
||||
private static String legacyType(List<String> types) {
|
||||
return types.get(0);
|
||||
return types.contains("CARRIER") ? "CARRIER" : types.get(0);
|
||||
}
|
||||
|
||||
static String normalizeCarrierCode(List<String> types, String carrierCode) {
|
||||
boolean carrier = types.contains("CARRIER");
|
||||
if (!carrier) {
|
||||
if (carrierCode != null && !carrierCode.isBlank()) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "非船公司不得设置船司标准编码");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (carrierCode == null || carrierCode.isBlank()) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "船公司必须选择船司标准编码");
|
||||
}
|
||||
String normalized = carrierCode.trim().toUpperCase(Locale.ROOT);
|
||||
if (!ALLOWED_CARRIER_CODES.contains(normalized)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"船司标准编码仅支持 MATSON、SML、ZIM、COSCO、EMC、HEDE 或 OTHER");
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ 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.dto.SharedOverseasWarehouseResponse;
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
@@ -86,6 +88,40 @@ 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 List<SharedOverseasWarehouseResponse> activeSharedOverseasWarehouses() {
|
||||
return repository.findActiveByType(WarehouseSaveNormalizer.PUBLIC_TENANT_ID,
|
||||
WarehouseSaveNormalizer.TYPE_OVERSEAS)
|
||||
.stream()
|
||||
.filter(warehouse -> hasReceivingCapability(warehouse.getId()))
|
||||
.map(SharedOverseasWarehouseResponse::of)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public SharedOverseasWarehouseResponse activeSharedOverseasWarehouse(long warehouseId) {
|
||||
WarehouseEntity warehouse = repository.findOwned(WarehouseSaveNormalizer.PUBLIC_TENANT_ID, warehouseId);
|
||||
if (warehouse == null || !"ACTIVE".equals(warehouse.getStatus())
|
||||
|| !WarehouseSaveNormalizer.TYPE_OVERSEAS.equals(warehouse.getWarehouseType())
|
||||
|| !hasReceivingCapability(warehouseId)) {
|
||||
throw new BizException(ResultCode.NOT_FOUND,
|
||||
"启用的公共海外收货仓不存在");
|
||||
}
|
||||
return SharedOverseasWarehouseResponse.of(warehouse);
|
||||
}
|
||||
|
||||
private boolean hasReceivingCapability(long warehouseId) {
|
||||
return repository.findCapabilities(WarehouseSaveNormalizer.PUBLIC_TENANT_ID, warehouseId).stream()
|
||||
.anyMatch(capability -> "RECEIVING".equals(capability.getCapabilityCode())
|
||||
&& Boolean.TRUE.equals(capability.getEnabled()));
|
||||
}
|
||||
|
||||
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]);
|
||||
@@ -203,7 +203,7 @@ public class WarehouseImportService {
|
||||
optionalLength(row, errors, "仓库名称", row.name(), 120);
|
||||
}
|
||||
if (!row.type().isBlank() && !row.type().matches("(?i)DOMESTIC|FBA|OVERSEAS|OVERSEAS_PARTNER")) {
|
||||
errors.add(new WarehouseImportError(row.row(), "仓库类型", "只能填写 DOMESTIC、FBA 或 OVERSEAS"));
|
||||
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(), "国家二字码", "必须是两个英文字母"));
|
||||
|
||||
@@ -71,7 +71,8 @@ final class WarehouseSaveNormalizer {
|
||||
static NormalizedSaveRequest normalize(WarehouseRequests.SaveRequest request, long tenantId) {
|
||||
String warehouseType = upperOrDefault(request.warehouseType(), TYPE_DOMESTIC);
|
||||
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");
|
||||
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)) {
|
||||
|
||||
@@ -102,7 +102,8 @@ CALL add_column_if_missing('wh_partner', 'tenant_id', 'BIGINT UNSIGNED NOT NULL
|
||||
CALL add_column_if_missing('wh_partner', 'partner_code', 'VARCHAR(50) NOT NULL DEFAULT '''' AFTER tenant_id');
|
||||
CALL add_column_if_missing('wh_partner', 'partner_name', 'VARCHAR(128) NOT NULL DEFAULT '''' AFTER partner_code');
|
||||
CALL add_column_if_missing('wh_partner', 'partner_type', 'VARCHAR(20) NOT NULL DEFAULT ''CUSTOMER'' AFTER partner_name');
|
||||
CALL add_column_if_missing('wh_partner', 'contact_name', 'VARCHAR(64) NOT NULL DEFAULT '''' AFTER partner_type');
|
||||
CALL add_column_if_missing('wh_partner', 'carrier_code', 'VARCHAR(16) NULL COMMENT ''船司标准编码:MATSON、SML、ZIM、COSCO、EMC、HEDE或OTHER;仅CARRIER必填'' AFTER partner_type');
|
||||
CALL add_column_if_missing('wh_partner', 'contact_name', 'VARCHAR(64) NOT NULL DEFAULT '''' AFTER carrier_code');
|
||||
CALL add_column_if_missing('wh_partner', 'contact_phone', 'VARCHAR(32) NOT NULL DEFAULT '''' AFTER contact_name');
|
||||
CALL add_column_if_missing('wh_partner', 'contact_email', 'VARCHAR(128) NULL');
|
||||
CALL add_column_if_missing('wh_partner', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' AFTER contact_email');
|
||||
@@ -118,6 +119,8 @@ CALL add_column_if_missing('wh_partner', 'updated_by', 'BIGINT UNSIGNED NULL');
|
||||
CALL add_column_if_missing('wh_partner', 'updated_at', 'DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)');
|
||||
CALL add_column_if_missing('wh_partner', 'deleted', 'TINYINT(1) NOT NULL DEFAULT 0');
|
||||
CALL add_column_if_missing('wh_partner', 'version', 'INT NOT NULL DEFAULT 0');
|
||||
UPDATE wh_partner SET carrier_code = 'OTHER'
|
||||
WHERE id = 191000000000000001 AND partner_type = 'CARRIER' AND carrier_code IS NULL;
|
||||
|
||||
-- 该表在旧库中不存在时先由 warehouse-express-company.sql 建表;此段补齐早期部署缺列。
|
||||
CALL add_column_if_missing('wh_express_company', 'tenant_id', 'BIGINT UNSIGNED NOT NULL DEFAULT 0 AFTER id');
|
||||
|
||||
@@ -5,6 +5,7 @@ CREATE TABLE IF NOT EXISTS wh_partner (
|
||||
data_scope_id BIGINT UNSIGNED NOT NULL COMMENT '所属业务数据范围',
|
||||
partner_code VARCHAR(50) NOT NULL COMMENT '平台内唯一往来单位编码', partner_name VARCHAR(128) NOT NULL COMMENT '往来单位名称',
|
||||
partner_type VARCHAR(20) NOT NULL COMMENT '往来单位类型:CARRIER、SHIPPER或CONSIGNEE',
|
||||
carrier_code VARCHAR(16) NULL COMMENT '船司标准编码:MATSON、SML、ZIM、COSCO、EMC、HEDE或OTHER;仅CARRIER必填',
|
||||
contact_name VARCHAR(64) NOT NULL COMMENT '联系人姓名', contact_phone VARCHAR(32) NOT NULL COMMENT '联系电话',
|
||||
contact_email VARCHAR(128) NULL COMMENT '联系邮箱', country_code CHAR(2) NOT NULL COMMENT '国家二字码',
|
||||
province VARCHAR(64) NULL COMMENT '省或州', city VARCHAR(64) NOT NULL COMMENT '城市', district VARCHAR(64) NULL COMMENT '区县',
|
||||
@@ -16,7 +17,8 @@ CREATE TABLE IF NOT EXISTS wh_partner (
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记:0未删除、1已删除', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
PRIMARY KEY(id), UNIQUE KEY uk_wh_partner_tenant_code(tenant_id,partner_code,deleted),
|
||||
KEY idx_wh_partner_tenant_status(tenant_id,status,deleted),
|
||||
CONSTRAINT chk_wh_partner_type CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE'))
|
||||
CONSTRAINT chk_wh_partner_type CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE')),
|
||||
CONSTRAINT chk_wh_partner_carrier_code CHECK (carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'))
|
||||
) ENGINE=InnoDB COMMENT='平台统一维护、供全部客户共享的往来单位主数据';
|
||||
CREATE TABLE IF NOT EXISTS wh_partner_type (
|
||||
partner_id BIGINT UNSIGNED NOT NULL COMMENT '往来单位ID', tenant_id BIGINT UNSIGNED NOT NULL COMMENT '平台公共往来单位固定使用 tenant_id=0',
|
||||
|
||||
@@ -162,7 +162,7 @@ JOIN wh_warehouse warehouse
|
||||
ON DUPLICATE KEY UPDATE
|
||||
tenant_id = VALUES(tenant_id);
|
||||
|
||||
-- 国内固定作业仓必须启用收货能力,FBA 目的地址仍不得创建作业能力。
|
||||
-- 四个固定平台作业仓必须启用收货能力,FBA 目的地址仍不得创建作业能力。
|
||||
INSERT INTO wh_service_capability (
|
||||
id, tenant_id, warehouse_id, capability_code, capability_name, enabled, cutoff_time,
|
||||
description, created_by, created_at, updated_by, updated_at, deleted, version
|
||||
@@ -171,7 +171,11 @@ VALUES
|
||||
(193000000000400001,0,193000000000200001,'RECEIVING','头程收货',1,'16:00:00',
|
||||
'义乌仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0),
|
||||
(193000000000400002,0,193000000000200002,'RECEIVING','头程收货',1,'16:00:00',
|
||||
'东莞仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0)
|
||||
'东莞仓头程订单收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0),
|
||||
(193000000000400003,0,193000000000200003,'RECEIVING','海外收货',1,'16:00:00',
|
||||
'洛杉矶仓海外接柜入仓收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0),
|
||||
(193000000000400004,0,193000000000200004,'RECEIVING','海外收货',1,'16:00:00',
|
||||
'纽约仓海外接柜入仓收货能力',NULL,CURRENT_TIMESTAMP(3),NULL,CURRENT_TIMESTAMP(3),0,0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
capability_name = VALUES(capability_name),
|
||||
enabled = VALUES(enabled),
|
||||
@@ -677,11 +681,11 @@ BEGIN
|
||||
IF (SELECT COUNT(*)
|
||||
FROM wh_service_capability capability
|
||||
WHERE capability.tenant_id = 0
|
||||
AND capability.warehouse_id IN (193000000000200001,193000000000200002)
|
||||
AND capability.warehouse_id IN (193000000000200001,193000000000200002,193000000000200003,193000000000200004)
|
||||
AND capability.capability_code = 'RECEIVING'
|
||||
AND capability.enabled = 1
|
||||
AND capability.deleted = 0) <> 2 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '国内固定作业仓收货能力写入校验失败';
|
||||
AND capability.deleted = 0) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台作业仓收货能力写入校验失败';
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
|
||||
@@ -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、OVERSEAS_PARTNER',
|
||||
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 '仓库联系人',
|
||||
|
||||
+26
@@ -3,6 +3,8 @@ 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.dto.SharedOverseasWarehouseResponse;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -71,4 +73,28 @@ 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();
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesSharedOverseasReceivingDirectoryOnlyWithInternalToken() {
|
||||
var warehouse = new SharedOverseasWarehouseResponse(200003L, "US-LAX-01",
|
||||
"洛杉矶海外仓", "US", "ACTIVE");
|
||||
when(service.activeSharedOverseasWarehouse(200003L)).thenReturn(warehouse);
|
||||
|
||||
assertThat(controller.sharedOverseasWarehouse(INTERNAL_TOKEN, 200003L).data())
|
||||
.isEqualTo(warehouse);
|
||||
assertThatThrownBy(() -> controller.sharedOverseasWarehouse("bad", 200003L))
|
||||
.isInstanceOf(BizException.class);
|
||||
verify(service).activeSharedOverseasWarehouse(200003L);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-3
@@ -25,11 +25,12 @@ class WarehouseSchemaContractTest {
|
||||
void shouldHandleCase02() throws IOException {
|
||||
String sql = read("warehouse-partner.sql");
|
||||
assertColumns(sql, "wh_partner", "data_scope_id", "partner_code", "partner_name", "partner_type", "contact_name",
|
||||
"contact_phone", "contact_email", "country_code", "province", "city", "district", "address_line",
|
||||
"carrier_code", "contact_phone", "contact_email", "country_code", "province", "city", "district", "address_line",
|
||||
"postal_code", "status", "remark");
|
||||
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_partner_type")
|
||||
.contains("idx_wh_partner_type_scope")
|
||||
.contains("chk_wh_partner_type CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE'))")
|
||||
.contains("chk_wh_partner_carrier_code CHECK (carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'))")
|
||||
.contains("chk_wh_partner_type_value CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE'))")
|
||||
.doesNotContain("CUSTOMS_BROKER", "SUPPLIER或BOTH");
|
||||
assertBaseColumns(sql);
|
||||
@@ -100,6 +101,8 @@ class WarehouseSchemaContractTest {
|
||||
.contains("CALL add_column_if_missing('wh_warehouse_address'")
|
||||
.contains("CALL add_column_if_missing('wh_service_capability'")
|
||||
.contains("CALL add_column_if_missing('wh_partner'")
|
||||
.contains("CALL add_column_if_missing('wh_partner', 'carrier_code', 'VARCHAR(16) NULL")
|
||||
.contains("UPDATE wh_partner SET carrier_code = 'OTHER'")
|
||||
.contains("CALL add_column_if_missing('wh_express_company'")
|
||||
.contains("DROP PROCEDURE IF EXISTS add_column_if_missing")
|
||||
.doesNotContain("ADD COLUMN IF NOT EXISTS")
|
||||
@@ -109,7 +112,7 @@ class WarehouseSchemaContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSeedReceivingCapabilityForDomesticOperatingWarehouses() throws IOException {
|
||||
void shouldSeedReceivingCapabilityForFixedOperatingWarehouses() throws IOException {
|
||||
String masterData = read("warehouse-public-master-data.sql");
|
||||
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
|
||||
|
||||
@@ -117,7 +120,11 @@ class WarehouseSchemaContractTest {
|
||||
assertThat(sql)
|
||||
.contains("193000000000200001,'RECEIVING','头程收货',1")
|
||||
.contains("193000000000200002,'RECEIVING','头程收货',1")
|
||||
.contains("国内固定作业仓收货能力写入校验失败")
|
||||
.contains("193000000000400003,0,193000000000200003,'RECEIVING','海外收货',1")
|
||||
.contains("'洛杉矶仓海外接柜入仓收货能力'")
|
||||
.contains("193000000000400004,0,193000000000200004,'RECEIVING','海外收货',1")
|
||||
.contains("'纽约仓海外接柜入仓收货能力'")
|
||||
.contains("固定平台作业仓收货能力写入校验失败")
|
||||
.contains("warehouse.warehouse_type = 'FBA'")
|
||||
.contains("FBA公共地址不得创建平台作业能力");
|
||||
}
|
||||
@@ -146,7 +153,13 @@ class WarehouseSchemaContractTest {
|
||||
String unified = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"));
|
||||
assertThat(tableDefinition(unified, "wh_partner"))
|
||||
.contains("chk_wh_partner_type CHECK (partner_type IN ('CARRIER','SHIPPER','CONSIGNEE'))")
|
||||
.contains("carrier_code VARCHAR(16) NULL")
|
||||
.contains("chk_wh_partner_carrier_code CHECK (carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'))")
|
||||
.doesNotContain("CUSTOMS_BROKER", "SUPPLIER或BOTH");
|
||||
assertThat(unified)
|
||||
.contains("CALL add_column_if_missing('wh_partner', 'carrier_code', 'VARCHAR(16) NULL")
|
||||
.contains("UPDATE wh_partner SET carrier_code = 'OTHER'")
|
||||
.contains("queue_column_comment('baiyue_warehouse', 'wh_partner', 'carrier_code'");
|
||||
}
|
||||
|
||||
private static String read(String file) throws IOException {
|
||||
|
||||
@@ -40,7 +40,7 @@ class PartnerImportServiceTest {
|
||||
void shouldImportValidMultiTypeRow() throws Exception {
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"CAR-100", "测试船公司", "CARRIER,SHIPPER", "张三", "13800138000", "test@example.com",
|
||||
"CN", "广东省", "深圳市", "南山区", "测试路 1 号", "518000", "ACTIVE", "测试备注"}
|
||||
"MATSON", "CN", "广东省", "深圳市", "南山区", "测试路 1 号", "518000", "ACTIVE", "测试备注"}
|
||||
});
|
||||
|
||||
PartnerImportResult result = service.importFile(0L, "partners.xlsx", file);
|
||||
@@ -52,6 +52,7 @@ class PartnerImportServiceTest {
|
||||
assertThat(captor.getValue().getTenantId()).isZero();
|
||||
assertThat(captor.getValue().getDataScopeId()).isZero();
|
||||
assertThat(captor.getValue().getTypes()).containsExactly("CARRIER", "SHIPPER");
|
||||
assertThat(captor.getValue().getCarrierCode()).isEqualTo("MATSON");
|
||||
assertThat(captor.getValue().getRemark()).isEqualTo("测试备注");
|
||||
}
|
||||
|
||||
@@ -59,8 +60,8 @@ class PartnerImportServiceTest {
|
||||
@DisplayName("文件内任一错误时整批零写入并返回行号")
|
||||
void shouldRejectWholeBatchWhenAnyRowIsInvalid() throws Exception {
|
||||
byte[] file = withRows(service.template(), new String[][] {
|
||||
{"CAR-100", "测试船公司一", "CARRIER", "张三", "13800138000", "", "CN", "", "深圳市", "", "测试路 1 号", "", "ACTIVE", ""},
|
||||
{"CAR-100", "测试单位二", "UNKNOWN", "李四", "13900139000", "bad-email", "C", "", "", "", "", "", "ENABLED", ""}
|
||||
{"CAR-100", "测试船公司一", "CARRIER", "张三", "13800138000", "", "MATSON", "CN", "", "深圳市", "", "测试路 1 号", "", "ACTIVE", ""},
|
||||
{"CAR-100", "测试单位二", "UNKNOWN", "李四", "13900139000", "bad-email", "", "C", "", "", "", "", "", "ENABLED", ""}
|
||||
});
|
||||
|
||||
PartnerImportResult result = service.importFile(0L, "partners.xlsx", file);
|
||||
|
||||
+2
-1
@@ -79,6 +79,7 @@ class PlatformMasterDataManagementServiceTest {
|
||||
assertThat(entity.getValue().getPartnerCode()).isEqualTo("CUS-001");
|
||||
assertThat(entity.getValue().getPartnerName()).isEqualTo("示例客户");
|
||||
assertThat(entity.getValue().getTypes()).containsExactly("CARRIER", "SHIPPER");
|
||||
assertThat(entity.getValue().getCarrierCode()).isEqualTo("OTHER");
|
||||
assertThat(entity.getValue().getRemark()).isEqualTo("测试备注");
|
||||
}
|
||||
|
||||
@@ -210,7 +211,7 @@ class PlatformMasterDataManagementServiceTest {
|
||||
}
|
||||
|
||||
private static PartnerSaveRequest partnerRequest() {
|
||||
return new PartnerSaveRequest("CUS-001", "示例客户", "CARRIER", "张三", "13800138000",
|
||||
return new PartnerSaveRequest("CUS-001", "示例客户", "CARRIER", "OTHER", "张三", "13800138000",
|
||||
"test@example.com", "CN", "广东省", "深圳市", "南山区", "示例地址", "518000", "ACTIVE",
|
||||
java.util.List.of("CARRIER", "SHIPPER"), "测试备注");
|
||||
}
|
||||
|
||||
+52
@@ -116,4 +116,56 @@ 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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void exposesOnlyActiveSharedOverseasWarehousesWithReceivingCapability() {
|
||||
WarehouseEntity receiving = new WarehouseEntity();
|
||||
receiving.setId(3L); receiving.setWarehouseCode("US-LAX-01");
|
||||
receiving.setWarehouseName("洛杉矶海外仓"); receiving.setCountryCode("US");
|
||||
receiving.setWarehouseType("OVERSEAS"); receiving.setStatus("ACTIVE");
|
||||
ServiceCapabilityEntity capability = new ServiceCapabilityEntity();
|
||||
capability.setCapabilityCode("RECEIVING"); capability.setEnabled(true);
|
||||
when(repository.findActiveByType(0L, "OVERSEAS")).thenReturn(java.util.List.of(receiving));
|
||||
when(repository.findOwned(0L, 3L)).thenReturn(receiving);
|
||||
when(repository.findCapabilities(0L, 3L)).thenReturn(java.util.List.of(capability));
|
||||
|
||||
assertThat(service.activeSharedOverseasWarehouses())
|
||||
.extracting("warehouseId", "warehouseCode", "status")
|
||||
.containsExactly(tuple(3L, "US-LAX-01", "ACTIVE"));
|
||||
assertThat(service.activeSharedOverseasWarehouse(3L).warehouseName())
|
||||
.isEqualTo("洛杉矶海外仓");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -19,6 +19,38 @@ SELECT COUNT(*) AS fixed_platform_address_count
|
||||
AND address.deleted = 0
|
||||
AND warehouse.deleted = 0;
|
||||
|
||||
SELECT COUNT(*) AS fixed_platform_receiving_capability_count
|
||||
FROM wh_service_capability capability
|
||||
WHERE capability.tenant_id = 0
|
||||
AND capability.warehouse_id IN (
|
||||
193000000000200001, 193000000000200002,
|
||||
193000000000200003, 193000000000200004
|
||||
)
|
||||
AND capability.capability_code = 'RECEIVING'
|
||||
AND capability.enabled = 1
|
||||
AND capability.deleted = 0;
|
||||
|
||||
SELECT COUNT(*) AS overseas_receiving_capability_violations
|
||||
FROM wh_service_capability capability
|
||||
WHERE capability.tenant_id = 0
|
||||
AND (
|
||||
(capability.id = 193000000000400003
|
||||
AND (capability.warehouse_id <> 193000000000200003
|
||||
OR capability.capability_code <> 'RECEIVING'
|
||||
OR capability.capability_name <> '海外收货'
|
||||
OR capability.description <> '洛杉矶仓海外接柜入仓收货能力'
|
||||
OR capability.enabled <> 1
|
||||
OR capability.deleted <> 0))
|
||||
OR
|
||||
(capability.id = 193000000000400004
|
||||
AND (capability.warehouse_id <> 193000000000200004
|
||||
OR capability.capability_code <> 'RECEIVING'
|
||||
OR capability.capability_name <> '海外收货'
|
||||
OR capability.description <> '纽约仓海外接柜入仓收货能力'
|
||||
OR capability.enabled <> 1
|
||||
OR capability.deleted <> 0))
|
||||
);
|
||||
|
||||
SELECT COUNT(*) AS fba_public_warehouse_count
|
||||
FROM wh_warehouse
|
||||
WHERE tenant_id = 0
|
||||
|
||||
@@ -111,6 +111,12 @@ 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, /四个固定平台作业仓必须启用收货能力/)
|
||||
assert.match(sql, /INSERT INTO wh_service_capability[\s\S]+?'RECEIVING'/i)
|
||||
assert.match(sql, /193000000000400003,0,193000000000200003,'RECEIVING','海外收货',1,'16:00:00',[\s\S]*?'洛杉矶仓海外接柜入仓收货能力'/)
|
||||
assert.match(sql, /193000000000400004,0,193000000000200004,'RECEIVING','海外收货',1,'16:00:00',[\s\S]*?'纽约仓海外接柜入仓收货能力'/)
|
||||
assert.match(sql, /warehouse_id IN \(193000000000200001,193000000000200002,193000000000200003,193000000000200004\)[\s\S]*?<> 4 THEN[\s\S]*?固定平台作业仓收货能力写入校验失败/)
|
||||
const fbaBlock = sql.slice(sql.indexOf('CREATE TEMPORARY TABLE tmp_wh_fba_public_master_data'))
|
||||
assert.doesNotMatch(fbaBlock, /INSERT INTO wh_service_capability/i)
|
||||
assert.match(sql, /FBA公共主数据地址种子ID与既有数据冲突/)
|
||||
assert.match(sql, /FBA公共地址不得创建平台作业能力/)
|
||||
|
||||
Reference in New Issue
Block a user