feat(fleet): integrate costs quotes operations and in-transit handovers
This commit is contained in:
@@ -94,6 +94,65 @@ VALUES
|
||||
ON DUPLICATE KEY UPDATE
|
||||
subject_name = VALUES(subject_name), status = 'ACTIVE', updated_by = 0;
|
||||
|
||||
CREATE TABLE fin_vehicle_cost (
|
||||
id BIGINT NOT NULL COMMENT '公司费用主键',
|
||||
subject_id BIGINT UNSIGNED NOT NULL COMMENT '平台财务主体',
|
||||
cost_no VARCHAR(64) NOT NULL COMMENT '公司车辆费用编号',
|
||||
source_type VARCHAR(32) NOT NULL COMMENT '费用来源类型',
|
||||
source_no VARCHAR(64) NOT NULL COMMENT '来源业务编号',
|
||||
source_version BIGINT NOT NULL COMMENT '来源快照版本',
|
||||
source_hash VARCHAR(71) NOT NULL COMMENT '来源内容摘要',
|
||||
source_json JSON NOT NULL COMMENT '原币及折算不可变来源快照',
|
||||
supplier_id BIGINT NOT NULL COMMENT '收款供应商标识',
|
||||
supplier_name VARCHAR(200) NOT NULL COMMENT '供应商名称快照',
|
||||
amount DECIMAL(17,2) NOT NULL COMMENT 'CNY公司费用金额',
|
||||
paid_amount DECIMAL(17,2) NOT NULL DEFAULT 0 COMMENT 'CNY累计实际付款',
|
||||
status VARCHAR(32) NOT NULL COMMENT '入账与付款状态',
|
||||
record_version BIGINT NOT NULL COMMENT '并发版本',
|
||||
created_at DATETIME(3) NOT NULL COMMENT '创建时间UTC',
|
||||
updated_at DATETIME(3) NOT NULL COMMENT '更新时间UTC',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_fin_vehicle_cost_no (cost_no),
|
||||
UNIQUE KEY uk_fin_vehicle_cost_source (subject_id,source_type,source_no),
|
||||
KEY idx_fin_vehicle_cost_page (subject_id,updated_at,id),
|
||||
CONSTRAINT fk_fin_vehicle_cost_subject FOREIGN KEY (subject_id) REFERENCES fin_subject(id),
|
||||
CONSTRAINT chk_fin_vehicle_cost_amount CHECK (amount>0 AND paid_amount>=0 AND paid_amount<=amount),
|
||||
CONSTRAINT chk_fin_vehicle_cost_owner CHECK (subject_id>0 AND supplier_id>0),
|
||||
CONSTRAINT chk_fin_vehicle_cost_status CHECK (status IN ('PENDING','APPROVED','PARTIALLY_PAID','PAID','VOIDED'))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司车辆费用,不进入客户账务';
|
||||
|
||||
CREATE TABLE fin_vehicle_cost_revision (
|
||||
id BIGINT NOT NULL COMMENT '公司车辆费用历史主键',
|
||||
cost_id BIGINT NOT NULL COMMENT '所属公司费用',
|
||||
record_version BIGINT NOT NULL COMMENT '费用版本',
|
||||
action VARCHAR(32) NOT NULL COMMENT '接收入账作废付款动作',
|
||||
request_key VARCHAR(128) NOT NULL COMMENT '动作幂等键',
|
||||
request_hash VARCHAR(71) NOT NULL COMMENT '请求摘要',
|
||||
operator_id BIGINT NOT NULL COMMENT '操作人或系统标识',
|
||||
operator_name VARCHAR(128) NOT NULL COMMENT '操作人名称快照',
|
||||
reason VARCHAR(1000) NULL COMMENT '更正或操作说明',
|
||||
snapshot_json JSON NOT NULL COMMENT '不可变费用快照',
|
||||
occurred_at DATETIME(3) NOT NULL COMMENT '操作时间UTC',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_fin_vehicle_cost_revision (cost_id,record_version),
|
||||
UNIQUE KEY uk_fin_vehicle_cost_request (operator_id,action,request_key),
|
||||
CONSTRAINT fk_fin_vehicle_revision_cost FOREIGN KEY (cost_id) REFERENCES fin_vehicle_cost(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司车辆费用不可变审计';
|
||||
|
||||
CREATE TABLE fin_vehicle_cost_payment (
|
||||
id BIGINT NOT NULL COMMENT '公司车辆付款主键',
|
||||
cost_id BIGINT NOT NULL COMMENT '所属公司费用',
|
||||
payment_no VARCHAR(64) NOT NULL COMMENT '公司付款业务编号',
|
||||
amount DECIMAL(17,2) NOT NULL COMMENT 'CNY实际付款金额',
|
||||
payment_json JSON NOT NULL COMMENT '实际付款及凭证不可变快照',
|
||||
created_at DATETIME(3) NOT NULL COMMENT '付款登记时间UTC',
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_fin_vehicle_payment_no (payment_no),
|
||||
KEY idx_fin_vehicle_payment_cost (cost_id,id),
|
||||
CONSTRAINT fk_fin_vehicle_payment_cost FOREIGN KEY (cost_id) REFERENCES fin_vehicle_cost(id),
|
||||
CONSTRAINT chk_fin_vehicle_payment_amount CHECK (amount>0)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司车辆实际付款记录';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS fin_customer_settlement_profile (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
tenant_id BIGINT UNSIGNED NOT NULL COMMENT '客户ID和数据隔离键',
|
||||
|
||||
@@ -0,0 +1,287 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: 公司车辆运营成本
|
||||
version: 1.0.0
|
||||
description: >-
|
||||
公司费用归平台财务主体PLATFORM_DEFAULT,不进入客户费用池、客户账单、客户应付单或客户钱包。
|
||||
来源接收只生成PENDING;独立入账后才产生应付,付款只登记有凭证的实际CNY支付。
|
||||
原币支持CNY/USD,来源提供业务月份有效汇率与不可变折算快照;Finance复核金额守恒。
|
||||
未付款可作废;已付款禁止直接改写、作废或替换。更高来源版本仅在PENDING/VOIDED且未付款时接收。
|
||||
security:
|
||||
- bearerAuth: []
|
||||
x-integration-required:
|
||||
status: 源码合同,仍待共享集成与总控编译验证,不代表可部署
|
||||
numbers: FinanceDocumentNumberGenerator登记VC公司费用与VP付款业务号前缀
|
||||
file: BusinessFileCategory登记FINANCE_VEHICLE_VOUCHER;平台0、费用ID复合归属、非空JPG/PNG、10MiB、签名校验
|
||||
event: CompanyVehicleCostSubmitted v1;LASTLEG直投内部事件入口;aggregateType=COMPANY_VEHICLE_COST,aggregateId=sourceType:sourceNo,aggregateVersion=sourceVersion;平台信封tenantId为字符串0,不携带customerId
|
||||
delivery: 注册EventTypes/EventDeliveryRoute及Lastleg Relay;只有匹配eventId/payloadHash的APPLIED或DUPLICATE代表接收完成,不能显示为已入账/已付款
|
||||
lastleg: 提交时锁真实来源并冻结版本、供应商、金额、月份汇率;映射与Outbox同事务提交;禁止把维修照片或reportedCost自动解释为付款证据
|
||||
sql:
|
||||
fin_vehicle_cost:
|
||||
columns: id BIGINT PK, subject_id BIGINT, cost_no VARCHAR(64), source_type VARCHAR(32), source_no VARCHAR(64), source_version BIGINT, source_hash VARCHAR(71), source_json JSON, supplier_id BIGINT, supplier_name VARCHAR(200), amount DECIMAL(17,2), paid_amount DECIMAL(17,2), status VARCHAR(32), record_version BIGINT, created_at DATETIME(3), updated_at DATETIME(3)
|
||||
unique: [cost_no, 'subject_id + source_type + source_no']
|
||||
checks: 正数主体和供应商;amount>0;0<=paid_amount<=amount;状态PENDING/APPROVED/PARTIALLY_PAID/PAID/VOIDED;无customer_id/tenant_id
|
||||
indexes: [subject_id + updated_at + id]
|
||||
fin_vehicle_cost_revision:
|
||||
columns: id BIGINT PK, cost_id BIGINT, record_version BIGINT, action VARCHAR(32), request_key VARCHAR(128), request_hash VARCHAR(71), operator_id BIGINT, operator_name VARCHAR(128), reason VARCHAR(1000), snapshot_json JSON, occurred_at DATETIME(3)
|
||||
unique: ['cost_id + record_version', 'operator_id + action + request_key']
|
||||
description: RECEIVE/APPROVE/VOID/PAY不可变审计及幂等记录,和主记录及付款同事务提交
|
||||
fin_vehicle_cost_payment:
|
||||
columns: id BIGINT PK, cost_id BIGINT, payment_no VARCHAR(64), amount DECIMAL(17,2), payment_json JSON, created_at DATETIME(3)
|
||||
unique: [payment_no]
|
||||
indexes: [cost_id + id]
|
||||
description: 不可变实际付款凭证快照;amount>0;付款与余额、审计同事务
|
||||
paths:
|
||||
/api/platform/finance/v1/vehicle-costs:
|
||||
get:
|
||||
description: platform:finance:vehicle-cost:list;只查询公司成本
|
||||
parameters: [{$ref: '#/components/parameters/Page'}, {$ref: '#/components/parameters/Size'}]
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Page'}
|
||||
/api/platform/finance/v1/vehicle-costs/{costNo}:
|
||||
parameters: [{$ref: '#/components/parameters/CostNo'}]
|
||||
get:
|
||||
description: platform:finance:vehicle-cost:list
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Cost'}
|
||||
/api/platform/finance/v1/vehicle-costs/{costNo}/history:
|
||||
parameters: [{$ref: '#/components/parameters/CostNo'}]
|
||||
get:
|
||||
description: platform:finance:vehicle-cost:list;不可变版本与当时金额快照
|
||||
parameters: [{$ref: '#/components/parameters/Page'}, {$ref: '#/components/parameters/Size'}]
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Page'}
|
||||
/api/platform/finance/v1/vehicle-costs/{costNo}/approve:
|
||||
parameters: [{$ref: '#/components/parameters/CostNo'}]
|
||||
post:
|
||||
description: platform:finance:vehicle-cost:approve;PENDING入账为APPROVED,复核供应商;不是付款
|
||||
parameters: [{$ref: '#/components/parameters/Key'}]
|
||||
requestBody: {$ref: '#/components/requestBodies/Command'}
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Cost'}
|
||||
/api/platform/finance/v1/vehicle-costs/{costNo}/void:
|
||||
parameters: [{$ref: '#/components/parameters/CostNo'}]
|
||||
post:
|
||||
description: platform:finance:vehicle-cost:void;仅未付款PENDING/APPROVED可作废;更高来源版本可重新待入账
|
||||
parameters: [{$ref: '#/components/parameters/Key'}]
|
||||
requestBody: {$ref: '#/components/requestBodies/Command'}
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Cost'}
|
||||
/api/platform/finance/v1/vehicle-costs/{costNo}/payments:
|
||||
parameters: [{$ref: '#/components/parameters/CostNo'}]
|
||||
get:
|
||||
description: platform:finance:vehicle-cost:list;CNY付款历史
|
||||
parameters: [{$ref: '#/components/parameters/Page'}, {$ref: '#/components/parameters/Size'}]
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Page'}
|
||||
post:
|
||||
description: platform:finance:vehicle-cost:pay;仅APPROVED/PARTIALLY_PAID,付款金额不超过未付余额,必须有本费用的公司付款凭证
|
||||
parameters: [{$ref: '#/components/parameters/Key'}]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: {$ref: '#/components/schemas/Payment'}
|
||||
responses:
|
||||
'200': {$ref: '#/components/responses/Cost'}
|
||||
/api/platform/finance/v1/vehicle-costs/{costNo}/vouchers:
|
||||
parameters: [{$ref: '#/components/parameters/CostNo'}]
|
||||
post:
|
||||
description: platform:finance:vehicle-cost:upload;上传不是付款,不生成付款流水
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
required: [file]
|
||||
properties:
|
||||
file: {type: string, format: binary}
|
||||
responses:
|
||||
'200': {description: ApiResponse,data为本费用专属凭证元数据}
|
||||
/api/platform/finance/v1/vehicle-costs/{costNo}/vouchers/{fileId}/content:
|
||||
parameters:
|
||||
- {$ref: '#/components/parameters/CostNo'}
|
||||
- {name: fileId, in: path, required: true, schema: {type: integer, format: int64, minimum: 1}}
|
||||
get:
|
||||
description: platform:finance:vehicle-cost:list;校验平台、文件类别和费用归属
|
||||
responses:
|
||||
'200': {description: 图片字节及File响应头}
|
||||
/internal/finance/v1/vehicle-costs/events:
|
||||
post:
|
||||
security: [{internalService: []}]
|
||||
description: 内部身份;同eventId异载荷拒绝,同来源版本异内容拒绝,旧来源版本拒绝;业务与Inbox同事务提交后ACK
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [eventId, eventType, eventVersion, occurredAt, tenantId, aggregateType, aggregateId, aggregateVersion, traceId, correlationId, payload, payloadHash]
|
||||
properties:
|
||||
eventId: {type: string, format: uuid}
|
||||
eventType: {type: string, enum: [CompanyVehicleCostSubmitted]}
|
||||
eventVersion: {type: integer, enum: [1]}
|
||||
occurredAt: {type: string, format: date-time}
|
||||
tenantId: {type: string, enum: ['0']}
|
||||
aggregateType: {type: string, enum: [COMPANY_VEHICLE_COST]}
|
||||
aggregateId: {type: string}
|
||||
aggregateVersion: {type: integer, minimum: 1}
|
||||
traceId: {type: string}
|
||||
correlationId: {type: string}
|
||||
payloadHash: {type: string}
|
||||
payload: {$ref: '#/components/schemas/Source'}
|
||||
responses:
|
||||
'200': {description: 'ApiResponse<DeliveryAck>;eventId、payloadHash与请求一致,outcome为APPLIED/DUPLICATE;仅表示接收完成'}
|
||||
/internal/finance/v1/vehicle-costs/sources/query:
|
||||
post:
|
||||
security: [{internalService: []}]
|
||||
description: 最多100组来源批量查询,未接收来源不返回;来源不存在不能解释为已付款或零成本
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [sources]
|
||||
properties:
|
||||
sources:
|
||||
type: array
|
||||
minItems: 1
|
||||
maxItems: 100
|
||||
items:
|
||||
type: object
|
||||
required: [sourceType, sourceNo]
|
||||
properties:
|
||||
sourceType: {type: string, enum: [REPAIR, RESCUE, TRIP_DIRECT]}
|
||||
sourceNo: {type: string, maxLength: 64}
|
||||
responses:
|
||||
'200': {description: 'ApiResponse<List<Cost>>'}
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth: {type: http, scheme: bearer, bearerFormat: JWT}
|
||||
internalService: {type: apiKey, in: header, name: X-Internal-Service-Token}
|
||||
parameters:
|
||||
CostNo: {name: costNo, in: path, required: true, schema: {type: string}}
|
||||
Page: {name: page, in: query, schema: {type: integer, minimum: 1, default: 1}}
|
||||
Size: {name: size, in: query, schema: {type: integer, minimum: 1, maximum: 100, default: 20}}
|
||||
Key: {name: Idempotency-Key, in: header, required: true, schema: {type: string, minLength: 1, maxLength: 128}}
|
||||
requestBodies:
|
||||
Command:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [expectedVersion]
|
||||
properties:
|
||||
expectedVersion: {type: integer, format: int64, minimum: 0}
|
||||
reason: {type: string, maxLength: 1000}
|
||||
responses:
|
||||
Cost:
|
||||
description: 业务响应;FIN_400参数/归属、FIN_404不存在、FIN_409状态/版本/幂等冲突
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/Envelope'}
|
||||
- type: object
|
||||
properties:
|
||||
data: {$ref: '#/components/schemas/Cost'}
|
||||
Page:
|
||||
description: ApiResponse;data含records/page/size/total/pages,列表Cost、历史History或付款Paid
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- {$ref: '#/components/schemas/Envelope'}
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
page: {type: integer, format: int64}
|
||||
size: {type: integer, format: int64}
|
||||
total: {type: integer, format: int64}
|
||||
pages: {type: integer, format: int64}
|
||||
records:
|
||||
type: array
|
||||
items:
|
||||
oneOf:
|
||||
- {$ref: '#/components/schemas/Cost'}
|
||||
- {$ref: '#/components/schemas/History'}
|
||||
- {$ref: '#/components/schemas/Paid'}
|
||||
schemas:
|
||||
Envelope:
|
||||
type: object
|
||||
properties:
|
||||
code: {type: string}
|
||||
message: {type: string}
|
||||
requestId: {type: string}
|
||||
traceId: {type: string}
|
||||
data: {type: object}
|
||||
Source:
|
||||
type: object
|
||||
additionalProperties: false
|
||||
required: [sourceType, sourceNo, sourceVersion, vehicleId, vehiclePlate, supplierId, feeName, businessDate, originalCurrency, originalAmount, exchangeRate, exchangeRateMonth, amount]
|
||||
properties:
|
||||
sourceType: {type: string, enum: [REPAIR, RESCUE, TRIP_DIRECT]}
|
||||
sourceNo: {type: string, maxLength: 64}
|
||||
sourceVersion: {type: integer, format: int64, minimum: 1}
|
||||
vehicleId: {type: string, pattern: '^[1-9][0-9]*$'}
|
||||
vehiclePlate: {type: string, maxLength: 64}
|
||||
tripNo: {type: string, nullable: true, maxLength: 64, description: TRIP_DIRECT必须提供真实车次号}
|
||||
supplierId: {type: integer, format: int64, minimum: 1}
|
||||
feeName: {type: string, maxLength: 200}
|
||||
businessDate: {type: string, format: date}
|
||||
originalCurrency: {type: string, enum: [CNY, USD]}
|
||||
originalAmount: {type: number, minimum: 0, exclusiveMinimum: true, multipleOf: 0.01}
|
||||
exchangeRate: {type: number, minimum: 0, exclusiveMinimum: true, description: CNY必须为1;USD来自业务月份有效汇率}
|
||||
exchangeRateMonth: {type: string, pattern: '^[0-9]{4}-[0-9]{2}$'}
|
||||
amount: {type: number, minimum: 0, exclusiveMinimum: true, multipleOf: 0.01, description: CNY折算金额,与原币乘汇率四舍五入到分严格核平}
|
||||
remark: {type: string, nullable: true, maxLength: 1000}
|
||||
Cost:
|
||||
type: object
|
||||
properties:
|
||||
costNo: {type: string}
|
||||
subjectCode: {type: string, enum: [PLATFORM_DEFAULT]}
|
||||
source: {$ref: '#/components/schemas/Source'}
|
||||
supplierName: {type: string}
|
||||
currency: {type: string, enum: [CNY]}
|
||||
status: {type: string, enum: [PENDING, APPROVED, PARTIALLY_PAID, PAID, VOIDED]}
|
||||
paidAmount: {type: number}
|
||||
outstandingAmount: {type: number, nullable: true, description: 未入账或已作废时为空,不能当已知零应付}
|
||||
recordVersion: {type: integer, format: int64}
|
||||
Payment:
|
||||
type: object
|
||||
required: [expectedVersion, amount, paidAt, method, voucherFileIds]
|
||||
properties:
|
||||
expectedVersion: {type: integer, format: int64, minimum: 0}
|
||||
amount: {type: number, minimum: 0, exclusiveMinimum: true, multipleOf: 0.01, description: CNY实际付款}
|
||||
paidAt: {type: string, format: date-time}
|
||||
method: {type: string, enum: [ONLINE, BANK_TRANSFER, CASH, OTHER]}
|
||||
referenceNo: {type: string, maxLength: 128}
|
||||
voucherFileIds: {type: array, minItems: 1, maxItems: 20, uniqueItems: true, items: {type: string, pattern: '^[1-9][0-9]*$'}}
|
||||
remark: {type: string, maxLength: 1000}
|
||||
History:
|
||||
type: object
|
||||
properties:
|
||||
recordVersion: {type: integer, format: int64}
|
||||
action: {type: string, enum: [RECEIVE, APPROVE, VOID, PAY]}
|
||||
reason: {type: string}
|
||||
operatorName: {type: string}
|
||||
occurredAt: {type: string, format: date-time}
|
||||
snapshot: {$ref: '#/components/schemas/Cost'}
|
||||
Paid:
|
||||
type: object
|
||||
properties:
|
||||
paymentNo: {type: string}
|
||||
currency: {type: string, enum: [CNY]}
|
||||
amount: {type: number}
|
||||
paidAt: {type: string, format: date-time}
|
||||
method: {type: string}
|
||||
referenceNo: {type: string, nullable: true}
|
||||
voucherFileIds: {type: array, items: {type: string}}
|
||||
operatorName: {type: string}
|
||||
recordedAt: {type: string, format: date-time}
|
||||
remark: {type: string, nullable: true}
|
||||
Reference in New Issue
Block a user