暂存提交
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
package com.baiyue.warehouse;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 仓库微服务启动入口,启用 Spring Boot 自动配置并扫描仓库服务的 MyBatis Mapper。
|
||||
*/
|
||||
@MapperScan("com.baiyue.warehouse.mapper")
|
||||
@SpringBootApplication(scanBasePackages = "com.baiyue")
|
||||
public class BaiyueWarehouseApplication {
|
||||
public static void main(String[] args) { SpringApplication.run(BaiyueWarehouseApplication.class, args); }
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.entity.PartnerEntity;
|
||||
import com.baiyue.warehouse.service.PartnerQueryService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/internal/warehouse-partners")
|
||||
public class InternalPartnerController {
|
||||
private final PartnerQueryService service; private final byte[] expected;
|
||||
public InternalPartnerController(PartnerQueryService service,
|
||||
@Value("${baiyue.security.internal-service-token}") String token) {
|
||||
this.service=service; this.expected=token.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
@GetMapping("/{tenantId}/{id}/active")
|
||||
public ApiResponse<PartnerEntity> active(@RequestHeader(value="X-Internal-Service-Token",required=false) String token,
|
||||
@PathVariable long tenantId,@PathVariable long id) {
|
||||
if (token==null || !MessageDigest.isEqual(expected,token.getBytes(StandardCharsets.UTF_8)))
|
||||
throw new BizException(ResultCode.FORBIDDEN,"内部服务凭证无效");
|
||||
return ApiResponse.success(service.active(tenantId,id));
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.dto.WarehouseDetailResponse;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/internal/warehouses")
|
||||
public class InternalWarehouseController {
|
||||
private final WarehouseApplicationService service;
|
||||
private final byte[] expected;
|
||||
public InternalWarehouseController(WarehouseApplicationService service,
|
||||
@Value("${baiyue.security.internal-service-token}") String token) {
|
||||
this.service = service; this.expected = token.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
@GetMapping("/{tenantId}/{id}/active")
|
||||
public ApiResponse<WarehouseDetailResponse> active(
|
||||
@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
|
||||
@PathVariable long tenantId, @PathVariable long id) {
|
||||
if (token == null || !MessageDigest.isEqual(expected, token.getBytes(StandardCharsets.UTF_8)))
|
||||
throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
|
||||
return ApiResponse.success(service.activeDetail(tenantId, id));
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.warehouse.entity.PartnerEntity;
|
||||
import com.baiyue.warehouse.service.PartnerQueryService;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/merchant/v1/partners")
|
||||
public class MerchantPartnerController {
|
||||
private final PartnerQueryService service;
|
||||
public MerchantPartnerController(PartnerQueryService service) { this.service=service; }
|
||||
@GetMapping("/candidates")
|
||||
public ApiResponse<List<PartnerEntity>> candidates(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam(required=false) String keyword,@RequestParam(required=false) String type) {
|
||||
return ApiResponse.success(service.candidates(user.tenantId(),keyword,type));
|
||||
}
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<PartnerEntity> detail(@AuthenticationPrincipal AuthenticatedUser user,@PathVariable long id) {
|
||||
return ApiResponse.success(service.active(user.tenantId(),id));
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
import com.baiyue.common.security.AuthenticatedUser;
|
||||
import com.baiyue.warehouse.dto.WarehouseDetailResponse;
|
||||
import com.baiyue.warehouse.dto.WarehouseRequests;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 商户端仓库查询接口,仅允许在认证令牌所属租户范围内读取仓库列表和详情。
|
||||
*/
|
||||
@Tag(
|
||||
name = "Merchant warehouses / 商户仓库",
|
||||
description = "Merchant read APIs restricted to the tenant in the authenticated JWT. / 商户端只读接口,租户范围取自认证 JWT。"
|
||||
)
|
||||
@RestController
|
||||
@RequestMapping("/api/merchant/v1/warehouses")
|
||||
public class MerchantWarehouseController {
|
||||
@Resource
|
||||
private WarehouseApplicationService service;
|
||||
|
||||
@Operation(
|
||||
summary = "List available warehouses / 查询可用仓库",
|
||||
description = "Tenant comes only from the authenticated JWT; clients cannot override it. / 租户仅从认证 JWT 获取,客户端不可覆盖。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid query / 查询参数无效"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Authentication required / 需要登录")
|
||||
})
|
||||
@GetMapping
|
||||
public ApiResponse<PageResult<WarehouseEntity>> page(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid WarehouseRequests.QueryRequest query) {
|
||||
return ApiResponse.success(service.merchantPage(user.tenantId(), query));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Get warehouse details / 查询仓库详情",
|
||||
description = "Returns warehouse details only within the authenticated merchant tenant. / 仅返回当前认证商户租户内的仓库详情。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Authentication required / 需要登录"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "Warehouse not found in current tenant / 当前租户下仓库不存在")
|
||||
})
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<WarehouseDetailResponse> detail(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id) {
|
||||
return ApiResponse.success(service.activeDetail(user.tenantId(), id));
|
||||
}
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
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.WarehouseRequests;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.service.WarehouseApplicationService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
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 jakarta.validation.constraints.NotNull;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PatchMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
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.RestController;
|
||||
|
||||
/**
|
||||
* 平台端仓库管理接口,提供跨租户管理场景下的仓库创建、修改、查询和状态维护能力。
|
||||
*/
|
||||
@Validated
|
||||
@Tag(
|
||||
name = "Platform warehouse administration / 平台仓库管理",
|
||||
description = "Platform APIs for managing warehouses across tenants. / 平台端按租户管理仓库的接口。"
|
||||
)
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/warehouse-admin")
|
||||
public class PlatformWarehouseController {
|
||||
@Resource
|
||||
private WarehouseApplicationService service;
|
||||
|
||||
@Operation(
|
||||
summary = "Create warehouse / 新建仓库",
|
||||
description = "Creates a warehouse, address and service capabilities for the target tenant. / 为目标租户创建仓库、地址和服务能力。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Created successfully / 创建成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid request / 请求参数无效"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform access required / 需要平台端权限"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "409", description = "Warehouse code already exists / 仓库编码已存在")
|
||||
})
|
||||
@PostMapping
|
||||
public ApiResponse<Long> create(@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Valid @RequestBody WarehouseRequests.SaveRequest request) {
|
||||
requirePlatform(user);
|
||||
requireTenant(request.tenantId());
|
||||
return ApiResponse.success(service.create(request));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Update warehouse / 更新仓库",
|
||||
description = "Updates the warehouse and replaces its address and capabilities. / 更新仓库,并整体替换地址和服务能力。"
|
||||
)
|
||||
@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 access or tenant 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 / 仓库编码已存在")
|
||||
})
|
||||
@PutMapping("/{id}")
|
||||
public ApiResponse<Void> update(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
|
||||
@RequestParam @NotNull Long tenantId,
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id,
|
||||
@Valid @RequestBody WarehouseRequests.SaveRequest request) {
|
||||
requirePlatform(user);
|
||||
requireTenant(tenantId);
|
||||
service.update(tenantId, id, request);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "List tenant warehouses / 查询租户仓库",
|
||||
description = "Returns a tenant-scoped warehouse page with optional country, capability and status filters. / 按租户分页查询仓库,可按国家、能力和状态筛选。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid query / 查询参数无效"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform access required / 需要平台端权限")
|
||||
})
|
||||
@GetMapping
|
||||
public ApiResponse<PageResult<WarehouseEntity>> page(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
|
||||
@RequestParam @NotNull Long tenantId,
|
||||
@Valid WarehouseRequests.QueryRequest request) {
|
||||
requirePlatform(user);
|
||||
requireTenant(tenantId);
|
||||
return ApiResponse.success(service.page(tenantId, request));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Get tenant warehouse details / 查询租户仓库详情",
|
||||
description = "Returns the warehouse, addresses and service capabilities in the target tenant. / 返回目标租户下的仓库、地址及服务能力。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Query succeeded / 查询成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform access required / 需要平台端权限"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "Warehouse not found / 仓库不存在")
|
||||
})
|
||||
@GetMapping("/{id}")
|
||||
public ApiResponse<WarehouseDetailResponse> detail(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
|
||||
@RequestParam @NotNull Long tenantId,
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id) {
|
||||
requirePlatform(user);
|
||||
requireTenant(tenantId);
|
||||
return ApiResponse.success(service.detail(tenantId, id));
|
||||
}
|
||||
|
||||
@Operation(
|
||||
summary = "Enable or disable warehouse / 启用或停用仓库",
|
||||
description = "Changes warehouse status to ACTIVE or INACTIVE. / 将仓库状态切换为 ACTIVE 或 INACTIVE。"
|
||||
)
|
||||
@ApiResponses({
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Status changed / 状态变更成功"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid status / 状态无效"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "403", description = "Platform access required / 需要平台端权限"),
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "Warehouse not found / 仓库不存在")
|
||||
})
|
||||
@PatchMapping("/{id}/status")
|
||||
public ApiResponse<Void> status(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@Parameter(description = "Target tenant ID / 目标租户 ID", example = "10001", required = true)
|
||||
@RequestParam @NotNull Long tenantId,
|
||||
@Parameter(description = "Warehouse ID / 仓库 ID", example = "190000000000000001", required = true)
|
||||
@PathVariable long id,
|
||||
@Parameter(description = "Target status: ACTIVE or INACTIVE / 目标状态:ACTIVE 或 INACTIVE", example = "ACTIVE", required = true)
|
||||
@RequestParam String status) {
|
||||
requirePlatform(user);
|
||||
requireTenant(tenantId);
|
||||
service.changeStatus(tenantId, id, status);
|
||||
return ApiResponse.success(null);
|
||||
}
|
||||
|
||||
private static void requirePlatform(AuthenticatedUser user) {
|
||||
if (user == null || !"PLATFORM".equals(user.appCode())) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "Platform application required / 仅允许平台端访问");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireTenant(Long tenantId) {
|
||||
if (tenantId == null || tenantId <= 0) {
|
||||
throw new BizException(ResultCode.BAD_REQUEST, "Target tenant is required / 必须指定有效目标租户");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.baiyue.warehouse.controller;
|
||||
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 仓库微服务可用性接口,向平台端暴露服务逻辑状态。
|
||||
*/
|
||||
@Tag(name = "Warehouse service status / 仓储服务状态", description = "Warehouse service availability API. / 仓储服务可用性接口。")
|
||||
@RestController
|
||||
@RequestMapping("/api/platform/v1/warehouses")
|
||||
public class WarehouseHealthController {
|
||||
@Operation(summary = "Get service status / 查询服务状态", description = "Returns the logical status of warehouse-service. / 返回 warehouse-service 的逻辑运行状态。")
|
||||
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Service is available / 服务可用")
|
||||
@GetMapping("/status")
|
||||
public ApiResponse<Map<String, String>> status() {
|
||||
return ApiResponse.success(Map.of("service", "warehouse-service", "status", "UP"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.baiyue.warehouse.dto;
|
||||
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 仓库聚合详情响应,组合仓库主数据、地址列表和服务能力列表。
|
||||
*/
|
||||
@Schema(name = "WarehouseDetailResponse", description = "Warehouse aggregate details / 仓库聚合详情")
|
||||
public record WarehouseDetailResponse(
|
||||
@Schema(description = "Warehouse master data / 仓库主数据") WarehouseEntity warehouse,
|
||||
@Schema(description = "Warehouse addresses / 仓库地址列表") List<WarehouseAddressEntity> addresses,
|
||||
@Schema(description = "Warehouse service capabilities / 仓库服务能力列表") List<ServiceCapabilityEntity> capabilities
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.baiyue.warehouse.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
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 java.math.BigDecimal;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 仓库接口请求模型容器,集中定义仓库保存、地址、服务能力、查询和状态变更请求契约。
|
||||
*/
|
||||
public final class WarehouseRequests {
|
||||
private 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 = "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", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank 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 = "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 = "Remark / 备注", example = "Supports appointment receiving / 支持预约入库", maxLength = 500)
|
||||
@Size(max = 500) String remark,
|
||||
@Schema(description = "Primary warehouse address / 仓库主地址", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Valid @NotNull AddressRequest address,
|
||||
@Schema(description = "Supported service capabilities / 支持的服务能力")
|
||||
@Valid List<CapabilityRequest> capabilities
|
||||
) {
|
||||
}
|
||||
|
||||
@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 = "State or province / 州或省", example = "California", maxLength = 100)
|
||||
@Size(max = 100) String stateProvince,
|
||||
@Schema(description = "City / 城市", example = "Los Angeles", maxLength = 100, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 100) String city,
|
||||
@Schema(description = "District / 区县", example = "Commerce", maxLength = 100)
|
||||
@Size(max = 100) String district,
|
||||
@Schema(description = "Postal code / 邮政编码", example = "90040", maxLength = 20)
|
||||
@Size(max = 20) String postalCode,
|
||||
@Schema(description = "Address line 1 / 地址第一行", example = "123 Logistics Ave", maxLength = 255, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 255) String addressLine1,
|
||||
@Schema(description = "Address line 2 / 地址第二行", example = "Building A", maxLength = 255)
|
||||
@Size(max = 255) String addressLine2,
|
||||
@Schema(description = "Latitude / 纬度", example = "34.052235")
|
||||
BigDecimal latitude,
|
||||
@Schema(description = "Longitude / 经度", example = "-118.243683")
|
||||
BigDecimal longitude
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "WarehouseCapabilityRequest", description = "Warehouse service capability / 仓库服务能力")
|
||||
public record CapabilityRequest(
|
||||
@Schema(description = "Capability code / 能力编码", example = "STORAGE", maxLength = 50, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 50) String capabilityCode,
|
||||
@Schema(description = "Capability name / 能力名称", example = "Storage / 仓储", maxLength = 100, requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank @Size(max = 100) String capabilityName,
|
||||
@Schema(description = "Whether enabled / 是否启用", example = "true", defaultValue = "true")
|
||||
Boolean enabled,
|
||||
@Schema(description = "Daily cutoff time / 每日截单时间", example = "16:00:00", type = "string", format = "time")
|
||||
LocalTime cutoffTime,
|
||||
@Schema(description = "Capability description / 能力说明", example = "Standard pallet storage / 标准托盘仓储", maxLength = 500)
|
||||
@Size(max = 500) String description
|
||||
) {
|
||||
}
|
||||
|
||||
@Schema(name = "WarehouseQueryRequest", description = "Warehouse page query / 仓库分页查询条件")
|
||||
public record QueryRequest(
|
||||
@Schema(description = "Page number, starting at 1 / 页码,从 1 开始", example = "1", defaultValue = "1", minimum = "1")
|
||||
@Min(1) long page,
|
||||
@Schema(description = "Page size / 每页条数", example = "20", defaultValue = "20", minimum = "1", maximum = "200")
|
||||
@Min(1) @Max(200) long size,
|
||||
@Schema(description = "Country filter / 国家筛选", example = "US")
|
||||
String countryCode,
|
||||
@Schema(description = "Enabled capability code filter / 已启用能力编码筛选", example = "STORAGE")
|
||||
String capabilityCode,
|
||||
@Schema(description = "Status filter / 状态筛选", example = "ACTIVE", allowableValues = {"ACTIVE", "INACTIVE"})
|
||||
String status
|
||||
) {
|
||||
public QueryRequest {
|
||||
if (page == 0) {
|
||||
page = 1;
|
||||
}
|
||||
if (size == 0) {
|
||||
size = 20;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.baiyue.warehouse.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/** 仓储域内的租户往来单位配置。 */
|
||||
@Getter @Setter
|
||||
@TableName("wh_partner")
|
||||
public class PartnerEntity extends BaseEntity {
|
||||
@TableId(type = IdType.ASSIGN_ID) private Long id;
|
||||
private Long tenantId;
|
||||
private String partnerCode;
|
||||
private String partnerName;
|
||||
private String partnerType;
|
||||
private String contactName;
|
||||
private String contactPhone;
|
||||
private String contactEmail;
|
||||
private String countryCode;
|
||||
private String province;
|
||||
private String city;
|
||||
private String district;
|
||||
private String addressLine;
|
||||
private String postalCode;
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.baiyue.warehouse.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.time.LocalTime;
|
||||
|
||||
/**
|
||||
* 仓库服务能力持久化实体,对应 {@code wh_service_capability} 表。
|
||||
* 用于记录租户仓库可提供的能力、启用状态和每日截单时间。
|
||||
*/
|
||||
@Schema(name = "WarehouseServiceCapability", description = "Warehouse service capability / 仓库服务能力")
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("wh_service_capability")
|
||||
public class ServiceCapabilityEntity extends BaseEntity {
|
||||
@Schema(description = "Capability record ID / 能力记录 ID", example = "190000000000000021")
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@Schema(description = "Tenant ID / 租户 ID", example = "10001")
|
||||
private Long tenantId;
|
||||
@Schema(description = "Warehouse ID / 仓库 ID", example = "190000000000000001")
|
||||
private Long warehouseId;
|
||||
@Schema(description = "Capability code / 能力编码", example = "STORAGE")
|
||||
private String capabilityCode;
|
||||
@Schema(description = "Capability name / 能力名称", example = "Storage / 仓储")
|
||||
private String capabilityName;
|
||||
@Schema(description = "Whether enabled / 是否启用", example = "true")
|
||||
private Boolean enabled;
|
||||
@Schema(description = "Daily cutoff time / 每日截单时间", example = "16:00:00", type = "string", format = "time")
|
||||
private LocalTime cutoffTime;
|
||||
@Schema(description = "Capability description / 能力说明", example = "Standard pallet storage / 标准托盘仓储")
|
||||
private String description;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.baiyue.warehouse.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 仓库地址持久化实体,对应 {@code wh_warehouse_address} 表。
|
||||
* 用于保存租户仓库的结构化地址与经纬度信息。
|
||||
*/
|
||||
@Schema(name = "WarehouseAddress", description = "Warehouse address data / 仓库地址数据")
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("wh_warehouse_address")
|
||||
public class WarehouseAddressEntity extends BaseEntity {
|
||||
@Schema(description = "Address ID / 地址 ID", example = "190000000000000011")
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@Schema(description = "Tenant ID / 租户 ID", example = "10001")
|
||||
private Long tenantId;
|
||||
@Schema(description = "Warehouse ID / 仓库 ID", example = "190000000000000001")
|
||||
private Long warehouseId;
|
||||
@Schema(description = "Address type / 地址类型", example = "WAREHOUSE")
|
||||
private String addressType;
|
||||
@Schema(description = "ISO two-letter country code / ISO 两位国家代码", example = "US")
|
||||
private String countryCode;
|
||||
@Schema(description = "State or province / 州或省", example = "California")
|
||||
private String stateProvince;
|
||||
@Schema(description = "City / 城市", example = "Los Angeles")
|
||||
private String city;
|
||||
@Schema(description = "District / 区县", example = "Commerce")
|
||||
private String district;
|
||||
@Schema(description = "Postal code / 邮政编码", example = "90040")
|
||||
private String postalCode;
|
||||
@Schema(description = "Address line 1 / 地址第一行", example = "123 Logistics Ave")
|
||||
private String addressLine1;
|
||||
@Schema(description = "Address line 2 / 地址第二行", example = "Building A")
|
||||
private String addressLine2;
|
||||
@Schema(description = "Latitude / 纬度", example = "34.052235")
|
||||
private BigDecimal latitude;
|
||||
@Schema(description = "Longitude / 经度", example = "-118.243683")
|
||||
private BigDecimal longitude;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.baiyue.warehouse.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baiyue.common.core.model.BaseEntity;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
/**
|
||||
* 仓库主数据持久化实体,对应 {@code wh_warehouse} 表。
|
||||
* 用于记录租户下仓库的编码、类型、国家、联系方式和启停状态。
|
||||
*/
|
||||
@Schema(name = "Warehouse", description = "Warehouse master data / 仓库主数据")
|
||||
@Getter
|
||||
@Setter
|
||||
@TableName("wh_warehouse")
|
||||
public class WarehouseEntity extends BaseEntity {
|
||||
@Schema(description = "Warehouse ID / 仓库 ID", example = "190000000000000001")
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
@Schema(description = "Tenant ID / 租户 ID", example = "10001")
|
||||
private Long tenantId;
|
||||
@Schema(description = "Warehouse code / 仓库编码", example = "US-LAX-01")
|
||||
private String warehouseCode;
|
||||
@Schema(description = "Warehouse name / 仓库名称", example = "Los Angeles Main Warehouse / 洛杉矶主仓")
|
||||
private String warehouseName;
|
||||
@Schema(description = "Warehouse type / 仓库类型", example = "OVERSEAS")
|
||||
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")
|
||||
private String timezone;
|
||||
@Schema(description = "Contact name / 联系人", example = "Alex Chen")
|
||||
private String contactName;
|
||||
@Schema(description = "Contact phone / 联系电话", example = "+1-213-555-0100")
|
||||
private String contactPhone;
|
||||
@Schema(description = "Contact email / 联系邮箱", example = "warehouse@example.com")
|
||||
private String contactEmail;
|
||||
@Schema(description = "Warehouse status / 仓库状态", example = "ACTIVE", allowableValues = {"ACTIVE", "INACTIVE"})
|
||||
private String status;
|
||||
@Schema(description = "Remark / 备注", example = "Supports appointment receiving / 支持预约入库")
|
||||
private String remark;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.baiyue.warehouse.mapper;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.warehouse.entity.PartnerEntity;
|
||||
public interface PartnerMapper extends BaseMapper<PartnerEntity> { }
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.warehouse.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.warehouse.entity.ServiceCapabilityEntity;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 仓库服务能力 MyBatis-Plus Mapper,负责 {@code wh_service_capability} 表访问及仓库子记录的物理清理。
|
||||
*/
|
||||
public interface ServiceCapabilityMapper extends BaseMapper<ServiceCapabilityEntity> {
|
||||
|
||||
@Delete("DELETE FROM wh_service_capability WHERE tenant_id = #{tenantId} AND warehouse_id = #{warehouseId}")
|
||||
int physicallyDeleteByWarehouse(@Param("tenantId") long tenantId,
|
||||
@Param("warehouseId") long warehouseId);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.baiyue.warehouse.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 仓库地址 MyBatis-Plus Mapper,负责 {@code wh_warehouse_address} 表访问及仓库子记录的物理清理。
|
||||
*/
|
||||
public interface WarehouseAddressMapper extends BaseMapper<WarehouseAddressEntity> {
|
||||
|
||||
@Delete("DELETE FROM wh_warehouse_address WHERE tenant_id = #{tenantId} AND warehouse_id = #{warehouseId}")
|
||||
int physicallyDeleteByWarehouse(@Param("tenantId") long tenantId,
|
||||
@Param("warehouseId") long warehouseId);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.baiyue.warehouse.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
|
||||
/**
|
||||
* 仓库主数据 MyBatis-Plus Mapper,提供 {@code wh_warehouse} 表的单表持久化访问。
|
||||
*/
|
||||
public interface WarehouseMapper extends BaseMapper<WarehouseEntity> {}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.baiyue.warehouse.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.warehouse.entity.PartnerEntity;
|
||||
import com.baiyue.warehouse.mapper.PartnerMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
import java.util.List;
|
||||
|
||||
@Repository
|
||||
public class PartnerRepository {
|
||||
private final PartnerMapper mapper;
|
||||
public PartnerRepository(PartnerMapper mapper) { this.mapper = mapper; }
|
||||
public List<PartnerEntity> activeCandidates(long tenantId, String keyword, String type) {
|
||||
return mapper.selectList(new LambdaQueryWrapper<PartnerEntity>()
|
||||
.eq(PartnerEntity::getTenantId, tenantId).eq(PartnerEntity::getStatus, "ACTIVE")
|
||||
.eq(StringUtils.hasText(type), PartnerEntity::getPartnerType, type)
|
||||
.and(StringUtils.hasText(keyword), q -> q.like(PartnerEntity::getPartnerCode, keyword)
|
||||
.or().like(PartnerEntity::getPartnerName, keyword).or().like(PartnerEntity::getContactName, keyword))
|
||||
.orderByAsc(PartnerEntity::getPartnerName));
|
||||
}
|
||||
public PartnerEntity findActive(long tenantId, long id) {
|
||||
return mapper.selectOne(new LambdaQueryWrapper<PartnerEntity>()
|
||||
.eq(PartnerEntity::getTenantId, tenantId).eq(PartnerEntity::getId, id)
|
||||
.eq(PartnerEntity::getStatus, "ACTIVE"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.baiyue.warehouse.repository;
|
||||
|
||||
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.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.springframework.stereotype.Repository;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 仓库聚合持久化仓储,组合仓库、地址和服务能力 Mapper,并强制按租户范围执行查询与写入。
|
||||
*/
|
||||
@Repository
|
||||
public class WarehouseRepository {
|
||||
private final WarehouseMapper warehouses;
|
||||
private final WarehouseAddressMapper addresses;
|
||||
private final ServiceCapabilityMapper capabilities;
|
||||
|
||||
public WarehouseRepository(WarehouseMapper warehouses,
|
||||
WarehouseAddressMapper addresses,
|
||||
ServiceCapabilityMapper capabilities) {
|
||||
this.warehouses = warehouses;
|
||||
this.addresses = addresses;
|
||||
this.capabilities = capabilities;
|
||||
}
|
||||
|
||||
public void insertWarehouse(WarehouseEntity warehouse) {
|
||||
warehouses.insert(warehouse);
|
||||
}
|
||||
|
||||
public void saveWarehouse(WarehouseEntity warehouse) {
|
||||
warehouses.updateById(warehouse);
|
||||
}
|
||||
|
||||
public WarehouseEntity findOwned(long tenantId, long id) {
|
||||
return warehouses.selectOne(new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
.eq(WarehouseEntity::getId, id));
|
||||
}
|
||||
|
||||
public WarehouseEntity findActiveOwned(long tenantId, long id) {
|
||||
return warehouses.selectOne(new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
.eq(WarehouseEntity::getId, id)
|
||||
.eq(WarehouseEntity::getStatus, "ACTIVE"));
|
||||
}
|
||||
|
||||
public boolean existsCode(long tenantId, String code, Long excludedId) {
|
||||
return warehouses.selectCount(new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
.eq(WarehouseEntity::getWarehouseCode, code)
|
||||
.ne(excludedId != null, WarehouseEntity::getId, excludedId)) > 0;
|
||||
}
|
||||
|
||||
public PageResult<WarehouseEntity> page(long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
LambdaQueryWrapper<WarehouseEntity> wrapper = new LambdaQueryWrapper<WarehouseEntity>()
|
||||
.eq(WarehouseEntity::getTenantId, tenantId)
|
||||
.eq(StringUtils.hasText(query.status()), WarehouseEntity::getStatus, query.status())
|
||||
.eq(StringUtils.hasText(query.countryCode()), WarehouseEntity::getCountryCode, query.countryCode())
|
||||
.orderByDesc(WarehouseEntity::getCreatedAt);
|
||||
|
||||
if (StringUtils.hasText(query.capabilityCode())) {
|
||||
List<Long> ids = capabilities.selectList(new LambdaQueryWrapper<ServiceCapabilityEntity>()
|
||||
.select(ServiceCapabilityEntity::getWarehouseId)
|
||||
.eq(ServiceCapabilityEntity::getTenantId, tenantId)
|
||||
.eq(ServiceCapabilityEntity::getCapabilityCode, query.capabilityCode())
|
||||
.eq(ServiceCapabilityEntity::getEnabled, true))
|
||||
.stream()
|
||||
.map(ServiceCapabilityEntity::getWarehouseId)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (ids.isEmpty()) {
|
||||
return new PageResult<>(List.of(), query.page(), query.size(), 0, 0);
|
||||
}
|
||||
wrapper.in(WarehouseEntity::getId, ids);
|
||||
}
|
||||
return PageResult.from(warehouses.selectPage(new Page<>(query.page(), query.size()), wrapper));
|
||||
}
|
||||
|
||||
public PageResult<WarehouseEntity> activePage(long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
return page(tenantId, new WarehouseRequests.QueryRequest(query.page(), query.size(),
|
||||
query.countryCode(), query.capabilityCode(), "ACTIVE"));
|
||||
}
|
||||
|
||||
public List<WarehouseAddressEntity> findAddresses(long tenantId, long warehouseId) {
|
||||
return addresses.selectList(new LambdaQueryWrapper<WarehouseAddressEntity>()
|
||||
.eq(WarehouseAddressEntity::getTenantId, tenantId)
|
||||
.eq(WarehouseAddressEntity::getWarehouseId, warehouseId));
|
||||
}
|
||||
|
||||
public List<ServiceCapabilityEntity> findCapabilities(long tenantId, long warehouseId) {
|
||||
return capabilities.selectList(new LambdaQueryWrapper<ServiceCapabilityEntity>()
|
||||
.eq(ServiceCapabilityEntity::getTenantId, tenantId)
|
||||
.eq(ServiceCapabilityEntity::getWarehouseId, warehouseId));
|
||||
}
|
||||
|
||||
public void physicallyDeleteChildren(long tenantId, long warehouseId) {
|
||||
addresses.physicallyDeleteByWarehouse(tenantId, warehouseId);
|
||||
capabilities.physicallyDeleteByWarehouse(tenantId, warehouseId);
|
||||
}
|
||||
|
||||
public void insertAddress(WarehouseAddressEntity address) {
|
||||
addresses.insert(address);
|
||||
}
|
||||
|
||||
public void insertCapability(ServiceCapabilityEntity capability) {
|
||||
capabilities.insert(capability);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.entity.PartnerEntity;
|
||||
import com.baiyue.warehouse.repository.PartnerRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class PartnerQueryService {
|
||||
private final PartnerRepository repository;
|
||||
public PartnerQueryService(PartnerRepository repository) { this.repository = repository; }
|
||||
public List<PartnerEntity> candidates(long tenantId, String keyword, String type) {
|
||||
return repository.activeCandidates(tenantId, keyword, type);
|
||||
}
|
||||
public PartnerEntity active(long tenantId, long id) {
|
||||
PartnerEntity value=repository.findActive(tenantId,id);
|
||||
if (value==null) throw new BizException(ResultCode.NOT_FOUND,"往来单位不存在、已停用或不属于当前企业");
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
import com.baiyue.common.audit.BusinessAudit;
|
||||
import com.baiyue.common.core.api.PageResult;
|
||||
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.entity.ServiceCapabilityEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseAddressEntity;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 仓库应用服务,负责仓库主数据、地址和服务能力的租户级用例编排、事务控制与业务校验。
|
||||
*/
|
||||
@Service
|
||||
public class WarehouseApplicationService {
|
||||
@Resource
|
||||
private WarehouseRepository repository;
|
||||
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(
|
||||
event = "WAREHOUSE_CREATE",
|
||||
resourceType = "WAREHOUSE",
|
||||
resourceId = "#r.warehouseCode",
|
||||
description = "Create warehouse / 新建仓库",
|
||||
captureBefore = false
|
||||
)
|
||||
public Long create(WarehouseRequests.SaveRequest request) {
|
||||
ensureCodeUnique(request.tenantId(), request.warehouseCode(), null);
|
||||
WarehouseEntity warehouse = toEntity(request);
|
||||
repository.insertWarehouse(warehouse);
|
||||
insertChildren(warehouse.getTenantId(), warehouse.getId(), request.address(), request.capabilities());
|
||||
return warehouse.getId();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(
|
||||
event = "WAREHOUSE_UPDATE",
|
||||
resourceType = "WAREHOUSE",
|
||||
resourceId = "#id",
|
||||
description = "Update warehouse / 更新仓库"
|
||||
)
|
||||
public void update(long tenantId, long id, WarehouseRequests.SaveRequest request) {
|
||||
if (request.tenantId() == null || tenantId != request.tenantId()) {
|
||||
throw new BizException(ResultCode.FORBIDDEN, "Tenant mismatch / 租户不匹配");
|
||||
}
|
||||
WarehouseEntity old = owned(tenantId, id);
|
||||
ensureCodeUnique(tenantId, request.warehouseCode(), id);
|
||||
|
||||
WarehouseEntity warehouse = toEntity(request);
|
||||
warehouse.setId(old.getId());
|
||||
repository.saveWarehouse(warehouse);
|
||||
|
||||
repository.physicallyDeleteChildren(tenantId, id);
|
||||
insertChildren(tenantId, id, request.address(), request.capabilities());
|
||||
}
|
||||
|
||||
public PageResult<WarehouseEntity> page(long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
return repository.page(tenantId, query);
|
||||
}
|
||||
|
||||
public WarehouseDetailResponse detail(long tenantId, long id) {
|
||||
WarehouseEntity warehouse = owned(tenantId, id);
|
||||
List<WarehouseAddressEntity> addresses = repository.findAddresses(tenantId, id);
|
||||
List<ServiceCapabilityEntity> capabilities = repository.findCapabilities(tenantId, id);
|
||||
return new WarehouseDetailResponse(warehouse, addresses, capabilities);
|
||||
}
|
||||
|
||||
public PageResult<WarehouseEntity> merchantPage(long tenantId, WarehouseRequests.QueryRequest query) {
|
||||
return repository.activePage(tenantId, query);
|
||||
}
|
||||
|
||||
public WarehouseDetailResponse activeDetail(long tenantId, long id) {
|
||||
WarehouseEntity warehouse = repository.findActiveOwned(tenantId, id);
|
||||
if (warehouse == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "仓库不存在、已停用或不属于当前企业");
|
||||
}
|
||||
return new WarehouseDetailResponse(warehouse, repository.findAddresses(tenantId, id),
|
||||
repository.findCapabilities(tenantId, id).stream().filter(item -> Boolean.TRUE.equals(item.getEnabled())).toList());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@BusinessAudit(
|
||||
event = "WAREHOUSE_STATUS_CHANGE",
|
||||
resourceType = "WAREHOUSE",
|
||||
resourceId = "#id",
|
||||
description = "Enable or disable warehouse / 启用或停用仓库"
|
||||
)
|
||||
public void changeStatus(long tenantId, long id, String status) {
|
||||
if (!"ACTIVE".equals(status) && !"INACTIVE".equals(status)) {
|
||||
throw new BizException("Invalid status / 无效状态");
|
||||
}
|
||||
WarehouseEntity warehouse = owned(tenantId, id);
|
||||
warehouse.setStatus(status);
|
||||
repository.saveWarehouse(warehouse);
|
||||
}
|
||||
|
||||
private WarehouseEntity owned(long tenantId, long id) {
|
||||
WarehouseEntity warehouse = repository.findOwned(tenantId, id);
|
||||
if (warehouse == null) {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "Warehouse not found / 仓库不存在");
|
||||
}
|
||||
return warehouse;
|
||||
}
|
||||
|
||||
private void ensureCodeUnique(long tenantId, String code, Long excludedId) {
|
||||
if (repository.existsCode(tenantId, code, excludedId)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "Warehouse code already exists / 仓库编码已存在");
|
||||
}
|
||||
}
|
||||
|
||||
private WarehouseEntity toEntity(WarehouseRequests.SaveRequest request) {
|
||||
WarehouseEntity warehouse = new WarehouseEntity();
|
||||
warehouse.setTenantId(request.tenantId());
|
||||
warehouse.setWarehouseCode(request.warehouseCode());
|
||||
warehouse.setWarehouseName(request.warehouseName());
|
||||
warehouse.setWarehouseType(request.warehouseType());
|
||||
warehouse.setCountryCode(request.countryCode());
|
||||
warehouse.setTimezone(request.timezone());
|
||||
warehouse.setContactName(request.contactName());
|
||||
warehouse.setContactPhone(request.contactPhone());
|
||||
warehouse.setContactEmail(request.contactEmail());
|
||||
warehouse.setStatus(request.status());
|
||||
warehouse.setRemark(request.remark());
|
||||
return warehouse;
|
||||
}
|
||||
|
||||
private void insertChildren(long tenantId, long warehouseId,
|
||||
WarehouseRequests.AddressRequest address,
|
||||
List<WarehouseRequests.CapabilityRequest> requestedCapabilities) {
|
||||
WarehouseAddressEntity addressEntity = new WarehouseAddressEntity();
|
||||
addressEntity.setTenantId(tenantId);
|
||||
addressEntity.setWarehouseId(warehouseId);
|
||||
addressEntity.setAddressType(address.addressType());
|
||||
addressEntity.setCountryCode(address.countryCode());
|
||||
addressEntity.setStateProvince(address.stateProvince());
|
||||
addressEntity.setCity(address.city());
|
||||
addressEntity.setDistrict(address.district());
|
||||
addressEntity.setPostalCode(address.postalCode());
|
||||
addressEntity.setAddressLine1(address.addressLine1());
|
||||
addressEntity.setAddressLine2(address.addressLine2());
|
||||
addressEntity.setLatitude(address.latitude());
|
||||
addressEntity.setLongitude(address.longitude());
|
||||
repository.insertAddress(addressEntity);
|
||||
|
||||
if (requestedCapabilities == null) {
|
||||
return;
|
||||
}
|
||||
for (WarehouseRequests.CapabilityRequest capability : requestedCapabilities) {
|
||||
ServiceCapabilityEntity entity = new ServiceCapabilityEntity();
|
||||
entity.setTenantId(tenantId);
|
||||
entity.setWarehouseId(warehouseId);
|
||||
entity.setCapabilityCode(capability.capabilityCode());
|
||||
entity.setCapabilityName(capability.capabilityName());
|
||||
entity.setEnabled(capability.enabled() == null || capability.enabled());
|
||||
entity.setCutoffTime(capability.cutoffTime());
|
||||
entity.setDescription(capability.description());
|
||||
repository.insertCapability(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
server:
|
||||
port: ${WAREHOUSE_PORT:9205}
|
||||
spring:
|
||||
application:
|
||||
name: warehouse-service
|
||||
config:
|
||||
import:
|
||||
- optional:nacos:baiyue-common.yml?group=BAIYUE&refreshEnabled=true
|
||||
- optional:nacos:${spring.application.name}.yml?group=BAIYUE&refreshEnabled=true
|
||||
cloud:
|
||||
nacos:
|
||||
server-addr: ${NACOS_SERVER_ADDR:127.0.0.1:8848}
|
||||
username: ${NACOS_USERNAME:nacos}
|
||||
password: ${NACOS_PASSWORD:nacos}
|
||||
discovery:
|
||||
enabled: ${NACOS_DISCOVERY_ENABLED:true}
|
||||
register-enabled: ${NACOS_REGISTER_ENABLED:true}
|
||||
group: ${NACOS_GROUP:BAIYUE}
|
||||
namespace: ${NACOS_NAMESPACE:public}
|
||||
config.namespace: ${NACOS_NAMESPACE:public}
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: ${WAREHOUSE_DB_URL:jdbc:mysql://127.0.0.1:3306/baiyue_warehouse?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true}
|
||||
username: ${WAREHOUSE_DB_USERNAME:root}
|
||||
password: ${WAREHOUSE_DB_PASSWORD:123456}
|
||||
baiyue:
|
||||
security:
|
||||
jwt-secret: ${JWT_SECRET:change-me-to-at-least-32-bytes-long-before-start}
|
||||
internal-service-token: ${INTERNAL_SERVICE_TOKEN:change-me-to-an-internal-token-at-least-32-bytes-long}
|
||||
management.endpoints.web.exposure.include: health,info,prometheus
|
||||
@@ -0,0 +1,20 @@
|
||||
SET NAMES utf8mb4;
|
||||
USE baiyue_warehouse;
|
||||
CREATE TABLE IF NOT EXISTS wh_partner (
|
||||
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
partner_code VARCHAR(50) NOT NULL, partner_name VARCHAR(128) NOT NULL,
|
||||
partner_type VARCHAR(20) NOT NULL COMMENT 'CUSTOMER、SUPPLIER或BOTH',
|
||||
contact_name VARCHAR(64) NOT NULL, contact_phone VARCHAR(32) NOT NULL,
|
||||
contact_email VARCHAR(128) NULL, country_code CHAR(2) NOT NULL,
|
||||
province VARCHAR(64) NULL, city VARCHAR(64) NOT NULL, district VARCHAR(64) NULL,
|
||||
address_line VARCHAR(255) NOT NULL, postal_code VARCHAR(20) NULL,
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', created_by BIGINT UNSIGNED NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), updated_by BIGINT UNSIGNED NULL,
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0, version INT NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY(id), UNIQUE KEY uk_wh_partner_tenant_code(tenant_id,partner_code,deleted),
|
||||
KEY idx_wh_partner_tenant_status(tenant_id,status,deleted)
|
||||
) ENGINE=InnoDB COMMENT='仓储域内的租户往来单位配置';
|
||||
INSERT INTO wh_partner(id,tenant_id,partner_code,partner_name,partner_type,contact_name,contact_phone,contact_email,country_code,province,city,district,address_line,postal_code,status)
|
||||
VALUES(191000000000000001,10001,'CUS-001','深圳示例贸易有限公司','CUSTOMER','张三','13800138000','contact@example.com','CN','广东省','深圳市','宝安区','航城街道示例路1号','518000','ACTIVE')
|
||||
ON DUPLICATE KEY UPDATE partner_name=VALUES(partner_name),status='ACTIVE';
|
||||
@@ -0,0 +1,12 @@
|
||||
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,10001,'CN-SZX-01','深圳头程收货仓','DOMESTIC','CN','Asia/Shanghai','仓库收货组','0755-88886666','ACTIVE','工作日预约收货')
|
||||
ON DUPLICATE KEY UPDATE warehouse_name=VALUES(warehouse_name),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,10001,190000000000000001,'OPERATING','CN','广东省','深圳市','宝安区','518000','航城街道示例物流园1号仓')
|
||||
ON DUPLICATE KEY UPDATE address_line1=VALUES(address_line1);
|
||||
INSERT INTO wh_service_capability(id,tenant_id,warehouse_id,capability_code,capability_name,enabled,cutoff_time,description)
|
||||
VALUES(190000000000000021,10001,190000000000000001,'RECEIVING','头程收货',1,'16:00:00','工作日16点前预约可当日收货')
|
||||
ON DUPLICATE KEY UPDATE enabled=1,cutoff_time=VALUES(cutoff_time);
|
||||
@@ -0,0 +1,76 @@
|
||||
SET NAMES utf8mb4;
|
||||
CREATE DATABASE IF NOT EXISTS baiyue_warehouse
|
||||
DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
|
||||
USE baiyue_warehouse;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_warehouse (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '仓库ID(雪花ID)',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '仓库所属租户ID',
|
||||
warehouse_code VARCHAR(50) NOT NULL COMMENT '租户内唯一仓库编码',
|
||||
warehouse_name VARCHAR(120) NOT NULL COMMENT '仓库名称',
|
||||
warehouse_type VARCHAR(32) NOT NULL DEFAULT 'OVERSEAS' COMMENT '仓库类型',
|
||||
country_code CHAR(2) NOT NULL COMMENT '所在国家二字码',
|
||||
timezone VARCHAR(64) NOT NULL COMMENT '仓库所在地IANA时区',
|
||||
contact_name VARCHAR(64) DEFAULT NULL COMMENT '仓库联系人',
|
||||
contact_phone VARCHAR(32) DEFAULT NULL COMMENT '联系电话',
|
||||
contact_email VARCHAR(128) DEFAULT NULL COMMENT '联系邮箱',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT '仓库状态',
|
||||
remark VARCHAR(500) DEFAULT NULL COMMENT '仓库备注',
|
||||
created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_wh_tenant_code (tenant_id, warehouse_code, deleted),
|
||||
KEY idx_wh_tenant_status (tenant_id, status, deleted),
|
||||
KEY idx_wh_country (tenant_id, country_code, deleted)
|
||||
) ENGINE=InnoDB COMMENT='仓库主数据表:以租户为边界维护仓库编码、名称、国家、时区和启停状态';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_warehouse_address (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '仓库地址ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
||||
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
|
||||
address_type VARCHAR(20) NOT NULL DEFAULT 'OPERATING' COMMENT '地址类型,例如OPERATING或RETURN',
|
||||
country_code CHAR(2) NOT NULL COMMENT '国家二字码',
|
||||
state_province VARCHAR(100) DEFAULT NULL COMMENT '省或州',
|
||||
city VARCHAR(100) NOT NULL COMMENT '城市',
|
||||
district VARCHAR(100) DEFAULT NULL COMMENT '区县',
|
||||
postal_code VARCHAR(20) DEFAULT NULL COMMENT '邮政编码',
|
||||
address_line1 VARCHAR(255) NOT NULL COMMENT '主地址行',
|
||||
address_line2 VARCHAR(255) DEFAULT NULL COMMENT '补充地址行',
|
||||
latitude DECIMAL(10,7) DEFAULT NULL COMMENT '纬度',
|
||||
longitude DECIMAL(10,7) DEFAULT NULL COMMENT '经度',
|
||||
created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_address_type (tenant_id, warehouse_id, address_type, deleted),
|
||||
KEY idx_address_warehouse (tenant_id, warehouse_id, deleted)
|
||||
) ENGINE=InnoDB COMMENT='仓库地址表:保存运营、退货等不同用途的地址及可选经纬度';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_service_capability (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '服务能力ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '数据所属租户ID',
|
||||
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '所属仓库ID',
|
||||
capability_code VARCHAR(50) NOT NULL COMMENT '能力编码:RECEIVING、STORAGE、PICK_PACK、RETURN等',
|
||||
capability_name VARCHAR(100) NOT NULL COMMENT '能力名称',
|
||||
enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
|
||||
cutoff_time TIME DEFAULT NULL COMMENT '当日作业截单时间',
|
||||
description VARCHAR(500) DEFAULT NULL COMMENT '能力说明',
|
||||
created_by BIGINT UNSIGNED DEFAULT NULL COMMENT '创建人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
updated_by BIGINT UNSIGNED DEFAULT NULL COMMENT '最后修改人ID',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||
deleted TINYINT(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_capability (tenant_id, warehouse_id, capability_code, deleted),
|
||||
KEY idx_capability_query (tenant_id, warehouse_id, enabled, deleted)
|
||||
) ENGINE=InnoDB COMMENT='仓库服务能力表:声明仓库可提供的收货、仓储、拣包、退货等能力及截单时间';
|
||||
|
||||
-- 不使用跨服务数据库外键;仓库聚合内的归属由服务代码通过 tenant_id 与 warehouse_id 共同校验。
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.baiyue.warehouse.repository;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baiyue.warehouse.entity.PartnerEntity;
|
||||
import com.baiyue.warehouse.mapper.PartnerMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import java.util.List;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class PartnerRepositoryTest {
|
||||
@BeforeAll static void 初始化实体字段缓存() {
|
||||
TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new Configuration(), "test"), PartnerEntity.class);
|
||||
}
|
||||
@Test void 候选查询强制租户和启用状态() {
|
||||
PartnerMapper mapper=mock(PartnerMapper.class); when(mapper.selectList(any())).thenReturn(List.of());
|
||||
new PartnerRepository(mapper).activeCandidates(10001,"示例","CUSTOMER");
|
||||
@SuppressWarnings("unchecked") ArgumentCaptor<LambdaQueryWrapper<PartnerEntity>> c=ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(mapper).selectList(c.capture()); c.getValue().getSqlSegment();
|
||||
assertThat(c.getValue().getParamNameValuePairs().values()).contains(10001L,"ACTIVE","CUSTOMER");
|
||||
}
|
||||
@Test void 详情查询强制租户和启用状态() {
|
||||
PartnerMapper mapper=mock(PartnerMapper.class); when(mapper.selectOne(any())).thenReturn(null);
|
||||
new PartnerRepository(mapper).findActive(10002,9);
|
||||
@SuppressWarnings("unchecked") ArgumentCaptor<LambdaQueryWrapper<PartnerEntity>> c=ArgumentCaptor.forClass(LambdaQueryWrapper.class);
|
||||
verify(mapper).selectOne(c.capture()); c.getValue().getSqlSegment();
|
||||
assertThat(c.getValue().getParamNameValuePairs().values()).contains(10002L,9L,"ACTIVE");
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.baiyue.warehouse.service;
|
||||
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.warehouse.entity.WarehouseEntity;
|
||||
import com.baiyue.warehouse.repository.WarehouseRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class WarehouseApplicationServiceTest {
|
||||
@Test
|
||||
void 商户详情只返回启用且属于租户的仓库() {
|
||||
WarehouseRepository repository=mock(WarehouseRepository.class);
|
||||
WarehouseEntity warehouse=new WarehouseEntity(); warehouse.setId(1L); warehouse.setTenantId(10001L); warehouse.setStatus("ACTIVE");
|
||||
when(repository.findActiveOwned(10001,1)).thenReturn(warehouse);
|
||||
when(repository.findAddresses(10001,1)).thenReturn(java.util.List.of());
|
||||
when(repository.findCapabilities(10001,1)).thenReturn(java.util.List.of());
|
||||
WarehouseApplicationService service=new WarehouseApplicationService(); ReflectionTestUtils.setField(service,"repository",repository);
|
||||
assertThat(service.activeDetail(10001,1).warehouse()).isSameAs(warehouse);
|
||||
verify(repository).findActiveOwned(10001,1);
|
||||
}
|
||||
@Test
|
||||
void 停用或跨租户仓库按不存在处理() {
|
||||
WarehouseRepository repository=mock(WarehouseRepository.class);
|
||||
WarehouseApplicationService service=new WarehouseApplicationService(); ReflectionTestUtils.setField(service,"repository",repository);
|
||||
assertThatThrownBy(() -> service.activeDetail(10002,1)).isInstanceOf(BizException.class)
|
||||
.hasMessageContaining("停用");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user