优化
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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_domestic_container_order (
|
||||
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '平台公共作用域,固定为0',
|
||||
business_type VARCHAR(16) NOT NULL COMMENT 'INBOUND或OUTBOUND', order_no VARCHAR(40) NOT NULL,
|
||||
warehouse_id BIGINT UNSIGNED NOT NULL COMMENT '平台国内仓ID', warehouse_name VARCHAR(120) NOT NULL,
|
||||
container_no VARCHAR(32) NOT NULL, cargo_name VARCHAR(128) NOT NULL, box_count INT UNSIGNED NOT NULL,
|
||||
volume_cbm DECIMAL(16,4) NOT NULL DEFAULT 0, operation_time DATETIME(3) NOT NULL,
|
||||
status VARCHAR(16) NOT NULL COMMENT 'PENDING或COMPLETED', remark VARCHAR(500) NULL,
|
||||
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_dc_order_no (tenant_id, order_no, deleted),
|
||||
KEY idx_dc_order_list (tenant_id, business_type, warehouse_id, status, operation_time),
|
||||
KEY idx_dc_order_container (tenant_id, warehouse_id, container_no, business_type),
|
||||
CONSTRAINT chk_dc_order_scope CHECK (tenant_id=0), CONSTRAINT chk_dc_order_type CHECK (business_type IN ('INBOUND','OUTBOUND')),
|
||||
CONSTRAINT chk_dc_order_status CHECK (status IN ('PENDING','COMPLETED')),
|
||||
CONSTRAINT chk_dc_order_quantity CHECK (box_count>0 AND volume_cbm>=0)
|
||||
) ENGINE=InnoDB COMMENT='国内仓独立整柜入出库单,不复用头程装柜货柜模型';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_domestic_container_inventory (
|
||||
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
warehouse_id BIGINT UNSIGNED NOT NULL, warehouse_name VARCHAR(120) NOT NULL,
|
||||
container_no VARCHAR(32) NOT NULL, cargo_name VARCHAR(128) NOT NULL,
|
||||
inbound_boxes INT UNSIGNED NOT NULL, outbound_boxes INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
available_boxes INT UNSIGNED NOT NULL, inbound_volume_cbm DECIMAL(16,4) NOT NULL,
|
||||
inventory_status VARCHAR(24) NOT NULL COMMENT 'IN_STOCK、PARTIALLY_OUTBOUND或OUTBOUND',
|
||||
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_dc_inventory_container (tenant_id, warehouse_id, container_no, deleted),
|
||||
KEY idx_dc_inventory_list (tenant_id, warehouse_id, inventory_status, updated_at),
|
||||
CONSTRAINT chk_dc_inventory_scope CHECK (tenant_id=0),
|
||||
CONSTRAINT chk_dc_inventory_balance CHECK (inbound_boxes=outbound_boxes+available_boxes),
|
||||
CONSTRAINT chk_dc_inventory_status CHECK (inventory_status IN ('IN_STOCK','PARTIALLY_OUTBOUND','OUTBOUND'))
|
||||
) ENGINE=InnoDB COMMENT='平台国内仓整柜库存台账';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_domestic_container_transaction (
|
||||
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
inventory_id BIGINT UNSIGNED NOT NULL, warehouse_id BIGINT UNSIGNED NOT NULL, container_no VARCHAR(32) NOT NULL,
|
||||
business_type VARCHAR(16) NOT NULL, order_id BIGINT UNSIGNED NOT NULL, order_no VARCHAR(40) NOT NULL,
|
||||
quantity INT UNSIGNED NOT NULL, operation_time DATETIME(3) NOT NULL, remark VARCHAR(500) NULL,
|
||||
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_dc_transaction_order (tenant_id, business_type, order_id),
|
||||
KEY idx_dc_transaction_detail (tenant_id, inventory_id, operation_time),
|
||||
CONSTRAINT chk_dc_transaction_scope CHECK (tenant_id=0), CONSTRAINT chk_dc_transaction_type CHECK (business_type IN ('INBOUND','OUTBOUND')),
|
||||
CONSTRAINT chk_dc_transaction_quantity CHECK (quantity>0)
|
||||
) ENGINE=InnoDB COMMENT='国内仓整柜不可变进销存交易流水';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_domestic_container_idempotency (
|
||||
id BIGINT UNSIGNED NOT NULL, tenant_id BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
operation_type VARCHAR(16) NOT NULL, idempotency_key VARCHAR(128) NOT NULL, request_hash CHAR(64) NOT NULL,
|
||||
resource_id BIGINT UNSIGNED NULL, resource_no VARCHAR(40) NULL, operation_status VARCHAR(16) NOT NULL,
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), completed_at DATETIME(3) NULL,
|
||||
PRIMARY KEY (id), UNIQUE KEY uk_dc_idempotency (tenant_id, operation_type, idempotency_key),
|
||||
CONSTRAINT chk_dc_idempotency_scope CHECK (tenant_id=0), CONSTRAINT chk_dc_idempotency_type CHECK (operation_type IN ('INBOUND','OUTBOUND')),
|
||||
CONSTRAINT chk_dc_idempotency_status CHECK (operation_status IN ('PROCESSING','COMPLETED'))
|
||||
) ENGINE=InnoDB COMMENT='国内仓整柜写操作幂等记录';
|
||||
|
||||
-- IAM 权限由统一部署脚本合并:platform:domestic-container:list、
|
||||
-- platform:domestic-container:inbound:create、platform:domestic-container:outbound:create。
|
||||
@@ -0,0 +1,91 @@
|
||||
-- 平台公共供应商业务档案。可用于空库初始化,也可重复执行升级存量结构。
|
||||
USE baiyue_warehouse;
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wh_supplier (
|
||||
id BIGINT UNSIGNED NOT NULL COMMENT '供应商ID',
|
||||
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 '联系人',
|
||||
phone VARCHAR(40) NOT NULL COMMENT '联系电话',
|
||||
email VARCHAR(160) NULL COMMENT '电子邮箱',
|
||||
address VARCHAR(255) NULL COMMENT '公司地址',
|
||||
business_license_no VARCHAR(64) NULL COMMENT '营业执照号',
|
||||
license_file_id VARCHAR(128) NULL COMMENT 'File服务营业执照文件ID',
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE' COMMENT 'ACTIVE/INACTIVE',
|
||||
settlement_period VARCHAR(32) NOT NULL COMMENT '结算周期',
|
||||
remark VARCHAR(500) NULL COMMENT '备注',
|
||||
created_by BIGINT UNSIGNED NULL COMMENT '创建人ID',
|
||||
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
updated_by BIGINT UNSIGNED NULL COMMENT '最后修改人ID',
|
||||
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后修改时间',
|
||||
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记',
|
||||
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_wh_supplier_name (tenant_id,name),
|
||||
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_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='平台公共供应商业务档案';
|
||||
|
||||
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)
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND column_name=p_name) THEN
|
||||
SET @supplier_ddl=CONCAT('ALTER TABLE wh_supplier ADD COLUMN ',p_name,' ',p_definition);
|
||||
PREPARE supplier_statement FROM @supplier_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
|
||||
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 '创建时间'");
|
||||
CALL supplier_add_column_if_missing('updated_by', "BIGINT UNSIGNED NULL COMMENT '最后修改人ID'");
|
||||
CALL supplier_add_column_if_missing('updated_at', "DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '最后修改时间'");
|
||||
CALL supplier_add_column_if_missing('deleted', "TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记'");
|
||||
CALL supplier_add_column_if_missing('version', "INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本'");
|
||||
DROP PROCEDURE supplier_add_column_if_missing;
|
||||
|
||||
SET @supplier_index_ddl=IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND index_name='uk_wh_supplier_name'),'SELECT 1','ALTER TABLE wh_supplier ADD UNIQUE KEY uk_wh_supplier_name (tenant_id,name)');
|
||||
PREPARE supplier_statement FROM @supplier_index_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
|
||||
SET @supplier_index_ddl=IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND index_name='idx_wh_supplier_filter'),'SELECT 1','ALTER TABLE wh_supplier ADD KEY idx_wh_supplier_filter (tenant_id,category,status,region)');
|
||||
PREPARE supplier_statement FROM @supplier_index_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
|
||||
|
||||
DROP PROCEDURE IF EXISTS supplier_add_check_if_missing;
|
||||
DELIMITER $$
|
||||
CREATE PROCEDURE supplier_add_check_if_missing(IN p_name VARCHAR(64), IN p_expression TEXT)
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.table_constraints WHERE table_schema='baiyue_warehouse' AND table_name='wh_supplier' AND constraint_name=p_name AND constraint_type='CHECK') THEN
|
||||
SET @supplier_ddl=CONCAT('ALTER TABLE wh_supplier ADD CONSTRAINT ',p_name,' CHECK (',p_expression,')');
|
||||
PREPARE supplier_statement FROM @supplier_ddl; EXECUTE supplier_statement; DEALLOCATE PREPARE supplier_statement;
|
||||
END IF;
|
||||
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_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;
|
||||
|
||||
DROP PROCEDURE IF EXISTS assert_supplier_schema;
|
||||
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(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_scope','chk_wh_supplier_status','chk_wh_supplier_settlement')) <> 4 THEN
|
||||
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='供应商表、索引或约束初始化不完整';
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
CALL assert_supplier_schema();
|
||||
DROP PROCEDURE assert_supplier_schema;
|
||||
@@ -0,0 +1,183 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Domestic Container Business API
|
||||
version: 1.0.0
|
||||
description: 平台国内仓独立整柜业务契约,不复用 Firstleg 装柜或货柜模型。
|
||||
paths:
|
||||
/api/platform/v1/domestic-containers/inbounds:
|
||||
get:
|
||||
summary: 查询整柜入库单
|
||||
x-permission: platform:domestic-container:list
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/OrderPageResponse' } } } }
|
||||
post:
|
||||
summary: 新建整柜入库单
|
||||
description: COMPLETED 原子增加库存并写交易流水;PENDING 只建单。
|
||||
x-permission: platform:domestic-container:inbound:create
|
||||
requestBody:
|
||||
required: true
|
||||
content: { application/json: { schema: { $ref: '#/components/schemas/CreateInboundRequest' } } }
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/CreateResponse' } } } }
|
||||
/api/platform/v1/domestic-containers/outbounds:
|
||||
get:
|
||||
summary: 查询整柜出库单
|
||||
x-permission: platform:domestic-container:list
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/OrderPageResponse' } } } }
|
||||
post:
|
||||
summary: 新建整柜出库单
|
||||
description: COMPLETED 锁库存后扣减,支持部分/全部出库;PENDING 不扣减。
|
||||
x-permission: platform:domestic-container:outbound:create
|
||||
requestBody:
|
||||
required: true
|
||||
content: { application/json: { schema: { $ref: '#/components/schemas/CreateOutboundRequest' } } }
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/CreateResponse' } } } }
|
||||
/api/platform/v1/domestic-containers/inventories:
|
||||
get:
|
||||
summary: 查询整柜库存与当前筛选汇总
|
||||
x-permission: platform:domestic-container:list
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/InventoryPageResponse' } } } }
|
||||
/api/platform/v1/domestic-containers/inventories/{inventoryId}:
|
||||
get:
|
||||
summary: 查询库存详情及不可变交易流水
|
||||
x-permission: platform:domestic-container:list
|
||||
parameters:
|
||||
- { in: path, name: inventoryId, required: true, schema: { type: integer, format: int64 } }
|
||||
responses:
|
||||
'200': { description: OK, content: { application/json: { schema: { $ref: '#/components/schemas/DetailResponse' } } } }
|
||||
components:
|
||||
schemas:
|
||||
CreateInboundRequest:
|
||||
type: object
|
||||
required: [warehouseId, cargoName, containerNo, boxCount, volumeCbm, inboundTime, status, idempotencyKey]
|
||||
properties:
|
||||
warehouseId: { type: integer, format: int64, description: 平台公共且启用的 DOMESTIC 仓库 ID }
|
||||
cargoName: { type: string, maxLength: 128 }
|
||||
containerNo: { type: string, maxLength: 32 }
|
||||
boxCount: { type: integer, minimum: 1 }
|
||||
volumeCbm: { type: number, minimum: 0.0001 }
|
||||
inboundTime: { type: string, format: date-time }
|
||||
status: { type: string, enum: [PENDING, COMPLETED] }
|
||||
remark: { type: string, maxLength: 500 }
|
||||
idempotencyKey: { type: string, maxLength: 128 }
|
||||
CreateOutboundRequest:
|
||||
type: object
|
||||
required: [warehouseId, containerNo, boxCount, outboundTime, status, idempotencyKey]
|
||||
properties:
|
||||
warehouseId: { type: integer, format: int64 }
|
||||
containerNo: { type: string, maxLength: 32 }
|
||||
boxCount: { type: integer, minimum: 1 }
|
||||
outboundTime: { type: string, format: date-time }
|
||||
status: { type: string, enum: [PENDING, COMPLETED] }
|
||||
remark: { type: string, maxLength: 500 }
|
||||
idempotencyKey: { type: string, maxLength: 128 }
|
||||
inventoryVersion: { type: integer, minimum: 0 }
|
||||
ApiEnvelope:
|
||||
type: object
|
||||
required: [code, message, timestamp]
|
||||
properties:
|
||||
code: { type: string, example: '200' }
|
||||
message: { type: string }
|
||||
requestId: { type: string, nullable: true }
|
||||
traceId: { type: string, nullable: true }
|
||||
timestamp: { type: string, format: date-time }
|
||||
CreateResponse:
|
||||
allOf:
|
||||
- { $ref: '#/components/schemas/ApiEnvelope' }
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
orderNo: { type: string }
|
||||
replayed: { type: boolean }
|
||||
OrderView:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
businessType: { type: string, enum: [INBOUND, OUTBOUND] }
|
||||
orderNo: { type: string }
|
||||
warehouseId: { type: integer, format: int64 }
|
||||
warehouseName: { type: string }
|
||||
containerNo: { type: string }
|
||||
cargoName: { type: string }
|
||||
boxCount: { type: integer }
|
||||
volumeCbm: { type: number }
|
||||
operationTime: { type: string, format: date-time }
|
||||
status: { type: string, enum: [PENDING, COMPLETED] }
|
||||
remark: { type: string, nullable: true }
|
||||
version: { type: integer }
|
||||
createdAt: { type: string, format: date-time }
|
||||
InventoryView:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
warehouseId: { type: integer, format: int64 }
|
||||
warehouseName: { type: string }
|
||||
containerNo: { type: string }
|
||||
cargoName: { type: string }
|
||||
inboundBoxes: { type: integer }
|
||||
outboundBoxes: { type: integer }
|
||||
availableBoxes: { type: integer }
|
||||
inboundVolumeCbm: { type: number }
|
||||
inventoryStatus: { type: string, enum: [IN_STOCK, PARTIALLY_OUTBOUND, OUTBOUND] }
|
||||
version: { type: integer }
|
||||
updatedAt: { type: string, format: date-time }
|
||||
TransactionView:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer, format: int64 }
|
||||
businessType: { type: string, enum: [INBOUND, OUTBOUND] }
|
||||
orderNo: { type: string }
|
||||
quantity: { type: integer }
|
||||
operationTime: { type: string, format: date-time }
|
||||
remark: { type: string, nullable: true }
|
||||
OrderPageResponse:
|
||||
allOf:
|
||||
- { $ref: '#/components/schemas/ApiEnvelope' }
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
records: { type: array, items: { $ref: '#/components/schemas/OrderView' } }
|
||||
page: { type: integer }
|
||||
size: { type: integer }
|
||||
total: { type: integer }
|
||||
pages: { type: integer }
|
||||
InventoryPageResponse:
|
||||
allOf:
|
||||
- { $ref: '#/components/schemas/ApiEnvelope' }
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
records: { type: array, items: { $ref: '#/components/schemas/InventoryView' } }
|
||||
page: { type: integer }
|
||||
size: { type: integer }
|
||||
total: { type: integer }
|
||||
pages: { type: integer }
|
||||
summary:
|
||||
type: object
|
||||
properties:
|
||||
inboundBoxes: { type: integer }
|
||||
outboundBoxes: { type: integer }
|
||||
availableBoxes: { type: integer }
|
||||
inboundVolumeCbm: { type: number }
|
||||
DetailResponse:
|
||||
allOf:
|
||||
- { $ref: '#/components/schemas/ApiEnvelope' }
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
inventory: { $ref: '#/components/schemas/InventoryView' }
|
||||
inboundOrderCount: { type: integer }
|
||||
outboundOrderCount: { type: integer }
|
||||
transactions: { type: array, items: { $ref: '#/components/schemas/TransactionView' } }
|
||||
@@ -0,0 +1,238 @@
|
||||
# 平台供应商前端接口交接单
|
||||
|
||||
## 接入约定
|
||||
|
||||
- Base path:`/api/platform/v1/suppliers`
|
||||
- 浏览器请求继续使用 Gateway `/api` 前缀,不在前端硬编码服务地址。
|
||||
- 仅平台端账号可访问,供应商固定属于平台公共作用域 `tenantId=0`。
|
||||
- 供应商是独立业务档案,不复用或修改往来单位 Partner。
|
||||
- 所有接口使用统一 `ApiResponse<T>`:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "200",
|
||||
"message": "success",
|
||||
"data": {},
|
||||
"requestId": null,
|
||||
"traceId": null,
|
||||
"timestamp": "2026-08-13T03:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
- 普通业务失败、参数校验失败通常仍返回 HTTP 200,前端必须以 body `code === "200"` 判断业务成功。认证、网关或基础设施拒绝仍可能返回真实 HTTP 401、403、503。
|
||||
- Java `Long` 标识在 JSON 中按字符串消费,包括 `id`、`tenantId`、`createdBy`、`updatedBy`,不得转成 JavaScript `number`。
|
||||
|
||||
## 接口清单
|
||||
|
||||
| 功能 | 方法与路径 | 权限码 |
|
||||
|---|---|---|
|
||||
| 列表 | `GET /api/platform/v1/suppliers` | `platform:supplier:list` |
|
||||
| 分类统计 | `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` |
|
||||
| 编辑 | `PUT /api/platform/v1/suppliers/{id}?tenantId=0` | `platform:supplier:edit` |
|
||||
| 启停 | `PATCH /api/platform/v1/suppliers/{id}/status?tenantId=0` | `platform:supplier:status` |
|
||||
|
||||
列表和统计的 `tenantId` 可省略;传入时只能是 `0`。详情、新增、编辑、启停当前要求显式传 `tenantId=0`。
|
||||
|
||||
## 列表查询
|
||||
|
||||
查询参数:
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|---|---|---|---|
|
||||
| `tenantId` | string | 否 | 只能为 `0` |
|
||||
| `page` | integer | 否 | 默认 `1`,最小 `1` |
|
||||
| `size` | integer | 否 | 默认 `20`,范围 `1..200` |
|
||||
| `category` | enum | 否 | 供应商分类 |
|
||||
| `scope` | enum | 否 | 仅订舱分类适用 |
|
||||
| `region` | string | 否 | 地区精确匹配,最长 80 |
|
||||
| `status` | enum | 否 | 合作状态 |
|
||||
| `keyword` | string | 否 | 按供应商名称或联系人模糊匹配 |
|
||||
|
||||
分页响应 `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"records": [],
|
||||
"page": 1,
|
||||
"size": 20,
|
||||
"total": 0,
|
||||
"pages": 0
|
||||
}
|
||||
```
|
||||
|
||||
`records` 元素使用下述 Supplier 字段。`page/size/total/pages` 是整数分页元数据。
|
||||
|
||||
## Supplier 字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `id` | string | 供应商 ID |
|
||||
| `tenantId` | string | 固定为 `0` |
|
||||
| `name` | string | 供应商名称,平台内唯一 |
|
||||
| `category` | enum | 分类 |
|
||||
| `scope` | enum/null | 订舱供应商必填;其他分类为 `null` |
|
||||
| `region` | string | 地区 |
|
||||
| `legalRepresentative` | string | 法人代表 |
|
||||
| `contactName` | string | 联系人 |
|
||||
| `phone` | string | 联系电话 |
|
||||
| `email` | string/null | 电子邮箱 |
|
||||
| `address` | string/null | 公司地址 |
|
||||
| `businessLicenseNo` | string/null | 营业执照号/统一社会信用代码 |
|
||||
| `licenseFileId` | string/null | File 服务返回的营业执照文件 ID,只允许十进制数字字符串 |
|
||||
| `status` | enum | 合作状态 |
|
||||
| `settlementPeriod` | enum | 结算周期 |
|
||||
| `remark` | string/null | 备注 |
|
||||
| `createdBy` | string/null | 创建人 ID |
|
||||
| `createdAt` | datetime | 创建时间 |
|
||||
| `updatedBy` | string/null | 最后修改人 ID |
|
||||
| `updatedAt` | datetime | 最后修改时间 |
|
||||
| `deleted` | boolean | 逻辑删除标记;正常响应为 `false` |
|
||||
| `version` | integer | 乐观锁版本 |
|
||||
|
||||
营业执照上传由 File 服务完成。Supplier 新增或编辑只提交 `licenseFileId`;查看操作使用该 ID获取文件,不把 URL、base64 或文件二进制提交到 Supplier 接口。原型要求营业执照查看由前端使用居中弹框承载。
|
||||
|
||||
## 新增与编辑
|
||||
|
||||
新增和编辑使用相同请求体:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "示例供应商有限公司",
|
||||
"category": "BOOKING",
|
||||
"scope": "DOMESTIC",
|
||||
"region": "宁波",
|
||||
"legalRepresentative": "张三",
|
||||
"contactName": "李经理",
|
||||
"phone": "13800000000",
|
||||
"email": "supplier@example.com",
|
||||
"address": "宁波市示例路 1 号",
|
||||
"businessLicenseNo": "91330000XXXXXXXXXX",
|
||||
"licenseFileId": "192000000000000001",
|
||||
"status": "ACTIVE",
|
||||
"settlementPeriod": "MONTHLY_30",
|
||||
"remark": ""
|
||||
}
|
||||
```
|
||||
|
||||
必填字段:`name`、`category`、`region`、`legalRepresentative`、`contactName`、`phone`、`status`、`settlementPeriod`。`BOOKING` 时 `scope` 必填;其他分类 `scope` 必须传 `null` 或省略。
|
||||
|
||||
新增成功的 `data` 是供应商 ID 字符串。编辑成功的 `data` 为 `null`。
|
||||
|
||||
## 启停
|
||||
|
||||
```json
|
||||
{ "status": "INACTIVE" }
|
||||
```
|
||||
|
||||
只接受 `ACTIVE` 或 `INACTIVE`。接口不物理删除供应商。
|
||||
|
||||
## 分类统计
|
||||
|
||||
`GET /stats` 的响应 `data`:
|
||||
|
||||
```json
|
||||
{
|
||||
"booking": 8,
|
||||
"customs": 5,
|
||||
"trucking": 6,
|
||||
"certificate": 3
|
||||
}
|
||||
```
|
||||
|
||||
## 枚举
|
||||
|
||||
供应商分类:
|
||||
|
||||
| 值 | 展示文案 |
|
||||
|---|---|
|
||||
| `BOOKING` | 订舱 |
|
||||
| `CUSTOMS` | 报关 |
|
||||
| `TRUCKING` | 拖车 |
|
||||
| `CERTIFICATE` | 证书 |
|
||||
|
||||
订舱范围:
|
||||
|
||||
| 值 | 展示文案 |
|
||||
|---|---|
|
||||
| `DOMESTIC` | 国内 |
|
||||
| `INTERNATIONAL` | 国外 |
|
||||
|
||||
合作状态:
|
||||
|
||||
| 值 | 展示文案 |
|
||||
|---|---|
|
||||
| `ACTIVE` | 合作中 |
|
||||
| `INACTIVE` | 停用 |
|
||||
|
||||
结算周期:
|
||||
|
||||
| 值 | 展示文案 |
|
||||
|---|---|
|
||||
| `MONTHLY_30` | 月结30天 |
|
||||
| `MONTHLY_45` | 月结45天 |
|
||||
| `MONTHLY_60` | 月结60天 |
|
||||
| `MONTHLY_90` | 月结90天 |
|
||||
| `PER_TRANSACTION` | 票结 |
|
||||
| `IMMEDIATE` | 现结 |
|
||||
|
||||
## 错误码
|
||||
|
||||
| `code` | 场景 | 前端处理建议 |
|
||||
|---|---|---|
|
||||
| `200` | 成功 | 使用 `data` |
|
||||
| `A0400` | `tenantId` 非 0、请求格式或查询参数非法 | 显示参数错误,不重试 |
|
||||
| `A0401` | 未登录或登录态失效 | 进入统一登录失效流程 |
|
||||
| `A0403` | 非平台用户或缺少对应权限 | 显示无权限 |
|
||||
| `A0404` | 供应商不存在 | 提示记录不存在并刷新列表 |
|
||||
| `A0409` | 供应商名称重复或并发唯一键冲突 | 保留表单并提示名称已存在 |
|
||||
| `A0422` | 字段校验失败、订舱范围规则不满足 | 使用 `message`/`data` 显示字段错误 |
|
||||
| `S0500` | 未预期服务异常 | 展示通用错误并记录 `requestId/traceId` |
|
||||
| `S0503` | Warehouse 数据库不可用 | 展示稍后重试并记录 `requestId/traceId` |
|
||||
|
||||
校验错误时 `data` 可能为字段到错误信息的映射,例如:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "A0422",
|
||||
"message": "name: must not be blank",
|
||||
"data": { "name": "must not be blank" },
|
||||
"requestId": "...",
|
||||
"traceId": "...",
|
||||
"timestamp": "..."
|
||||
}
|
||||
```
|
||||
|
||||
## 契约来源
|
||||
|
||||
- OpenAPI:`warehouse/src/main/resources/openapi/supplier-v1.yaml`
|
||||
- Controller:`warehouse/src/main/java/com/baiyue/warehouse/controller/PlatformSupplierController.java`
|
||||
- DTO:`warehouse/src/main/java/com/baiyue/warehouse/dto/SupplierDtos.java`
|
||||
|
||||
权限码已在接口层冻结,但 IAM 权限初始化不属于本次 warehouse 文件所有权,联调前需确认 IAM 已落库并授予目标平台角色。
|
||||
|
||||
## 初始化与验收
|
||||
|
||||
- Warehouse 模块表迁移:`warehouse/src/main/resources/db/warehouse-supplier.sql`
|
||||
- IAM 权限迁移:`iam/src/main/resources/db/platform-supplier-permissions.sql`
|
||||
- 测试/存量环境唯一执行入口:`deploy/sql/头程数据库统一增量-MySQL8.sql`
|
||||
- 权限固定 ID:`5103080..5103083`,依次对应 `list/create/edit/status`;`PLATFORM_ADMIN` 默认获得全部四项权限。
|
||||
- Admin 测试环境验收模块:`scripts/admin-test-env-modules/suppliers.mjs`
|
||||
- 验收模块静态契约:`scripts/admin-test-env-modules/suppliers-contract.test.mjs`
|
||||
|
||||
离线验证:
|
||||
|
||||
```powershell
|
||||
mvn -pl warehouse,iam -am test
|
||||
node --test scripts/admin-test-env-modules/suppliers-contract.test.mjs
|
||||
pwsh scripts/local-db-full-acceptance.ps1 -DryRun
|
||||
```
|
||||
|
||||
仅在当前对话已明确授权测试环境 API 验收后,才能运行下列命令。该模块会新增一条带本轮 marker 的供应商档案,并在结束时停用;由于供应商契约不提供删除接口,记录会保留为 `INACTIVE`。
|
||||
|
||||
```powershell
|
||||
node scripts/admin-test-env-acceptance.mjs --modules suppliers
|
||||
```
|
||||
|
||||
模块覆盖列表、统计、新增、详情、编辑、启停、重复名称、非法 scope、客户账号权限拒绝、`tenantId != 0` 和错误响应 `requestId/traceId`。本轮代码交付不执行该测试环境命令。
|
||||
@@ -0,0 +1,84 @@
|
||||
openapi: 3.0.3
|
||||
info: { title: Platform Supplier API, version: 1.0.0 }
|
||||
paths:
|
||||
/api/platform/v1/suppliers:
|
||||
get:
|
||||
summary: 分页查询平台公共供应商
|
||||
description: 权限 platform:supplier:list;tenantId 仅兼容 0。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/TenantId' }
|
||||
- { 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 } }
|
||||
responses: { '200': { description: 统一 ApiResponse 包装的分页结果 } }
|
||||
post:
|
||||
summary: 新增供应商
|
||||
description: 权限 platform:supplier:create。
|
||||
security: [{ bearerAuth: [] }]
|
||||
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/stats:
|
||||
get:
|
||||
summary: 查询四类供应商统计
|
||||
description: 权限 platform:supplier:list。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ $ref: '#/components/parameters/TenantId' }]
|
||||
responses: { '200': { description: booking/customs/trucking/certificate 计数 } }
|
||||
/api/platform/v1/suppliers/{id}:
|
||||
parameters: [{ name: id, in: path, required: true, schema: { type: integer, format: int64 } }]
|
||||
get:
|
||||
summary: 查询供应商详情
|
||||
description: 权限 platform:supplier:list。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ $ref: '#/components/parameters/RequiredTenantId' }]
|
||||
responses: { '200': { description: 统一 ApiResponse 包装的 Supplier } }
|
||||
put:
|
||||
summary: 编辑供应商
|
||||
description: 权限 platform:supplier:edit。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ $ref: '#/components/parameters/RequiredTenantId' }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { $ref: '#/components/schemas/SupplierSave' } } } }
|
||||
responses: { '200': { description: 成功 } }
|
||||
/api/platform/v1/suppliers/{id}/status:
|
||||
parameters: [{ name: id, in: path, required: true, schema: { type: integer, format: int64 } }]
|
||||
patch:
|
||||
summary: 启用或停用供应商
|
||||
description: 权限 platform:supplier:status;不物理删除。
|
||||
security: [{ bearerAuth: [] }]
|
||||
parameters: [{ $ref: '#/components/parameters/RequiredTenantId' }]
|
||||
requestBody: { required: true, content: { application/json: { schema: { type: object, required: [status], properties: { status: { $ref: '#/components/schemas/Status' } } } } } }
|
||||
responses: { '200': { description: 成功 } }
|
||||
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] }
|
||||
Scope: { type: string, enum: [DOMESTIC, INTERNATIONAL] }
|
||||
Status: { type: string, enum: [ACTIVE, INACTIVE] }
|
||||
SupplierSave:
|
||||
type: object
|
||||
required: [name, category, region, legalRepresentative, contactName, phone, status, settlementPeriod]
|
||||
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 }
|
||||
phone: { type: string, maxLength: 40 }
|
||||
email: { type: string, format: email, maxLength: 160 }
|
||||
address: { type: string, maxLength: 255 }
|
||||
businessLicenseNo: { type: string, maxLength: 64 }
|
||||
licenseFileId: { type: string, pattern: '^\d+$', maxLength: 128, description: File 服务文件引用 }
|
||||
status: { $ref: '#/components/schemas/Status' }
|
||||
settlementPeriod: { type: string, enum: [MONTHLY_30, MONTHLY_45, MONTHLY_60, MONTHLY_90, PER_TRANSACTION, IMMEDIATE] }
|
||||
remark: { type: string, maxLength: 500 }
|
||||
securitySchemes:
|
||||
bearerAuth: { type: http, scheme: bearer, bearerFormat: JWT }
|
||||
Reference in New Issue
Block a user