feat(warehouse): support supplier carrier associations
This commit is contained in:
@@ -33,6 +33,18 @@ CREATE TABLE IF NOT EXISTS wh_supplier (
|
||||
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='平台公共供应商业务档案';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_supplier_carrier (
|
||||
supplier_id BIGINT UNSIGNED NOT NULL COMMENT 'BOOKING供应商ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0',
|
||||
carrier_code VARCHAR(16) NOT NULL COMMENT '船司标准编码',
|
||||
created_by BIGINT UNSIGNED NULL COMMENT '创建人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
PRIMARY KEY (supplier_id,carrier_code),
|
||||
KEY idx_wh_supplier_carrier_code (tenant_id,carrier_code,supplier_id),
|
||||
CONSTRAINT chk_wh_supplier_carrier_tenant CHECK (tenant_id = 0),
|
||||
CONSTRAINT chk_wh_supplier_carrier_code CHECK (carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='BOOKING供应商与标准船司编码多对多关联';
|
||||
|
||||
DROP PROCEDURE IF EXISTS supplier_add_column_if_missing;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE supplier_add_column_if_missing(IN p_name VARCHAR(64), IN p_definition TEXT)
|
||||
@@ -79,7 +91,10 @@ DELIMITER $$
|
||||
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(*) FROM information_schema.tables WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier_carrier') <> 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(DISTINCT index_name) FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier_carrier' AND index_name IN ('PRIMARY','idx_wh_supplier_carrier_code')) <> 2
|
||||
OR (SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier_carrier' AND constraint_type='CHECK' AND constraint_name IN ('chk_wh_supplier_carrier_tenant','chk_wh_supplier_carrier_code')) <> 2
|
||||
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;
|
||||
|
||||
@@ -27,6 +27,9 @@
|
||||
| 功能 | 方法与路径 | 权限码 |
|
||||
|---|---|---|
|
||||
| 列表 | `GET /api/platform/v1/suppliers` | `platform:supplier:list` |
|
||||
| 导出 | `GET /api/platform/v1/suppliers/export` | `platform:supplier:list` |
|
||||
| 导入模板 | `GET /api/platform/v1/suppliers/import-template` | `platform:supplier:create` |
|
||||
| 导入 | `POST /api/platform/v1/suppliers/import` | `platform:supplier:create` |
|
||||
| 分类统计 | `GET /api/platform/v1/suppliers/stats` | `platform:supplier:list` |
|
||||
| 详情 | `GET /api/platform/v1/suppliers/{id}?tenantId=0` | `platform:supplier:list` |
|
||||
| 新增 | `POST /api/platform/v1/suppliers?tenantId=0` | `platform:supplier:create` |
|
||||
@@ -48,6 +51,7 @@
|
||||
| `region` | string | 否 | 地区精确匹配,最长 80 |
|
||||
| `status` | enum | 否 | 合作状态 |
|
||||
| `keyword` | string | 否 | 按供应商名称或联系人模糊匹配 |
|
||||
| `carrierCode` | enum | 否 | 船司标准编码;传入后服务端只返回 `ACTIVE + BOOKING` 且存在有效关联的供应商 |
|
||||
|
||||
分页响应 `data`:
|
||||
|
||||
@@ -71,6 +75,7 @@
|
||||
| `tenantId` | string | 固定为 `0` |
|
||||
| `name` | string | 供应商名称,平台内唯一 |
|
||||
| `category` | enum | 分类 |
|
||||
| `carrierCodes` | string[] | BOOKING关联的标准船司编码数组;响应始终返回数组 |
|
||||
| `region` | string | 地区 |
|
||||
| `legalRepresentative` | string | 法人代表 |
|
||||
| `contactName` | string | 联系人 |
|
||||
@@ -99,6 +104,7 @@
|
||||
{
|
||||
"name": "示例供应商有限公司",
|
||||
"category": "BOOKING",
|
||||
"carrierCodes": ["MATSON", "ZIM"],
|
||||
"region": "宁波",
|
||||
"legalRepresentative": "张三",
|
||||
"contactName": "李经理",
|
||||
@@ -113,7 +119,9 @@
|
||||
}
|
||||
```
|
||||
|
||||
必填字段:`name`、`category`、`region`、`legalRepresentative`、`contactName`、`phone`、`status`、`settlementPeriod`。订舱供应商不区分国内或国外。
|
||||
必填字段:`name`、`category`、`region`、`legalRepresentative`、`contactName`、`phone`、`status`、`settlementPeriod`。`BOOKING` 必须提交至少一个去重后的 `carrierCodes`;非 `BOOKING` 可不提交该字段或提交空数组,提交非空数组返回 `A0400`。订舱供应商不区分国内或国外。
|
||||
|
||||
Admin 订舱供应商候选固定复用分页接口:`GET /api/platform/v1/suppliers?category=BOOKING&status=ACTIVE&carrierCode=MATSON&page=1&size=200`,权限为 `platform:supplier:list`,响应为 `PageResult<Supplier>`。服务端完成关联过滤,前端不得先拉全量再自行过滤。
|
||||
|
||||
新增成功的 `data` 是供应商 ID 字符串。编辑成功的 `data` 为 `null`。
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ paths:
|
||||
- { name: region, in: query, schema: { type: string } }
|
||||
- { name: status, in: query, schema: { $ref: '#/components/schemas/Status' } }
|
||||
- { name: keyword, in: query, description: 供应商名称或联系人, schema: { type: string } }
|
||||
- { name: carrierCode, in: query, description: 按船司编码筛选时强制只返回ACTIVE且BOOKING的有效关联供应商, schema: { $ref: '#/components/schemas/CarrierCode' } }
|
||||
responses: { '200': { description: 成功, content: { application/json: { schema: { $ref: '#/components/schemas/SupplierPageResponse' } } } }, '400': { $ref: '#/components/responses/BadRequest' } }
|
||||
post:
|
||||
summary: 新增供应商
|
||||
@@ -22,6 +23,33 @@ paths:
|
||||
parameters: [{ $ref: '#/components/parameters/RequiredTenantId' }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/SupplierSave' } } } }
|
||||
responses: { '200': { description: 统一 ApiResponse 包装的供应商 ID } }
|
||||
/api/platform/v1/suppliers/import-template:
|
||||
get:
|
||||
summary: 下载供应商XLSX导入模板
|
||||
description: 权限 platform:supplier:create。
|
||||
security: [{ bearerAuth: [] }]
|
||||
responses: { '200': { description: XLSX模板, content: { application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: { schema: { type: string, format: binary } } } } }
|
||||
/api/platform/v1/suppliers/import:
|
||||
post:
|
||||
summary: 原子批量导入供应商
|
||||
description: 权限 platform:supplier:create;整批校验通过后才写入。BOOKING的多值船司编码在文件中使用逗号分隔,数据库始终保存为关联行。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ $ref: '#/components/parameters/RequiredTenantId' }]
|
||||
requestBody: { required: true, content: { multipart/form-data: { schema: { type: object, required: [file], properties: { file: { type: string, format: binary } } } } } }
|
||||
responses: { '200': { description: 导入结果 } }
|
||||
/api/platform/v1/suppliers/export:
|
||||
get:
|
||||
summary: 按当前查询条件导出供应商CSV
|
||||
description: 权限 platform:supplier:list;carrierCodes在CSV中使用竖线分隔,不改变数据库多对多结构。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/TenantId' }
|
||||
- { name: category, in: query, schema: { $ref: '#/components/schemas/Category' } }
|
||||
- { name: region, in: query, schema: { type: string } }
|
||||
- { name: status, in: query, schema: { $ref: '#/components/schemas/Status' } }
|
||||
- { name: keyword, in: query, schema: { type: string } }
|
||||
- { name: carrierCode, in: query, schema: { $ref: '#/components/schemas/CarrierCode' } }
|
||||
responses: { '200': { description: CSV文件, content: { text/csv: { schema: { type: string, format: binary } } } } }
|
||||
/api/platform/v1/suppliers/stats:
|
||||
get:
|
||||
summary: 查询四类供应商统计
|
||||
@@ -66,12 +94,25 @@ paths:
|
||||
'200': { description: 成功, content: { application/json: { schema: { $ref: '#/components/schemas/InternalSnapshotResponse' } } } }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'404': { $ref: '#/components/responses/NotFound' }
|
||||
/internal/warehouse/v1/suppliers:
|
||||
get:
|
||||
summary: 内部查询有效订舱供应商候选
|
||||
description: 仅服务间调用;服务端固定返回ACTIVE、BOOKING且存在指定carrierCode关联的供应商。
|
||||
security: [{ internalServiceToken: [] }]
|
||||
parameters:
|
||||
- { name: category, in: query, required: true, schema: { type: string, enum: [BOOKING] } }
|
||||
- { name: carrierCode, in: query, required: true, schema: { $ref: '#/components/schemas/CarrierCode' } }
|
||||
responses:
|
||||
'200': { description: 成功, content: { application/json: { schema: { $ref: '#/components/schemas/InternalSnapshotListResponse' } } } }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
components:
|
||||
parameters:
|
||||
TenantId: { name: tenantId, in: query, required: false, schema: { type: integer, format: int64, enum: [0] } }
|
||||
RequiredTenantId: { name: tenantId, in: query, required: true, schema: { type: integer, format: int64, enum: [0] } }
|
||||
schemas:
|
||||
Category: { type: string, enum: [BOOKING, CUSTOMS, TRUCKING, CERTIFICATE] }
|
||||
CarrierCode: { type: string, enum: [MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER] }
|
||||
CarrierCodes: { type: array, uniqueItems: true, maxItems: 7, items: { $ref: '#/components/schemas/CarrierCode' }, description: BOOKING必填且至少一个;非BOOKING可省略或为空,提交非空数组返回A0400 }
|
||||
Status: { type: string, enum: [ACTIVE, INACTIVE] }
|
||||
SupplierSave:
|
||||
type: object
|
||||
@@ -79,6 +120,7 @@ components:
|
||||
properties:
|
||||
name: { type: string, maxLength: 160 }
|
||||
category: { $ref: '#/components/schemas/Category' }
|
||||
carrierCodes: { $ref: '#/components/schemas/CarrierCodes' }
|
||||
region: { type: string, maxLength: 80 }
|
||||
legalRepresentative: { type: string, maxLength: 80 }
|
||||
contactName: { type: string, maxLength: 80 }
|
||||
@@ -94,10 +136,11 @@ components:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/SupplierSave'
|
||||
- type: object
|
||||
required: [id, tenantId]
|
||||
required: [id, tenantId, carrierCodes]
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
tenantId: { type: integer, format: int64, enum: [0] }
|
||||
carrierCodes: { $ref: '#/components/schemas/CarrierCodes' }
|
||||
PageResult:
|
||||
type: object
|
||||
required: [records, total, current, size]
|
||||
@@ -131,14 +174,17 @@ components:
|
||||
allOf: [{ $ref: '#/components/schemas/ApiResponse' }, { type: object, properties: { data: { $ref: '#/components/schemas/Stats' } } }]
|
||||
InternalSnapshot:
|
||||
type: object
|
||||
required: [supplierId, name, category, status]
|
||||
required: [supplierId, name, category, status, carrierCodes]
|
||||
properties:
|
||||
supplierId: { type: integer, format: int64 }
|
||||
name: { type: string }
|
||||
category: { $ref: '#/components/schemas/Category' }
|
||||
status: { $ref: '#/components/schemas/Status' }
|
||||
carrierCodes: { $ref: '#/components/schemas/CarrierCodes' }
|
||||
InternalSnapshotResponse:
|
||||
allOf: [{ $ref: '#/components/schemas/ApiResponse' }, { type: object, properties: { data: { $ref: '#/components/schemas/InternalSnapshot' } } }]
|
||||
InternalSnapshotListResponse:
|
||||
allOf: [{ $ref: '#/components/schemas/ApiResponse' }, { type: object, properties: { data: { type: array, items: { $ref: '#/components/schemas/InternalSnapshot' } } } }]
|
||||
Error:
|
||||
type: object
|
||||
properties: { code: { type: string }, message: { type: string }, requestId: { type: string }, traceId: { type: string } }
|
||||
|
||||
Reference in New Issue
Block a user