feat(fleet): connect operations quotes costs and handover workspaces
This commit is contained in:
@@ -13,6 +13,66 @@ type Wire = Omit<VehicleCareRecord, 'recordVersion' | 'details' | 'files'> & {
|
||||
files: Array<Omit<VehicleCareRecord['files'][number], 'sizeBytes'> & { sizeBytes: Numeric }>
|
||||
}
|
||||
const path = (vehicleId: string, recordNo?: string) => `/platform/v1/last-leg/fleet/vehicles/${encodeURIComponent(vehicleId)}/records${recordNo ? `/${encodeURIComponent(recordNo)}` : ''}`
|
||||
export const vehicleCareFinanceSubmitPermission = 'platform:overseas-master-data:vehicle:record-finance-submit'
|
||||
export type VehicleCareFinanceState = 'UNLINKED' | 'PENDING_FINANCE' | 'FINANCE_ATTENTION' | 'PENDING' | 'APPROVED' | 'PARTIALLY_PAID' | 'PAID' | 'VOIDED'
|
||||
export interface VehicleCareFinanceStatus {
|
||||
recordNo: string; status: VehicleCareFinanceState; sourceVersion: string | null; submittedRecordVersion: string | null
|
||||
finance: { costNo: string; status: string; currency: 'CNY'; supplierName: string; paidAmount: number; outstandingAmount: number | null;
|
||||
source: { originalCurrency: 'CNY' | 'USD'; originalAmount: number; amount: number; businessDate: string; exchangeRate: number; exchangeRateMonth: string } } | null
|
||||
}
|
||||
export interface VehicleCareFinanceSubmit { recordVersion: number; supplierId: string; businessDate: string; idempotencyKey: string }
|
||||
export interface VehicleCareFinanceSubmitted { recordNo: string; sourceVersion: string; submittedRecordVersion: string; replayed: boolean; status: 'PENDING_FINANCE' }
|
||||
export const vehicleCareFinanceLabels: Record<VehicleCareFinanceState, string> = {
|
||||
UNLINKED: '未提交财务', PENDING_FINANCE: '待财务接收', FINANCE_ATTENTION: '财务接收需处理', PENDING: '待财务入账', APPROVED: '已入账 · 未付款',
|
||||
PARTIALLY_PAID: '部分付款', PAID: '已付款', VOIDED: '已作废',
|
||||
}
|
||||
function financeVersion(value: Numeric): string {
|
||||
if (!/^(0|[1-9]\d*)$/.test(String(value)) || (typeof value === 'number' && !Number.isSafeInteger(value))) throw new Error('财务版本无效')
|
||||
return String(value)
|
||||
}
|
||||
function financeAmount(value: Numeric): number {
|
||||
if ((typeof value !== 'string' && typeof value !== 'number') || !/^\d+(\.\d{1,2})?$/.test(String(value))) throw new Error('财务金额无效')
|
||||
const [whole, fraction = ''] = String(value).split('.')
|
||||
const cents = BigInt(whole!) * 100n + BigInt(fraction.padEnd(2, '0'))
|
||||
if (cents > BigInt(Number.MAX_SAFE_INTEGER) || BigInt(Number(value).toFixed(2).replace('.', '')) !== cents) throw new Error('财务金额超过精确展示范围')
|
||||
return Number(value)
|
||||
}
|
||||
export async function listVehicleCareFinance(vehicleId: string, recordNos: string[]): Promise<VehicleCareFinanceStatus[]> {
|
||||
if (!recordNos.length) return []
|
||||
type Cost = NonNullable<VehicleCareFinanceStatus['finance']>
|
||||
type StatusWire = Omit<VehicleCareFinanceStatus, 'sourceVersion' | 'submittedRecordVersion' | 'finance'> & {
|
||||
sourceVersion?: Numeric | null; submittedRecordVersion?: Numeric | null
|
||||
finance?: (Omit<Cost, 'paidAmount' | 'outstandingAmount' | 'source'> & { paidAmount: Numeric; outstandingAmount?: Numeric | null;
|
||||
source: Omit<Cost['source'], 'originalAmount' | 'amount' | 'exchangeRate'> & { originalAmount: Numeric; amount: Numeric; exchangeRate: Numeric } }) | null
|
||||
}
|
||||
const response = unwrapApiResponse(await http.post<ApiEnvelope<StatusWire[]>, ApiEnvelope<StatusWire[]>>(`${path(vehicleId)}/finance-status`, { recordNos }))
|
||||
const seen = new Set<string>()
|
||||
const result = response.map(value => {
|
||||
if (!recordNos.includes(value.recordNo) || seen.has(value.recordNo) || !Object.hasOwn(vehicleCareFinanceLabels, value.status)) throw new Error('财务状态归属或内容无效')
|
||||
seen.add(value.recordNo)
|
||||
const cost = value.finance
|
||||
if (!['UNLINKED', 'PENDING_FINANCE', 'FINANCE_ATTENTION'].includes(value.status) && (!cost || cost.status !== value.status)) throw new Error('财务入账或付款状态缺少真实依据')
|
||||
if (cost && cost.currency !== 'CNY') throw new Error('财务结算币种无效')
|
||||
return { ...value, sourceVersion: value.sourceVersion == null ? null : financeVersion(value.sourceVersion),
|
||||
submittedRecordVersion: value.submittedRecordVersion == null ? null : financeVersion(value.submittedRecordVersion),
|
||||
finance: cost ? { ...cost, paidAmount: financeAmount(cost.paidAmount), outstandingAmount: cost.outstandingAmount == null ? null : financeAmount(cost.outstandingAmount),
|
||||
source: { ...cost.source, originalAmount: financeAmount(cost.source.originalAmount), amount: financeAmount(cost.source.amount), exchangeRate: decimal(cost.source.exchangeRate) } } : null }
|
||||
})
|
||||
if (seen.size !== new Set(recordNos).size) throw new Error('财务状态未返回全部所选记录')
|
||||
return result
|
||||
}
|
||||
export async function submitVehicleCareFinance(vehicleId: string, recordNo: string, request: VehicleCareFinanceSubmit): Promise<VehicleCareFinanceSubmitted> {
|
||||
type Wire = Omit<VehicleCareFinanceSubmitted, 'sourceVersion' | 'submittedRecordVersion'> & { sourceVersion: Numeric; submittedRecordVersion: Numeric }
|
||||
const response = unwrapApiResponse(await http.post<ApiEnvelope<Wire>, ApiEnvelope<Wire>>(`${path(vehicleId, recordNo)}/finance`, request))
|
||||
if (response.recordNo !== recordNo || response.status !== 'PENDING_FINANCE') throw new Error('财务提交响应不匹配,请使用原请求重试')
|
||||
return { ...response, sourceVersion: financeVersion(response.sourceVersion), submittedRecordVersion: financeVersion(response.submittedRecordVersion) }
|
||||
}
|
||||
export async function listVehicleCareSuppliers(keyword = ''): Promise<Array<{ id: string; name: string }>> {
|
||||
type Supplier = { id: Numeric; name: string; status: string }
|
||||
const response = unwrapApiResponse(await http.get<ApiEnvelope<{ records: Supplier[] }>, ApiEnvelope<{ records: Supplier[] }>>('/platform/v1/suppliers',
|
||||
{ params: { page: 1, size: 50, status: 'ACTIVE', keyword } }))
|
||||
return response.records.filter(row => row.status === 'ACTIVE').map(row => ({ id: financeVersion(row.id), name: row.name }))
|
||||
}
|
||||
function integer(value: Numeric): number {
|
||||
const result = Number(value)
|
||||
if (!Number.isSafeInteger(result) || result < 0) throw new Error('运维接口返回了无效的分页或版本')
|
||||
|
||||
Reference in New Issue
Block a user