Merge branch 'test' into test-02huogui
# Conflicts: # deploy/sql/头程数据库统一增量-MySQL8.sql
This commit is contained in:
+4
-5
@@ -9,27 +9,26 @@ import com.baiyue.warehouse.service.PartnerQueryService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/merchant/v1/partners")
|
||||
public class MerchantPartnerController {
|
||||
private static final long PUBLIC_TENANT_ID = 0L;
|
||||
|
||||
@Resource
|
||||
private PartnerQueryService service;
|
||||
@Value("${baiyue.warehouse.public-tenant-id:0}")
|
||||
private long publicTenantId;
|
||||
|
||||
@GetMapping("/candidates")
|
||||
public ApiResponse<List<PartnerEntity>> candidates(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required=false) String keyword,@RequestParam(required=false) String type) {
|
||||
requireMerchant(user);
|
||||
return ApiResponse.success(service.candidates(publicTenantId,keyword,type));
|
||||
return ApiResponse.success(service.candidates(PUBLIC_TENANT_ID,keyword,type));
|
||||
}
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<PartnerEntity> detail(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable long id) {
|
||||
requireMerchant(user);
|
||||
return ApiResponse.success(service.active(publicTenantId,id));
|
||||
return ApiResponse.success(service.active(PUBLIC_TENANT_ID,id));
|
||||
}
|
||||
|
||||
private static void requireMerchant(AuthenticatedUser user) {
|
||||
|
||||
+7
-9
@@ -15,7 +15,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@@ -23,7 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 商户端公共仓库查询接口。认证用户共享平台默认租户维护的仓库配置。
|
||||
* 商户端公共仓库查询接口。认证用户共享平台公共作用域维护的仓库配置。
|
||||
*/
|
||||
@Tag(
|
||||
name = "Merchant warehouses / 商户仓库",
|
||||
@@ -32,15 +31,14 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@RestController
|
||||
@RequestMapping("/api/merchant/v1/warehouses")
|
||||
public class MerchantWarehouseController {
|
||||
private static final long PUBLIC_TENANT_ID = 0L;
|
||||
|
||||
@Resource
|
||||
private WarehouseApplicationService service;
|
||||
|
||||
@Value("${baiyue.warehouse.public-tenant-id:0}")
|
||||
private long publicTenantId;
|
||||
|
||||
@Operation(
|
||||
summary = "List available warehouses / 查询可用仓库",
|
||||
description = "Returns active public warehouses maintained under the configured public tenant. / 返回平台默认租户维护的可用公共仓库。"
|
||||
description = "Returns active public warehouses maintained under the platform public scope. / 返回平台公共作用域维护的可用公共仓库。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@@ -51,12 +49,12 @@ public class MerchantWarehouseController {
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid WarehouseRequests.QueryRequest query) {
|
||||
requireMerchant(user);
|
||||
return ApiResponse.success(service.merchantPage(publicTenantId, query));
|
||||
return ApiResponse.success(service.merchantPage(PUBLIC_TENANT_ID, query));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Get warehouse details / 查询仓库详情",
|
||||
description = "Returns a public warehouse maintained under the configured public tenant. / 返回平台默认租户维护的公共仓库详情。"
|
||||
description = "Returns a public warehouse maintained under the platform public scope. / 返回平台公共作用域维护的公共仓库详情。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@@ -68,7 +66,7 @@ public class MerchantWarehouseController {
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id) {
|
||||
requireMerchant(user);
|
||||
return ApiResponse.success(service.activeDetail(publicTenantId, id));
|
||||
return ApiResponse.success(service.activeDetail(PUBLIC_TENANT_ID, id));
|
||||
}
|
||||
|
||||
private static void requireMerchant(AuthenticatedUser user) {
|
||||
|
||||
+7
-7
@@ -36,11 +36,11 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 平台库位管理接口;分页支持跨企业查询,写操作仍以目标 tenantId 隔离。 */
|
||||
/** 平台库位管理接口;统一维护平台公共作用域下的库位资源。 */
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/locations")
|
||||
@Tag(name = "平台库位管理", description = "按企业租户和仓库数据范围管理库位基础资料")
|
||||
@Tag(name = "平台库位管理", description = "按平台公共作用域和仓库管理库位基础资料")
|
||||
public class PlatformLocationController {
|
||||
@Resource
|
||||
private LocationApplicationService service;
|
||||
@@ -48,14 +48,14 @@ public class PlatformLocationController {
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('platform:location:list')")
|
||||
@Operation(summary = "分页查询库位",
|
||||
description = "默认分页查询全部企业库位;可按目标企业租户、仓库、库区、编码、类型和状态筛选")
|
||||
description = "默认分页查询全部平台公共库位;可按仓库、库区、编码、类型和状态筛选")
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "需要 platform:location:list 权限,且仓库须属于目标企业租户")
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "需要 platform:location:list 权限,且仓库须属于平台公共作用域")
|
||||
})
|
||||
public ApiResponse<PageResult<LocationEntity>> page(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "可选目标企业租户ID;不传时查询全部企业")
|
||||
@Parameter(description = "兼容作用域;仅允许 0,不传时查询平台公共资源")
|
||||
@RequestParam(required = false) @Min(0) Long tenantId,
|
||||
@Valid @ParameterObject PageQuery query) {
|
||||
requirePlatform(user);
|
||||
@@ -65,7 +65,7 @@ public class PlatformLocationController {
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@PreAuthorize("hasAuthority('platform:location:list')")
|
||||
@Operation(summary = "查询库位详情", description = "详情查询同时校验企业租户和仓库归属")
|
||||
@Operation(summary = "查询库位详情", description = "详情查询同时校验平台公共作用域和仓库归属")
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "查询成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "需要 platform:location:list 权限或仓库超出数据范围"),
|
||||
@@ -98,7 +98,7 @@ public class PlatformLocationController {
|
||||
|
||||
@PostMapping
|
||||
@PreAuthorize("hasAuthority('platform:location:create')")
|
||||
@Operation(summary = "新增库位", description = "在目标企业租户的指定仓库内新增唯一编码库位")
|
||||
@Operation(summary = "新增库位", description = "在平台公共仓库内新增唯一编码库位")
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "新增成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "需要 platform:location:create 权限或仓库超出数据范围"),
|
||||
|
||||
+3
-3
@@ -32,17 +32,17 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/pallets")
|
||||
@Tag(name = "平台托盘管理", description = "按租户和仓库维护托盘主数据")
|
||||
@Tag(name = "平台托盘管理", description = "按平台公共作用域和仓库维护托盘主数据")
|
||||
public class PlatformPalletController {
|
||||
@Resource
|
||||
private PalletApplicationService service;
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('platform:pallet:list')")
|
||||
@Operation(summary = "分页查询托盘", description = "默认查询全部企业;可按企业、仓库、托盘编码和类型筛选")
|
||||
@Operation(summary = "分页查询托盘", description = "默认查询全部平台公共托盘;可按仓库、托盘编码和类型筛选")
|
||||
@ApiResponses(@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "需要 platform:pallet:list 权限"))
|
||||
public ApiResponse<PageResult<PalletRequests.View>> page(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "目标企业租户ID;不传时查询全部企业")
|
||||
@Parameter(description = "兼容作用域;仅允许 0,不传时查询平台公共资源")
|
||||
@RequestParam(required = false) @Min(0) Long tenantId,
|
||||
@Valid @ParameterObject PalletRequests.PageQuery query) {
|
||||
requirePlatform(user);
|
||||
|
||||
+83
-16
@@ -6,8 +6,11 @@ import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.warehouse.dto.WarehouseDetailResponse;
|
||||
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.WarehouseEntity;
|
||||
import com.baiyue.warehouse.dto.WarehouseSummaryResponse;
|
||||
import com.baiyue.warehouse.service.WarehouseImportService;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -16,6 +19,11 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.springdoc.core.annotations.ParameterObject;
|
||||
import org.springframework.http.ContentDisposition;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -27,25 +35,32 @@ import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 平台端仓库管理接口,提供跨租户管理场景下的仓库创建、修改、查询和状态维护能力。
|
||||
* 平台端仓库管理接口,统一维护平台公共作用域下的仓库主数据。
|
||||
*/
|
||||
@Validated
|
||||
@Tag(
|
||||
name = "Platform warehouse administration / 平台仓库管理",
|
||||
description = "Platform APIs for managing warehouses across tenants. / 平台端按租户管理仓库的接口。"
|
||||
description = "Platform APIs for managing public warehouses. / 平台端管理公共仓库的接口。"
|
||||
)
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/warehouse-admin")
|
||||
public class PlatformWarehouseController {
|
||||
@Resource
|
||||
private WarehouseApplicationService service;
|
||||
@Resource
|
||||
private WarehouseImportService importService;
|
||||
|
||||
@Operation(
|
||||
summary = "Create warehouse / 新建仓库",
|
||||
description = "Creates a warehouse, address and service capabilities for the target tenant. / 为目标租户创建仓库、地址和服务能力。"
|
||||
description = "Creates a public warehouse, address and service capabilities. / 创建平台公共仓库、地址和服务能力。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Created successfully / 创建成功"),
|
||||
@@ -58,7 +73,7 @@ public class PlatformWarehouseController {
|
||||
public ApiResponse<Long> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody WarehouseRequests.SaveRequest request) {
|
||||
requirePlatform(user);
|
||||
requireTenant(request.tenantId());
|
||||
if (request.tenantId() != null) requireTenant(request.tenantId());
|
||||
return ApiResponse.success(service.create(request));
|
||||
}
|
||||
|
||||
@@ -69,7 +84,7 @@ public class PlatformWarehouseController {
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Updated successfully / 更新成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid request / 请求参数无效"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform warehouse edit permission required (platform:warehouse:edit), or tenant validation failed / 需要平台仓库编辑权限,或租户校验失败"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform warehouse edit permission required (platform:warehouse:edit), or scope validation failed / 需要平台仓库编辑权限,或作用域校验失败"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "Warehouse not found / 仓库不存在"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "Warehouse code already exists / 仓库编码已存在")
|
||||
})
|
||||
@@ -77,7 +92,7 @@ public class PlatformWarehouseController {
|
||||
@PreAuthorize("hasAuthority('platform:warehouse:edit')")
|
||||
public ApiResponse<Void> update(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
|
||||
@Parameter(description = "Compatibility scope; only 0 is allowed / 兼容作用域,仅允许 0", example = "0", required = true)
|
||||
@RequestParam @NotNull Long tenantId,
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id,
|
||||
@@ -90,7 +105,7 @@ public class PlatformWarehouseController {
|
||||
|
||||
@Operation(
|
||||
summary = "List platform warehouses / 查询平台仓库",
|
||||
description = "Returns all platform warehouses by default; customer ID is an optional compatibility filter. / 默认分页查询平台全部仓库,客户 ID 仅作为兼容筛选条件。"
|
||||
description = "Returns public platform warehouses; tenantId is kept only for compatibility and must be 0. / 分页查询平台公共仓库;tenantId 仅保留兼容且必须为 0。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@@ -99,19 +114,64 @@ public class PlatformWarehouseController {
|
||||
})
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAuthority('platform:warehouse:list')")
|
||||
public ApiResponse<PageResult<WarehouseEntity>> page(
|
||||
public ApiResponse<PageResult<WarehouseSummaryResponse>> page(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Optional customer ID / 可选客户 ID", example = "10001")
|
||||
@Parameter(description = "Compatibility scope; only 0 is allowed / 兼容作用域,仅允许 0", example = "0")
|
||||
@RequestParam(required = false) Long tenantId,
|
||||
@Valid WarehouseRequests.QueryRequest request) {
|
||||
@Valid @ParameterObject WarehouseRequests.QueryRequest request) {
|
||||
requirePlatform(user);
|
||||
if (tenantId != null) requireTenant(tenantId);
|
||||
return ApiResponse.success(service.page(0L, request));
|
||||
return ApiResponse.success(service.pageSummaries(0L, request));
|
||||
}
|
||||
|
||||
@GetMapping("/import-template")
|
||||
@PreAuthorize("hasAuthority('platform:warehouse:create')")
|
||||
@Operation(summary = "Download warehouse import template / 下载仓库导入模板",
|
||||
description = "Downloads the current XLSX template for warehouse and FBA warehouse import. / 下载仓库与 FBA 仓库导入的当前 XLSX 模板。")
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Template downloaded / 模板下载成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform warehouse create permission required (platform:warehouse:create) / 需要平台仓库新增权限")
|
||||
})
|
||||
public ResponseEntity<byte[]> importTemplate(@AuthenticationPrincipal AuthenticatedUser user) {
|
||||
requirePlatform(user);
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentDisposition(ContentDisposition.attachment()
|
||||
.filename("warehouse-import-template-v1.xlsx", StandardCharsets.UTF_8).build());
|
||||
headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
|
||||
return ResponseEntity.ok().headers(headers).body(importService.template());
|
||||
}
|
||||
|
||||
@PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
@PreAuthorize("hasAuthority('platform:warehouse:create')")
|
||||
@Operation(summary = "Import warehouses / 批量导入仓库",
|
||||
description = "Validates the whole XLSX file and atomically creates or optionally updates warehouses. Default mode is CREATE_ONLY. / 整表校验 XLSX 后原子新增或可选更新仓库,默认 CREATE_ONLY。")
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Import precheck completed / 导入预检完成"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid file or query / 文件或参数无效"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform warehouse create permission required; UPSERT also requires edit permission (platform:warehouse:create, platform:warehouse:edit) / 需要平台仓库新增权限;UPSERT 还需要编辑权限")
|
||||
})
|
||||
public ApiResponse<WarehouseImportResult> importWarehouses(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Compatibility scope; only 0 is allowed / 兼容作用域,仅允许 0")
|
||||
@RequestParam(required = false) Long tenantId,
|
||||
@Parameter(description = "Import mode, defaults to CREATE_ONLY / 导入模式,默认 CREATE_ONLY")
|
||||
@RequestParam(required = false) ImportMode mode,
|
||||
@Parameter(description = "Whether to only precheck without writing data / 是否只预检不写入", example = "true")
|
||||
@RequestParam(defaultValue = "false") boolean dryRun,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
requirePlatform(user);
|
||||
requireImportPermission(user, mode);
|
||||
if (tenantId != null) requireTenant(tenantId);
|
||||
try {
|
||||
return ApiResponse.success(importService.importFile(0L, file.getOriginalFilename(), file.getBytes(), mode, dryRun));
|
||||
} catch (IOException exception) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "导入文件读取失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Get tenant warehouse details / 查询租户仓库详情",
|
||||
description = "Returns the warehouse, addresses and service capabilities in the target tenant. / 返回目标租户下的仓库、地址及服务能力。"
|
||||
summary = "Get public warehouse details / 查询公共仓库详情",
|
||||
description = "Returns the public warehouse, addresses and service capabilities. / 返回平台公共仓库、地址及服务能力。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@@ -122,7 +182,7 @@ public class PlatformWarehouseController {
|
||||
@PreAuthorize("hasAuthority('platform:warehouse:list')")
|
||||
public ApiResponse<WarehouseDetailResponse> detail(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
|
||||
@Parameter(description = "Compatibility scope; only 0 is allowed / 兼容作用域,仅允许 0", example = "0", required = true)
|
||||
@RequestParam @NotNull Long tenantId,
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id) {
|
||||
@@ -145,7 +205,7 @@ public class PlatformWarehouseController {
|
||||
@PreAuthorize("hasAuthority('platform:warehouse:status')")
|
||||
public ApiResponse<Void> status(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
|
||||
@Parameter(description = "Compatibility scope; only 0 is allowed / 兼容作用域,仅允许 0", example = "0", required = true)
|
||||
@RequestParam @NotNull Long tenantId,
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id,
|
||||
@@ -169,4 +229,11 @@ public class PlatformWarehouseController {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "Platform resource scope must be 0 / 平台公共资源作用域必须为0");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireImportPermission(AuthenticatedUser user, ImportMode mode) {
|
||||
if (mode == ImportMode.UPSERT && !user.permissions().contains("platform:warehouse:edit")) {
|
||||
throw new BizException(ResultCode.FORBIDDEN,
|
||||
"UPSERT import requires platform:warehouse:edit / 新增并更新导入需要平台仓库编辑权限");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,15 +22,18 @@ public final class PalletRequests {
|
||||
@NotNull @Min(1)
|
||||
@Schema(description = "所属仓库ID", example = "190000000000000002", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
Long warehouseId,
|
||||
@NotBlank @Size(max = 50)
|
||||
@Schema(description = "仓库内唯一托盘编码", example = "TP-001", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
String palletCode,
|
||||
@NotBlank @Size(max = 32)
|
||||
@Schema(description = "托盘类型", example = "木托盘", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
String palletType,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) @Digits(integer = 12, fraction = 6)
|
||||
@Schema(description = "托盘容积,单位m³", example = "1.200000", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
BigDecimal capacityCbm,
|
||||
@NotNull @DecimalMin(value = "0", inclusive = false) @Digits(integer = 12, fraction = 3)
|
||||
@Schema(description = "托盘重量,单位kg", example = "8.000", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
BigDecimal weightKg,
|
||||
@NotBlank @Size(max = 32)
|
||||
@Schema(description = "托盘颜色", example = "黄色", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
String color,
|
||||
@Size(max = 500)
|
||||
@Schema(description = "备注", example = "标准川字托盘")
|
||||
String remark
|
||||
@@ -64,6 +67,8 @@ public final class PalletRequests {
|
||||
String palletCode,
|
||||
String palletType,
|
||||
BigDecimal capacityCbm,
|
||||
BigDecimal weightKg,
|
||||
String color,
|
||||
String remark,
|
||||
LocalDateTime createdAt,
|
||||
LocalDateTime updatedAt
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.baiyue.warehouse.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 仓库与 FBA 仓库 XLSX 导入契约。 */
|
||||
public final class WarehouseImportDtos {
|
||||
private WarehouseImportDtos() {
|
||||
}
|
||||
|
||||
@Schema(name = "WarehouseImportMode", description = "Warehouse import mode / 仓库导入模式")
|
||||
public enum ImportMode {
|
||||
@Schema(description = "Only create new rows; existing rows are reported but not modified / 仅新增,现有数据只预检分类不修改")
|
||||
CREATE_ONLY,
|
||||
@Schema(description = "Create new rows and update existing rows without overriding contact or remark / 新增并更新,更新不覆盖联系人和备注")
|
||||
UPSERT
|
||||
}
|
||||
|
||||
@Schema(name = "WarehouseImportError", description = "Warehouse import row error / 仓库导入行错误")
|
||||
public record WarehouseImportError(
|
||||
@Schema(description = "Excel row number / Excel 行号") int row,
|
||||
@Schema(description = "Field name / 字段") String field,
|
||||
@Schema(description = "Error message / 错误原因") String message
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "WarehouseImportPreviewRow", description = "Warehouse import preview row / 仓库导入预检行")
|
||||
public record WarehouseImportPreviewRow(
|
||||
@Schema(description = "Excel row number / Excel 行号") int row,
|
||||
@Schema(description = "Warehouse code / 仓库编码") String warehouseCode,
|
||||
@Schema(description = "Resolved action / 预检动作", allowableValues = {"CREATE", "UPDATE", "UNCHANGED", "ERROR"}) String action,
|
||||
@Schema(description = "Reason / 原因") String reason
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "WarehouseImportResult", description = "Warehouse import result / 仓库导入结果")
|
||||
public record WarehouseImportResult(
|
||||
@Schema(description = "Original file name / 原始文件名") String fileName,
|
||||
@Schema(description = "Import mode / 导入模式") ImportMode mode,
|
||||
@Schema(description = "Data row count / 数据行总数") int totalRows,
|
||||
@Schema(description = "Created row count / 新增行数") int createdRows,
|
||||
@Schema(description = "Updated row count / 更新行数") int updatedRows,
|
||||
@Schema(description = "Unchanged row count / 无变化行数") int unchangedRows,
|
||||
@Schema(description = "Whether the import passed validation and committed writable rows / 是否校验通过并写入可写行") boolean success,
|
||||
@Schema(description = "Whether writable rows have been committed / 是否已经写入数据") boolean committed,
|
||||
@Schema(description = "Per-row preview classifications / 逐行预检分类") List<WarehouseImportPreviewRow> previewRows,
|
||||
@Schema(description = "Per-row errors / 逐行错误") List<WarehouseImportError> errors
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -5,10 +5,10 @@ import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalTime;
|
||||
@@ -23,26 +23,26 @@ public final class WarehouseRequests {
|
||||
|
||||
@Schema(name = "WarehouseSaveRequest", description = "Warehouse create or update request / 仓库新增或更新请求")
|
||||
public record SaveRequest(
|
||||
@Schema(description = "Target tenant ID / 目标租户 ID", example = "10001", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull Long tenantId,
|
||||
@Schema(description = "Compatibility tenant ID; platform APIs force it to 0 / 兼容租户字段,平台接口固定为 0", example = "0")
|
||||
Long tenantId,
|
||||
@Schema(description = "Tenant-unique warehouse code / 租户内唯一仓库编码", example = "US-LAX-01", maxLength = 50, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@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 / 仓库类型", example = "OVERSEAS", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS"}, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Pattern(regexp = "DOMESTIC|FBA|OVERSEAS") String warehouseType,
|
||||
@Schema(description = "ISO 3166-1 alpha-2 country code / ISO 两位国家代码", example = "US", pattern = "[A-Z]{2}", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Pattern(regexp = "[A-Z]{2}") String countryCode,
|
||||
@Schema(description = "IANA timezone / IANA 时区", example = "America/Los_Angeles", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank String timezone,
|
||||
@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 = "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")
|
||||
String timezone,
|
||||
@Schema(description = "Contact name / 联系人姓名", example = "Alex Chen", maxLength = 64)
|
||||
@Size(max = 64) String contactName,
|
||||
@Schema(description = "Contact phone / 联系电话", example = "+1-213-555-0100", maxLength = 32)
|
||||
@Size(max = 32) String contactPhone,
|
||||
@Schema(description = "Contact email / 联系邮箱", example = "warehouse@example.com")
|
||||
@Email String contactEmail,
|
||||
@Schema(description = "Warehouse status / 仓库状态", example = "ACTIVE", allowableValues = {"ACTIVE", "INACTIVE"}, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank String status,
|
||||
@Schema(description = "Warehouse status; defaults to ACTIVE / 仓库状态,默认 ACTIVE", example = "ACTIVE", allowableValues = {"ACTIVE", "INACTIVE"}, defaultValue = "ACTIVE")
|
||||
@Pattern(regexp = "ACTIVE|INACTIVE") String status,
|
||||
@Schema(description = "Remark / 备注", example = "Supports appointment receiving / 支持预约入库", maxLength = 500)
|
||||
@Size(max = 500) String remark,
|
||||
@Schema(description = "Primary warehouse address / 仓库主地址", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@@ -54,10 +54,10 @@ public final class WarehouseRequests {
|
||||
|
||||
@Schema(name = "WarehouseAddressRequest", description = "Warehouse address / 仓库地址")
|
||||
public record AddressRequest(
|
||||
@Schema(description = "Address type / 地址类型", example = "WAREHOUSE", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank String addressType,
|
||||
@Schema(description = "ISO two-letter country code / ISO 两位国家代码", example = "US", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Pattern(regexp = "[A-Z]{2}") String countryCode,
|
||||
@Schema(description = "Address type; defaults to WAREHOUSE / 地址类型,默认 WAREHOUSE", example = "WAREHOUSE")
|
||||
String addressType,
|
||||
@Schema(description = "ISO two-letter country code; defaults from warehouse country / ISO 两位国家代码,默认跟随仓库国家", example = "US")
|
||||
@Pattern(regexp = "[A-Z]{2}") String countryCode,
|
||||
@Schema(description = "State or province / 州或省", example = "California", maxLength = 100)
|
||||
@Size(max = 100) String stateProvince,
|
||||
@Schema(description = "City / 城市", example = "Los Angeles", maxLength = 100, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@@ -98,6 +98,8 @@ public final class WarehouseRequests {
|
||||
@Min(1) long page,
|
||||
@Schema(description = "Page size / 每页条数", example = "20", defaultValue = "20", minimum = "1", maximum = "200")
|
||||
@Min(1) @Max(200) long size,
|
||||
@Schema(description = "Keyword across code, name, primary city, state and address / 关键字,覆盖编码、名称、主地址城市、州和地址", example = "Los Angeles")
|
||||
@Size(max = 128) String keyword,
|
||||
@Schema(description = "Country filter / 国家筛选", example = "US")
|
||||
String countryCode,
|
||||
@Schema(description = "Enabled capability code filter / 已启用能力编码筛选", example = "STORAGE")
|
||||
@@ -105,7 +107,7 @@ public final class WarehouseRequests {
|
||||
@Schema(description = "Warehouse type filter / 仓库类型筛选", example = "DOMESTIC", allowableValues = {"DOMESTIC", "FBA", "OVERSEAS"})
|
||||
@Pattern(regexp = "DOMESTIC|FBA|OVERSEAS") String warehouseType,
|
||||
@Schema(description = "Status filter / 状态筛选", example = "ACTIVE", allowableValues = {"ACTIVE", "INACTIVE"})
|
||||
String status
|
||||
@Pattern(regexp = "ACTIVE|INACTIVE") String status
|
||||
) {
|
||||
public QueryRequest {
|
||||
if (page == 0) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.baiyue.warehouse.dto;
|
||||
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** 仓库列表响应,带主地址摘要,避免列表页逐条请求详情。 */
|
||||
@Schema(name = "WarehouseSummaryResponse", description = "Warehouse list item with primary address summary / 仓库列表项及主地址摘要")
|
||||
public record WarehouseSummaryResponse(
|
||||
@Schema(description = "Warehouse ID / 仓库 ID") Long id,
|
||||
@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 = "ISO two-letter country code / 国家二字码") String countryCode,
|
||||
@Schema(description = "UTC offset / UTC 偏移量") String timezone,
|
||||
@Schema(description = "Contact name / 联系人") String contactName,
|
||||
@Schema(description = "Contact phone / 联系电话") String contactPhone,
|
||||
@Schema(description = "Contact email / 联系邮箱") String contactEmail,
|
||||
@Schema(description = "Warehouse status / 仓库状态", allowableValues = {"ACTIVE", "INACTIVE"}) String status,
|
||||
@Schema(description = "Remark / 备注") String remark,
|
||||
@Schema(description = "Creation time / 创建时间") LocalDateTime createdAt,
|
||||
@Schema(description = "Last update time / 更新时间") LocalDateTime updatedAt,
|
||||
@Schema(description = "Primary address summary / 主地址摘要") AddressSummary primaryAddress
|
||||
) {
|
||||
public static WarehouseSummaryResponse of(WarehouseEntity warehouse, WarehouseAddressEntity address) {
|
||||
return new WarehouseSummaryResponse(
|
||||
warehouse.getId(), warehouse.getTenantId(), warehouse.getWarehouseCode(), warehouse.getWarehouseName(),
|
||||
warehouse.getWarehouseType(), warehouse.getCountryCode(), warehouse.getTimezone(),
|
||||
warehouse.getContactName(), warehouse.getContactPhone(), warehouse.getContactEmail(),
|
||||
warehouse.getStatus(), warehouse.getRemark(), warehouse.getCreatedAt(), warehouse.getUpdatedAt(),
|
||||
address == null ? null : AddressSummary.of(address)
|
||||
);
|
||||
}
|
||||
|
||||
@Schema(name = "WarehouseAddressSummary", description = "Primary warehouse address summary / 仓库主地址摘要")
|
||||
public record AddressSummary(
|
||||
@Schema(description = "Address ID / 地址 ID") Long id,
|
||||
@Schema(description = "Address type / 地址类型") String addressType,
|
||||
@Schema(description = "Country code / 国家二字码") String countryCode,
|
||||
@Schema(description = "State or province / 州或省") String stateProvince,
|
||||
@Schema(description = "City / 城市") String city,
|
||||
@Schema(description = "District / 区县") String district,
|
||||
@Schema(description = "Postal code / 邮政编码") String postalCode,
|
||||
@Schema(description = "Address line 1 / 地址第一行") String addressLine1,
|
||||
@Schema(description = "Address line 2 / 地址第二行") String addressLine2,
|
||||
@Schema(description = "Display address / 展示地址") String displayAddress
|
||||
) {
|
||||
static AddressSummary of(WarehouseAddressEntity address) {
|
||||
return new AddressSummary(address.getId(), address.getAddressType(), address.getCountryCode(),
|
||||
address.getStateProvince(), address.getCity(), address.getDistrict(), address.getPostalCode(),
|
||||
address.getAddressLine1(), address.getAddressLine2(), displayAddress(address));
|
||||
}
|
||||
|
||||
private static String displayAddress(WarehouseAddressEntity address) {
|
||||
return java.util.stream.Stream.of(address.getStateProvince(), address.getCity(), address.getDistrict(),
|
||||
address.getAddressLine1(), address.getAddressLine2())
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.reduce((left, right) -> left + " " + right)
|
||||
.orElse("");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import lombok.Setter;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 托盘主数据实体。状态同时承载可用、预占、占用和停用四种资源状态。
|
||||
* 托盘主数据实体。状态仅供内部预占、占用流程使用,不属于平台维护字段。
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -37,6 +37,12 @@ public class PalletEntity extends BaseEntity {
|
||||
@Schema(description = "托盘容积,单位m³", example = "1.200000")
|
||||
private BigDecimal capacityCbm;
|
||||
|
||||
@Schema(description = "托盘重量,单位kg", example = "8.000")
|
||||
private BigDecimal weightKg;
|
||||
|
||||
@Schema(description = "托盘颜色", example = "黄色")
|
||||
private String color;
|
||||
|
||||
@Schema(description = "资源状态", allowableValues = {"AVAILABLE", "RESERVED", "OCCUPIED", "DISABLED"})
|
||||
private String status;
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ public class WarehouseEntity extends BaseEntity {
|
||||
private String warehouseType;
|
||||
@Schema(description = "ISO two-letter country code / ISO 两位国家代码", example = "US")
|
||||
private String countryCode;
|
||||
@Schema(description = "IANA timezone / IANA 时区", example = "America/Los_Angeles")
|
||||
@Schema(description = "UTC offset / UTC 偏移量", example = "UTC-8")
|
||||
private String timezone;
|
||||
@Schema(description = "Contact name / 联系人", example = "Alex Chen")
|
||||
private String contactName;
|
||||
|
||||
@@ -2,7 +2,14 @@ package com.baiyue.warehouse.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.warehouse.entity.PalletEntity;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** 托盘主数据 Mapper。 */
|
||||
public interface PalletMapper extends BaseMapper<PalletEntity> {
|
||||
/** 编码序列必须包含逻辑删除记录,避免历史编码被复用。 */
|
||||
@Select("SELECT pallet_code FROM wh_pallet WHERE tenant_id = #{tenantId} AND pallet_code LIKE 'TP-%'")
|
||||
List<String> selectAllCodes(@Param("tenantId") long tenantId);
|
||||
}
|
||||
|
||||
@@ -13,10 +13,14 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/** 托盘持久化仓储;平台分页支持跨租户筛选,资源操作始终包含租户和仓库边界。 */
|
||||
@Repository
|
||||
public class PalletRepository {
|
||||
private static final Pattern PALLET_CODE = Pattern.compile("TP-(\\d+)");
|
||||
|
||||
@Resource
|
||||
private PalletMapper mapper;
|
||||
|
||||
@@ -59,6 +63,21 @@ public class PalletRepository {
|
||||
.last("LIMIT " + limit));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成平台作用域内下一个编码。编码序列跨仓库共用,且包含逻辑删除记录,确保编码永不复用。
|
||||
*/
|
||||
public String nextCode(long tenantId) {
|
||||
List<String> existing = mapper.selectAllCodes(tenantId);
|
||||
int maxSequence = (existing == null ? List.<String>of() : existing)
|
||||
.stream()
|
||||
.map(PALLET_CODE::matcher)
|
||||
.filter(Matcher::matches)
|
||||
.mapToInt(matcher -> Integer.parseInt(matcher.group(1)))
|
||||
.max()
|
||||
.orElse(0);
|
||||
return "TP-%03d".formatted(maxSequence + 1);
|
||||
}
|
||||
|
||||
public void insert(PalletEntity entity) {
|
||||
mapper.insert(entity);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import com.baiyue.warehouse.dto.WarehouseSummaryResponse;
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
@@ -14,7 +15,10 @@ import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 仓库聚合持久化仓储,组合仓库、地址和服务能力 Mapper,并强制按租户范围执行查询与写入。
|
||||
@@ -42,6 +46,12 @@ public class WarehouseRepository {
|
||||
.eq(WarehouseEntity::getId, id));
|
||||
}
|
||||
|
||||
public WarehouseEntity findByCode(long tenantId, String code) {
|
||||
return warehouses.selectOne(new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
.eq(WarehouseEntity::getWarehouseCode, code));
|
||||
}
|
||||
|
||||
public WarehouseEntity findActiveOwned(long tenantId, long id) {
|
||||
return warehouses.selectOne(new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
@@ -57,11 +67,21 @@ public class WarehouseRepository {
|
||||
}
|
||||
|
||||
public PageResult<WarehouseEntity> page(Long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
String keyword = StringUtils.hasText(query.keyword()) ? query.keyword().trim() : null;
|
||||
List<Long> addressMatches = StringUtils.hasText(keyword) ? addressWarehouseIdsByKeyword(tenantId, keyword) : List.of();
|
||||
LambdaQueryWrapper<WarehouseEntity> wrapper = new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(tenantId != null, WarehouseEntity::getTenantId, tenantId)
|
||||
.eq(StringUtils.hasText(query.status()), WarehouseEntity::getStatus, query.status())
|
||||
.eq(StringUtils.hasText(query.warehouseType()), WarehouseEntity::getWarehouseType, query.warehouseType())
|
||||
.eq(StringUtils.hasText(query.countryCode()), WarehouseEntity::getCountryCode, query.countryCode())
|
||||
.and(StringUtils.hasText(keyword), condition -> {
|
||||
condition.like(WarehouseEntity::getWarehouseCode, keyword)
|
||||
.or()
|
||||
.like(WarehouseEntity::getWarehouseName, keyword);
|
||||
if (!addressMatches.isEmpty()) {
|
||||
condition.or().in(WarehouseEntity::getId, addressMatches);
|
||||
}
|
||||
})
|
||||
.orderByDesc(WarehouseEntity::getCreatedAt);
|
||||
|
||||
if (StringUtils.hasText(query.capabilityCode())) {
|
||||
@@ -82,8 +102,19 @@ public class WarehouseRepository {
|
||||
return PageResult.from(warehouses.selectPage(new Page<>(query.page(), query.size()), wrapper));
|
||||
}
|
||||
|
||||
public PageResult<WarehouseSummaryResponse> pageSummaries(Long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
PageResult<WarehouseEntity> page = page(tenantId, query);
|
||||
Map<Long, WarehouseAddressEntity> primaryAddresses = primaryAddresses(tenantId,
|
||||
page.records().stream().map(WarehouseEntity::getId).toList());
|
||||
return new PageResult<>(
|
||||
page.records().stream()
|
||||
.map(warehouse -> WarehouseSummaryResponse.of(warehouse, primaryAddresses.get(warehouse.getId())))
|
||||
.toList(),
|
||||
page.page(), page.size(), page.total(), page.pages());
|
||||
}
|
||||
|
||||
public PageResult<WarehouseEntity> activePage(long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
return page(tenantId, new WarehouseRequests.QueryRequest(query.page(), query.size(),
|
||||
return page(tenantId, new WarehouseRequests.QueryRequest(query.page(), query.size(), query.keyword(),
|
||||
query.countryCode(), query.capabilityCode(), query.warehouseType(), "ACTIVE"));
|
||||
}
|
||||
|
||||
@@ -104,6 +135,11 @@ public class WarehouseRepository {
|
||||
capabilities.physicallyDeleteByWarehouse(tenantId, warehouseId);
|
||||
}
|
||||
|
||||
public void replaceAddress(long tenantId, long warehouseId, WarehouseAddressEntity address) {
|
||||
addresses.physicallyDeleteByWarehouse(tenantId, warehouseId);
|
||||
addresses.insert(address);
|
||||
}
|
||||
|
||||
public void insertAddress(WarehouseAddressEntity address) {
|
||||
addresses.insert(address);
|
||||
}
|
||||
@@ -111,4 +147,40 @@ public class WarehouseRepository {
|
||||
public void insertCapability(ServiceCapabilityEntity capability) {
|
||||
capabilities.insert(capability);
|
||||
}
|
||||
|
||||
private List<Long> addressWarehouseIdsByKeyword(Long tenantId, String keyword) {
|
||||
return addresses.selectList(new LambdaQueryWrapper<WarehouseAddressEntity>()
|
||||
.select(WarehouseAddressEntity::getWarehouseId)
|
||||
.eq(tenantId != null, WarehouseAddressEntity::getTenantId, tenantId)
|
||||
.and(condition -> condition
|
||||
.like(WarehouseAddressEntity::getStateProvince, keyword)
|
||||
.or()
|
||||
.like(WarehouseAddressEntity::getCity, keyword)
|
||||
.or()
|
||||
.like(WarehouseAddressEntity::getAddressLine1, keyword)
|
||||
.or()
|
||||
.like(WarehouseAddressEntity::getAddressLine2, keyword))
|
||||
.orderByAsc(WarehouseAddressEntity::getWarehouseId))
|
||||
.stream()
|
||||
.map(WarehouseAddressEntity::getWarehouseId)
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Map<Long, WarehouseAddressEntity> primaryAddresses(Long tenantId, Collection<Long> warehouseIds) {
|
||||
if (warehouseIds == null || warehouseIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<WarehouseAddressEntity> records = addresses.selectList(new LambdaQueryWrapper<WarehouseAddressEntity>()
|
||||
.eq(tenantId != null, WarehouseAddressEntity::getTenantId, tenantId)
|
||||
.in(WarehouseAddressEntity::getWarehouseId, warehouseIds)
|
||||
.orderByAsc(WarehouseAddressEntity::getWarehouseId)
|
||||
.orderByDesc(WarehouseAddressEntity::getAddressType)
|
||||
.orderByAsc(WarehouseAddressEntity::getId));
|
||||
Map<Long, WarehouseAddressEntity> grouped = new LinkedHashMap<>();
|
||||
for (WarehouseAddressEntity address : records) {
|
||||
grouped.putIfAbsent(address.getWarehouseId(), address);
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
}
|
||||
|
||||
+31
-17
@@ -16,6 +16,8 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
/** 托盘主数据应用服务。 */
|
||||
@Service
|
||||
public class PalletApplicationService {
|
||||
private static final int CODE_INSERT_ATTEMPTS = 5;
|
||||
|
||||
@Resource
|
||||
private PalletRepository repository;
|
||||
@Resource
|
||||
@@ -34,19 +36,23 @@ public class PalletApplicationService {
|
||||
@Transactional
|
||||
@BusinessAudit(event = "PALLET_CREATE", resourceType = "PALLET", description = "新增托盘", captureBefore = false)
|
||||
public Long create(long tenantId, PalletRequests.SaveRequest request) {
|
||||
requireWarehouse(tenantId, request.warehouseId());
|
||||
ensureCodeUnique(tenantId, request.warehouseId(), request.palletCode(), null);
|
||||
requireWarehouse(tenantId, request.warehouseId(), true);
|
||||
PalletEntity entity = new PalletEntity();
|
||||
entity.setTenantId(tenantId);
|
||||
entity.setWarehouseId(request.warehouseId());
|
||||
copy(entity, request);
|
||||
entity.setStatus("AVAILABLE");
|
||||
try {
|
||||
repository.insert(entity);
|
||||
} catch (DataIntegrityViolationException exception) {
|
||||
throw new BizException(ResultCode.CONFLICT, "托盘编码已存在");
|
||||
String code = repository.nextCode(tenantId);
|
||||
for (int attempt = 0; attempt < CODE_INSERT_ATTEMPTS; attempt++) {
|
||||
entity.setPalletCode(code);
|
||||
try {
|
||||
repository.insert(entity);
|
||||
return entity.getId();
|
||||
} catch (DataIntegrityViolationException exception) {
|
||||
code = incrementCode(code);
|
||||
}
|
||||
}
|
||||
return entity.getId();
|
||||
throw new BizException(ResultCode.CONFLICT, "托盘编码生成冲突,请重试");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -55,8 +61,8 @@ public class PalletApplicationService {
|
||||
if (warehouseId != request.warehouseId()) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "请求仓库与托盘所属仓库不一致");
|
||||
}
|
||||
requireWarehouse(tenantId, warehouseId, false);
|
||||
PalletEntity entity = owned(tenantId, warehouseId, id);
|
||||
ensureCodeUnique(tenantId, warehouseId, request.palletCode(), id);
|
||||
copy(entity, request);
|
||||
try {
|
||||
if (repository.save(entity) != 1) {
|
||||
@@ -84,28 +90,36 @@ public class PalletApplicationService {
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void requireWarehouse(long tenantId, long warehouseId) {
|
||||
if (warehouseRepository.findOwned(tenantId, warehouseId) == null) {
|
||||
private void requireWarehouse(long tenantId, long warehouseId, boolean requireActive) {
|
||||
var warehouse = warehouseRepository.findOwned(tenantId, warehouseId);
|
||||
if (warehouse == null) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "仓库不存在或不属于目标租户");
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureCodeUnique(long tenantId, long warehouseId, String code, Long excludedId) {
|
||||
if (repository.existsCode(tenantId, warehouseId, code, excludedId)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "托盘编码已存在");
|
||||
if (!"DOMESTIC".equalsIgnoreCase(warehouse.getWarehouseType())) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "托盘仅支持国内仓");
|
||||
}
|
||||
if (requireActive && !"ACTIVE".equalsIgnoreCase(warehouse.getStatus())) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "新增托盘仅支持启用中的国内仓");
|
||||
}
|
||||
}
|
||||
|
||||
private static void copy(PalletEntity entity, PalletRequests.SaveRequest request) {
|
||||
entity.setPalletCode(request.palletCode());
|
||||
entity.setPalletType(request.palletType());
|
||||
entity.setCapacityCbm(request.capacityCbm());
|
||||
entity.setWeightKg(request.weightKg());
|
||||
entity.setColor(request.color());
|
||||
entity.setRemark(request.remark());
|
||||
}
|
||||
|
||||
private static String incrementCode(String code) {
|
||||
int sequence = Integer.parseInt(code.substring("TP-".length())) + 1;
|
||||
return "TP-%03d".formatted(sequence);
|
||||
}
|
||||
|
||||
private static PalletRequests.View view(PalletEntity entity) {
|
||||
return new PalletRequests.View(entity.getId(), entity.getTenantId(), entity.getWarehouseId(),
|
||||
entity.getPalletCode(), entity.getPalletType(), entity.getCapacityCbm(), entity.getRemark(),
|
||||
entity.getPalletCode(), entity.getPalletType(), entity.getCapacityCbm(), entity.getWeightKg(),
|
||||
entity.getColor(), entity.getRemark(),
|
||||
entity.getCreatedAt(), entity.getUpdatedAt());
|
||||
}
|
||||
}
|
||||
|
||||
+17
-8
@@ -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.WarehouseSummaryResponse;
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
@@ -34,10 +35,12 @@ public class WarehouseApplicationService {
|
||||
captureBefore = false
|
||||
)
|
||||
public Long create(WarehouseRequests.SaveRequest request) {
|
||||
ensureCodeUnique(request.tenantId(), request.warehouseCode(), null);
|
||||
WarehouseEntity warehouse = toEntity(request);
|
||||
WarehouseSaveNormalizer.NormalizedSaveRequest normalized = WarehouseSaveNormalizer.normalize(
|
||||
request, WarehouseSaveNormalizer.PUBLIC_TENANT_ID);
|
||||
ensureCodeUnique(normalized.tenantId(), normalized.warehouseCode(), null);
|
||||
WarehouseEntity warehouse = toEntity(normalized);
|
||||
repository.insertWarehouse(warehouse);
|
||||
insertChildren(warehouse.getTenantId(), warehouse.getId(), request.address(), request.capabilities());
|
||||
insertChildren(warehouse.getTenantId(), warehouse.getId(), normalized.address(), normalized.capabilities());
|
||||
return warehouse.getId();
|
||||
}
|
||||
|
||||
@@ -49,24 +52,29 @@ public class WarehouseApplicationService {
|
||||
description = "Update warehouse / 更新仓库"
|
||||
)
|
||||
public void update(long tenantId, long id, WarehouseRequests.SaveRequest request) {
|
||||
if (request.tenantId() == null || tenantId != request.tenantId()) {
|
||||
if (request.tenantId() != null && tenantId != request.tenantId()) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "Tenant mismatch / 租户不匹配");
|
||||
}
|
||||
WarehouseSaveNormalizer.NormalizedSaveRequest normalized = WarehouseSaveNormalizer.normalize(request, tenantId);
|
||||
WarehouseEntity old = owned(tenantId, id);
|
||||
ensureCodeUnique(tenantId, request.warehouseCode(), id);
|
||||
ensureCodeUnique(tenantId, normalized.warehouseCode(), id);
|
||||
|
||||
WarehouseEntity warehouse = toEntity(request);
|
||||
WarehouseEntity warehouse = toEntity(normalized);
|
||||
warehouse.setId(old.getId());
|
||||
repository.saveWarehouse(warehouse);
|
||||
|
||||
repository.physicallyDeleteChildren(tenantId, id);
|
||||
insertChildren(tenantId, id, request.address(), request.capabilities());
|
||||
insertChildren(tenantId, id, normalized.address(), normalized.capabilities());
|
||||
}
|
||||
|
||||
public PageResult<WarehouseEntity> page(Long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
return repository.page(tenantId, query);
|
||||
}
|
||||
|
||||
public PageResult<WarehouseSummaryResponse> pageSummaries(Long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
return repository.pageSummaries(tenantId, query);
|
||||
}
|
||||
|
||||
public WarehouseDetailResponse detail(long tenantId, long id) {
|
||||
WarehouseEntity warehouse = owned(tenantId, id);
|
||||
List<WarehouseAddressEntity> addresses = repository.findAddresses(tenantId, id);
|
||||
@@ -120,7 +128,7 @@ public class WarehouseApplicationService {
|
||||
}
|
||||
}
|
||||
|
||||
private WarehouseEntity toEntity(WarehouseRequests.SaveRequest request) {
|
||||
private WarehouseEntity toEntity(WarehouseSaveNormalizer.NormalizedSaveRequest request) {
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setTenantId(request.tenantId());
|
||||
warehouse.setWarehouseCode(request.warehouseCode());
|
||||
@@ -169,4 +177,5 @@ public class WarehouseApplicationService {
|
||||
repository.insertCapability(entity);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.WarehouseImportDtos.ImportMode;
|
||||
import com.baiyue.warehouse.dto.WarehouseImportDtos.WarehouseImportError;
|
||||
import com.baiyue.warehouse.dto.WarehouseImportDtos.WarehouseImportPreviewRow;
|
||||
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 jakarta.annotation.Resource;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.DataFormatter;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.springframework.dao.DataIntegrityViolationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/** 仓库与 FBA 仓库标准模板生成、整表预检和原子导入。 */
|
||||
@Service
|
||||
public class WarehouseImportService {
|
||||
private static final int MAX_FILE_BYTES = 5 * 1024 * 1024;
|
||||
private static final int MAX_ROWS = 1000;
|
||||
private static final String TEMPLATE_VERSION = "1";
|
||||
private static final String[] HEADERS = {
|
||||
"仓库编码*", "仓库名称", "仓库类型", "国家二字码", "省/州", "城市*", "区/县",
|
||||
"详细地址*", "邮政编码", "时区", "状态", "联系人", "联系电话", "联系邮箱", "备注"
|
||||
};
|
||||
|
||||
@Resource
|
||||
private WarehouseRepository repository;
|
||||
@Resource
|
||||
private WarehouseApplicationService warehouseService;
|
||||
|
||||
public byte[] template() {
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook(); ByteArrayOutputStream output = new ByteArrayOutputStream()) {
|
||||
XSSFSheet sheet = workbook.createSheet("仓库");
|
||||
Row version = sheet.createRow(0);
|
||||
version.createCell(0).setCellValue("模板版本");
|
||||
version.createCell(1).setCellValue(TEMPLATE_VERSION);
|
||||
Row instruction = sheet.createRow(1);
|
||||
instruction.createCell(0).setCellValue("说明");
|
||||
instruction.createCell(1).setCellValue(
|
||||
"默认仅新增;仓库类型默认 DOMESTIC,状态默认 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]);
|
||||
sheet.setColumnWidth(i, Math.min(48, Math.max(14, HEADERS[i].length() * 2 + 4)) * 256);
|
||||
}
|
||||
workbook.write(output);
|
||||
return output.toByteArray();
|
||||
} catch (IOException exception) {
|
||||
throw new IllegalStateException("生成仓库导入模板失败", exception);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "WAREHOUSE_IMPORT", resourceType = "WAREHOUSE_IMPORT", description = "批量导入仓库", captureBefore = false)
|
||||
public WarehouseImportResult importFile(long tenantId, String fileName, byte[] content, ImportMode mode) {
|
||||
return importFile(tenantId, fileName, content, mode, false);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(event = "WAREHOUSE_IMPORT", resourceType = "WAREHOUSE_IMPORT", description = "批量导入仓库", captureBefore = false)
|
||||
public WarehouseImportResult importFile(long tenantId, String fileName, byte[] content, ImportMode mode, boolean dryRun) {
|
||||
if (tenantId != WarehouseSaveNormalizer.PUBLIC_TENANT_ID) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "平台公共仓库作用域必须为0");
|
||||
}
|
||||
ImportMode resolvedMode = mode == null ? ImportMode.CREATE_ONLY : mode;
|
||||
String safeFileName = safeFileName(fileName);
|
||||
if (content == null || content.length == 0) return failure(safeFileName, resolvedMode, 0, "文件", "文件不能为空");
|
||||
if (content.length > MAX_FILE_BYTES) return failure(safeFileName, resolvedMode, 0, "文件", "文件不能超过 5MB");
|
||||
if (!safeFileName.toLowerCase(Locale.ROOT).endsWith(".xlsx")) {
|
||||
return failure(safeFileName, resolvedMode, 0, "文件", "只支持 .xlsx 文件");
|
||||
}
|
||||
|
||||
List<ImportRow> rows = new ArrayList<>();
|
||||
List<WarehouseImportError> errors = new ArrayList<>();
|
||||
try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(content))) {
|
||||
if (workbook.getNumberOfSheets() == 0) return failure(safeFileName, resolvedMode, 0, "模板", "缺少仓库工作表");
|
||||
XSSFSheet sheet = workbook.getSheetAt(0);
|
||||
DataFormatter formatter = new DataFormatter();
|
||||
validateTemplate(sheet, formatter, errors);
|
||||
if (!errors.isEmpty()) return result(safeFileName, resolvedMode, 0, 0, 0, 0, List.of(), errors);
|
||||
for (int index = 3; index <= sheet.getLastRowNum(); index++) {
|
||||
Row row = sheet.getRow(index);
|
||||
if (isBlank(row, formatter)) continue;
|
||||
if (rows.size() >= MAX_ROWS) {
|
||||
errors.add(new WarehouseImportError(index + 1, "文件", "单次最多导入 1000 行"));
|
||||
break;
|
||||
}
|
||||
rows.add(new ImportRow(index + 1,
|
||||
value(row, 0, formatter), value(row, 1, formatter), value(row, 2, formatter),
|
||||
value(row, 3, formatter), value(row, 4, formatter), value(row, 5, formatter),
|
||||
value(row, 6, formatter), value(row, 7, formatter), value(row, 8, formatter),
|
||||
value(row, 9, formatter), value(row, 10, formatter), value(row, 11, formatter),
|
||||
value(row, 12, formatter), value(row, 13, formatter), value(row, 14, formatter)));
|
||||
}
|
||||
} catch (IOException | RuntimeException exception) {
|
||||
return failure(safeFileName, resolvedMode, rows.size(), "文件", "文件格式无法读取,请使用标准 XLSX 模板");
|
||||
}
|
||||
if (rows.isEmpty() && errors.isEmpty()) {
|
||||
errors.add(new WarehouseImportError(0, "文件", "文件中没有可导入的数据行"));
|
||||
}
|
||||
|
||||
List<PlanRow> plan = precheck(rows, errors, resolvedMode);
|
||||
if (!errors.isEmpty()) {
|
||||
return result(safeFileName, resolvedMode, rows.size(), 0, 0, 0, preview(plan), errors);
|
||||
}
|
||||
|
||||
int plannedCreates = (int) plan.stream().filter(row -> "CREATE".equals(row.action)).count();
|
||||
int plannedUpdates = (int) plan.stream().filter(row -> "UPDATE".equals(row.action)).count();
|
||||
int plannedUnchanged = (int) plan.stream().filter(row -> "UNCHANGED".equals(row.action)).count();
|
||||
if (dryRun) {
|
||||
return result(safeFileName, resolvedMode, rows.size(), plannedCreates, plannedUpdates, plannedUnchanged,
|
||||
false, preview(plan), List.of());
|
||||
}
|
||||
|
||||
int created = 0;
|
||||
int updated = 0;
|
||||
int unchanged = 0;
|
||||
try {
|
||||
for (PlanRow row : plan) {
|
||||
if ("CREATE".equals(row.action)) {
|
||||
warehouseService.create(row.createRequest());
|
||||
created++;
|
||||
} else if ("UPDATE".equals(row.action) && resolvedMode == ImportMode.UPSERT) {
|
||||
warehouseService.update(WarehouseSaveNormalizer.PUBLIC_TENANT_ID, row.existing().getId(), row.updateRequest());
|
||||
updated++;
|
||||
} else if ("UNCHANGED".equals(row.action)) {
|
||||
unchanged++;
|
||||
}
|
||||
}
|
||||
} catch (DataIntegrityViolationException exception) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
return failure(safeFileName, resolvedMode, rows.size(), "仓库编码", "与现有仓库重复,整批数据未写入");
|
||||
}
|
||||
return result(safeFileName, resolvedMode, rows.size(), created, updated, unchanged, true, preview(plan), List.of());
|
||||
}
|
||||
|
||||
private List<PlanRow> precheck(List<ImportRow> rows, List<WarehouseImportError> errors, ImportMode mode) {
|
||||
List<PlanRow> plan = new ArrayList<>();
|
||||
for (ImportRow row : rows) {
|
||||
validateRequired(row, rows, errors);
|
||||
WarehouseRequests.SaveRequest createRequest = row.toCreateRequest();
|
||||
WarehouseSaveNormalizer.NormalizedSaveRequest normalized = null;
|
||||
try {
|
||||
normalized = WarehouseSaveNormalizer.normalize(createRequest, WarehouseSaveNormalizer.PUBLIC_TENANT_ID);
|
||||
} catch (BizException exception) {
|
||||
errors.add(new WarehouseImportError(row.row(), "时区", exception.getMessage()));
|
||||
}
|
||||
WarehouseEntity existing = row.code().isBlank()
|
||||
? null
|
||||
: repository.findByCode(WarehouseSaveNormalizer.PUBLIC_TENANT_ID, row.code());
|
||||
String action = existing == null ? "CREATE" : "UPDATE";
|
||||
String reason = existing == null ? "新增仓库" : "现有仓库存在差异";
|
||||
if (existing != null && normalized != null) {
|
||||
if (unchanged(existing, firstAddress(existing), normalized)) {
|
||||
action = "UNCHANGED";
|
||||
reason = "与现有仓库无变化";
|
||||
} else if (mode == ImportMode.CREATE_ONLY) {
|
||||
action = "UNCHANGED";
|
||||
reason = "仅新增模式,现有仓库不修改";
|
||||
}
|
||||
}
|
||||
plan.add(new PlanRow(row, existing, normalized, action, reason));
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
private static void validateTemplate(XSSFSheet sheet, DataFormatter formatter, List<WarehouseImportError> errors) {
|
||||
if (!"模板版本".equals(value(sheet.getRow(0), 0, formatter))
|
||||
|| !TEMPLATE_VERSION.equals(value(sheet.getRow(0), 1, formatter))) {
|
||||
errors.add(new WarehouseImportError(1, "模板版本", "模板版本不匹配,请下载最新模板"));
|
||||
}
|
||||
for (int i = 0; i < HEADERS.length; i++) {
|
||||
if (!HEADERS[i].equals(value(sheet.getRow(2), i, formatter))) {
|
||||
errors.add(new WarehouseImportError(3, "表头", "第 " + (i + 1) + " 列应为“" + HEADERS[i] + "”"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRequired(ImportRow row, List<ImportRow> rows, List<WarehouseImportError> errors) {
|
||||
required(row, errors, "仓库编码", row.code(), 50);
|
||||
if (row.name().isBlank() && !"FBA".equalsIgnoreCase(row.type())) {
|
||||
errors.add(new WarehouseImportError(row.row(), "仓库名称", "不能为空;FBA 仓可不填并默认使用仓库编码"));
|
||||
} 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.countryCode().isBlank() && !row.countryCode().matches("(?i)[A-Z]{2}")) {
|
||||
errors.add(new WarehouseImportError(row.row(), "国家二字码", "必须是两个英文字母"));
|
||||
}
|
||||
optionalLength(row, errors, "省/州", row.stateProvince(), 100);
|
||||
required(row, errors, "城市", row.city(), 100);
|
||||
optionalLength(row, errors, "区/县", row.district(), 100);
|
||||
required(row, errors, "详细地址", row.addressLine(), 255);
|
||||
optionalLength(row, errors, "邮政编码", row.postalCode(), 20);
|
||||
optionalLength(row, errors, "时区", row.timezone(), 64);
|
||||
if (!row.status().isBlank() && !row.status().matches("(?i)ACTIVE|INACTIVE")) {
|
||||
errors.add(new WarehouseImportError(row.row(), "状态", "只能填写 ACTIVE 或 INACTIVE"));
|
||||
}
|
||||
optionalLength(row, errors, "联系人", row.contactName(), 64);
|
||||
optionalLength(row, errors, "联系电话", row.contactPhone(), 32);
|
||||
optionalLength(row, errors, "联系邮箱", row.contactEmail(), 128);
|
||||
optionalLength(row, errors, "备注", row.remark(), 500);
|
||||
for (ImportRow previous : rows) {
|
||||
if (previous == row) break;
|
||||
if (!row.code().isBlank() && previous.code().equalsIgnoreCase(row.code())) {
|
||||
errors.add(new WarehouseImportError(row.row(), "仓库编码", "与文件内其他行重复"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private WarehouseAddressEntity firstAddress(WarehouseEntity existing) {
|
||||
return repository.findAddresses(WarehouseSaveNormalizer.PUBLIC_TENANT_ID, existing.getId()).stream()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private static boolean unchanged(WarehouseEntity existing, WarehouseAddressEntity address,
|
||||
WarehouseSaveNormalizer.NormalizedSaveRequest normalized) {
|
||||
return Objects.equals(existing.getWarehouseName(), normalized.warehouseName())
|
||||
&& Objects.equals(existing.getWarehouseType(), normalized.warehouseType())
|
||||
&& Objects.equals(existing.getCountryCode(), normalized.countryCode())
|
||||
&& Objects.equals(existing.getTimezone(), normalized.timezone())
|
||||
&& Objects.equals(existing.getStatus(), normalized.status())
|
||||
&& address != null
|
||||
&& Objects.equals(address.getCountryCode(), normalized.address().countryCode())
|
||||
&& Objects.equals(address.getStateProvince(), normalized.address().stateProvince())
|
||||
&& Objects.equals(address.getCity(), normalized.address().city())
|
||||
&& Objects.equals(address.getDistrict(), normalized.address().district())
|
||||
&& Objects.equals(address.getPostalCode(), normalized.address().postalCode())
|
||||
&& Objects.equals(address.getAddressLine1(), normalized.address().addressLine1())
|
||||
&& Objects.equals(address.getAddressLine2(), normalized.address().addressLine2());
|
||||
}
|
||||
|
||||
private static List<WarehouseImportPreviewRow> preview(List<PlanRow> plan) {
|
||||
return plan.stream()
|
||||
.map(row -> new WarehouseImportPreviewRow(row.source().row(), row.source().code(), row.action(), row.reason()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private static WarehouseImportResult failure(String fileName, ImportMode mode, int total, String field, String message) {
|
||||
return result(fileName, mode, total, 0, 0, 0, List.of(), List.of(new WarehouseImportError(0, field, message)));
|
||||
}
|
||||
|
||||
private static WarehouseImportResult result(String fileName, ImportMode mode, int total, int created, int updated,
|
||||
int unchanged, List<WarehouseImportPreviewRow> preview,
|
||||
List<WarehouseImportError> errors) {
|
||||
return result(fileName, mode, total, created, updated, unchanged, false, preview, errors);
|
||||
}
|
||||
|
||||
private static WarehouseImportResult result(String fileName, ImportMode mode, int total, int created, int updated,
|
||||
int unchanged, boolean committed, List<WarehouseImportPreviewRow> preview,
|
||||
List<WarehouseImportError> errors) {
|
||||
return new WarehouseImportResult(fileName, mode, total, created, updated, unchanged, errors.isEmpty(), committed, preview, errors);
|
||||
}
|
||||
|
||||
private static void required(ImportRow row, List<WarehouseImportError> errors, String field, String value, int max) {
|
||||
if (value.isBlank()) errors.add(new WarehouseImportError(row.row(), field, "不能为空"));
|
||||
else optionalLength(row, errors, field, value, max);
|
||||
}
|
||||
|
||||
private static void optionalLength(ImportRow row, List<WarehouseImportError> errors, String field, String value, int max) {
|
||||
if (value.length() > max) errors.add(new WarehouseImportError(row.row(), field, "不能超过 " + max + " 个字符"));
|
||||
}
|
||||
|
||||
private static String value(Row row, int index, DataFormatter formatter) {
|
||||
if (row == null) return "";
|
||||
Cell cell = row.getCell(index);
|
||||
return cell == null ? "" : formatter.formatCellValue(cell).trim();
|
||||
}
|
||||
|
||||
private static boolean isBlank(Row row, DataFormatter formatter) {
|
||||
if (row == null) return true;
|
||||
for (int i = 0; i < HEADERS.length; i++) if (!value(row, i, formatter).isBlank()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String safeFileName(String value) {
|
||||
if (value == null || value.isBlank()) return "未命名文件.xlsx";
|
||||
String normalized = value.replace('\\', '/');
|
||||
return normalized.substring(normalized.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
private record PlanRow(ImportRow source, WarehouseEntity existing,
|
||||
WarehouseSaveNormalizer.NormalizedSaveRequest normalized,
|
||||
String action, String reason) {
|
||||
WarehouseRequests.SaveRequest createRequest() {
|
||||
return source.toCreateRequest();
|
||||
}
|
||||
|
||||
WarehouseRequests.SaveRequest updateRequest() {
|
||||
String status = "INACTIVE".equals(existing.getStatus()) ? existing.getStatus() : source.status();
|
||||
return source.toRequest(existing.getContactName(), existing.getContactPhone(), existing.getContactEmail(),
|
||||
existing.getRemark(), status);
|
||||
}
|
||||
}
|
||||
|
||||
private record ImportRow(int row, String code, String name, String type, String countryCode, String stateProvince,
|
||||
String city, String district, String addressLine, String postalCode, String timezone,
|
||||
String status, String contactName, String contactPhone, String contactEmail, String remark) {
|
||||
WarehouseRequests.SaveRequest toCreateRequest() {
|
||||
return toRequest(contactName, contactPhone, contactEmail, remark, status);
|
||||
}
|
||||
|
||||
WarehouseRequests.SaveRequest toRequest(String resolvedContactName, String resolvedContactPhone,
|
||||
String resolvedContactEmail, String resolvedRemark, String resolvedStatus) {
|
||||
String warehouseType = StringUtils.hasText(type) ? type.trim().toUpperCase(Locale.ROOT) : null;
|
||||
String resolvedCountry = StringUtils.hasText(countryCode) ? countryCode.trim().toUpperCase(Locale.ROOT) : null;
|
||||
String resolvedName = StringUtils.hasText(name) ? name : code;
|
||||
return new WarehouseRequests.SaveRequest(WarehouseSaveNormalizer.PUBLIC_TENANT_ID, code, resolvedName, warehouseType,
|
||||
resolvedCountry, WarehouseSaveNormalizer.trimToNull(timezone),
|
||||
WarehouseSaveNormalizer.trimToNull(resolvedContactName),
|
||||
WarehouseSaveNormalizer.trimToNull(resolvedContactPhone),
|
||||
WarehouseSaveNormalizer.trimToNull(resolvedContactEmail),
|
||||
StringUtils.hasText(resolvedStatus) ? resolvedStatus.trim().toUpperCase(Locale.ROOT) : null,
|
||||
WarehouseSaveNormalizer.trimToNull(resolvedRemark),
|
||||
new WarehouseRequests.AddressRequest(null, resolvedCountry, stateProvince, city, district, postalCode,
|
||||
addressLine, null, null, null),
|
||||
List.of());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** 仓库轻量新增/导入共用默认规则。 */
|
||||
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 STATUS_ACTIVE = "ACTIVE";
|
||||
static final String DEFAULT_ADDRESS_TYPE = "WAREHOUSE";
|
||||
static final String DEFAULT_DOMESTIC_COUNTRY = "CN";
|
||||
static final String DEFAULT_DOMESTIC_TIMEZONE = "UTC+8";
|
||||
private static final Map<String, String> US_SINGLE_TIMEZONE_STATES = Map.ofEntries(
|
||||
Map.entry("AL", "UTC-6"), Map.entry("ALABAMA", "UTC-6"),
|
||||
Map.entry("AK", "UTC-9"), Map.entry("ALASKA", "UTC-9"),
|
||||
Map.entry("AZ", "UTC-7"), Map.entry("ARIZONA", "UTC-7"),
|
||||
Map.entry("CA", "UTC-8"), Map.entry("CALIFORNIA", "UTC-8"),
|
||||
Map.entry("CO", "UTC-7"), Map.entry("COLORADO", "UTC-7"),
|
||||
Map.entry("CT", "UTC-5"), Map.entry("CONNECTICUT", "UTC-5"),
|
||||
Map.entry("DE", "UTC-5"), Map.entry("DELAWARE", "UTC-5"),
|
||||
Map.entry("GA", "UTC-5"), Map.entry("GEORGIA", "UTC-5"),
|
||||
Map.entry("HI", "UTC-10"), Map.entry("HAWAII", "UTC-10"),
|
||||
Map.entry("IA", "UTC-6"), Map.entry("IOWA", "UTC-6"),
|
||||
Map.entry("IL", "UTC-6"), Map.entry("ILLINOIS", "UTC-6"),
|
||||
Map.entry("LA", "UTC-6"), Map.entry("LOUISIANA", "UTC-6"),
|
||||
Map.entry("MA", "UTC-5"), Map.entry("MASSACHUSETTS", "UTC-5"),
|
||||
Map.entry("MD", "UTC-5"), Map.entry("MARYLAND", "UTC-5"),
|
||||
Map.entry("ME", "UTC-5"), Map.entry("MAINE", "UTC-5"),
|
||||
Map.entry("MN", "UTC-6"), Map.entry("MINNESOTA", "UTC-6"),
|
||||
Map.entry("MO", "UTC-6"), Map.entry("MISSOURI", "UTC-6"),
|
||||
Map.entry("MS", "UTC-6"), Map.entry("MISSISSIPPI", "UTC-6"),
|
||||
Map.entry("MT", "UTC-7"), Map.entry("MONTANA", "UTC-7"),
|
||||
Map.entry("NC", "UTC-5"), Map.entry("NORTH CAROLINA", "UTC-5"),
|
||||
Map.entry("NH", "UTC-5"), Map.entry("NEW HAMPSHIRE", "UTC-5"),
|
||||
Map.entry("NJ", "UTC-5"), Map.entry("NEW JERSEY", "UTC-5"),
|
||||
Map.entry("NM", "UTC-7"), Map.entry("NEW MEXICO", "UTC-7"),
|
||||
Map.entry("NV", "UTC-8"), Map.entry("NEVADA", "UTC-8"),
|
||||
Map.entry("NY", "UTC-5"), Map.entry("NEW YORK", "UTC-5"),
|
||||
Map.entry("OH", "UTC-5"), Map.entry("OHIO", "UTC-5"),
|
||||
Map.entry("OK", "UTC-6"), Map.entry("OKLAHOMA", "UTC-6"),
|
||||
Map.entry("PA", "UTC-5"), Map.entry("PENNSYLVANIA", "UTC-5"),
|
||||
Map.entry("RI", "UTC-5"), Map.entry("RHODE ISLAND", "UTC-5"),
|
||||
Map.entry("SC", "UTC-5"), Map.entry("SOUTH CAROLINA", "UTC-5"),
|
||||
Map.entry("VA", "UTC-5"), Map.entry("VIRGINIA", "UTC-5"),
|
||||
Map.entry("VT", "UTC-5"), Map.entry("VERMONT", "UTC-5"),
|
||||
Map.entry("WA", "UTC-8"), Map.entry("WASHINGTON", "UTC-8"),
|
||||
Map.entry("WI", "UTC-6"), Map.entry("WISCONSIN", "UTC-6"),
|
||||
Map.entry("WV", "UTC-5"), Map.entry("WEST VIRGINIA", "UTC-5"),
|
||||
Map.entry("WY", "UTC-7"), Map.entry("WYOMING", "UTC-7")
|
||||
);
|
||||
private static final Set<String> US_MULTI_TIMEZONE_STATES = Set.of(
|
||||
"FL", "FLORIDA", "ID", "IDAHO", "IN", "INDIANA", "KS", "KANSAS", "KY", "KENTUCKY",
|
||||
"MI", "MICHIGAN", "ND", "NORTH DAKOTA", "NE", "NEBRASKA", "OR", "OREGON",
|
||||
"SD", "SOUTH DAKOTA", "TN", "TENNESSEE", "TX", "TEXAS"
|
||||
);
|
||||
|
||||
private 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");
|
||||
}
|
||||
String status = upperOrDefault(request.status(), STATUS_ACTIVE);
|
||||
if (!STATUS_ACTIVE.equals(status) && !"INACTIVE".equals(status)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "Warehouse status must be ACTIVE or INACTIVE / 仓库状态只能为 ACTIVE 或 INACTIVE");
|
||||
}
|
||||
WarehouseRequests.AddressRequest address = request.address();
|
||||
if (address == null) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "Primary address is required / 主地址不能为空");
|
||||
}
|
||||
String countryCode = upperOrDefault(firstText(request.countryCode(), address.countryCode()),
|
||||
TYPE_DOMESTIC.equals(warehouseType) ? DEFAULT_DOMESTIC_COUNTRY : null);
|
||||
if (!StringUtils.hasText(countryCode)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "Country code is required / 国家二字码不能为空");
|
||||
}
|
||||
if (!countryCode.matches("[A-Z]{2}")) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "Country code must be two uppercase letters / 国家二字码必须为两个大写字母");
|
||||
}
|
||||
String timezone = resolveTimezone(warehouseType, countryCode, request.timezone(), address.stateProvince());
|
||||
WarehouseRequests.AddressRequest normalizedAddress = new WarehouseRequests.AddressRequest(
|
||||
upperOrDefault(address.addressType(), DEFAULT_ADDRESS_TYPE),
|
||||
upperOrDefault(address.countryCode(), countryCode),
|
||||
trimToNull(address.stateProvince()), address.city(), trimToNull(address.district()),
|
||||
trimToNull(address.postalCode()), address.addressLine1(), trimToNull(address.addressLine2()),
|
||||
address.latitude(), address.longitude());
|
||||
List<WarehouseRequests.CapabilityRequest> capabilities = TYPE_FBA.equals(warehouseType)
|
||||
? List.of()
|
||||
: request.capabilities();
|
||||
return new NormalizedSaveRequest(tenantId, request.warehouseCode().trim(), request.warehouseName().trim(),
|
||||
warehouseType, countryCode, timezone, trimToNull(request.contactName()), trimToNull(request.contactPhone()),
|
||||
trimToNull(request.contactEmail()), status, trimToNull(request.remark()), normalizedAddress, capabilities);
|
||||
}
|
||||
|
||||
static String resolveTimezone(String warehouseType, String countryCode, String explicitTimezone, String stateProvince) {
|
||||
if (StringUtils.hasText(explicitTimezone)) {
|
||||
return explicitTimezone.trim();
|
||||
}
|
||||
if (TYPE_DOMESTIC.equals(warehouseType) && DEFAULT_DOMESTIC_COUNTRY.equals(countryCode)) {
|
||||
return DEFAULT_DOMESTIC_TIMEZONE;
|
||||
}
|
||||
if (TYPE_FBA.equals(warehouseType) && "US".equals(countryCode)) {
|
||||
String stateKey = normalizeState(stateProvince);
|
||||
if (US_MULTI_TIMEZONE_STATES.contains(stateKey)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"FBA warehouse in multi-timezone state requires explicit timezone / 多时区州的 FBA 仓需要显式确认时区");
|
||||
}
|
||||
String timezone = US_SINGLE_TIMEZONE_STATES.get(stateKey);
|
||||
if (timezone != null) {
|
||||
return timezone;
|
||||
}
|
||||
}
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"Timezone is required unless a confirmed default rule applies / 未命中明确默认规则时必须显式填写时区");
|
||||
}
|
||||
|
||||
static String trimToNull(String value) {
|
||||
return StringUtils.hasText(value) ? value.trim() : null;
|
||||
}
|
||||
|
||||
private static String firstText(String left, String right) {
|
||||
return StringUtils.hasText(left) ? left : right;
|
||||
}
|
||||
|
||||
private static String upperOrDefault(String value, String defaultValue) {
|
||||
String resolved = StringUtils.hasText(value) ? value.trim() : defaultValue;
|
||||
return resolved == null ? null : resolved.toUpperCase(Locale.ROOT);
|
||||
}
|
||||
|
||||
private static String normalizeState(String value) {
|
||||
return StringUtils.hasText(value)
|
||||
? value.trim().replace(".", "").toUpperCase(Locale.ROOT)
|
||||
: "";
|
||||
}
|
||||
|
||||
record NormalizedSaveRequest(
|
||||
long tenantId,
|
||||
String warehouseCode,
|
||||
String warehouseName,
|
||||
String warehouseType,
|
||||
String countryCode,
|
||||
String timezone,
|
||||
String contactName,
|
||||
String contactPhone,
|
||||
String contactEmail,
|
||||
String status,
|
||||
String remark,
|
||||
WarehouseRequests.AddressRequest address,
|
||||
List<WarehouseRequests.CapabilityRequest> capabilities
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
source_row,warehouse_id_seed,address_id_seed,warehouse_code,warehouse_name,country_code,state_province,city,postal_code,timezone,address_line1,source_internal_id
|
||||
238,193000000000000001,193000000000100001,ABE2,ABE2,US,PA,Breinigsville,18031,UTC-5,705 Boulder Drive,
|
||||
237,193000000000000002,193000000000100002,ABE3,ABE3,US,PA,Breinigsville,18031,UTC-5,650 Boulder Drive,
|
||||
134,193000000000000003,193000000000100003,ABE4,ABE4,US,PA,Easton,18045,UTC-5,1610 Van Buren Road,126
|
||||
231,193000000000000004,193000000000100004,ABE5,ABE5,US,PA,Harrisburg,17112,UTC-5,6455 Allentown Boulevard,
|
||||
125,193000000000000005,193000000000100005,ABE8,ABE8,US,NJ,Florence,08518,UTC-5,401 Independence Road 08518-2200,39
|
||||
99,193000000000000006,193000000000100006,ABQ2,ABQ2,US,NM,LOS LUNAS,87031,UTC-7,6251 Pioneer Trail NW,76
|
||||
84,193000000000000007,193000000000100007,ACY2,ACY2,US,NJ,BURLINGTON,08016,UTC-5,1101 E PEARL ST,101
|
||||
141,193000000000000008,193000000000100008,AFW1,AFW1,US,TX,FORT WORTH,76131,UTC-6,1851 NE LOOP 820,
|
||||
27,193000000000000009,193000000000100009,AFW5,AFW5,US,TX,BALCH SPRINGS,75180-1636,UTC-6,12374 LAKE JUNE RD,181
|
||||
148,193000000000000010,193000000000100010,AKR1,AKR1,US,OH,Canton,44705,UTC-5,4747 Rebar Ave NE,153
|
||||
174,193000000000000011,193000000000100011,ALB1,ALB1,US,NY,Castleton,12033-9634,UTC-5,1835 US Route 9,102
|
||||
129,193000000000000012,193000000000100012,AMA1,AMA1,US,TX,Amarillo,79108,UTC-6,8590 NE 24th Avenue,132
|
||||
31,193000000000000013,193000000000100013,ATL2N,ATL2N,US,GA,Carrollton,30117,UTC-5,3101 N. Highway 27,157
|
||||
147,193000000000000014,193000000000100014,ATL3,ATL3,US,GA,Pendergrass,30567,UTC-5,445 Valentine Industrial Parkway,
|
||||
202,193000000000000015,193000000000100015,ATL6,ATL6,US,GA,East Point,30344,UTC-5,4200 North Commerce,
|
||||
233,193000000000000016,193000000000100016,AVP1,AVP1,US,PA,Hazle Township,18202-9361,UTC-5,550 Oak Ridge Road,70
|
||||
232,193000000000000017,193000000000100017,AVP3,AVP3,US,PA,GOULDSBORO,18424-9492,UTC-5,298 1ST AVE,
|
||||
289,193000000000000018,193000000000100018,BDL1,BDL1,US,CT,Windsor,06095,UTC-5,801 Day Hill Rd.,
|
||||
208,193000000000000019,193000000000100019,BFI1,BFI1,US,WA,Sumner,98390,UTC-8,1800 140th Avenue E.,
|
||||
207,193000000000000020,193000000000100020,BFI3,BFI3,US,WA,Dupont,98327-9607,UTC-8,2700 Center Drive,34
|
||||
206,193000000000000021,193000000000100021,BFI7,BFI7,US,WA,Sumner,98390,UTC-8,1901 140th Ave E,
|
||||
11,193000000000000022,193000000000100022,BJC1,BJC1,US,CO,AURORA,80019,UTC-7,6300 N Powhaton Road,186
|
||||
221,193000000000000023,193000000000100023,BNA1,BNA1,US,TN,Lebanon,37090,UTC-6,14840 Central Pike Suite 190,
|
||||
131,193000000000000024,193000000000100024,BNA2,BNA2,US,TN,Lebanon,37090,UTC-6,500 Duke DR,37
|
||||
140,193000000000000025,193000000000100025,BNA3,BNA3,US,TN,Murfreesboro,37127,UTC-6,2020 Joe B Jackson Pkwy,
|
||||
220,193000000000000026,193000000000100026,BNA4,BNA4,US,TN,Nashville,37217,UTC-6,50 airways Blvd,
|
||||
110,193000000000000027,193000000000100027,BNA6,BNA6,US,TN,CLARKSVILLE,37040-5502,UTC-6,3875 GUTHRIE HWY,99
|
||||
243,193000000000000028,193000000000100028,BOS1,BOS1,US,NH,Nashua,03063,UTC-5,10 State St.,
|
||||
193,193000000000000029,193000000000100029,BOS2,BOS2,US,MA,Drive Stoughton,02072,UTC-5,1000 Tech Centre,
|
||||
192,193000000000000030,193000000000100030,BOS7,BOS7,US,MA,Fall River,02720,UTC-5,1180 Innovation Way,103
|
||||
242,193000000000000031,193000000000100031,BWI1,BWI1,US,VA,Sterling,20166-2058,UTC-5,45121 Global Plaza,
|
||||
241,193000000000000032,193000000000100032,BWI2,BWI2,US,MD,Baltimore,21224,UTC-5,2010 Broening Hwy,
|
||||
102,193000000000000033,193000000000100033,BWI4,BWI4,US,VA,CLEARBROOK,22624,UTC-5,165 Business Blvd,150
|
||||
225,193000000000000034,193000000000100034,CAE1,CAE1,US,SC,West Columbia,29172,UTC-5,4400 12TH STREET EXT,
|
||||
223,193000000000000035,193000000000100035,CHA1,CHA1,US,TN,Chattanooga,37416,UTC-5,7200 Discovery Drive,
|
||||
222,193000000000000036,193000000000100036,CHA2,CHA2,US,TN,Charleston,37310,UTC-5,225 Infinity Dr NW,66
|
||||
75,193000000000000037,193000000000100037,CHO1,CHO1,US,VA,FISHERSVILLE,22939,UTC-5,32 TRADER RD,120
|
||||
191,193000000000000038,193000000000100038,CLT2,CLT2,US,NC,Charlotte,28214-8082,UTC-5,10240 Old Dowd Rd,59
|
||||
114,193000000000000039,193000000000100039,CLT3,CLT3,US,NC,CONCORD,28027,UTC-5,6500 Davidson Hwy,104
|
||||
240,193000000000000040,193000000000100040,CMH2,CMH2,US,OH,Groveport,43125-9016,UTC-5,6050 Gateway Court,123
|
||||
181,193000000000000041,193000000000100041,CMH3,CMH3,US,OH,Monroe,45050-1848,UTC-5,700 GATEWAY BLVD,32
|
||||
262,193000000000000042,193000000000100042,CVG1,CVG1,US,KY,Hebron,41048,UTC-5,1155 Worldwide Blvd,
|
||||
261,193000000000000043,193000000000100043,CVG2,CVG2,US,KY,Hebron,41048,UTC-5,1600 Worldwide Blvd,
|
||||
260,193000000000000044,193000000000100044,CVG3,CVG3,US,KY,Hebron,41048,UTC-5,3680 Langley Dr.,
|
||||
259,193000000000000045,193000000000100045,CVG5,CVG5,US,KY,Hebron,41048,UTC-5,2285 Litton Lane,
|
||||
135,193000000000000046,193000000000100046,DCA6,DCA6,US,MD,BALTIMORE,21219,UTC-5,6001 Bethlehem Blvd,135
|
||||
123,193000000000000047,193000000000100047,DEN2,DEN2,US,CO,Aurora,80019,UTC-7,22205 East 19th Ave,107
|
||||
120,193000000000000048,193000000000100048,DEN8,DEN8,US,CO,Aurora,80018,UTC-7,21000 E 13th Ave,73
|
||||
189,193000000000000049,193000000000100049,DET1,DET1,US,MI,Livonia,48150-1543,UTC-5,39000 Amrhein Road,118
|
||||
182,193000000000000050,193000000000100050,DET2,DET2,US,MI,Shelby Township,48317-1318,UTC-5,50500 Mound Rd,136
|
||||
143,193000000000000051,193000000000100051,DFW5S,DFW5S,US,TX,Lancaster,75146,UTC-6,"2500 E Belt Line Rd,",
|
||||
219,193000000000000052,193000000000100052,DFW6,DFW6,US,TX,Coppell,75019,UTC-6,940 W Bethel Road,105
|
||||
217,193000000000000053,193000000000100053,DFW8,DFW8,US,TX,DFW Airport,75261,UTC-6,2700 Regent Blvd,
|
||||
256,193000000000000054,193000000000100054,EWR4,EWR4,US,NJ,Robbinsville,08691,UTC-5,50 New Canton Way,
|
||||
255,193000000000000055,193000000000100055,EWR5,EWR5,US,NJ,Avenel,07001,UTC-5,301 Blair Rd.,
|
||||
254,193000000000000056,193000000000100056,EWR7,EWR7,US,NJ,Avenel,07001,UTC-5,301 Blair Rd.,
|
||||
253,193000000000000057,193000000000100057,EWR9,EWR9,US,NJ,Carteret,07008,UTC-5,8003 Industrial Parkway,
|
||||
117,193000000000000058,193000000000100058,FAT2,FAT2,US,CA,VISALIA,93291-1882,UTC-8,3315 N KELSEYST,43
|
||||
176,193000000000000059,193000000000100059,FOE1,FOE1,US,KS,KANSAS CITY,66109-3665,UTC-6,9400 LEAVENWORTH RD,117
|
||||
212,193000000000000060,193000000000100060,FTW1,FTW1,US,TX,Dallas,75241,UTC-6,33333 Lyndon B Johnson FREEWAY,24
|
||||
211,193000000000000061,193000000000100061,FTW2,FTW2,US,TX,Coppell,75019,UTC-6,2701 West Bethel Road,
|
||||
89,193000000000000062,193000000000100062,FTW3,FTW3,US,TX,Fort Worth,76177-2517,UTC-6,15201 Heritage Pkwy,
|
||||
121,193000000000000063,193000000000100063,FTW5,FTW5,US,TX,Forney,75126,UTC-6,1475 AKRON WAY,48
|
||||
171,193000000000000064,193000000000100064,FWA4,FWA4,US,IN,FORT WAYNE,46809-9771,UTC-5,9798 Smith Road,58
|
||||
151,193000000000000065,193000000000100065,GEG2,GEG2,US,WA,SPOKANE,99216,UTC-8,18007 E Garland Ave,108
|
||||
67,193000000000000066,193000000000100066,GEU2,GEU2,US,AZ,Glendale,85307,UTC-7,15301 West Northern Avenue,97
|
||||
78,193000000000000067,193000000000100067,GEU3,GEU3,US,AZ,BUCKEYE,85396,UTC-7,18900 W McDowell Road,81
|
||||
68,193000000000000068,193000000000100068,GEU5,GEU5,US,AZ,GOODYEAR,85395,UTC-7,4660 North Cotton Lane,100
|
||||
164,193000000000000069,193000000000100069,GSO1,GSO1,US,NC,KERNERSVILLE,27284,UTC-5,1656 OLD GREENSBORO RD,131
|
||||
224,193000000000000070,193000000000100070,GSP1,GSP1,US,SC,Spartanburg,29303,UTC-5,402 John Dodd Rd,90
|
||||
188,193000000000000071,193000000000100071,GYR2,GYR2,US,AZ,GOODYEAR,85395-7672,UTC-7,17341 W MINNEZONA AVE,7
|
||||
187,193000000000000072,193000000000100072,GYR3,GYR3,US,AZ,PHOENIX,85043-2356,UTC-7,8181 W ROOSEVELT ST,8
|
||||
80,193000000000000073,193000000000100073,HGR6,HGR6,US,MD,HAGERSTOWN,21740-7301,UTC-5,55 West Oak Ridge Drive,130
|
||||
87,193000000000000074,193000000000100074,HIA1,HIA1,US,PA,MIDDLETOWN,17057,UTC-5,3327 E Harrisburg Pik,148
|
||||
44,193000000000000075,193000000000100075,HLI2,HLI2,US,CA,HOLLISTER,95023,UTC-8,600 Ernie Drive,137
|
||||
216,193000000000000076,193000000000100076,HOU1,HOU1,US,TX,Humble,77338,UTC-6,8120 Humble Westfield Rd,
|
||||
215,193000000000000077,193000000000100077,HOU2,HOU2,US,TX,Houston,77038-2324,UTC-6,10550 Ella Blvd.,
|
||||
214,193000000000000078,193000000000100078,HOU3,HOU3,US,TX,Brookshire,77423,UTC-6,31555 Highway 90 E,
|
||||
154,193000000000000079,193000000000100079,HOU8,HOU8,US,TX,FRESNO,77545,UTC-6,2303 Hurricane Lane,49
|
||||
142,193000000000000080,193000000000100080,HSV1,HSV1,US,AL,Madison,35756,UTC-6,7817 Greenbrier Road,80
|
||||
126,193000000000000081,193000000000100081,HSV2,HSV2,US,AL,MADISON,35756,UTC-6,28869 Fanning Dr NW,
|
||||
155,193000000000000082,193000000000100082,IAH3,IAH3,US,TX,Houston,77032,UTC-6,15525 Milner Road,57
|
||||
3,193000000000000083,193000000000100083,IAZ1,IAZ1,US,AZ,GOODYEAR,85395-4515,UTC-7,"4660 N COTTON LN 85395-4515 - GOODYEAR, AZ - United States",179
|
||||
136,193000000000000084,193000000000100084,ICT2,ICT2,US,KS,PARK CITY,67219,UTC-6,7130 N BROADWAY AVE,109
|
||||
2,193000000000000085,193000000000100085,IGA3,IGA3,US,GA,MACON,31216-6427,UTC-5,"7001 Skipper Rd 31216-6427 MACON, GA - United States",
|
||||
152,193000000000000086,193000000000100086,IGQ2,IGQ2,US,IL,University park,60484,UTC-6,23257 S. Central Avenue,110
|
||||
119,193000000000000087,193000000000100087,ILG1,ILG1,US,DE,NEW CASTLE,19720,UTC-5,780 S. DuPont Highway,35
|
||||
10,193000000000000088,193000000000100088,IMO1,IMO1,US,MO,LIBERTY,64068-8933,UTC-6,2361 S Withers RD,178
|
||||
6,193000000000000089,193000000000100089,IMS1,IMS1,US,MS,OLIVE BRANCH,38654,UTC-6,IMS1-11505 Progress Way,38654-2510,OLIVE BRANCH,MS,US,189
|
||||
5,193000000000000090,193000000000100090,INC1,INC1,US,NC,FAYETTEVILLE,28303-2215,UTC-5,"6309 Bragg Blvd 28303-2215 - FAYETTEVILLE, NC - United States",190
|
||||
88,193000000000000091,193000000000100091,IND2,IND2,US,IN,Plainfield,46168-7442,UTC-5,715 Airtech Pkwy,127
|
||||
276,193000000000000092,193000000000100092,IND4,IND4,US,IN,Indianapolis,46231,UTC-5,710 S. Girls School Rd,
|
||||
275,193000000000000093,193000000000100093,IND5,IND5,US,IN,Plainfield,46168,UTC-5,800 Perry Road,140
|
||||
274,193000000000000094,193000000000100094,IND6,IND6,US,IN,Jeffersonville,47130,UTC-5,1453 E 10th St,
|
||||
173,193000000000000095,193000000000100095,IND9,IND9,US,IN,GREENWOOD,46143-7830,UTC-5,1151 S GRAHAM RD,17
|
||||
9,193000000000000096,193000000000100096,ITX3,ITX3,US,TX,AMARILLO,79108-7749,UTC-6,"ITX3-8590 NE 24th Ave,79108-7749,AMARILLO,TX,US",187
|
||||
22,193000000000000097,193000000000100097,IUSF,IUSF,US,TX,DALLAS,75241,UTC-6,3351 Balmorhea Dr,169
|
||||
69,193000000000000098,193000000000100098,IUSJ,IUSJ,US,CA,BEAUMONT,92223,UTC-8,36900 West 4th Street,98
|
||||
74,193000000000000099,193000000000100099,IUSP,IUSP,US,CA,"HESPERIA,",92344,UTC-8,8140 Caliente Rd,83
|
||||
70,193000000000000100,193000000000100100,IUSQ,IUSQ,US,CA,REDLANDS,92374,UTC-8,2125 W San Bernardino Ave,95
|
||||
40,193000000000000101,193000000000100101,IUSR,IUSR,US,SC,COWPENS,29330,UTC-5,1120 Mt Olive Rd,158
|
||||
39,193000000000000102,193000000000100102,IUST,IUST,US,PA,GREENCASTLE,17225,UTC-5,"3PL AWD (Greencastle, PA, US) 15651 Greenmount RdGREENCASTLE, PA 17225 United States",149
|
||||
33,193000000000000103,193000000000100103,IUSW,IUSW,US,CA,Hesperia,92344,UTC-8,"8130 Caliente Rd 92344 - Hesperia, CA - United States",156
|
||||
21,193000000000000104,193000000000100104,IUTE,IUTE,US,AZ,GOODYEAR,85338,UTC-7,16155 W Elwood St,170
|
||||
8,193000000000000105,193000000000100105,IUTJ,IUTJ,US,CA,Fontana,92335,UTC-8,"9253 Dreamland Drive 92335 - Fontana, CA - United",177
|
||||
7,193000000000000106,193000000000100106,IUTL,IUTL,US,CA,Moreno Valley,92551,UTC-8,"24405 Krameria Ave 92551 - Moreno Valley, CA - United States",188
|
||||
4,193000000000000107,193000000000100107,IWA6,IWA6,US,WA,PASCO,99301-6161,UTC-8,"1202 S Road 40 E 99301-6161 - PASCO, WA - United States",191
|
||||
72,193000000000000108,193000000000100108,JOT1,JOT1,US,CA,SAN BERNARDINO,92408,UTC-8,1494 S WATERMAN AVE,93
|
||||
150,193000000000000109,193000000000100109,JVL1,JVL1,US,WI,BELOIT,53511,UTC-6,1255 Gateway Blvd,125
|
||||
178,193000000000000110,193000000000100110,KRB4,KRB4,US,CA,PERRIS,92571-9722,UTC-8,4501 PATTERSON AVE,
|
||||
161,193000000000000111,193000000000100111,KY1,KY1,US,KY,Shepherdsville,40165,UTC-5,"120 Velocity Way.,",60
|
||||
100,193000000000000112,193000000000100112,LAN2,LAN2,US,MI,LANSING,48917,UTC-5,6500 W Mt Hope Hwy,96
|
||||
247,193000000000000113,193000000000100113,LAS1,LAS1,US,NV,HENDERSON,89044,UTC-8,12300 Bermuda Road,5
|
||||
246,193000000000000114,193000000000100114,LAS2,LAS2,US,NV,Las Vegas,89030,UTC-8,"3837 Bay Lake Trail, Suite 111 North",
|
||||
132,193000000000000115,193000000000100115,LAS6,LAS6,US,NV,Las Vegas,89115,UTC-8,4550 Nexus Way,50
|
||||
162,193000000000000116,193000000000100116,LAX2T,LAX2T,US,CA,CHINO,91708,UTC-8,6730 Kimball Ave,19
|
||||
183,193000000000000117,193000000000100117,LAX9,LAX9,US,CA,FONTANA,92337,UTC-8,11263 Oleander Ave,3
|
||||
86,193000000000000118,193000000000100118,LBE1,LBE1,US,PA,Greensburg,15605,UTC-5,165 Glen Fox Road,112
|
||||
268,193000000000000119,193000000000100119,LEX1,LEX1,US,KY,Lexington,40511,UTC-5,1850 Mercer RD,
|
||||
267,193000000000000120,193000000000100120,LEX2,LEX2,US,KY,Lexington,40511,UTC-5,172 Trade St.,
|
||||
107,193000000000000121,193000000000100121,LFT1,LFT1,US,LA,CARENCRO,70520-5976,UTC-6,3550 NE EVANGELINE TRWY,31
|
||||
291,193000000000000122,193000000000100122,LGB3,LGB3,US,CA,Eastvale,91752,UTC-8,4950 Goodman Way,
|
||||
292,193000000000000123,193000000000100123,LGB4,LGB4,US,CA,Redlands,92374,UTC-8,27517 Pioneer Avenue,45
|
||||
293,193000000000000124,193000000000100124,LGB6,LGB6,US,CA,Riverside,92518-1513,UTC-8,20901 Krameria Ave,26
|
||||
294,193000000000000125,193000000000100125,LGB8,LGB8,US,CA,Rialto,92376,UTC-8,1568 N Linden Ave,2
|
||||
106,193000000000000126,193000000000100126,LIT2,LIT2,US,AR,NORTH LITTLE ROCK,72117,UTC-6,13001 HIGHWAY 70,159
|
||||
32,193000000000000127,193000000000100127,MCC1,MCC1,US,CA,RANCHO CORDOVA,95742,UTC-8,"3300 Rancho Cordova Pkwy 95742 - RANCHO CORDOVA, CA - United States",161
|
||||
157,193000000000000128,193000000000100128,MCE1,MCE1,US,CA,TURLOCK,95380,UTC-8,FULKERTH RD,65
|
||||
271,193000000000000129,193000000000100129,MCI1,MCI1,US,KS,Lenexa,66219,UTC-6,113th St & Renner Blvd.,
|
||||
12,193000000000000130,193000000000100130,MCI4,MCI4,US,MO,KANSAS CITY,64163,UTC-6,MCI4 - 10501 NW 136th Street,176
|
||||
115,193000000000000131,193000000000100131,MCO2,MCO2,US,FL,Deltona,32725-2610,UTC-5,2600 N Normandy Blvd,106
|
||||
230,193000000000000132,193000000000100132,MDT1,MDT1,US,PA,Carlisle,17015,UTC-5,2Ames Drive,121
|
||||
128,193000000000000133,193000000000100133,MDT4,MDT4,US,PA,York,17408,UTC-5,5125 Commerce Drive,144
|
||||
200,193000000000000134,193000000000100134,MDW2,MDW2,US,IL,Joliet,60433-3280,UTC-6,250 Emerald Drive,46
|
||||
199,193000000000000135,193000000000100135,MDW6,MDW6,US,IL,ROMEOVILLE,60446-6529,UTC-6,1125 W REMINGTON BLVD ROMEOVILLE,143
|
||||
198,193000000000000136,193000000000100136,MDW7,MDW7,US,IL,Monee,60449,UTC-6,6605 Monee Manhattan Road,
|
||||
197,193000000000000137,193000000000100137,MDW8,MDW8,US,IL,Waukegan,60085,UTC-6,1750 Bridge Drive,
|
||||
196,193000000000000138,193000000000100138,MDW9,MDW9,US,IL,Aurora,60502,UTC-6,2865 Duke Parkway,36
|
||||
163,193000000000000139,193000000000100139,MEM1,MEM1,US,TN,Memphis,38118,UTC-6,3292 E Holmes Rd,85
|
||||
118,193000000000000140,193000000000100140,MEM6,MEM6,US,MS,OLIVE BRANCH,38654,UTC-6,11505 Progress Way,51
|
||||
201,193000000000000141,193000000000100141,MGE3,MGE3,US,GA,Jefferson,30549-2010,UTC-5,808 Hog Mountain Road,133
|
||||
77,193000000000000142,193000000000100142,MIT2,MIT2,US,CA,SHAFTER,93263,UTC-8,5408 Express Avenue,82
|
||||
270,193000000000000143,193000000000100143,MKC4,MKC4,US,KS,Edgerton,66021-9588,UTC-6,19645 Waverly Rd,67
|
||||
269,193000000000000144,193000000000100144,MKC6,MKC6,US,KS,Kansas City,66102,UTC-6,6925 Riverview Avenue,
|
||||
203,193000000000000145,193000000000100145,MKE1,MKE1,US,WI,Kenosha,53144,UTC-6,3501 120th Ave,
|
||||
94,193000000000000146,193000000000100146,MQJ1,MQJ1,US,IN,GREENFIELD,46140,UTC-5,4412 W 300 RD N,88
|
||||
190,193000000000000147,193000000000100147,MSP1,MSP1,US,MN,Shakopee,55379,UTC-6,2601 4th Avenue East,
|
||||
17,193000000000000148,193000000000100148,NJ3,NJ3,US,NJ,Pedricktown,08067,UTC-5,3 Sorbello Way,175
|
||||
299,193000000000000149,193000000000100149,OAK3,OAK3,US,CA,Patterson,95363-8876,UTC-8,255 Park Center Drive,41
|
||||
300,193000000000000150,193000000000100150,OAK4,OAK4,US,CA,Tracy,95304-9370,UTC-8,1555 N. Chrisman Rd,
|
||||
133,193000000000000151,193000000000100151,OKC2,OKC2,US,OK,OKLAHOMA CITY,73159,UTC-6,8991 S PORTLAND AVENUE,33
|
||||
19,193000000000000152,193000000000100152,OLM1,OLM1,US,WA,OLYMPIA,98516,UTC-8,"3300 HOGUM BAY RD NE 98516-3111 - OLYMPIA, WA - United States",172
|
||||
303,193000000000000153,193000000000100153,ONT2,ONT2,US,CA,San Bernardino,92408,UTC-8,1910 E Central Ave,
|
||||
302,193000000000000154,193000000000100154,ONT6,ONT6,US,CA,Moreno Valley,92551-9561,UTC-8,24208 San Michele Rd,
|
||||
301,193000000000000155,193000000000100155,ONT8,ONT8,US,CA,Moreno Valley,92551-9534,UTC-8,24300 Nandina Ave,1
|
||||
298,193000000000000156,193000000000100156,ONT9,ONT9,US,CA,Redlands,92374,UTC-8,2125 W. San Bernandino Ave,25
|
||||
103,193000000000000157,193000000000100157,ORD2,ORD2,US,IL,CHANNAHON,60410-8758,UTC-6,23714 W Amoco Rd,21
|
||||
195,193000000000000158,193000000000100158,ORD6,ORD6,US,IL,Wood Dale,60191,UTC-6,1250 N Mittel Blvd,
|
||||
113,193000000000000159,193000000000100159,ORF2,ORF2,US,VA,CHESAPEAKE,23321-1425,UTC-5,ORF2 - 5045 PORTSMOUTH BLVD,38
|
||||
81,193000000000000160,193000000000100160,PBI3,PBI3,US,FL,Jacksonville,32206,UTC-5,21 Jump Street,113
|
||||
127,193000000000000161,193000000000100161,PDX7,PDX7,US,OR,Salem,97317,UTC-8,4775 Depot Ct SE,68
|
||||
288,193000000000000162,193000000000100162,PHL1,PHL1,US,DE,New Castle,19720,UTC-5,1 Centerpoint Blvd.,
|
||||
287,193000000000000163,193000000000100163,PHL3,PHL3,US,DE,New Castle,19720,UTC-5,1600 Johnson Way,
|
||||
236,193000000000000164,193000000000100164,PHL4,PHL4,US,PA,Carlisle,17015,UTC-5,21 Roadway Dr,52
|
||||
235,193000000000000165,193000000000100165,PHL5,PHL5,US,PA,Lewisberry,17339,UTC-5,500 McCarthy Dr,
|
||||
20,193000000000000166,193000000000100166,PHL5S,PHL5S,US,PA,Greencastle,17225,UTC-5,"1915 Ebberts Spg Ct, Greencastle, PA 17225",171
|
||||
234,193000000000000167,193000000000100167,PHL6,PHL6,US,PA,Carlisle,17015,UTC-5,675 Allen Rd.,69
|
||||
286,193000000000000168,193000000000100168,PHL7,PHL7,US,DE,Middletown,19709,UTC-5,560 Merrimac Ave,
|
||||
285,193000000000000169,193000000000100169,PHL8,PHL8,US,DE,Middletown,19709,UTC-5,727 N Broad St,
|
||||
307,193000000000000170,193000000000100170,PHX3,PHX3,US,AZ,Phoenix,85043,UTC-7,6835 West Buckeye Road,
|
||||
306,193000000000000171,193000000000100171,PHX5,PHX5,US,AZ,Goodyear,85338,UTC-7,16920 W Commerce Drive,72
|
||||
305,193000000000000172,193000000000100172,PHX6,PHX6,US,AZ,Phoenix,85043,UTC-7,4750 West Mohave St,
|
||||
304,193000000000000173,193000000000100173,PHX7,PHX7,US,AZ,Phoenix,85043,UTC-7,800 N 75th Ave,74
|
||||
229,193000000000000174,193000000000100174,PIT1,PIT1,US,PA,Pittsburgh,15205,UTC-5,2250 Roswell Drive,
|
||||
158,193000000000000175,193000000000100175,PIT2,PIT2,US,PA,IMPERIAL,15126-1002,UTC-5,1200 Westport Rd,78
|
||||
46,193000000000000176,193000000000100176,POC1,POC1,US,CA,FONTANA,92336-1123,UTC-8,4121 LYTLE CREEK RD,128
|
||||
38,193000000000000177,193000000000100177,POC2,POC2,US,CA,ONTARIO,91761,UTC-8,4000 South Hamner Avenue,151
|
||||
45,193000000000000178,193000000000100178,POC3,POC3,US,CA,JURUPA VALLEY,92509,UTC-8,6120 Clinker Drive,129
|
||||
49,193000000000000179,193000000000100179,PPO4,PPO4,US,IN,MERRILLVILLE,46410,UTC-6,9850 Mississippi Street,122
|
||||
93,193000000000000180,193000000000100180,PSC2,PSC2,US,WA,PASCO,99301,UTC-8,1351 S Road 40 E,75
|
||||
28,193000000000000181,193000000000100181,PSP3,PSP3,US,CA,DESERT HOT SPRINGS,92240,UTC-8,64165 19th AVE,162
|
||||
71,193000000000000182,193000000000100182,QXY5,QXY5,US,CA,STOCKTON,95215,UTC-8,4532 NEWCASTLE RD,92
|
||||
35,193000000000000183,193000000000100183,QXY8,QXY8,US,CA,FRESNO,93725-1962,UTC-8,3611 S NORTHPOINTE DR,154
|
||||
14,193000000000000184,193000000000100184,QZZ7,QZZ7,US,AZ,MESA,85212-9608,UTC-7,8560 E ELLIOT RD,184
|
||||
146,193000000000000185,193000000000100185,RDU2,RDU2,US,NC,Smithfield,27577,UTC-5,2150 US HWY 70 Business West,56
|
||||
95,193000000000000186,193000000000100186,RDU4,RDU4,US,NC,FAYETTEVILLE,28303,UTC-5,6309 Bragg Blvd,145
|
||||
145,193000000000000187,193000000000100187,RFD2,RFD2,US,IL,Huntley,60142,UTC-6,11500 Freeman Road,71
|
||||
16,193000000000000188,193000000000100188,RFD4,RFD4,US,IL,HUNTLEY,60142,UTC-6,"11400 Venture Court 60142-8075 - HUNTLEY, IL - United States",174
|
||||
210,193000000000000189,193000000000100189,RIC1,RIC1,US,VA,Petersburg,23803,UTC-5,5000 Commerce Way,142
|
||||
209,193000000000000190,193000000000100190,RIC2,RIC2,US,VA,Chester,23836,UTC-5,1901 Meadowville Technology Pkwy,
|
||||
144,193000000000000191,193000000000100191,RMN3,RMN3,US,VA,Fredericksburg,22406,UTC-5,220 Centreport Parkway,84
|
||||
248,193000000000000192,193000000000100192,RNO1,RNO1,US,NV,Fernley,89408,UTC-8,1600 East Newlands RD,
|
||||
245,193000000000000193,193000000000100193,RNO2,RNO2,US,NV,Reno,85906,UTC-8,8000 North Virginia Street,
|
||||
244,193000000000000194,193000000000100194,RNO4,RNO4,US,NV,Reno,89506,UTC-8,8000 North Virginia Street,
|
||||
73,193000000000000195,193000000000100195,RYY2,RYY2,US,GA,White,30184,UTC-5,"1250 Cassville White Road,",94
|
||||
218,193000000000000196,193000000000100196,SAT1,SAT1,US,TX,Schertz,78154-1461,UTC-6,6000 Enterprise Avenue,23
|
||||
149,193000000000000197,193000000000100197,SAT4,SAT4,US,TX,SAN ANTONIO,78245,UTC-6,10384 W US HIGHWAY 90,61
|
||||
15,193000000000000198,193000000000100198,SAT6,SAT6,US,TX,SAN MARCOS,78666,UTC-6,"1346 Fortuna Road 78666-0001 - SAN MARCOS, TX - United States",183
|
||||
98,193000000000000199,193000000000100199,SAV3,SAV3,US,GA,Macon,31216-6427,UTC-5,7001 Skipper Rd,124
|
||||
185,193000000000000200,193000000000100200,SBD1,SBD1,US,CA,BLOOMINGTON,92316-3819,UTC-8,3388 S Cactus Ave,4
|
||||
179,193000000000000201,193000000000100201,SBD2,SBD2,US,CA,SAN BERNARDINO,92408-2805,UTC-8,1494 S WATERMAN AVE,15
|
||||
97,193000000000000202,193000000000100202,SCK1,SCK1,US,CA,STOCKTON,95215,UTC-8,4532 NEWCASTLE RD,20
|
||||
60,193000000000000203,193000000000100203,SCK3,SCK3,US,CA,MANTECA,95336,UTC-8,3565 N AIRPORT WAY MANTECA,54
|
||||
186,193000000000000204,193000000000100204,SCK4,SCK4,US,CA,STOCKTON,95215-8354,UTC-8,6001 S AUSTIN RD,10
|
||||
104,193000000000000205,193000000000100205,SCK8,SCK8,US,CA,OAKLEY,94561-2677,UTC-8,4700 WILBUR AVE,141
|
||||
266,193000000000000206,193000000000100206,SDF1,SDF1,US,KY,Campbellsville,42718,UTC-5,1050 South Columbia AVE,
|
||||
265,193000000000000207,193000000000100207,SDF2,SDF2,US,KY,Watterson Park,40218,UTC-5,4360 Robards Ln,
|
||||
264,193000000000000208,193000000000100208,SDF4,SDF4,US,KY,Shepherdsville,40165,UTC-5,376 Zappos.com Blvd,
|
||||
263,193000000000000209,193000000000100209,SDF6,SDF6,US,KY,Shepherdsville,40165,UTC-5,271 Omega Pkwy,
|
||||
258,193000000000000210,193000000000100210,SDF7,SDF7,US,KY,Shepherdsville,40165,UTC-5,300 Omicron Court,
|
||||
273,193000000000000211,193000000000100211,SDF8,SDF8,US,IN,Jeffersonville,47130,UTC-5,900 Patrol Rd,
|
||||
257,193000000000000212,193000000000100212,SDF9,SDF9,US,KY,Shepherdsville,40165,UTC-5,100 W. Thomas P. Echols Lane,
|
||||
205,193000000000000213,193000000000100213,SEA6,SEA6,US,WA,South Seattle,98144,UTC-8,2646 Rainier Ave.,
|
||||
204,193000000000000214,193000000000100214,SEA8,SEA8,US,WA,Northeast Bellevue,98005,UTC-8,1475 124th Avenue,
|
||||
295,193000000000000215,193000000000100215,SJC7,SJC7,US,CA,Tracy,95377-8906,UTC-8,188 Mountain House Parkway,30
|
||||
153,193000000000000216,193000000000100216,SLC2,SLC2,US,UT,West Jordan,84081,UTC-7,7148 W. Old Bingham Hwy,86
|
||||
13,193000000000000217,193000000000100217,SLC3,SLC3,US,UT,SALT LAKE CITY,84116-4413,UTC-7,355 N JOHN GLENN RD,185
|
||||
184,193000000000000218,193000000000100218,SMF3,SMF3,US,CA,Stockton,95206-8202,UTC-8,3923 S B ST,9
|
||||
105,193000000000000219,193000000000100219,SMF6,SMF6,US,CA,SACRAMENTO,95837-9109,UTC-8,4930 ALLBAUGH DR,28
|
||||
296,193000000000000220,193000000000100220,SNA4,SNA4,US,CA,Rialto,92376-3009,UTC-8,2496 W Walnut Ave,18
|
||||
109,193000000000000221,193000000000100221,STL3,STL3,US,MO,REPUBLIC,65738-2779,UTC-6,3200 E SAWYER RD,152
|
||||
194,193000000000000222,193000000000100222,STL4,STL4,US,IL,Edwardsville,62025-2815,UTC-6,3050 GATEWAY COMMERCE CENTER DR S,115
|
||||
122,193000000000000223,193000000000100223,SWF1,SWF1,US,NY,ROCK TAVERN,12575,UTC-5,635 INTERNATIONAL BLVD,134
|
||||
76,193000000000000224,193000000000100224,SWF2,SWF2,US,NY,Hopewell Junction,12533,UTC-5,76 Patriot Way Hopewell Junction,62
|
||||
41,193000000000000225,193000000000100225,TCY1,TCY1,US,CA,STOCKTON,95206,UTC-8,2690 East Arch Airport Road,139
|
||||
43,193000000000000226,193000000000100226,TCY2,TCY2,US,CA,STOCKTON,95215,UTC-8,6201 Newcastle Rd,138
|
||||
250,193000000000000227,193000000000100227,TEB3,TEB3,US,NJ,Logan Township,08085,UTC-5,2651 Oldmans Creek Rd,47
|
||||
130,193000000000000228,193000000000100228,TEB4,TEB4,US,NJ,PENNS GROVE,08069,UTC-5,747 COURSES LANDING RD,55
|
||||
249,193000000000000229,193000000000100229,TEB6,TEB6,US,NJ,Cranbury,08512,UTC-5,22 Hightstown Cranbury State Road,114
|
||||
156,193000000000000230,193000000000100230,TEB9,TEB9,US,NJ,Somerset,08873,UTC-5,601 Randolph Road,40
|
||||
65,193000000000000231,193000000000100231,TMB8,TMB8,US,FL,HOMESTEAD,33032-8597,UTC-5,27505 SW 132nd AVE,116
|
||||
18,193000000000000232,193000000000100232,TOL3,TOL3,US,OH,PERRYSBURG,43551,UTC-5,"9240 Fremont Pike 43551-4210 - PERRYSBURG, OH - United States",173
|
||||
284,193000000000000233,193000000000100233,TPA1,TPA1,US,FL,Ruskin,33570,UTC-5,3350 Laurel Ridge Ave.,
|
||||
283,193000000000000234,193000000000100234,TPA2,TPA2,US,FL,Lakeland,33811,UTC-5,1760 County Line Rd,22
|
||||
124,193000000000000235,193000000000100235,TPA3,TPA3,US,FL,Auburndale,33823,UTC-5,676 C. Fred Jones Boulevard,146
|
||||
272,193000000000000236,193000000000100236,TUL1,TUL1,US,KS,Coffeyville,67337,UTC-6,2654 North Highway 169,
|
||||
172,193000000000000237,193000000000100237,VGT2,VGT2,US,NV,LAS VEGAS,89115-2013,UTC-8,6401 Howdy Wells Ave,6
|
||||
228,193000000000000238,193000000000100238,VUBA,VUBA,US,PA,Scranton,18512,UTC-5,1000 Keystone Industrial Park,
|
||||
227,193000000000000239,193000000000100239,VUGA,VUGA,US,PA,Pittston,18643,UTC-5,508 Delaware Avenue West,
|
||||
82,193000000000000240,193000000000100240,WBW2,WBW2,US,PA,OLYPHANT,18447,UTC-5,1300 Corporate Way,119
|
||||
36,193000000000000241,193000000000100241,XAV3,XAV3,US,GA,PORT WENTWORTH,31407,UTC-5,"XAV3-120Crossgate Dr 31407-Port Wentworth, GA- United States",180
|
||||
37,193000000000000242,193000000000100242,XEW5,XEW5,US,NJ,Cinnaminson,08077-2015,UTC-5,995 Taylors Ln 08077-2015,163
|
||||
25,193000000000000243,193000000000100243,XIN5,XIN5,US,IN,Avon,46123-1700,UTC-5,8838 E COUNTY ROAD 100 S,182
|
||||
34,193000000000000244,193000000000100244,XLX1,XLX1,US,MD,HAGERSTOWN,21740,UTC-5,"1115 Wesel Blvd HAGERSTOWN,MD 21740,USA",160
|
||||
23,193000000000000245,193000000000100245,XLX6,XLX6,US,NC,PINEVILLE,28134,UTC-5,"12220 Carolina Logistics Drive PINEVILLE,NC 28134,USA",166
|
||||
63,193000000000000246,193000000000100246,XLX7,XLX7,US,CA,VICTORVILLE,92394,UTC-8,"18580 Gateway Dr,Victorville,CA 92394,USA",27
|
||||
26,193000000000000247,193000000000100247,XPB2,XPB2,US,FL,APOPKA,32712,UTC-5,"XPB2 - 4661 Apopka Logistics Pkwy 32712 - Apopka, FL - United States",165
|
||||
24,193000000000000248,193000000000100248,XRI3,XRI3,US,VA,Richmond,23234,UTC-5,"4949 Commerce Rd, Richmond, VA 23234",167
|
||||
213,193000000000000249,193000000000100249,XUSB,XUSB,US,TX,Fort Worth,76155,UTC-6,14900 Frye Rd.,
|
||||
226,193000000000000250,193000000000100250,XUSC,XUSC,US,PA,Carlisle,17013,UTC-5,40 Logistics Drive,
|
||||
297,193000000000000251,193000000000100251,XUSD,XUSD,US,CA,Stockton,95206,UTC-8,1909 Zephyr St,
|
||||
280,193000000000000252,193000000000100252,XUSE,XUSE,US,IN,Whitestown,46075,UTC-5,5100 S Indianapolis Rd,
|
||||
|
@@ -0,0 +1,55 @@
|
||||
source_row,warehouse_code,reject_reason,country_code,state_province,city,postal_code,timezone,address_line1,source_internal_id
|
||||
29,纽约仓一件代发,invalid_fba_code|non_fba_business_node,US,NJ,New Jersey,08817,UTC-5,"Door13-14,75 Carter Dr Edison,",NJDF
|
||||
30,纽约仓,invalid_fba_code|non_fba_business_node,US,NJ,New Jersey,08817,UTC-5,"13、14号门75 Carter Dr Edison,",
|
||||
42,海外仓打托,missing_city|missing_state|invalid_fba_code|non_fba_business_node,US,,,91730,,91730,13
|
||||
47,FEDEX,missing_city|missing_state|non_fba_business_node,US,,,92337,,10767,12
|
||||
48,UPS,missing_city|missing_state|non_fba_business_node,US,,,92337,,10767,11
|
||||
50,JAX3,missing_city|missing_state,US,,,32210,,JAX3-32210,111
|
||||
51,私人仓,missing_city|missing_state|invalid_fba_code|non_fba_business_node,US,,,私人仓,,私人仓,14
|
||||
52,洛杉矶仓一件代发,invalid_fba_code|non_fba_business_node,US,CA,RANCHO CUCAMONGA,91730,UTC-8,10767 7TH STREET,CADF
|
||||
53,XBN3,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,91
|
||||
54,XMI3,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,89
|
||||
55,XCH2,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,87
|
||||
56,CEG2,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,79
|
||||
57,HEA2,missing_city|missing_state,US,,,18031,,"650 BOULDER DR Suite A 18031-1536 - BREINIGSVILLE, PA - United States",77
|
||||
58,STL6,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,64
|
||||
59,HOU7,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,63
|
||||
61,SMF7,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,53
|
||||
62,IAK3,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,44
|
||||
64,AZA4,missing_city|missing_state|missing_address|missing_postal_code,US,,,,,,16
|
||||
66,TMB8-UPS,invalid_fba_code|non_fba_business_node,US,FL,Miami Gardens,33055,UTC-5,8675309 Jenny Lane,
|
||||
79,NJ3-WALMART,invalid_fba_code|non_fba_business_node,US,NJ,Pedricktown,08067,UTC-5,3 Sorbello Way,
|
||||
83,IUSP-AWD,invalid_fba_code,US,CA,HESPERIA,92344,UTC-8,8140 Caliente Rd,83
|
||||
85,LBE1-UPS,invalid_fba_code|non_fba_business_node,US,PA,New Stanton,15672,UTC-5,165 GLENN FOX RD,
|
||||
90,PHL5S-WALMART,invalid_fba_code|non_fba_business_node,US,PA,Greencastle,17225,UTC-5,1915 Ebberts Spg Ct,
|
||||
91,ORD1S-WALMART,invalid_fba_code|non_fba_business_node,US,IL,Joliet,60436,UTC-6,3501 Brandon Road,
|
||||
92,PSC2-UPS,invalid_fba_code|non_fba_business_node,US,OR,Portland,97242,UTC-8,9930 NE Alderwood Rd,
|
||||
96,ABQ2-UPS,invalid_fba_code|non_fba_business_node,US,AZ,GOODYEAR,85395,UTC-7,16601 W. SELLS DRIVE,
|
||||
101,LAN2-48917,invalid_fba_code,US,MI,LANSING,48917,UTC-5,6500 W Mt Hope Hwy,
|
||||
108,MCO1-WALMART,invalid_fba_code|non_fba_business_node,US,FL,Davenport,33897,UTC-5,5100 NORTH RIDGE TRAIL,
|
||||
111,MDW2-60433-3280,invalid_fba_code,US,IL,Joliet,60433-3280,UTC-6,250 Emerald Drive,
|
||||
112,VGT2-FEDEX,invalid_fba_code|non_fba_business_node,US,NV,LAS VEGAS,89115-201,UTC-8,6401 Howdy Wells Ave,
|
||||
116,ORF2-UPS,invalid_fba_code|non_fba_business_node,US,VA,Richmond,23297,UTC-5,9603 Coach Road,
|
||||
137,SWF2-10710,invalid_fba_code,US,NY,Yonkers,10710,UTC-5,557 Tuckahoe Road,
|
||||
138,ATL1-WALMART,invalid_fba_code|non_fba_business_node,US,GA,Atlanta,30349,UTC-5,"6055 S Fulton Pkwy,",
|
||||
139,IND3-亚马逊,invalid_fba_code|timezone_pending_confirmation,US,IN,Pkwy Plainfield,46168,,"717 Airtech Pkwy Plainfield IN 46168,USA",
|
||||
159,ORF2-FEDEX,invalid_fba_code|non_fba_business_node,US,VA,CHESAPEAKE,23321-1425,UTC-5,ORF2 - 5045 PORTSMOUTH BLVD,
|
||||
160,SWF2-12533,invalid_fba_code,US,NY,Hopewell Junction,12533-6159,UTC-5,76 Patriot Way,
|
||||
165,SMF1-WALMART,invalid_fba_code|non_fba_business_node,US,CA,Sacramento,95837,UTC-8,7000 Powerline Rd,
|
||||
166,MIA1-33054,invalid_fba_code,US,FL,Opa-Locka,33054,UTC-5,14000 NW 37th Avenue,
|
||||
167,JAX3-32221,invalid_fba_code,US,FL,Jacksonville,32221,UTC-5,13333 103rd Street,
|
||||
168,IND1-WALMART,invalid_fba_code|non_fba_business_node,US,IN,Avon,46123,UTC-5,9590 AllPoints Pkwy,
|
||||
169,IND2-WALMART,invalid_fba_code|non_fba_business_node,US,IN,Avon,46123,UTC-5,9360 AllPoints Pkwy,
|
||||
170,VGT2-UPS,invalid_fba_code|non_fba_business_node,US,CA,March Air Reserve Base,92518,UTC-8,20701 Krameria Ave,
|
||||
175,MEM1S-WALMART,invalid_fba_code|non_fba_business_node,US,MS,OLIVE BRANCH,38654,UTC-6,9200 Alexander Road,
|
||||
177,LAX1-WALMART,invalid_fba_code|non_fba_business_node,US,CA,Chino,91708,UTC-8,6750 Kimball Avenue,
|
||||
180,LAX2T-WALMART,invalid_fba_code|non_fba_business_node,US,CA,Chino,91708,UTC-8,6720 Kimball Ave,
|
||||
239,CMH1-11999,invalid_fba_code,US,OH,Etna,43062-7793,UTC-5,11999 National Road SW,
|
||||
251,ABE8-309,invalid_fba_code,US,NJ,Florence,08518-2802,UTC-5,309 Cedar Lane,
|
||||
252,ABE8-401,invalid_fba_code,US,NJ,Florence,08518,UTC-5,401 Independence Road,
|
||||
277,IND3-沃尔玛,invalid_fba_code,US,IN,Mccordsville,46055,UTC-5,"5756 Enterprise Dr, Mccordsville, IN 46055",
|
||||
278,IND2-AMAZON,invalid_fba_code,US,IN,Plainfield,46168,UTC-5,715 Airtech Pkwy,
|
||||
279,IND1-AMAZON,invalid_fba_code,US,IN,Whitestown,46075,UTC-5,4255 Anson Blvd,
|
||||
281,JAX3-32210,invalid_fba_code,US,FL,Jacksonville,32210-8686,UTC-5,13333 103rd Street,111
|
||||
282,MIA1-33182,invalid_fba_code,US,FL,Miami,33182,UTC-5,1900 NW 132nd Place Doral,
|
||||
290,SMF1-AMAZON,invalid_fba_code,US,CA,Sacramento,95835-9505,UTC-8,4900 W Elkhorn Blvd,
|
||||
|
@@ -0,0 +1,5 @@
|
||||
warehouse_id_seed,address_id_seed,warehouse_code,warehouse_name,warehouse_type,country_code,timezone,state_province,city,postal_code,address_line1,note
|
||||
193000000000200001,193000000000300001,CN-YIWU-01,义乌仓,DOMESTIC,CN,UTC+8,浙江省,义乌市,322000,义乌仓通用地址待维护,按用户确认生成通用格式;后续运营可维护真实地址。
|
||||
193000000000200002,193000000000300002,CN-DONGGUAN-01,东莞仓,DOMESTIC,CN,UTC+8,广东省,东莞市,523000,东莞仓通用地址待维护,按用户确认生成通用格式;后续运营可维护真实地址。
|
||||
193000000000200003,193000000000300003,US-LAX-01,洛杉矶仓,OVERSEAS,US,UTC-8,CA,Los Angeles,90001,Los Angeles warehouse generic address pending maintenance,按用户确认生成通用格式;后续运营可维护真实地址。
|
||||
193000000000200004,193000000000300004,US-NYC-01,纽约仓,OVERSEAS,US,UTC-5,NY,New York,10001,New York warehouse generic address pending maintenance,按用户确认生成通用格式;后续运营可维护真实地址。
|
||||
|
@@ -36,7 +36,7 @@ DELIMITER ;
|
||||
-- 早期 wh_warehouse 缺少的业务与审计字段。
|
||||
CALL add_column_if_missing('wh_warehouse', 'warehouse_type', 'VARCHAR(32) NOT NULL DEFAULT ''DOMESTIC'' COMMENT ''仓库类型'' AFTER warehouse_name');
|
||||
CALL add_column_if_missing('wh_warehouse', 'country_code', 'CHAR(2) NOT NULL DEFAULT ''CN'' COMMENT ''所在国家二字码'' AFTER warehouse_type');
|
||||
CALL add_column_if_missing('wh_warehouse', 'timezone', 'VARCHAR(64) NOT NULL DEFAULT ''Asia/Shanghai'' COMMENT ''IANA时区'' AFTER country_code');
|
||||
CALL add_column_if_missing('wh_warehouse', 'timezone', 'VARCHAR(64) NOT NULL DEFAULT ''UTC+8'' COMMENT ''UTC偏移量'' AFTER country_code');
|
||||
CALL add_column_if_missing('wh_warehouse', 'contact_name', 'VARCHAR(64) NULL AFTER timezone');
|
||||
CALL add_column_if_missing('wh_warehouse', 'contact_phone', 'VARCHAR(32) NULL AFTER contact_name');
|
||||
CALL add_column_if_missing('wh_warehouse', 'contact_email', 'VARCHAR(128) NULL AFTER contact_phone');
|
||||
|
||||
@@ -3,6 +3,19 @@
|
||||
SET NAMES utf8mb4;
|
||||
USE baiyue_warehouse;
|
||||
|
||||
-- 托盘展示字段增量:重量、颜色;平台接口不暴露内部状态。
|
||||
SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND column_name = 'weight_kg') = 0,
|
||||
'ALTER TABLE wh_pallet ADD COLUMN weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT ''托盘重量kg'' AFTER capacity_cbm',
|
||||
'SELECT 1');
|
||||
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
|
||||
|
||||
SET @sql = IF((SELECT COUNT(*) FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet' AND column_name = 'color') = 0,
|
||||
'ALTER TABLE wh_pallet ADD COLUMN color VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''托盘颜色'' AFTER weight_kg',
|
||||
'SELECT 1');
|
||||
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
|
||||
|
||||
-- 活动预占必须同时保证“一托一位”;A0 共享只放宽库位,不放宽托盘唯一性。
|
||||
SET @schema_name = DATABASE();
|
||||
DROP PROCEDURE IF EXISTS add_pallet_reservation_column_if_missing;
|
||||
@@ -41,6 +54,35 @@ UPDATE wh_location SET tenant_id = 0 WHERE tenant_id <> 0;
|
||||
UPDATE wh_location_reservation SET tenant_id = 0 WHERE tenant_id <> 0;
|
||||
UPDATE wh_location_occupancy SET tenant_id = 0 WHERE tenant_id <> 0;
|
||||
|
||||
-- 托盘编码在平台公共作用域全局唯一,避免不同仓库并发生成重复编码。
|
||||
DROP PROCEDURE IF EXISTS assert_pallet_code_globally_unique;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE assert_pallet_code_globally_unique()
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM wh_pallet
|
||||
GROUP BY tenant_id, pallet_code
|
||||
HAVING COUNT(*) > 1
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000'
|
||||
SET MESSAGE_TEXT = '托盘编码迁移失败:不同仓库存在重复编码,请先清理';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_pallet_code_globally_unique();
|
||||
DROP PROCEDURE IF EXISTS assert_pallet_code_globally_unique;
|
||||
|
||||
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet'
|
||||
AND index_name = 'uk_pallet_tenant_warehouse_code') > 0,
|
||||
'ALTER TABLE wh_pallet DROP INDEX uk_pallet_tenant_warehouse_code', 'SELECT 1');
|
||||
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
|
||||
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE() AND table_name = 'wh_pallet'
|
||||
AND index_name = 'uk_pallet_tenant_code') = 0,
|
||||
'ALTER TABLE wh_pallet ADD UNIQUE KEY uk_pallet_tenant_code (tenant_id, pallet_code)', 'SELECT 1');
|
||||
PREPARE statement FROM @sql; EXECUTE statement; DEALLOCATE PREPARE statement;
|
||||
|
||||
SET @sql = IF((SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = @schema_name
|
||||
AND table_name = 'wh_location_reservation'
|
||||
|
||||
@@ -15,6 +15,8 @@ CREATE TABLE IF NOT EXISTS wh_pallet (
|
||||
pallet_type VARCHAR(32) NOT NULL COMMENT '托盘类型',
|
||||
pallet_name VARCHAR(100) NULL COMMENT '托盘名称',
|
||||
capacity_cbm DECIMAL(18,6) NOT NULL COMMENT '托盘可用容积CBM',
|
||||
weight_kg DECIMAL(18,3) NOT NULL DEFAULT 0 COMMENT '托盘重量kg',
|
||||
color VARCHAR(32) NOT NULL DEFAULT '' COMMENT '托盘颜色',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'AVAILABLE' COMMENT '内部状态:AVAILABLE、RESERVED或OCCUPIED',
|
||||
remark VARCHAR(500) NULL COMMENT '备注',
|
||||
created_by BIGINT UNSIGNED NOT NULL COMMENT '创建人ID',
|
||||
@@ -24,7 +26,7 @@ CREATE TABLE IF NOT EXISTS wh_pallet (
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_pallet_tenant_warehouse_code (tenant_id, warehouse_id, pallet_code),
|
||||
UNIQUE KEY uk_pallet_tenant_code (tenant_id, pallet_code),
|
||||
KEY idx_pallet_available (tenant_id, warehouse_id, status, deleted),
|
||||
KEY idx_pallet_type (tenant_id, warehouse_id, pallet_type, status, deleted),
|
||||
CONSTRAINT fk_pallet_warehouse FOREIGN KEY (warehouse_id) REFERENCES wh_warehouse (id),
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- 二期托盘初始化数据。执行顺序:warehouse-seed.sql、phase2-pallet-location-schema.sql、增量迁移、本文件。
|
||||
SET NAMES utf8mb4;
|
||||
USE baiyue_warehouse;
|
||||
|
||||
-- 编码由平台维护接口自动生成;种子固定为 TP-001、TP-002。
|
||||
INSERT INTO wh_pallet(id,tenant_id,warehouse_id,pallet_code,pallet_type,capacity_cbm,weight_kg,color,status,remark,created_by,updated_by)
|
||||
VALUES
|
||||
(190000000000000101,0,190000000000000001,'TP-001','木托盘',1.200000,8.000,'黄色','AVAILABLE',NULL,0,0),
|
||||
(190000000000000102,0,190000000000000001,'TP-002','胶托盘',2.400000,12.000,'黑色','AVAILABLE',NULL,0,0)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
tenant_id=0,
|
||||
warehouse_id=VALUES(warehouse_id),
|
||||
pallet_type=VALUES(pallet_type),
|
||||
capacity_cbm=VALUES(capacity_cbm),
|
||||
weight_kg=VALUES(weight_kg),
|
||||
color=VALUES(color),
|
||||
remark=VALUES(remark);
|
||||
@@ -0,0 +1,45 @@
|
||||
# 平台公共仓库主数据
|
||||
|
||||
## 范围
|
||||
|
||||
- 正式脚本:`warehouse-public-master-data.sql`。
|
||||
- 清洗通过清单:`master-data/fba-public-addresses.accepted.csv`。
|
||||
- 拒绝清单:`master-data/fba-public-addresses.rejected.csv`。
|
||||
- 四个固定仓清单:`master-data/platform-fixed-warehouses.csv`。
|
||||
|
||||
本脚本写平台公共作用域 `tenant_id=0` 的四个固定作业仓和 FBA 地址主数据,不写演示仓,不写 Admin 代码。当前内容已内联到 `deploy/sql/头程数据库统一增量-MySQL8.sql`,测试环境应执行固定入口写入正式主数据。
|
||||
|
||||
## 清洗规则
|
||||
|
||||
- 读取 `C:\Users\BY-OY\Downloads\FBA地址表.xls`,原始文件只读。
|
||||
- 对核心字段去首尾空格。
|
||||
- FBA 编码转大写,接受 `^[A-Z0-9]{3,5}$`。
|
||||
- 邮编按文本保存,保留 `31216-6427` 这类后缀。
|
||||
- 城市、州、详细地址、邮编任一缺失则拒绝。
|
||||
- 带 `UPS`、`FEDEX`、`WALMART`、`一件代发`、`私人仓`、`纽约仓`、`洛杉矶仓`、`海外仓打托` 等非 FBA 业务节点语义的记录拒绝。
|
||||
- 多时区州必须按城市映射时区;无法可靠判断时列入拒绝清单的 `timezone_pending_confirmation`,不使用州级默认值。
|
||||
- 数据库存储固定 UTC 偏移量(如 `UTC+8`、`UTC-8`),当前仅作为维护和展示字段使用。
|
||||
|
||||
## 固定仓规则
|
||||
|
||||
- 国内固定仓:义乌、东莞,时区固定 `UTC+8`。
|
||||
- 海外固定仓:洛杉矶、纽约,分别使用 `UTC-8`、`UTC-5`。
|
||||
- 地址按通用占位格式写入,后续运营维护真实地址后,重复执行脚本不会把地址覆盖回占位文本。
|
||||
- 固定仓本轮不批量创建 `wh_service_capability`,避免在真实作业规则确认前扩大业务可操作范围。
|
||||
|
||||
## 写入语义
|
||||
|
||||
- 新环境使用稳定 ID 区间:FBA 仓库 `193000000000000001+`、FBA 地址 `193000000000100001+`,固定仓库 `193000000000200001+`、固定仓地址 `193000000000300001+`。
|
||||
- 已有环境同编码仓库存在时,通过 `tenant_id=0 + warehouse_code + deleted=0` 解析实际 `warehouse_id` 后写地址。
|
||||
- 重复执行不会把停用仓库重新启用。
|
||||
- 重复执行不会覆盖运营维护的联系人、电话、邮箱、备注。
|
||||
- FBA 地址不创建 `RECEIVING`、`STORAGE`、`PICK_PACK`、`RETURN` 等平台作业能力。
|
||||
- 如果稳定种子 ID 已被其他数据占用,或同编码存在已删除仓库,脚本会 `SIGNAL` 终止,交由人工确认。
|
||||
|
||||
## 当前结果
|
||||
|
||||
- Excel 数据行:306。
|
||||
- 接受:252。
|
||||
- 拒绝:54。
|
||||
- 时区待确认:1,且该行同时存在无效编码和异常城市文本。
|
||||
- 固定仓:4,分别为义乌、东莞、洛杉矶、纽约。
|
||||
@@ -0,0 +1,663 @@
|
||||
SET NAMES utf8mb4;
|
||||
USE baiyue_warehouse;
|
||||
|
||||
-- 正式平台公共仓库主数据:固定作业仓 + FBA 地址库。
|
||||
-- 来源:C:\Users\BY-OY\Downloads\FBA地址表.xls,只读清洗后生成。
|
||||
-- 接受 252 条,拒绝 54 条;拒绝清单见 master-data/fba-public-addresses.rejected.csv。
|
||||
-- 四个固定作业仓按通用占位地址写入;清单见 master-data/platform-fixed-warehouses.csv。
|
||||
-- FBA 是目的地址主数据,不创建 RECEIVING、STORAGE、PICK_PACK、RETURN 等平台作业能力。
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fixed_platform_warehouse;
|
||||
CREATE TEMPORARY TABLE tmp_wh_fixed_platform_warehouse (
|
||||
warehouse_id_seed BIGINT UNSIGNED NOT NULL,
|
||||
address_id_seed BIGINT UNSIGNED NOT NULL,
|
||||
warehouse_code VARCHAR(50) NOT NULL,
|
||||
warehouse_name VARCHAR(120) NOT NULL,
|
||||
warehouse_type VARCHAR(32) NOT NULL,
|
||||
country_code CHAR(2) NOT NULL,
|
||||
timezone VARCHAR(64) NOT NULL,
|
||||
state_province VARCHAR(100) NOT NULL,
|
||||
city VARCHAR(100) NOT NULL,
|
||||
postal_code VARCHAR(20) NOT NULL,
|
||||
address_line1 VARCHAR(255) NOT NULL,
|
||||
PRIMARY KEY (warehouse_code),
|
||||
UNIQUE KEY uk_tmp_fixed_warehouse_id_seed (warehouse_id_seed),
|
||||
UNIQUE KEY uk_tmp_fixed_address_id_seed (address_id_seed)
|
||||
) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
INSERT INTO tmp_wh_fixed_platform_warehouse
|
||||
(warehouse_id_seed,address_id_seed,warehouse_code,warehouse_name,warehouse_type,country_code,timezone,state_province,city,postal_code,address_line1)
|
||||
VALUES
|
||||
(193000000000200001,193000000000300001,'CN-YIWU-01','义乌仓','DOMESTIC','CN','UTC+8','浙江省','义乌市','322000','义乌仓通用地址待维护'),
|
||||
(193000000000200002,193000000000300002,'CN-DONGGUAN-01','东莞仓','DOMESTIC','CN','UTC+8','广东省','东莞市','523000','东莞仓通用地址待维护'),
|
||||
(193000000000200003,193000000000300003,'US-LAX-01','洛杉矶仓','OVERSEAS','US','UTC-8','CA','Los Angeles','90001','Los Angeles warehouse generic address pending maintenance'),
|
||||
(193000000000200004,193000000000300004,'US-NYC-01','纽约仓','OVERSEAS','US','UTC-5','NY','New York','10001','New York warehouse generic address pending maintenance');
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_wh_fixed_platform_warehouse_seed;
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE assert_wh_fixed_platform_warehouse_seed()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM tmp_wh_fixed_platform_warehouse) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓源数量不是4';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tmp_wh_fixed_platform_warehouse
|
||||
WHERE warehouse_code <> UPPER(TRIM(warehouse_code))
|
||||
OR warehouse_type NOT IN ('DOMESTIC', 'OVERSEAS')
|
||||
OR country_code NOT IN ('CN', 'US')
|
||||
OR TRIM(timezone) = ''
|
||||
OR TRIM(city) = ''
|
||||
OR TRIM(state_province) = ''
|
||||
OR TRIM(address_line1) = ''
|
||||
OR TRIM(postal_code) = ''
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓主数据存在未清洗或必填缺失记录';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM wh_warehouse existing
|
||||
JOIN tmp_wh_fixed_platform_warehouse source
|
||||
ON existing.id = source.warehouse_id_seed
|
||||
AND (existing.tenant_id <> 0 OR existing.warehouse_code <> source.warehouse_code OR existing.deleted <> 0)
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓仓库种子ID与既有数据冲突';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM wh_warehouse_address existing
|
||||
JOIN tmp_wh_fixed_platform_warehouse source
|
||||
ON existing.id = source.address_id_seed
|
||||
LEFT JOIN wh_warehouse parent
|
||||
ON parent.id = existing.warehouse_id
|
||||
AND parent.tenant_id = existing.tenant_id
|
||||
WHERE existing.tenant_id <> 0
|
||||
OR existing.deleted <> 0
|
||||
OR parent.warehouse_code IS NULL
|
||||
OR parent.warehouse_code <> source.warehouse_code
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址种子ID与既有数据冲突';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM wh_warehouse existing
|
||||
JOIN tmp_wh_fixed_platform_warehouse source
|
||||
ON existing.tenant_id = 0
|
||||
AND existing.warehouse_code = source.warehouse_code
|
||||
AND existing.deleted = 1
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓存在同编码已删除仓库,需人工确认后恢复或清理';
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
CALL assert_wh_fixed_platform_warehouse_seed();
|
||||
DROP PROCEDURE IF EXISTS assert_wh_fixed_platform_warehouse_seed;
|
||||
|
||||
INSERT INTO wh_warehouse (
|
||||
id, tenant_id, warehouse_code, warehouse_name, warehouse_type, country_code, timezone,
|
||||
contact_name, contact_phone, contact_email, status, remark,
|
||||
created_by, created_at, updated_by, updated_at, deleted, version
|
||||
)
|
||||
SELECT
|
||||
source.warehouse_id_seed,
|
||||
0,
|
||||
source.warehouse_code,
|
||||
source.warehouse_name,
|
||||
source.warehouse_type,
|
||||
source.country_code,
|
||||
source.timezone,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
'ACTIVE',
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
0
|
||||
FROM tmp_wh_fixed_platform_warehouse source
|
||||
ON DUPLICATE KEY UPDATE
|
||||
warehouse_name = VALUES(warehouse_name),
|
||||
warehouse_type = VALUES(warehouse_type),
|
||||
country_code = VALUES(country_code),
|
||||
timezone = VALUES(timezone),
|
||||
updated_at = CURRENT_TIMESTAMP(3);
|
||||
|
||||
INSERT INTO wh_warehouse_address (
|
||||
id, tenant_id, warehouse_id, address_type, country_code, state_province, city,
|
||||
district, postal_code, address_line1, address_line2, latitude, longitude,
|
||||
created_by, created_at, updated_by, updated_at, deleted, version
|
||||
)
|
||||
SELECT
|
||||
source.address_id_seed,
|
||||
0,
|
||||
warehouse.id,
|
||||
'OPERATING',
|
||||
source.country_code,
|
||||
source.state_province,
|
||||
source.city,
|
||||
NULL,
|
||||
source.postal_code,
|
||||
source.address_line1,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
0
|
||||
FROM tmp_wh_fixed_platform_warehouse source
|
||||
JOIN wh_warehouse warehouse
|
||||
ON warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_code = source.warehouse_code
|
||||
AND warehouse.deleted = 0
|
||||
ON DUPLICATE KEY UPDATE
|
||||
tenant_id = VALUES(tenant_id);
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fba_public_master_data;
|
||||
CREATE TEMPORARY TABLE tmp_wh_fba_public_master_data (
|
||||
warehouse_id_seed BIGINT UNSIGNED NOT NULL,
|
||||
address_id_seed BIGINT UNSIGNED NOT NULL,
|
||||
warehouse_code VARCHAR(50) NOT NULL,
|
||||
warehouse_name VARCHAR(120) NOT NULL,
|
||||
state_province VARCHAR(100) NOT NULL,
|
||||
city VARCHAR(100) NOT NULL,
|
||||
postal_code VARCHAR(20) NOT NULL,
|
||||
timezone VARCHAR(64) NOT NULL,
|
||||
address_line1 VARCHAR(255) NOT NULL,
|
||||
source_row INT NOT NULL,
|
||||
PRIMARY KEY (warehouse_code),
|
||||
UNIQUE KEY uk_tmp_fba_warehouse_id_seed (warehouse_id_seed),
|
||||
UNIQUE KEY uk_tmp_fba_address_id_seed (address_id_seed)
|
||||
) ENGINE=MEMORY DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
INSERT INTO tmp_wh_fba_public_master_data
|
||||
(warehouse_id_seed,address_id_seed,warehouse_code,warehouse_name,state_province,city,postal_code,timezone,address_line1,source_row)
|
||||
VALUES
|
||||
(193000000000000001,193000000000100001,'ABE2','ABE2','PA','Breinigsville','18031','UTC-5','705 Boulder Drive',238),
|
||||
(193000000000000002,193000000000100002,'ABE3','ABE3','PA','Breinigsville','18031','UTC-5','650 Boulder Drive',237),
|
||||
(193000000000000003,193000000000100003,'ABE4','ABE4','PA','Easton','18045','UTC-5','1610 Van Buren Road',134),
|
||||
(193000000000000004,193000000000100004,'ABE5','ABE5','PA','Harrisburg','17112','UTC-5','6455 Allentown Boulevard',231),
|
||||
(193000000000000005,193000000000100005,'ABE8','ABE8','NJ','Florence','08518','UTC-5','401 Independence Road 08518-2200',125),
|
||||
(193000000000000006,193000000000100006,'ABQ2','ABQ2','NM','LOS LUNAS','87031','UTC-7','6251 Pioneer Trail NW',99),
|
||||
(193000000000000007,193000000000100007,'ACY2','ACY2','NJ','BURLINGTON','08016','UTC-5','1101 E PEARL ST',84),
|
||||
(193000000000000008,193000000000100008,'AFW1','AFW1','TX','FORT WORTH','76131','UTC-6','1851 NE LOOP 820',141),
|
||||
(193000000000000009,193000000000100009,'AFW5','AFW5','TX','BALCH SPRINGS','75180-1636','UTC-6','12374 LAKE JUNE RD',27),
|
||||
(193000000000000010,193000000000100010,'AKR1','AKR1','OH','Canton','44705','UTC-5','4747 Rebar Ave NE',148),
|
||||
(193000000000000011,193000000000100011,'ALB1','ALB1','NY','Castleton','12033-9634','UTC-5','1835 US Route 9',174),
|
||||
(193000000000000012,193000000000100012,'AMA1','AMA1','TX','Amarillo','79108','UTC-6','8590 NE 24th Avenue',129),
|
||||
(193000000000000013,193000000000100013,'ATL2N','ATL2N','GA','Carrollton','30117','UTC-5','3101 N. Highway 27',31),
|
||||
(193000000000000014,193000000000100014,'ATL3','ATL3','GA','Pendergrass','30567','UTC-5','445 Valentine Industrial Parkway',147),
|
||||
(193000000000000015,193000000000100015,'ATL6','ATL6','GA','East Point','30344','UTC-5','4200 North Commerce',202),
|
||||
(193000000000000016,193000000000100016,'AVP1','AVP1','PA','Hazle Township','18202-9361','UTC-5','550 Oak Ridge Road',233),
|
||||
(193000000000000017,193000000000100017,'AVP3','AVP3','PA','GOULDSBORO','18424-9492','UTC-5','298 1ST AVE',232),
|
||||
(193000000000000018,193000000000100018,'BDL1','BDL1','CT','Windsor','06095','UTC-5','801 Day Hill Rd.',289),
|
||||
(193000000000000019,193000000000100019,'BFI1','BFI1','WA','Sumner','98390','UTC-8','1800 140th Avenue E.',208),
|
||||
(193000000000000020,193000000000100020,'BFI3','BFI3','WA','Dupont','98327-9607','UTC-8','2700 Center Drive',207),
|
||||
(193000000000000021,193000000000100021,'BFI7','BFI7','WA','Sumner','98390','UTC-8','1901 140th Ave E',206),
|
||||
(193000000000000022,193000000000100022,'BJC1','BJC1','CO','AURORA','80019','UTC-7','6300 N Powhaton Road',11),
|
||||
(193000000000000023,193000000000100023,'BNA1','BNA1','TN','Lebanon','37090','UTC-6','14840 Central Pike Suite 190',221),
|
||||
(193000000000000024,193000000000100024,'BNA2','BNA2','TN','Lebanon','37090','UTC-6','500 Duke DR',131),
|
||||
(193000000000000025,193000000000100025,'BNA3','BNA3','TN','Murfreesboro','37127','UTC-6','2020 Joe B Jackson Pkwy',140),
|
||||
(193000000000000026,193000000000100026,'BNA4','BNA4','TN','Nashville','37217','UTC-6','50 airways Blvd',220),
|
||||
(193000000000000027,193000000000100027,'BNA6','BNA6','TN','CLARKSVILLE','37040-5502','UTC-6','3875 GUTHRIE HWY',110),
|
||||
(193000000000000028,193000000000100028,'BOS1','BOS1','NH','Nashua','03063','UTC-5','10 State St.',243),
|
||||
(193000000000000029,193000000000100029,'BOS2','BOS2','MA','Drive Stoughton','02072','UTC-5','1000 Tech Centre',193),
|
||||
(193000000000000030,193000000000100030,'BOS7','BOS7','MA','Fall River','02720','UTC-5','1180 Innovation Way',192),
|
||||
(193000000000000031,193000000000100031,'BWI1','BWI1','VA','Sterling','20166-2058','UTC-5','45121 Global Plaza',242),
|
||||
(193000000000000032,193000000000100032,'BWI2','BWI2','MD','Baltimore','21224','UTC-5','2010 Broening Hwy',241),
|
||||
(193000000000000033,193000000000100033,'BWI4','BWI4','VA','CLEARBROOK','22624','UTC-5','165 Business Blvd',102),
|
||||
(193000000000000034,193000000000100034,'CAE1','CAE1','SC','West Columbia','29172','UTC-5','4400 12TH STREET EXT',225),
|
||||
(193000000000000035,193000000000100035,'CHA1','CHA1','TN','Chattanooga','37416','UTC-5','7200 Discovery Drive',223),
|
||||
(193000000000000036,193000000000100036,'CHA2','CHA2','TN','Charleston','37310','UTC-5','225 Infinity Dr NW',222),
|
||||
(193000000000000037,193000000000100037,'CHO1','CHO1','VA','FISHERSVILLE','22939','UTC-5','32 TRADER RD',75),
|
||||
(193000000000000038,193000000000100038,'CLT2','CLT2','NC','Charlotte','28214-8082','UTC-5','10240 Old Dowd Rd',191),
|
||||
(193000000000000039,193000000000100039,'CLT3','CLT3','NC','CONCORD','28027','UTC-5','6500 Davidson Hwy',114),
|
||||
(193000000000000040,193000000000100040,'CMH2','CMH2','OH','Groveport','43125-9016','UTC-5','6050 Gateway Court',240),
|
||||
(193000000000000041,193000000000100041,'CMH3','CMH3','OH','Monroe','45050-1848','UTC-5','700 GATEWAY BLVD',181),
|
||||
(193000000000000042,193000000000100042,'CVG1','CVG1','KY','Hebron','41048','UTC-5','1155 Worldwide Blvd',262),
|
||||
(193000000000000043,193000000000100043,'CVG2','CVG2','KY','Hebron','41048','UTC-5','1600 Worldwide Blvd',261),
|
||||
(193000000000000044,193000000000100044,'CVG3','CVG3','KY','Hebron','41048','UTC-5','3680 Langley Dr.',260),
|
||||
(193000000000000045,193000000000100045,'CVG5','CVG5','KY','Hebron','41048','UTC-5','2285 Litton Lane',259),
|
||||
(193000000000000046,193000000000100046,'DCA6','DCA6','MD','BALTIMORE','21219','UTC-5','6001 Bethlehem Blvd',135),
|
||||
(193000000000000047,193000000000100047,'DEN2','DEN2','CO','Aurora','80019','UTC-7','22205 East 19th Ave',123),
|
||||
(193000000000000048,193000000000100048,'DEN8','DEN8','CO','Aurora','80018','UTC-7','21000 E 13th Ave',120),
|
||||
(193000000000000049,193000000000100049,'DET1','DET1','MI','Livonia','48150-1543','UTC-5','39000 Amrhein Road',189),
|
||||
(193000000000000050,193000000000100050,'DET2','DET2','MI','Shelby Township','48317-1318','UTC-5','50500 Mound Rd',182),
|
||||
(193000000000000051,193000000000100051,'DFW5S','DFW5S','TX','Lancaster','75146','UTC-6','2500 E Belt Line Rd,',143),
|
||||
(193000000000000052,193000000000100052,'DFW6','DFW6','TX','Coppell','75019','UTC-6','940 W Bethel Road',219),
|
||||
(193000000000000053,193000000000100053,'DFW8','DFW8','TX','DFW Airport','75261','UTC-6','2700 Regent Blvd',217),
|
||||
(193000000000000054,193000000000100054,'EWR4','EWR4','NJ','Robbinsville','08691','UTC-5','50 New Canton Way',256),
|
||||
(193000000000000055,193000000000100055,'EWR5','EWR5','NJ','Avenel','07001','UTC-5','301 Blair Rd.',255),
|
||||
(193000000000000056,193000000000100056,'EWR7','EWR7','NJ','Avenel','07001','UTC-5','301 Blair Rd.',254),
|
||||
(193000000000000057,193000000000100057,'EWR9','EWR9','NJ','Carteret','07008','UTC-5','8003 Industrial Parkway',253),
|
||||
(193000000000000058,193000000000100058,'FAT2','FAT2','CA','VISALIA','93291-1882','UTC-8','3315 N KELSEYST',117),
|
||||
(193000000000000059,193000000000100059,'FOE1','FOE1','KS','KANSAS CITY','66109-3665','UTC-6','9400 LEAVENWORTH RD',176),
|
||||
(193000000000000060,193000000000100060,'FTW1','FTW1','TX','Dallas','75241','UTC-6','33333 Lyndon B Johnson FREEWAY',212),
|
||||
(193000000000000061,193000000000100061,'FTW2','FTW2','TX','Coppell','75019','UTC-6','2701 West Bethel Road',211),
|
||||
(193000000000000062,193000000000100062,'FTW3','FTW3','TX','Fort Worth','76177-2517','UTC-6','15201 Heritage Pkwy',89),
|
||||
(193000000000000063,193000000000100063,'FTW5','FTW5','TX','Forney','75126','UTC-6','1475 AKRON WAY',121),
|
||||
(193000000000000064,193000000000100064,'FWA4','FWA4','IN','FORT WAYNE','46809-9771','UTC-5','9798 Smith Road',171),
|
||||
(193000000000000065,193000000000100065,'GEG2','GEG2','WA','SPOKANE','99216','UTC-8','18007 E Garland Ave',151),
|
||||
(193000000000000066,193000000000100066,'GEU2','GEU2','AZ','Glendale','85307','UTC-7','15301 West Northern Avenue',67),
|
||||
(193000000000000067,193000000000100067,'GEU3','GEU3','AZ','BUCKEYE','85396','UTC-7','18900 W McDowell Road',78),
|
||||
(193000000000000068,193000000000100068,'GEU5','GEU5','AZ','GOODYEAR','85395','UTC-7','4660 North Cotton Lane',68),
|
||||
(193000000000000069,193000000000100069,'GSO1','GSO1','NC','KERNERSVILLE','27284','UTC-5','1656 OLD GREENSBORO RD',164),
|
||||
(193000000000000070,193000000000100070,'GSP1','GSP1','SC','Spartanburg','29303','UTC-5','402 John Dodd Rd',224),
|
||||
(193000000000000071,193000000000100071,'GYR2','GYR2','AZ','GOODYEAR','85395-7672','UTC-7','17341 W MINNEZONA AVE',188),
|
||||
(193000000000000072,193000000000100072,'GYR3','GYR3','AZ','PHOENIX','85043-2356','UTC-7','8181 W ROOSEVELT ST',187),
|
||||
(193000000000000073,193000000000100073,'HGR6','HGR6','MD','HAGERSTOWN','21740-7301','UTC-5','55 West Oak Ridge Drive',80),
|
||||
(193000000000000074,193000000000100074,'HIA1','HIA1','PA','MIDDLETOWN','17057','UTC-5','3327 E Harrisburg Pik',87),
|
||||
(193000000000000075,193000000000100075,'HLI2','HLI2','CA','HOLLISTER','95023','UTC-8','600 Ernie Drive',44),
|
||||
(193000000000000076,193000000000100076,'HOU1','HOU1','TX','Humble','77338','UTC-6','8120 Humble Westfield Rd',216),
|
||||
(193000000000000077,193000000000100077,'HOU2','HOU2','TX','Houston','77038-2324','UTC-6','10550 Ella Blvd.',215),
|
||||
(193000000000000078,193000000000100078,'HOU3','HOU3','TX','Brookshire','77423','UTC-6','31555 Highway 90 E',214),
|
||||
(193000000000000079,193000000000100079,'HOU8','HOU8','TX','FRESNO','77545','UTC-6','2303 Hurricane Lane',154),
|
||||
(193000000000000080,193000000000100080,'HSV1','HSV1','AL','Madison','35756','UTC-6','7817 Greenbrier Road',142),
|
||||
(193000000000000081,193000000000100081,'HSV2','HSV2','AL','MADISON','35756','UTC-6','28869 Fanning Dr NW',126),
|
||||
(193000000000000082,193000000000100082,'IAH3','IAH3','TX','Houston','77032','UTC-6','15525 Milner Road',155),
|
||||
(193000000000000083,193000000000100083,'IAZ1','IAZ1','AZ','GOODYEAR','85395-4515','UTC-7','4660 N COTTON LN 85395-4515 - GOODYEAR, AZ - United States',3),
|
||||
(193000000000000084,193000000000100084,'ICT2','ICT2','KS','PARK CITY','67219','UTC-6','7130 N BROADWAY AVE',136),
|
||||
(193000000000000085,193000000000100085,'IGA3','IGA3','GA','MACON','31216-6427','UTC-5','7001 Skipper Rd 31216-6427 MACON, GA - United States',2),
|
||||
(193000000000000086,193000000000100086,'IGQ2','IGQ2','IL','University park','60484','UTC-6','23257 S. Central Avenue',152),
|
||||
(193000000000000087,193000000000100087,'ILG1','ILG1','DE','NEW CASTLE','19720','UTC-5','780 S. DuPont Highway',119),
|
||||
(193000000000000088,193000000000100088,'IMO1','IMO1','MO','LIBERTY','64068-8933','UTC-6','2361 S Withers RD',10),
|
||||
(193000000000000089,193000000000100089,'IMS1','IMS1','MS','OLIVE BRANCH','38654','UTC-6','IMS1-11505 Progress Way,38654-2510,OLIVE BRANCH,MS,US',6),
|
||||
(193000000000000090,193000000000100090,'INC1','INC1','NC','FAYETTEVILLE','28303-2215','UTC-5','6309 Bragg Blvd 28303-2215 - FAYETTEVILLE, NC - United States',5),
|
||||
(193000000000000091,193000000000100091,'IND2','IND2','IN','Plainfield','46168-7442','UTC-5','715 Airtech Pkwy',88),
|
||||
(193000000000000092,193000000000100092,'IND4','IND4','IN','Indianapolis','46231','UTC-5','710 S. Girls School Rd',276),
|
||||
(193000000000000093,193000000000100093,'IND5','IND5','IN','Plainfield','46168','UTC-5','800 Perry Road',275),
|
||||
(193000000000000094,193000000000100094,'IND6','IND6','IN','Jeffersonville','47130','UTC-5','1453 E 10th St',274),
|
||||
(193000000000000095,193000000000100095,'IND9','IND9','IN','GREENWOOD','46143-7830','UTC-5','1151 S GRAHAM RD',173),
|
||||
(193000000000000096,193000000000100096,'ITX3','ITX3','TX','AMARILLO','79108-7749','UTC-6','ITX3-8590 NE 24th Ave,79108-7749,AMARILLO,TX,US',9),
|
||||
(193000000000000097,193000000000100097,'IUSF','IUSF','TX','DALLAS','75241','UTC-6','3351 Balmorhea Dr',22),
|
||||
(193000000000000098,193000000000100098,'IUSJ','IUSJ','CA','BEAUMONT','92223','UTC-8','36900 West 4th Street',69),
|
||||
(193000000000000099,193000000000100099,'IUSP','IUSP','CA','HESPERIA,','92344','UTC-8','8140 Caliente Rd',74),
|
||||
(193000000000000100,193000000000100100,'IUSQ','IUSQ','CA','REDLANDS','92374','UTC-8','2125 W San Bernardino Ave',70),
|
||||
(193000000000000101,193000000000100101,'IUSR','IUSR','SC','COWPENS','29330','UTC-5','1120 Mt Olive Rd',40),
|
||||
(193000000000000102,193000000000100102,'IUST','IUST','PA','GREENCASTLE','17225','UTC-5','3PL AWD (Greencastle, PA, US) 15651 Greenmount RdGREENCASTLE, PA 17225 United States',39),
|
||||
(193000000000000103,193000000000100103,'IUSW','IUSW','CA','Hesperia','92344','UTC-8','8130 Caliente Rd 92344 - Hesperia, CA - United States',33),
|
||||
(193000000000000104,193000000000100104,'IUTE','IUTE','AZ','GOODYEAR','85338','UTC-7','16155 W Elwood St',21),
|
||||
(193000000000000105,193000000000100105,'IUTJ','IUTJ','CA','Fontana','92335','UTC-8','9253 Dreamland Drive 92335 - Fontana, CA - United',8),
|
||||
(193000000000000106,193000000000100106,'IUTL','IUTL','CA','Moreno Valley','92551','UTC-8','24405 Krameria Ave 92551 - Moreno Valley, CA - United States',7),
|
||||
(193000000000000107,193000000000100107,'IWA6','IWA6','WA','PASCO','99301-6161','UTC-8','1202 S Road 40 E 99301-6161 - PASCO, WA - United States',4),
|
||||
(193000000000000108,193000000000100108,'JOT1','JOT1','CA','SAN BERNARDINO','92408','UTC-8','1494 S WATERMAN AVE',72),
|
||||
(193000000000000109,193000000000100109,'JVL1','JVL1','WI','BELOIT','53511','UTC-6','1255 Gateway Blvd',150),
|
||||
(193000000000000110,193000000000100110,'KRB4','KRB4','CA','PERRIS','92571-9722','UTC-8','4501 PATTERSON AVE',178),
|
||||
(193000000000000111,193000000000100111,'KY1','KY1','KY','Shepherdsville','40165','UTC-5','120 Velocity Way.,',161),
|
||||
(193000000000000112,193000000000100112,'LAN2','LAN2','MI','LANSING','48917','UTC-5','6500 W Mt Hope Hwy',100),
|
||||
(193000000000000113,193000000000100113,'LAS1','LAS1','NV','HENDERSON','89044','UTC-8','12300 Bermuda Road',247),
|
||||
(193000000000000114,193000000000100114,'LAS2','LAS2','NV','Las Vegas','89030','UTC-8','3837 Bay Lake Trail, Suite 111 North',246),
|
||||
(193000000000000115,193000000000100115,'LAS6','LAS6','NV','Las Vegas','89115','UTC-8','4550 Nexus Way',132),
|
||||
(193000000000000116,193000000000100116,'LAX2T','LAX2T','CA','CHINO','91708','UTC-8','6730 Kimball Ave',162),
|
||||
(193000000000000117,193000000000100117,'LAX9','LAX9','CA','FONTANA','92337','UTC-8','11263 Oleander Ave',183),
|
||||
(193000000000000118,193000000000100118,'LBE1','LBE1','PA','Greensburg','15605','UTC-5','165 Glen Fox Road',86),
|
||||
(193000000000000119,193000000000100119,'LEX1','LEX1','KY','Lexington','40511','UTC-5','1850 Mercer RD',268),
|
||||
(193000000000000120,193000000000100120,'LEX2','LEX2','KY','Lexington','40511','UTC-5','172 Trade St.',267),
|
||||
(193000000000000121,193000000000100121,'LFT1','LFT1','LA','CARENCRO','70520-5976','UTC-6','3550 NE EVANGELINE TRWY',107),
|
||||
(193000000000000122,193000000000100122,'LGB3','LGB3','CA','Eastvale','91752','UTC-8','4950 Goodman Way',291),
|
||||
(193000000000000123,193000000000100123,'LGB4','LGB4','CA','Redlands','92374','UTC-8','27517 Pioneer Avenue',292),
|
||||
(193000000000000124,193000000000100124,'LGB6','LGB6','CA','Riverside','92518-1513','UTC-8','20901 Krameria Ave',293),
|
||||
(193000000000000125,193000000000100125,'LGB8','LGB8','CA','Rialto','92376','UTC-8','1568 N Linden Ave',294),
|
||||
(193000000000000126,193000000000100126,'LIT2','LIT2','AR','NORTH LITTLE ROCK','72117','UTC-6','13001 HIGHWAY 70',106),
|
||||
(193000000000000127,193000000000100127,'MCC1','MCC1','CA','RANCHO CORDOVA','95742','UTC-8','3300 Rancho Cordova Pkwy 95742 - RANCHO CORDOVA, CA - United States',32),
|
||||
(193000000000000128,193000000000100128,'MCE1','MCE1','CA','TURLOCK','95380','UTC-8','FULKERTH RD',157),
|
||||
(193000000000000129,193000000000100129,'MCI1','MCI1','KS','Lenexa','66219','UTC-6','113th St & Renner Blvd.',271),
|
||||
(193000000000000130,193000000000100130,'MCI4','MCI4','MO','KANSAS CITY','64163','UTC-6','MCI4 - 10501 NW 136th Street',12),
|
||||
(193000000000000131,193000000000100131,'MCO2','MCO2','FL','Deltona','32725-2610','UTC-5','2600 N Normandy Blvd',115),
|
||||
(193000000000000132,193000000000100132,'MDT1','MDT1','PA','Carlisle','17015','UTC-5','2Ames Drive',230),
|
||||
(193000000000000133,193000000000100133,'MDT4','MDT4','PA','York','17408','UTC-5','5125 Commerce Drive',128),
|
||||
(193000000000000134,193000000000100134,'MDW2','MDW2','IL','Joliet','60433-3280','UTC-6','250 Emerald Drive',200),
|
||||
(193000000000000135,193000000000100135,'MDW6','MDW6','IL','ROMEOVILLE','60446-6529','UTC-6','1125 W REMINGTON BLVD ROMEOVILLE',199),
|
||||
(193000000000000136,193000000000100136,'MDW7','MDW7','IL','Monee','60449','UTC-6','6605 Monee Manhattan Road',198),
|
||||
(193000000000000137,193000000000100137,'MDW8','MDW8','IL','Waukegan','60085','UTC-6','1750 Bridge Drive',197),
|
||||
(193000000000000138,193000000000100138,'MDW9','MDW9','IL','Aurora','60502','UTC-6','2865 Duke Parkway',196),
|
||||
(193000000000000139,193000000000100139,'MEM1','MEM1','TN','Memphis','38118','UTC-6','3292 E Holmes Rd',163),
|
||||
(193000000000000140,193000000000100140,'MEM6','MEM6','MS','OLIVE BRANCH','38654','UTC-6','11505 Progress Way',118),
|
||||
(193000000000000141,193000000000100141,'MGE3','MGE3','GA','Jefferson','30549-2010','UTC-5','808 Hog Mountain Road',201),
|
||||
(193000000000000142,193000000000100142,'MIT2','MIT2','CA','SHAFTER','93263','UTC-8','5408 Express Avenue',77),
|
||||
(193000000000000143,193000000000100143,'MKC4','MKC4','KS','Edgerton','66021-9588','UTC-6','19645 Waverly Rd',270),
|
||||
(193000000000000144,193000000000100144,'MKC6','MKC6','KS','Kansas City','66102','UTC-6','6925 Riverview Avenue',269),
|
||||
(193000000000000145,193000000000100145,'MKE1','MKE1','WI','Kenosha','53144','UTC-6','3501 120th Ave',203),
|
||||
(193000000000000146,193000000000100146,'MQJ1','MQJ1','IN','GREENFIELD','46140','UTC-5','4412 W 300 RD N',94),
|
||||
(193000000000000147,193000000000100147,'MSP1','MSP1','MN','Shakopee','55379','UTC-6','2601 4th Avenue East',190),
|
||||
(193000000000000148,193000000000100148,'NJ3','NJ3','NJ','Pedricktown','08067','UTC-5','3 Sorbello Way',17),
|
||||
(193000000000000149,193000000000100149,'OAK3','OAK3','CA','Patterson','95363-8876','UTC-8','255 Park Center Drive',299),
|
||||
(193000000000000150,193000000000100150,'OAK4','OAK4','CA','Tracy','95304-9370','UTC-8','1555 N. Chrisman Rd',300),
|
||||
(193000000000000151,193000000000100151,'OKC2','OKC2','OK','OKLAHOMA CITY','73159','UTC-6','8991 S PORTLAND AVENUE',133),
|
||||
(193000000000000152,193000000000100152,'OLM1','OLM1','WA','OLYMPIA','98516','UTC-8','3300 HOGUM BAY RD NE 98516-3111 - OLYMPIA, WA - United States',19),
|
||||
(193000000000000153,193000000000100153,'ONT2','ONT2','CA','San Bernardino','92408','UTC-8','1910 E Central Ave',303),
|
||||
(193000000000000154,193000000000100154,'ONT6','ONT6','CA','Moreno Valley','92551-9561','UTC-8','24208 San Michele Rd',302),
|
||||
(193000000000000155,193000000000100155,'ONT8','ONT8','CA','Moreno Valley','92551-9534','UTC-8','24300 Nandina Ave',301),
|
||||
(193000000000000156,193000000000100156,'ONT9','ONT9','CA','Redlands','92374','UTC-8','2125 W. San Bernandino Ave',298),
|
||||
(193000000000000157,193000000000100157,'ORD2','ORD2','IL','CHANNAHON','60410-8758','UTC-6','23714 W Amoco Rd',103),
|
||||
(193000000000000158,193000000000100158,'ORD6','ORD6','IL','Wood Dale','60191','UTC-6','1250 N Mittel Blvd',195),
|
||||
(193000000000000159,193000000000100159,'ORF2','ORF2','VA','CHESAPEAKE','23321-1425','UTC-5','ORF2 - 5045 PORTSMOUTH BLVD',113),
|
||||
(193000000000000160,193000000000100160,'PBI3','PBI3','FL','Jacksonville','32206','UTC-5','21 Jump Street',81),
|
||||
(193000000000000161,193000000000100161,'PDX7','PDX7','OR','Salem','97317','UTC-8','4775 Depot Ct SE',127),
|
||||
(193000000000000162,193000000000100162,'PHL1','PHL1','DE','New Castle','19720','UTC-5','1 Centerpoint Blvd.',288),
|
||||
(193000000000000163,193000000000100163,'PHL3','PHL3','DE','New Castle','19720','UTC-5','1600 Johnson Way',287),
|
||||
(193000000000000164,193000000000100164,'PHL4','PHL4','PA','Carlisle','17015','UTC-5','21 Roadway Dr',236),
|
||||
(193000000000000165,193000000000100165,'PHL5','PHL5','PA','Lewisberry','17339','UTC-5','500 McCarthy Dr',235),
|
||||
(193000000000000166,193000000000100166,'PHL5S','PHL5S','PA','Greencastle','17225','UTC-5','1915 Ebberts Spg Ct, Greencastle, PA 17225',20),
|
||||
(193000000000000167,193000000000100167,'PHL6','PHL6','PA','Carlisle','17015','UTC-5','675 Allen Rd.',234),
|
||||
(193000000000000168,193000000000100168,'PHL7','PHL7','DE','Middletown','19709','UTC-5','560 Merrimac Ave',286),
|
||||
(193000000000000169,193000000000100169,'PHL8','PHL8','DE','Middletown','19709','UTC-5','727 N Broad St',285),
|
||||
(193000000000000170,193000000000100170,'PHX3','PHX3','AZ','Phoenix','85043','UTC-7','6835 West Buckeye Road',307),
|
||||
(193000000000000171,193000000000100171,'PHX5','PHX5','AZ','Goodyear','85338','UTC-7','16920 W Commerce Drive',306),
|
||||
(193000000000000172,193000000000100172,'PHX6','PHX6','AZ','Phoenix','85043','UTC-7','4750 West Mohave St',305),
|
||||
(193000000000000173,193000000000100173,'PHX7','PHX7','AZ','Phoenix','85043','UTC-7','800 N 75th Ave',304),
|
||||
(193000000000000174,193000000000100174,'PIT1','PIT1','PA','Pittsburgh','15205','UTC-5','2250 Roswell Drive',229),
|
||||
(193000000000000175,193000000000100175,'PIT2','PIT2','PA','IMPERIAL','15126-1002','UTC-5','1200 Westport Rd',158),
|
||||
(193000000000000176,193000000000100176,'POC1','POC1','CA','FONTANA','92336-1123','UTC-8','4121 LYTLE CREEK RD',46),
|
||||
(193000000000000177,193000000000100177,'POC2','POC2','CA','ONTARIO','91761','UTC-8','4000 South Hamner Avenue',38),
|
||||
(193000000000000178,193000000000100178,'POC3','POC3','CA','JURUPA VALLEY','92509','UTC-8','6120 Clinker Drive',45),
|
||||
(193000000000000179,193000000000100179,'PPO4','PPO4','IN','MERRILLVILLE','46410','UTC-6','9850 Mississippi Street',49),
|
||||
(193000000000000180,193000000000100180,'PSC2','PSC2','WA','PASCO','99301','UTC-8','1351 S Road 40 E',93),
|
||||
(193000000000000181,193000000000100181,'PSP3','PSP3','CA','DESERT HOT SPRINGS','92240','UTC-8','64165 19th AVE',28),
|
||||
(193000000000000182,193000000000100182,'QXY5','QXY5','CA','STOCKTON','95215','UTC-8','4532 NEWCASTLE RD',71),
|
||||
(193000000000000183,193000000000100183,'QXY8','QXY8','CA','FRESNO','93725-1962','UTC-8','3611 S NORTHPOINTE DR',35),
|
||||
(193000000000000184,193000000000100184,'QZZ7','QZZ7','AZ','MESA','85212-9608','UTC-7','8560 E ELLIOT RD',14),
|
||||
(193000000000000185,193000000000100185,'RDU2','RDU2','NC','Smithfield','27577','UTC-5','2150 US HWY 70 Business West',146),
|
||||
(193000000000000186,193000000000100186,'RDU4','RDU4','NC','FAYETTEVILLE','28303','UTC-5','6309 Bragg Blvd',95),
|
||||
(193000000000000187,193000000000100187,'RFD2','RFD2','IL','Huntley','60142','UTC-6','11500 Freeman Road',145),
|
||||
(193000000000000188,193000000000100188,'RFD4','RFD4','IL','HUNTLEY','60142','UTC-6','11400 Venture Court 60142-8075 - HUNTLEY, IL - United States',16),
|
||||
(193000000000000189,193000000000100189,'RIC1','RIC1','VA','Petersburg','23803','UTC-5','5000 Commerce Way',210),
|
||||
(193000000000000190,193000000000100190,'RIC2','RIC2','VA','Chester','23836','UTC-5','1901 Meadowville Technology Pkwy',209),
|
||||
(193000000000000191,193000000000100191,'RMN3','RMN3','VA','Fredericksburg','22406','UTC-5','220 Centreport Parkway',144),
|
||||
(193000000000000192,193000000000100192,'RNO1','RNO1','NV','Fernley','89408','UTC-8','1600 East Newlands RD',248),
|
||||
(193000000000000193,193000000000100193,'RNO2','RNO2','NV','Reno','85906','UTC-8','8000 North Virginia Street',245),
|
||||
(193000000000000194,193000000000100194,'RNO4','RNO4','NV','Reno','89506','UTC-8','8000 North Virginia Street',244),
|
||||
(193000000000000195,193000000000100195,'RYY2','RYY2','GA','White','30184','UTC-5','1250 Cassville White Road,',73),
|
||||
(193000000000000196,193000000000100196,'SAT1','SAT1','TX','Schertz','78154-1461','UTC-6','6000 Enterprise Avenue',218),
|
||||
(193000000000000197,193000000000100197,'SAT4','SAT4','TX','SAN ANTONIO','78245','UTC-6','10384 W US HIGHWAY 90',149),
|
||||
(193000000000000198,193000000000100198,'SAT6','SAT6','TX','SAN MARCOS','78666','UTC-6','1346 Fortuna Road 78666-0001 - SAN MARCOS, TX - United States',15),
|
||||
(193000000000000199,193000000000100199,'SAV3','SAV3','GA','Macon','31216-6427','UTC-5','7001 Skipper Rd',98),
|
||||
(193000000000000200,193000000000100200,'SBD1','SBD1','CA','BLOOMINGTON','92316-3819','UTC-8','3388 S Cactus Ave',185),
|
||||
(193000000000000201,193000000000100201,'SBD2','SBD2','CA','SAN BERNARDINO','92408-2805','UTC-8','1494 S WATERMAN AVE',179),
|
||||
(193000000000000202,193000000000100202,'SCK1','SCK1','CA','STOCKTON','95215','UTC-8','4532 NEWCASTLE RD',97),
|
||||
(193000000000000203,193000000000100203,'SCK3','SCK3','CA','MANTECA','95336','UTC-8','3565 N AIRPORT WAY MANTECA',60),
|
||||
(193000000000000204,193000000000100204,'SCK4','SCK4','CA','STOCKTON','95215-8354','UTC-8','6001 S AUSTIN RD',186),
|
||||
(193000000000000205,193000000000100205,'SCK8','SCK8','CA','OAKLEY','94561-2677','UTC-8','4700 WILBUR AVE',104),
|
||||
(193000000000000206,193000000000100206,'SDF1','SDF1','KY','Campbellsville','42718','UTC-5','1050 South Columbia AVE',266),
|
||||
(193000000000000207,193000000000100207,'SDF2','SDF2','KY','Watterson Park','40218','UTC-5','4360 Robards Ln',265),
|
||||
(193000000000000208,193000000000100208,'SDF4','SDF4','KY','Shepherdsville','40165','UTC-5','376 Zappos.com Blvd',264),
|
||||
(193000000000000209,193000000000100209,'SDF6','SDF6','KY','Shepherdsville','40165','UTC-5','271 Omega Pkwy',263),
|
||||
(193000000000000210,193000000000100210,'SDF7','SDF7','KY','Shepherdsville','40165','UTC-5','300 Omicron Court',258),
|
||||
(193000000000000211,193000000000100211,'SDF8','SDF8','IN','Jeffersonville','47130','UTC-5','900 Patrol Rd',273),
|
||||
(193000000000000212,193000000000100212,'SDF9','SDF9','KY','Shepherdsville','40165','UTC-5','100 W. Thomas P. Echols Lane',257),
|
||||
(193000000000000213,193000000000100213,'SEA6','SEA6','WA','South Seattle','98144','UTC-8','2646 Rainier Ave.',205),
|
||||
(193000000000000214,193000000000100214,'SEA8','SEA8','WA','Northeast Bellevue','98005','UTC-8','1475 124th Avenue',204),
|
||||
(193000000000000215,193000000000100215,'SJC7','SJC7','CA','Tracy','95377-8906','UTC-8','188 Mountain House Parkway',295),
|
||||
(193000000000000216,193000000000100216,'SLC2','SLC2','UT','West Jordan','84081','UTC-7','7148 W. Old Bingham Hwy',153),
|
||||
(193000000000000217,193000000000100217,'SLC3','SLC3','UT','SALT LAKE CITY','84116-4413','UTC-7','355 N JOHN GLENN RD',13),
|
||||
(193000000000000218,193000000000100218,'SMF3','SMF3','CA','Stockton','95206-8202','UTC-8','3923 S B ST',184),
|
||||
(193000000000000219,193000000000100219,'SMF6','SMF6','CA','SACRAMENTO','95837-9109','UTC-8','4930 ALLBAUGH DR',105),
|
||||
(193000000000000220,193000000000100220,'SNA4','SNA4','CA','Rialto','92376-3009','UTC-8','2496 W Walnut Ave',296),
|
||||
(193000000000000221,193000000000100221,'STL3','STL3','MO','REPUBLIC','65738-2779','UTC-6','3200 E SAWYER RD',109),
|
||||
(193000000000000222,193000000000100222,'STL4','STL4','IL','Edwardsville','62025-2815','UTC-6','3050 GATEWAY COMMERCE CENTER DR S',194),
|
||||
(193000000000000223,193000000000100223,'SWF1','SWF1','NY','ROCK TAVERN','12575','UTC-5','635 INTERNATIONAL BLVD',122),
|
||||
(193000000000000224,193000000000100224,'SWF2','SWF2','NY','Hopewell Junction','12533','UTC-5','76 Patriot Way Hopewell Junction',76),
|
||||
(193000000000000225,193000000000100225,'TCY1','TCY1','CA','STOCKTON','95206','UTC-8','2690 East Arch Airport Road',41),
|
||||
(193000000000000226,193000000000100226,'TCY2','TCY2','CA','STOCKTON','95215','UTC-8','6201 Newcastle Rd',43),
|
||||
(193000000000000227,193000000000100227,'TEB3','TEB3','NJ','Logan Township','08085','UTC-5','2651 Oldmans Creek Rd',250),
|
||||
(193000000000000228,193000000000100228,'TEB4','TEB4','NJ','PENNS GROVE','08069','UTC-5','747 COURSES LANDING RD',130),
|
||||
(193000000000000229,193000000000100229,'TEB6','TEB6','NJ','Cranbury','08512','UTC-5','22 Hightstown Cranbury State Road',249),
|
||||
(193000000000000230,193000000000100230,'TEB9','TEB9','NJ','Somerset','08873','UTC-5','601 Randolph Road',156),
|
||||
(193000000000000231,193000000000100231,'TMB8','TMB8','FL','HOMESTEAD','33032-8597','UTC-5','27505 SW 132nd AVE',65),
|
||||
(193000000000000232,193000000000100232,'TOL3','TOL3','OH','PERRYSBURG','43551','UTC-5','9240 Fremont Pike 43551-4210 - PERRYSBURG, OH - United States',18),
|
||||
(193000000000000233,193000000000100233,'TPA1','TPA1','FL','Ruskin','33570','UTC-5','3350 Laurel Ridge Ave.',284),
|
||||
(193000000000000234,193000000000100234,'TPA2','TPA2','FL','Lakeland','33811','UTC-5','1760 County Line Rd',283),
|
||||
(193000000000000235,193000000000100235,'TPA3','TPA3','FL','Auburndale','33823','UTC-5','676 C. Fred Jones Boulevard',124),
|
||||
(193000000000000236,193000000000100236,'TUL1','TUL1','KS','Coffeyville','67337','UTC-6','2654 North Highway 169',272),
|
||||
(193000000000000237,193000000000100237,'VGT2','VGT2','NV','LAS VEGAS','89115-2013','UTC-8','6401 Howdy Wells Ave',172),
|
||||
(193000000000000238,193000000000100238,'VUBA','VUBA','PA','Scranton','18512','UTC-5','1000 Keystone Industrial Park',228),
|
||||
(193000000000000239,193000000000100239,'VUGA','VUGA','PA','Pittston','18643','UTC-5','508 Delaware Avenue West',227),
|
||||
(193000000000000240,193000000000100240,'WBW2','WBW2','PA','OLYPHANT','18447','UTC-5','1300 Corporate Way',82),
|
||||
(193000000000000241,193000000000100241,'XAV3','XAV3','GA','PORT WENTWORTH','31407','UTC-5','XAV3-120Crossgate Dr 31407-Port Wentworth, GA- United States',36),
|
||||
(193000000000000242,193000000000100242,'XEW5','XEW5','NJ','Cinnaminson','08077-2015','UTC-5','995 Taylors Ln 08077-2015',37),
|
||||
(193000000000000243,193000000000100243,'XIN5','XIN5','IN','Avon','46123-1700','UTC-5','8838 E COUNTY ROAD 100 S',25),
|
||||
(193000000000000244,193000000000100244,'XLX1','XLX1','MD','HAGERSTOWN','21740','UTC-5','1115 Wesel Blvd HAGERSTOWN,MD 21740,USA',34),
|
||||
(193000000000000245,193000000000100245,'XLX6','XLX6','NC','PINEVILLE','28134','UTC-5','12220 Carolina Logistics Drive PINEVILLE,NC 28134,USA',23),
|
||||
(193000000000000246,193000000000100246,'XLX7','XLX7','CA','VICTORVILLE','92394','UTC-8','18580 Gateway Dr,Victorville,CA 92394,USA',63),
|
||||
(193000000000000247,193000000000100247,'XPB2','XPB2','FL','APOPKA','32712','UTC-5','XPB2 - 4661 Apopka Logistics Pkwy 32712 - Apopka, FL - United States',26),
|
||||
(193000000000000248,193000000000100248,'XRI3','XRI3','VA','Richmond','23234','UTC-5','4949 Commerce Rd, Richmond, VA 23234',24),
|
||||
(193000000000000249,193000000000100249,'XUSB','XUSB','TX','Fort Worth','76155','UTC-6','14900 Frye Rd.',213),
|
||||
(193000000000000250,193000000000100250,'XUSC','XUSC','PA','Carlisle','17013','UTC-5','40 Logistics Drive',226),
|
||||
(193000000000000251,193000000000100251,'XUSD','XUSD','CA','Stockton','95206','UTC-8','1909 Zephyr St',297),
|
||||
(193000000000000252,193000000000100252,'XUSE','XUSE','IN','Whitestown','46075','UTC-5','5100 S Indianapolis Rd',280);
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_wh_fba_public_master_data_seed;
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE assert_wh_fba_public_master_data_seed()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*) FROM tmp_wh_fba_public_master_data) <> 252 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据源数量不是252';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tmp_wh_fba_public_master_data
|
||||
WHERE warehouse_code <> UPPER(TRIM(warehouse_code))
|
||||
OR warehouse_code NOT REGEXP '^[A-Z0-9]{3,5}$'
|
||||
OR TRIM(city) = ''
|
||||
OR TRIM(state_province) = ''
|
||||
OR TRIM(address_line1) = ''
|
||||
OR TRIM(postal_code) = ''
|
||||
OR TRIM(timezone) = ''
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据存在未清洗或必填缺失记录';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM wh_warehouse existing
|
||||
JOIN tmp_wh_fba_public_master_data source
|
||||
ON existing.id = source.warehouse_id_seed
|
||||
AND (existing.tenant_id <> 0 OR existing.warehouse_code <> source.warehouse_code OR existing.deleted <> 0)
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据仓库种子ID与既有数据冲突';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM wh_warehouse_address existing
|
||||
JOIN tmp_wh_fba_public_master_data source
|
||||
ON existing.id = source.address_id_seed
|
||||
LEFT JOIN wh_warehouse parent
|
||||
ON parent.id = existing.warehouse_id
|
||||
AND parent.tenant_id = existing.tenant_id
|
||||
WHERE existing.tenant_id <> 0
|
||||
OR existing.deleted <> 0
|
||||
OR parent.warehouse_code IS NULL
|
||||
OR parent.warehouse_code <> source.warehouse_code
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据地址种子ID与既有数据冲突';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM wh_warehouse existing
|
||||
JOIN tmp_wh_fba_public_master_data source
|
||||
ON existing.tenant_id = 0
|
||||
AND existing.warehouse_code = source.warehouse_code
|
||||
AND existing.deleted = 1
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共主数据存在同编码已删除仓库,需人工确认后恢复或清理';
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
CALL assert_wh_fba_public_master_data_seed();
|
||||
DROP PROCEDURE IF EXISTS assert_wh_fba_public_master_data_seed;
|
||||
|
||||
INSERT INTO wh_warehouse (
|
||||
id, tenant_id, warehouse_code, warehouse_name, warehouse_type, country_code, timezone,
|
||||
contact_name, contact_phone, contact_email, status, remark,
|
||||
created_by, created_at, updated_by, updated_at, deleted, version
|
||||
)
|
||||
SELECT
|
||||
source.warehouse_id_seed,
|
||||
0,
|
||||
source.warehouse_code,
|
||||
source.warehouse_name,
|
||||
'FBA',
|
||||
'US',
|
||||
source.timezone,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
'ACTIVE',
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
0
|
||||
FROM tmp_wh_fba_public_master_data source
|
||||
ON DUPLICATE KEY UPDATE
|
||||
warehouse_type = VALUES(warehouse_type),
|
||||
country_code = VALUES(country_code),
|
||||
timezone = VALUES(timezone),
|
||||
updated_at = CURRENT_TIMESTAMP(3);
|
||||
|
||||
INSERT INTO wh_warehouse_address (
|
||||
id, tenant_id, warehouse_id, address_type, country_code, state_province, city,
|
||||
district, postal_code, address_line1, address_line2, latitude, longitude,
|
||||
created_by, created_at, updated_by, updated_at, deleted, version
|
||||
)
|
||||
SELECT
|
||||
source.address_id_seed,
|
||||
0,
|
||||
warehouse.id,
|
||||
'OPERATING',
|
||||
'US',
|
||||
source.state_province,
|
||||
source.city,
|
||||
NULL,
|
||||
source.postal_code,
|
||||
source.address_line1,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
NULL,
|
||||
CURRENT_TIMESTAMP(3),
|
||||
0,
|
||||
0
|
||||
FROM tmp_wh_fba_public_master_data source
|
||||
JOIN wh_warehouse warehouse
|
||||
ON warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_code = source.warehouse_code
|
||||
AND warehouse.deleted = 0
|
||||
ON DUPLICATE KEY UPDATE
|
||||
tenant_id = VALUES(tenant_id),
|
||||
country_code = VALUES(country_code),
|
||||
state_province = VALUES(state_province),
|
||||
city = VALUES(city),
|
||||
postal_code = VALUES(postal_code),
|
||||
address_line1 = VALUES(address_line1),
|
||||
updated_at = CURRENT_TIMESTAMP(3);
|
||||
|
||||
DROP PROCEDURE IF EXISTS verify_wh_fba_public_master_data;
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE verify_wh_fba_public_master_data()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*)
|
||||
FROM wh_warehouse warehouse
|
||||
JOIN tmp_wh_fba_public_master_data source
|
||||
ON source.warehouse_code = warehouse.warehouse_code
|
||||
WHERE warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_type = 'FBA'
|
||||
AND warehouse.country_code = 'US'
|
||||
AND warehouse.deleted = 0) <> 252 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共仓库写入数量校验失败';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tmp_wh_fba_public_master_data source
|
||||
LEFT JOIN wh_warehouse warehouse
|
||||
ON warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_code = source.warehouse_code
|
||||
AND warehouse.deleted = 0
|
||||
LEFT JOIN wh_warehouse_address address
|
||||
ON address.tenant_id = 0
|
||||
AND address.warehouse_id = warehouse.id
|
||||
AND address.address_type = 'OPERATING'
|
||||
AND address.deleted = 0
|
||||
WHERE warehouse.id IS NULL
|
||||
OR address.id IS NULL
|
||||
OR address.country_code <> 'US'
|
||||
OR address.state_province <> source.state_province
|
||||
OR address.city <> source.city
|
||||
OR address.postal_code <> source.postal_code
|
||||
OR address.address_line1 <> source.address_line1
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共地址父子解析或地址字段校验失败';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
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 warehouse.warehouse_code IN (SELECT warehouse_code FROM tmp_wh_fba_public_master_data)
|
||||
AND capability.deleted = 0
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'FBA公共地址不得创建平台作业能力';
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
CALL verify_wh_fba_public_master_data();
|
||||
DROP PROCEDURE IF EXISTS verify_wh_fba_public_master_data;
|
||||
DROP PROCEDURE IF EXISTS verify_wh_fixed_platform_warehouse;
|
||||
DELIMITER //
|
||||
CREATE PROCEDURE verify_wh_fixed_platform_warehouse()
|
||||
BEGIN
|
||||
IF (SELECT COUNT(*)
|
||||
FROM wh_warehouse warehouse
|
||||
JOIN tmp_wh_fixed_platform_warehouse source
|
||||
ON source.warehouse_code = warehouse.warehouse_code
|
||||
WHERE warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_type = source.warehouse_type
|
||||
AND warehouse.country_code = source.country_code
|
||||
AND warehouse.deleted = 0) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓写入数量校验失败';
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM tmp_wh_fixed_platform_warehouse source
|
||||
LEFT JOIN wh_warehouse warehouse
|
||||
ON warehouse.tenant_id = 0
|
||||
AND warehouse.warehouse_code = source.warehouse_code
|
||||
AND warehouse.deleted = 0
|
||||
LEFT JOIN wh_warehouse_address address
|
||||
ON address.tenant_id = 0
|
||||
AND address.warehouse_id = warehouse.id
|
||||
AND address.address_type = 'OPERATING'
|
||||
AND address.deleted = 0
|
||||
WHERE warehouse.id IS NULL
|
||||
OR address.id IS NULL
|
||||
) THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '固定平台仓地址父子解析失败';
|
||||
END IF;
|
||||
END//
|
||||
DELIMITER ;
|
||||
CALL verify_wh_fixed_platform_warehouse();
|
||||
DROP PROCEDURE IF EXISTS verify_wh_fixed_platform_warehouse;
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fba_public_master_data;
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp_wh_fixed_platform_warehouse;
|
||||
@@ -2,8 +2,8 @@ SET NAMES utf8mb4;
|
||||
USE baiyue_warehouse;
|
||||
-- 一期联调初始化仓库,重复执行不会产生重复数据。
|
||||
INSERT INTO wh_warehouse(id,tenant_id,warehouse_code,warehouse_name,warehouse_type,country_code,timezone,contact_name,contact_phone,status,remark)
|
||||
VALUES(190000000000000001,0,'CN-SZX-01','深圳头程收货仓','DOMESTIC','CN','Asia/Shanghai','仓库收货组','0755-88886666','ACTIVE','工作日预约收货')
|
||||
ON DUPLICATE KEY UPDATE tenant_id=0,warehouse_name=VALUES(warehouse_name),status='ACTIVE';
|
||||
VALUES(190000000000000001,0,'CN-SZX-01','深圳头程收货仓','DOMESTIC','CN','UTC+8','仓库收货组','0755-88886666','ACTIVE','工作日预约收货')
|
||||
ON DUPLICATE KEY UPDATE tenant_id=0,warehouse_name=VALUES(warehouse_name),timezone=VALUES(timezone),status='ACTIVE';
|
||||
INSERT INTO wh_warehouse_address(id,tenant_id,warehouse_id,address_type,country_code,state_province,city,district,postal_code,address_line1)
|
||||
VALUES(190000000000000011,0,190000000000000001,'OPERATING','CN','广东省','深圳市','宝安区','518000','航城街道示例物流园1号仓')
|
||||
ON DUPLICATE KEY UPDATE tenant_id=0,address_line1=VALUES(address_line1);
|
||||
|
||||
@@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS wh_warehouse (
|
||||
warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称',
|
||||
warehouse_type VARCHAR(32) NOT NULL DEFAULT 'DOMESTIC' COMMENT '公共仓库类型:DOMESTIC、FBA、OVERSEAS',
|
||||
country_code CHAR(2) NOT NULL COMMENT '所在国家二字码',
|
||||
timezone VARCHAR(64) NOT NULL COMMENT '仓库所在地IANA时区',
|
||||
timezone VARCHAR(64) NOT NULL COMMENT '仓库所在地UTC偏移量',
|
||||
contact_name VARCHAR(64) DEFAULT NULL COMMENT '仓库联系人',
|
||||
contact_phone VARCHAR(32) DEFAULT NULL COMMENT '联系电话',
|
||||
contact_email VARCHAR(128) DEFAULT NULL COMMENT '联系邮箱',
|
||||
|
||||
+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