This commit is contained in:
2026-08-17 14:46:44 +08:00
parent b914bb7532
commit a34b5d5ba0
38 changed files with 249 additions and 115 deletions
@@ -22,11 +22,9 @@ public class InternalSupplierController {
@GetMapping("/{supplierId}")
public ApiResponse<InternalSnapshot> get(@RequestHeader(value="X-Internal-Service-Token", required=false) String token,
@PathVariable long supplierId, @RequestParam(defaultValue="false") boolean requireActive,
@RequestParam(required=false) String category, @RequestParam(required=false) String scope) {
@RequestParam(required=false) String category) {
if (token == null || !MessageDigest.isEqual(expected, token.getBytes(StandardCharsets.UTF_8))) throw new BizException(ResultCode.FORBIDDEN, "内部服务凭证无效");
if (category != null && !category.matches("BOOKING|CUSTOMS|TRUCKING|CERTIFICATE")) throw new BizException(ResultCode.VALIDATION_FAILED, "供应商类别无效");
if (scope != null && !scope.matches("DOMESTIC|INTERNATIONAL")) throw new BizException(ResultCode.VALIDATION_FAILED, "订舱范围无效");
if (scope != null && !"BOOKING".equals(category)) throw new BizException(ResultCode.VALIDATION_FAILED, "scope仅适用于BOOKING");
return ApiResponse.success(service.internalSnapshot(supplierId, requireActive, category, scope));
return ApiResponse.success(service.internalSnapshot(supplierId, requireActive, category));
}
}
@@ -13,7 +13,6 @@ public final class SupplierDtos {
public record PageQuery(@Min(1) Integer page, @Min(1) @Max(200) Integer size,
@Pattern(regexp = "BOOKING|CUSTOMS|TRUCKING|CERTIFICATE") String category,
@Pattern(regexp = "DOMESTIC|INTERNATIONAL") String scope,
@Size(max = 80) String region,
@Pattern(regexp = "ACTIVE|INACTIVE") String status,
String keyword) {
@@ -26,7 +25,6 @@ public final class SupplierDtos {
public record SaveRequest(
@NotBlank @Size(max = 160) String name,
@NotBlank @Pattern(regexp = "BOOKING|CUSTOMS|TRUCKING|CERTIFICATE") String category,
@Pattern(regexp = "DOMESTIC|INTERNATIONAL") String scope,
@NotBlank @Size(max = 80) String region,
@NotBlank @Size(max = 80) String legalRepresentative,
@NotBlank @Size(max = 80) String contactName,
@@ -42,5 +40,5 @@ public final class SupplierDtos {
public record StatusRequest(@NotNull @Pattern(regexp = "ACTIVE|INACTIVE") String status) {}
public record Stats(long booking, long customs, long trucking, long certificate) {}
public record InternalSnapshot(long supplierId, String name, String category, String scope, String status) {}
public record InternalSnapshot(long supplierId, String name, String category, String status) {}
}
@@ -19,7 +19,6 @@ public class SupplierEntity extends BaseEntity {
private Long tenantId;
private String name;
private String category;
private String scope;
private String region;
private String legalRepresentative;
private String contactName;
@@ -17,7 +17,6 @@ public class SupplierRepository {
public PageResult<SupplierEntity> page(long tenantId, PageQuery query) {
LambdaQueryWrapper<SupplierEntity> w = base(tenantId)
.eq(StringUtils.hasText(query.category()), SupplierEntity::getCategory, query.category())
.eq(StringUtils.hasText(query.scope()), SupplierEntity::getScope, query.scope())
.eq(StringUtils.hasText(query.region()), SupplierEntity::getRegion, query.region())
.eq(StringUtils.hasText(query.status()), SupplierEntity::getStatus, query.status())
.and(StringUtils.hasText(query.keyword()), c -> c.like(SupplierEntity::getName, query.keyword())
@@ -18,8 +18,6 @@ public class SupplierService {
@Resource private SupplierRepository repository;
public PageResult<SupplierEntity> page(PageQuery query) {
if (query.category() == null && query.scope() != null) throw new BizException(ResultCode.VALIDATION_FAILED, "scope仅适用于订舱供应商");
if (query.category() != null && !"BOOKING".equals(query.category()) && query.scope() != null) throw new BizException(ResultCode.VALIDATION_FAILED, "scope仅适用于订舱供应商");
return repository.page(PUBLIC_TENANT_ID, query);
}
public SupplierEntity detail(long id) { return owned(id); }
@@ -28,7 +26,6 @@ public class SupplierService {
@Transactional
@BusinessAudit(event = "SUPPLIER_CREATE", resourceType = "SUPPLIER", resourceId = "#id", description = "新增供应商", captureBefore = false)
public Long create(long id, SaveRequest request) {
validateScope(request);
ensureName(request.name(), null);
SupplierEntity entity = new SupplierEntity(); entity.setId(id); entity.setTenantId(0L); copy(entity, request);
try { repository.insert(entity); } catch (DataIntegrityViolationException e) { throw new BizException(ResultCode.CONFLICT, "供应商名称已存在"); }
@@ -38,7 +35,7 @@ public class SupplierService {
@Transactional
@BusinessAudit(event = "SUPPLIER_UPDATE", resourceType = "SUPPLIER", resourceId = "#id", description = "编辑供应商")
public void update(long id, SaveRequest request) {
SupplierEntity e = owned(id); validateScope(request); ensureName(request.name(), id); copy(e, request);
SupplierEntity e = owned(id); ensureName(request.name(), id); copy(e, request);
try { repository.save(e); } catch (DataIntegrityViolationException exception) { throw new BizException(ResultCode.CONFLICT, "供应商名称已存在"); }
}
@@ -46,16 +43,14 @@ public class SupplierService {
@BusinessAudit(event = "SUPPLIER_STATUS_CHANGE", resourceType = "SUPPLIER", resourceId = "#id", description = "启停供应商")
public void changeStatus(long id, StatusRequest request) { SupplierEntity e = owned(id); if (!request.status().equals(e.getStatus())) { e.setStatus(request.status()); repository.save(e); } }
public InternalSnapshot internalSnapshot(long id, boolean requireActive, String category, String scope) {
public InternalSnapshot internalSnapshot(long id, boolean requireActive, String category) {
SupplierEntity e = owned(id);
if (requireActive && !"ACTIVE".equals(e.getStatus())) throw new BizException(ResultCode.CONFLICT, "供应商未启用");
if (category != null && !category.equals(e.getCategory())) throw new BizException(ResultCode.VALIDATION_FAILED, "供应商类别不匹配");
if (scope != null && (!"BOOKING".equals(e.getCategory()) || !scope.equals(e.getScope()))) throw new BizException(ResultCode.VALIDATION_FAILED, "订舱范围不匹配");
return new InternalSnapshot(e.getId(), e.getName(), e.getCategory(), e.getScope(), e.getStatus());
return new InternalSnapshot(e.getId(), e.getName(), e.getCategory(), e.getStatus());
}
private SupplierEntity owned(long id) { SupplierEntity e = repository.find(0L, id); if (e == null) throw new BizException(ResultCode.NOT_FOUND, "供应商不存在"); return e; }
private void ensureName(String name, Long id) { if (repository.existsName(0L, name, id)) throw new BizException(ResultCode.CONFLICT, "供应商名称已存在"); }
private static void validateScope(SaveRequest r) { if (r.category().equals("BOOKING") != (r.scope() != null)) throw new BizException(ResultCode.VALIDATION_FAILED, "国内/国外范围仅适用于订舱供应商"); }
private static void copy(SupplierEntity e, SaveRequest r) { e.setName(r.name()); e.setCategory(r.category()); e.setScope(r.scope()); e.setRegion(r.region()); e.setLegalRepresentative(r.legalRepresentative()); e.setContactName(r.contactName()); e.setPhone(r.phone()); e.setEmail(r.email()); e.setAddress(r.address()); e.setBusinessLicenseNo(r.businessLicenseNo()); e.setLicenseFileId(r.licenseFileId()); e.setStatus(r.status()); e.setSettlementPeriod(r.settlementPeriod()); e.setRemark(r.remark()); }
private static void copy(SupplierEntity e, SaveRequest r) { e.setName(r.name()); e.setCategory(r.category()); e.setRegion(r.region()); e.setLegalRepresentative(r.legalRepresentative()); e.setContactName(r.contactName()); e.setPhone(r.phone()); e.setEmail(r.email()); e.setAddress(r.address()); e.setBusinessLicenseNo(r.businessLicenseNo()); e.setLicenseFileId(r.licenseFileId()); e.setStatus(r.status()); e.setSettlementPeriod(r.settlementPeriod()); e.setRemark(r.remark()); }
}
@@ -7,7 +7,6 @@ CREATE TABLE IF NOT EXISTS wh_supplier (
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0',
name VARCHAR(160) NOT NULL COMMENT '供应商名称',
category VARCHAR(20) NOT NULL COMMENT '供应商类别:BOOKING订舱、CUSTOMS报关、TRUCKING拖车、CERTIFICATE认证',
scope VARCHAR(20) NULL COMMENT '订舱分类范围:DOMESTIC/INTERNATIONAL',
region VARCHAR(80) NOT NULL COMMENT '地区',
legal_representative VARCHAR(80) NOT NULL COMMENT '法人代表',
contact_name VARCHAR(80) NOT NULL COMMENT '联系人',
@@ -30,7 +29,6 @@ CREATE TABLE IF NOT EXISTS wh_supplier (
KEY idx_wh_supplier_filter (tenant_id,category,status,region),
CONSTRAINT chk_wh_supplier_category CHECK (category IN ('BOOKING','CUSTOMS','TRUCKING','CERTIFICATE')),
CONSTRAINT chk_wh_supplier_tenant CHECK (tenant_id = 0),
CONSTRAINT chk_wh_supplier_scope CHECK ((category='BOOKING' AND scope IN ('DOMESTIC','INTERNATIONAL')) OR (category<>'BOOKING' AND scope IS NULL)),
CONSTRAINT chk_wh_supplier_status CHECK (status IN ('ACTIVE','INACTIVE')),
CONSTRAINT chk_wh_supplier_settlement CHECK (settlement_period IN ('MONTHLY_30','MONTHLY_45','MONTHLY_60','MONTHLY_90','PER_TRANSACTION','IMMEDIATE'))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='平台公共供应商业务档案';
@@ -46,7 +44,6 @@ BEGIN
END IF;
END$$
DELIMITER ;
CALL supplier_add_column_if_missing('scope', "VARCHAR(20) NULL COMMENT '订舱分类范围:DOMESTIC/INTERNATIONAL' AFTER category");
CALL supplier_add_column_if_missing('license_file_id', "VARCHAR(128) NULL COMMENT 'File服务营业执照文件ID' AFTER business_license_no");
CALL supplier_add_column_if_missing('created_by', "BIGINT UNSIGNED NULL COMMENT '创建人ID'");
CALL supplier_add_column_if_missing('created_at', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间'");
@@ -73,7 +70,6 @@ END$$
DELIMITER ;
CALL supplier_add_check_if_missing('chk_wh_supplier_category', "category IN ('BOOKING','CUSTOMS','TRUCKING','CERTIFICATE')");
CALL supplier_add_check_if_missing('chk_wh_supplier_tenant', "tenant_id = 0");
CALL supplier_add_check_if_missing('chk_wh_supplier_scope', "(category='BOOKING' AND scope IN ('DOMESTIC','INTERNATIONAL')) OR (category<>'BOOKING' AND scope IS NULL)");
CALL supplier_add_check_if_missing('chk_wh_supplier_status', "status IN ('ACTIVE','INACTIVE')");
CALL supplier_add_check_if_missing('chk_wh_supplier_settlement', "settlement_period IN ('MONTHLY_30','MONTHLY_45','MONTHLY_60','MONTHLY_90','PER_TRANSACTION','IMMEDIATE')");
DROP PROCEDURE supplier_add_check_if_missing;
@@ -84,7 +80,7 @@ CREATE PROCEDURE assert_supplier_schema()
BEGIN
IF (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier') <> 1
OR (SELECT COUNT(DISTINCT index_name) FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND index_name IN ('PRIMARY','uk_wh_supplier_name','idx_wh_supplier_filter')) <> 3
OR (SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND constraint_type='CHECK' AND constraint_name IN ('chk_wh_supplier_category','chk_wh_supplier_tenant','chk_wh_supplier_scope','chk_wh_supplier_status','chk_wh_supplier_settlement')) <> 5 THEN
OR (SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND constraint_type='CHECK' AND constraint_name IN ('chk_wh_supplier_category','chk_wh_supplier_tenant','chk_wh_supplier_status','chk_wh_supplier_settlement')) <> 4 THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='供应商表、索引或约束初始化不完整';
END IF;
END$$
@@ -45,7 +45,6 @@
| `page` | integer | 否 | 默认 `1`,最小 `1` |
| `size` | integer | 否 | 默认 `20`,范围 `1..200` |
| `category` | enum | 否 | 供应商分类 |
| `scope` | enum | 否 | 仅订舱分类适用 |
| `region` | string | 否 | 地区精确匹配,最长 80 |
| `status` | enum | 否 | 合作状态 |
| `keyword` | string | 否 | 按供应商名称或联系人模糊匹配 |
@@ -72,7 +71,6 @@
| `tenantId` | string | 固定为 `0` |
| `name` | string | 供应商名称,平台内唯一 |
| `category` | enum | 分类 |
| `scope` | enum/null | 订舱供应商必填;其他分类为 `null` |
| `region` | string | 地区 |
| `legalRepresentative` | string | 法人代表 |
| `contactName` | string | 联系人 |
@@ -101,7 +99,6 @@
{
"name": "示例供应商有限公司",
"category": "BOOKING",
"scope": "DOMESTIC",
"region": "宁波",
"legalRepresentative": "张三",
"contactName": "李经理",
@@ -116,7 +113,7 @@
}
```
必填字段:`name`、`category`、`region`、`legalRepresentative`、`contactName`、`phone`、`status`、`settlementPeriod`。`BOOKING` 时 `scope` 必填;其他分类 `scope` 必须传 `null` 或省略。
必填字段:`name`、`category`、`region`、`legalRepresentative`、`contactName`、`phone`、`status`、`settlementPeriod`。订舱供应商不区分国内或国外。
新增成功的 `data` 是供应商 ID 字符串。编辑成功的 `data` 为 `null`。
@@ -152,13 +149,6 @@
| `TRUCKING` | 拖车 |
| `CERTIFICATE` | 证书 |
订舱范围:
| 值 | 展示文案 |
|---|---|
| `DOMESTIC` | 国内 |
| `INTERNATIONAL` | 国外 |
合作状态:
| 值 | 展示文案 |
@@ -187,7 +177,7 @@
| `A0403` | 非平台用户或缺少对应权限 | 显示无权限 |
| `A0404` | 供应商不存在 | 提示记录不存在并刷新列表 |
| `A0409` | 供应商名称重复或并发唯一键冲突 | 保留表单并提示名称已存在 |
| `A0422` | 字段校验失败、订舱范围规则不满足 | 使用 `message`/`data` 显示字段错误 |
| `A0422` | 字段校验失败 | 使用 `message`/`data` 显示字段错误 |
| `S0500` | 未预期服务异常 | 展示通用错误并记录 `requestId/traceId` |
| `S0503` | Warehouse 数据库不可用 | 展示稍后重试并记录 `requestId/traceId` |
@@ -11,7 +11,6 @@ paths:
- { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
- { name: size, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 20 } }
- { name: category, in: query, schema: { $ref: '#/components/schemas/Category' } }
- { name: scope, in: query, description: 仅订舱供应商适用, schema: { $ref: '#/components/schemas/Scope' } }
- { name: region, in: query, schema: { type: string } }
- { name: status, in: query, schema: { $ref: '#/components/schemas/Status' } }
- { name: keyword, in: query, description: 供应商名称或联系人, schema: { type: string } }
@@ -57,13 +56,12 @@ paths:
/internal/warehouse/v1/suppliers/{supplierId}:
get:
summary: 内部服务查询并校验供应商快照
description: 仅服务间调用,需 X-Internal-Service-Token;不经 Gateway。可要求 ACTIVE,并校验类别与订舱范围。
description: 仅服务间调用,需 X-Internal-Service-Token;不经 Gateway。可要求 ACTIVE,并校验供应商类别。
security: [{ internalServiceToken: [] }]
parameters:
- { name: supplierId, in: path, required: true, schema: { type: integer, format: int64, minimum: 1 } }
- { name: requireActive, in: query, schema: { type: boolean, default: false } }
- { name: category, in: query, schema: { $ref: '#/components/schemas/Category' } }
- { name: scope, in: query, schema: { $ref: '#/components/schemas/Scope' } }
responses:
'200': { description: 成功, content: { application/json: { schema: { $ref: '#/components/schemas/InternalSnapshotResponse' } } } }
'403': { $ref: '#/components/responses/Forbidden' }
@@ -74,7 +72,6 @@ components:
RequiredTenantId: { name: tenantId, in: query, required: true, schema: { type: integer, format: int64, enum: [0] } }
schemas:
Category: { type: string, enum: [BOOKING, CUSTOMS, TRUCKING, CERTIFICATE] }
Scope: { type: string, enum: [DOMESTIC, INTERNATIONAL] }
Status: { type: string, enum: [ACTIVE, INACTIVE] }
SupplierSave:
type: object
@@ -82,7 +79,6 @@ components:
properties:
name: { type: string, maxLength: 160 }
category: { $ref: '#/components/schemas/Category' }
scope: { $ref: '#/components/schemas/Scope', description: BOOKING 必填,其他分类必须为空 }
region: { type: string, maxLength: 80 }
legalRepresentative: { type: string, maxLength: 80 }
contactName: { type: string, maxLength: 80 }
@@ -140,7 +136,6 @@ components:
supplierId: { type: integer, format: int64 }
name: { type: string }
category: { $ref: '#/components/schemas/Category' }
scope: { $ref: '#/components/schemas/Scope' }
status: { $ref: '#/components/schemas/Status' }
InternalSnapshotResponse:
allOf: [{ $ref: '#/components/schemas/ApiResponse' }, { type: object, properties: { data: { $ref: '#/components/schemas/InternalSnapshot' } } }]
@@ -0,0 +1,21 @@
package com.baiyue.warehouse.controller;
import org.junit.jupiter.api.Test;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.reflect.Method;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
class InternalSupplierControllerContractTest {
@Test void validatesSupplierByActiveStatusAndCategoryWithoutBookingScope() {
assertThat(InternalSupplierController.class.getAnnotation(RequestMapping.class).value())
.containsExactly("/internal/warehouse/v1/suppliers");
Method get = Arrays.stream(InternalSupplierController.class.getDeclaredMethods())
.filter(method -> method.getName().equals("get"))
.findFirst().orElseThrow();
assertThat(Arrays.stream(get.getParameters()).map(parameter -> parameter.getName()))
.containsExactly("token", "supplierId", "requireActive", "category");
}
}
@@ -8,17 +8,18 @@ import java.nio.file.Path;
import static org.assertj.core.api.Assertions.assertThat;
class SupplierSqlContractTest {
@Test void freezesPublicSupplierSchemaAndBookingScope() throws IOException {
@Test void freezesPublicSupplierSchemaWithoutBookingScope() throws IOException {
try (var stream = getClass().getResourceAsStream("/db/warehouse-supplier.sql")) {
assertThat(stream).isNotNull();
String sql = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_supplier", "tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0",
"chk_wh_supplier_tenant CHECK (tenant_id = 0)",
"category='BOOKING' AND scope IN ('DOMESTIC','INTERNATIONAL')", "license_file_id VARCHAR(128)",
"license_file_id VARCHAR(128)",
"MONTHLY_30','MONTHLY_45','MONTHLY_60','MONTHLY_90','PER_TRANSACTION','IMMEDIATE",
"created_by BIGINT UNSIGNED", "updated_by BIGINT UNSIGNED",
"supplier_add_column_if_missing", "uk_wh_supplier_name", "idx_wh_supplier_filter",
"assert_supplier_schema");
assertThat(sql).doesNotContain("scope VARCHAR", "chk_wh_supplier_scope", "DOMESTIC/INTERNATIONAL");
}
}
@@ -27,6 +28,8 @@ class SupplierSqlContractTest {
assertThat(sql).contains("CREATE TABLE IF NOT EXISTS wh_supplier", "uk_wh_supplier_name",
"idx_wh_supplier_filter", "platform:supplier:list", "platform:supplier:create",
"platform:supplier:edit", "platform:supplier:status", "assert_platform_supplier_permissions");
String supplierSection = sql.substring(sql.indexOf("-- 平台公共供应商业务档案及权限"));
assertThat(supplierSection).doesNotContain("chk_wh_supplier_scope", "订舱分类范围");
}
private static Path resolveUnifiedSql() {
@@ -2,7 +2,6 @@ package com.baiyue.warehouse.service;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.warehouse.dto.SupplierDtos.SaveRequest;
import com.baiyue.warehouse.dto.SupplierDtos.PageQuery;
import com.baiyue.warehouse.dto.SupplierDtos.StatusRequest;
import com.baiyue.warehouse.entity.SupplierEntity;
import com.baiyue.warehouse.repository.SupplierRepository;
@@ -13,8 +12,8 @@ import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -26,7 +25,7 @@ class SupplierServiceTest {
private SaveRequest booking;
@BeforeEach void setUp() {
booking = new SaveRequest("测试供应商", "BOOKING", "DOMESTIC", "宁波", "法人", "联系人",
booking = new SaveRequest("测试供应商", "BOOKING", "宁波", "法人", "联系人",
"13800000000", "supplier@example.com", "地址", "91330000TEST", "192000000000000001",
"ACTIVE", "MONTHLY_30", null);
}
@@ -37,24 +36,6 @@ class SupplierServiceTest {
verify(repository).insert(any(SupplierEntity.class));
}
@Test void rejectsScopeForNonBookingSupplier() {
SaveRequest customs = new SaveRequest("报关供应商", "CUSTOMS", "DOMESTIC", "宁波", "法人", "联系人",
"13800000000", null, null, null, null, "ACTIVE", "PER_TRANSACTION", null);
assertThatThrownBy(() -> service.create(101L, customs)).isInstanceOf(BizException.class)
.hasMessageContaining("仅适用于订舱供应商");
}
@Test void requiresScopeForBookingSupplier() {
SaveRequest missingScope = new SaveRequest("订舱供应商", "BOOKING", null, "宁波", "法人", "联系人",
"13800000000", null, null, null, null, "ACTIVE", "PER_TRANSACTION", null);
assertThatThrownBy(() -> service.create(101L, missingScope)).isInstanceOf(BizException.class);
}
@Test void rejectsScopeFilterWithoutBookingCategory() {
assertThatThrownBy(() -> service.page(new PageQuery(1, 20, "CUSTOMS", "DOMESTIC", null, null, null)))
.isInstanceOf(BizException.class).hasMessageContaining("scope仅适用于订舱供应商");
}
@Test void repeatedStatusChangeIsIdempotent() {
SupplierEntity entity = new SupplierEntity(); entity.setId(101L); entity.setTenantId(0L); entity.setStatus("ACTIVE");
when(repository.find(0L, 101L)).thenReturn(entity);
@@ -64,9 +45,23 @@ class SupplierServiceTest {
@Test void validatesInternalSupplierSnapshot() {
SupplierEntity entity = new SupplierEntity(); entity.setId(101L); entity.setTenantId(0L); entity.setName("测试供应商");
entity.setCategory("BOOKING"); entity.setScope("DOMESTIC"); entity.setStatus("ACTIVE");
entity.setCategory("BOOKING"); entity.setStatus("ACTIVE");
when(repository.find(0L, 101L)).thenReturn(entity);
var snapshot = service.internalSnapshot(101L, true, "BOOKING", "DOMESTIC");
var snapshot = service.internalSnapshot(101L, true, "BOOKING");
org.assertj.core.api.Assertions.assertThat(snapshot.supplierId()).isEqualTo(101L);
org.assertj.core.api.Assertions.assertThat(snapshot.category()).isEqualTo("BOOKING");
org.assertj.core.api.Assertions.assertThat(snapshot.status()).isEqualTo("ACTIVE");
}
@Test void internalSnapshotRequiresActiveSupplierAndMatchingCategory() {
SupplierEntity inactive = new SupplierEntity(); inactive.setId(101L); inactive.setTenantId(0L);
inactive.setName("停用订舱供应商"); inactive.setCategory("BOOKING"); inactive.setStatus("INACTIVE");
when(repository.find(0L, 101L)).thenReturn(inactive);
assertThatThrownBy(() -> service.internalSnapshot(101L, true, "BOOKING"))
.isInstanceOf(BizException.class).hasMessageContaining("未启用");
inactive.setStatus("ACTIVE");
assertThatThrownBy(() -> service.internalSnapshot(101L, true, "CUSTOMS"))
.isInstanceOf(BizException.class).hasMessageContaining("类别不匹配");
}
}