2026-08-11 09:25:48 +08:00
|
|
|
import { http, unwrapApiResponse, type ApiEnvelope } from '@/shared/api/http'
|
2026-08-19 09:14:35 +08:00
|
|
|
import type { CreatePayableSettlementPayload, CreateReceivableSettlementPayload, CreateReconciliationPayload, CreateSettlementPaymentPayload, FinanceAccountEntry, FinanceAccountSummary, FinanceBillDetail, FinanceBillLine, FinanceBillResource, FinanceBillRevision, FinanceCostResource, FinanceCustomerAccountReadModel, FinanceCustomerTerms, FinanceFeePoolItem, FinanceFeeSourceDetail, FinanceFeeSourceSummary, FinanceLedgerEntry, FinancePayableSettlement, FinanceReceivableSettlement, FinanceReceiptResource, FinanceReconciliationDetail, FinanceSettlementDetail, FinanceSettlementLine, FinanceStatementReadModel, FinanceVoucherResource, ReceivableStatisticsResult, ReconciliationActionPayload } from '../types'
|
2026-08-11 09:25:48 +08:00
|
|
|
|
2026-08-11 10:45:59 +08:00
|
|
|
const basePath = '/platform/finance/v1'
|
2026-08-13 12:22:41 +08:00
|
|
|
export interface FinanceCustomerAccountSummaryResource {
|
|
|
|
|
customerId: string
|
|
|
|
|
currency: 'CNY'
|
|
|
|
|
totalReceivable: number | string
|
|
|
|
|
receivedAmount: number | string
|
|
|
|
|
outstandingReceivable: number | string
|
|
|
|
|
pendingBilling: number | string
|
|
|
|
|
outstandingPayable: number | string
|
|
|
|
|
asOf: string
|
|
|
|
|
lastChangedAt: string | null
|
|
|
|
|
}
|
2026-08-11 18:17:05 +08:00
|
|
|
export const financePhase1Capabilities = {
|
2026-08-11 18:43:56 +08:00
|
|
|
accountList: true,
|
|
|
|
|
accountLedger: true,
|
2026-08-11 19:35:28 +08:00
|
|
|
feeSourceDetail: true,
|
|
|
|
|
feeSourceReview: true,
|
2026-08-19 09:14:35 +08:00
|
|
|
feeSourceEditReviewLifecycle: true,
|
2026-08-12 09:25:55 +08:00
|
|
|
reconciliationCreate: true,
|
|
|
|
|
reconciliationActions: true,
|
|
|
|
|
receivableSettlementCreate: true,
|
|
|
|
|
payableSettlementCreate: true,
|
|
|
|
|
settlementDetail: true,
|
|
|
|
|
settlementPayment: true,
|
2026-08-11 18:17:05 +08:00
|
|
|
} as const
|
2026-08-19 09:14:35 +08:00
|
|
|
|
|
|
|
|
export async function getReceivableStatistics(params: { month: string; customerId?: string; page?: number; pageSize?: number }): Promise<ReceivableStatisticsResult> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/receivable-statistics`, { params: {
|
|
|
|
|
month: params.month,
|
|
|
|
|
customerId: params.customerId?.trim() || undefined,
|
|
|
|
|
page: params.page ?? 1,
|
|
|
|
|
pageSize: params.pageSize ?? 20,
|
|
|
|
|
} })
|
|
|
|
|
const item = unwrapApiResponse(response)
|
|
|
|
|
return {
|
|
|
|
|
statisticsMonth: stringValue(item.statisticsMonth), currency: 'CNY',
|
|
|
|
|
totalReceivable: stringValue(item.totalReceivable), totalReceived: stringValue(item.totalReceived),
|
|
|
|
|
totalOutstanding: stringValue(item.totalOutstanding), totalCustomers: numberValue(item.totalCustomers),
|
|
|
|
|
page: numberValue(item.page), pageSize: numberValue(item.pageSize),
|
|
|
|
|
records: (Array.isArray(item.records) ? item.records : []).map((value) => {
|
|
|
|
|
const row = value as RawResource
|
|
|
|
|
return { customerId: stringValue(row.customerId), customerName: stringValue(row.customerName), currency: 'CNY' as const,
|
|
|
|
|
totalReceivable: stringValue(row.totalReceivable), totalReceived: stringValue(row.totalReceived), totalOutstanding: stringValue(row.totalOutstanding) }
|
|
|
|
|
}),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-11 10:45:59 +08:00
|
|
|
function idempotencyKey(operation: string) { return `finance-${operation}-${Date.now()}-${Math.random().toString(36).slice(2)}` }
|
|
|
|
|
type RawResource = Record<string, unknown>
|
|
|
|
|
function stringValue(value: unknown) { return value == null ? '' : String(value) }
|
|
|
|
|
function numberValue(value: unknown) { return Number(value ?? 0) }
|
2026-08-11 18:06:35 +08:00
|
|
|
function jsonRecord(value: unknown): RawResource {
|
|
|
|
|
if (value && typeof value === 'object' && !Array.isArray(value)) return value as RawResource
|
|
|
|
|
if (typeof value !== 'string' || !value.trim()) return {}
|
|
|
|
|
try { const parsed = JSON.parse(value) as unknown; return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as RawResource : {} } catch { return {} }
|
2026-08-11 10:45:59 +08:00
|
|
|
}
|
2026-08-11 18:06:35 +08:00
|
|
|
function jsonRecords(value: unknown): RawResource[] {
|
|
|
|
|
if (Array.isArray(value)) return value.filter((item): item is RawResource => Boolean(item) && typeof item === 'object' && !Array.isArray(item))
|
|
|
|
|
if (typeof value !== 'string' || !value.trim()) return []
|
|
|
|
|
try { const parsed = JSON.parse(value) as unknown; return Array.isArray(parsed) ? parsed.filter((item): item is RawResource => Boolean(item) && typeof item === 'object' && !Array.isArray(item)) : [] } catch { return [] }
|
2026-08-11 10:45:59 +08:00
|
|
|
}
|
2026-08-11 11:41:47 +08:00
|
|
|
function normalizeBill(item: RawResource, customerId = stringValue(item.tenant_id ?? item.customerId)): FinanceBillResource {
|
|
|
|
|
return {
|
|
|
|
|
id: stringValue(item.id), billNo: stringValue(item.bill_no ?? item.billNo), customerId,
|
|
|
|
|
customerName: stringValue(item.customer_name_snapshot ?? item.customerName) || `客户 ${customerId}`,
|
2026-08-11 10:45:59 +08:00
|
|
|
billDate: stringValue(item.bill_date ?? item.billDate), dueDate: stringValue(item.due_date ?? item.dueDate), currency: 'CNY',
|
2026-08-11 18:06:35 +08:00
|
|
|
totalAmount: numberValue(item.total_amount ?? item.totalAmount), paidAmount: numberValue(item.received_amount ?? item.receivedAmount ?? item.paidAmount),
|
2026-08-11 10:45:59 +08:00
|
|
|
outstandingAmount: numberValue(item.outstanding_amount ?? item.outstandingAmount), status: stringValue(item.status) as FinanceBillResource['status'],
|
|
|
|
|
version: numberValue(item.version),
|
2026-08-11 11:41:47 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function normalizeBillLine(item: RawResource): FinanceBillLine {
|
|
|
|
|
return {
|
2026-08-12 09:25:55 +08:00
|
|
|
statementLineId: stringValue(item.id ?? item.statementLineId),
|
2026-08-11 18:06:35 +08:00
|
|
|
sourceFeeId: stringValue(item.source_id ?? item.sourceId ?? item.sourceFeeId), sourceFeeVersion: numberValue(item.source_version ?? item.sourceVersion ?? item.sourceFeeVersion) || 1,
|
2026-08-11 11:41:47 +08:00
|
|
|
orderId: item.order_id == null && item.orderId == null ? null : stringValue(item.order_id ?? item.orderId),
|
|
|
|
|
inboundId: item.inbound_id == null && item.inboundId == null ? null : stringValue(item.inbound_id ?? item.inboundId),
|
|
|
|
|
feeItemName: stringValue(item.fee_item_name ?? item.feeItemName), amount: numberValue(item.amount),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
function normalizeBillDetail(item: RawResource): FinanceBillDetail {
|
2026-08-11 18:06:35 +08:00
|
|
|
const statement = item.statement && typeof item.statement === 'object' ? item.statement as RawResource : item
|
|
|
|
|
return { ...normalizeBill(statement), remark: stringValue(statement.remark), lines: Array.isArray(item.lines) ? item.lines.map((line) => normalizeBillLine(line as RawResource)) : [] }
|
2026-08-11 11:41:47 +08:00
|
|
|
}
|
|
|
|
|
function mutationHeaders(operation: string) { return { headers: { 'Idempotency-Key': idempotencyKey(operation) } } }
|
|
|
|
|
|
2026-08-11 18:06:35 +08:00
|
|
|
export async function getFinanceCustomerAccount(customerId: string): Promise<FinanceCustomerAccountReadModel> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/customer-accounts/${customerId}`)
|
|
|
|
|
const item = unwrapApiResponse(response)
|
|
|
|
|
return {
|
|
|
|
|
customerId: stringValue(item.customerId), currency: 'CNY', accountMode: 'MANUAL_RECEIVABLE_LEDGER',
|
|
|
|
|
totalReceivable: numberValue(item.totalReceivable), totalReceived: numberValue(item.totalReceived), outstandingAmount: numberValue(item.outstandingAmount),
|
|
|
|
|
entries: (Array.isArray(item.entries) ? item.entries : []).map((entry) => { const row = entry as RawResource; return { entryType: stringValue(row.entryType) as FinanceAccountEntry['entryType'], resourceId: stringValue(row.resourceId), resourceNo: stringValue(row.resourceNo), occurredAt: stringValue(row.occurredAt), debitAmount: numberValue(row.debitAmount), creditAmount: numberValue(row.creditAmount), currency: 'CNY', status: stringValue(row.status) } }),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-13 12:22:41 +08:00
|
|
|
export async function getFinanceCustomerAccountSummary(customerId: string): Promise<FinanceCustomerAccountSummaryResource> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/customer-accounts/${customerId}/summary`)
|
|
|
|
|
const item = unwrapApiResponse(response)
|
|
|
|
|
return {
|
|
|
|
|
customerId: stringValue(item.customerId), currency: 'CNY',
|
|
|
|
|
totalReceivable: stringValue(item.totalReceivable), receivedAmount: stringValue(item.receivedAmount),
|
|
|
|
|
outstandingReceivable: stringValue(item.outstandingReceivable), pendingBilling: stringValue(item.pendingBilling),
|
|
|
|
|
outstandingPayable: stringValue(item.outstandingPayable), asOf: stringValue(item.asOf),
|
|
|
|
|
lastChangedAt: item.lastChangedAt == null ? null : stringValue(item.lastChangedAt),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-12 17:29:35 +08:00
|
|
|
export async function listFinanceFeePool(customerId?: string, direction?: 'RECEIVABLE' | 'PAYABLE'): Promise<FinanceFeePoolItem[]> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]>, ApiEnvelope<RawResource[]>>(`${basePath}/fee-pool`, { params: { customerId: customerId?.trim() || undefined, direction } })
|
2026-08-11 19:35:28 +08:00
|
|
|
return unwrapApiResponse(response).map((item) => {
|
|
|
|
|
const poolItemId = stringValue(item.poolItemId)
|
|
|
|
|
const feeSourceId = stringValue(item.sourceType) === 'MANUAL_COST' ? null : (poolItemId.match(/^[A-Z_]+:(\d+)$/)?.[1] ?? null)
|
2026-08-13 11:57:44 +08:00
|
|
|
return { poolItemId, feeSourceId, customerId: stringValue(item.customerId), customerName: stringValue(item.customerName), direction: stringValue(item.direction) as FinanceFeePoolItem['direction'], sourceType: stringValue(item.sourceType), sourceId: stringValue(item.sourceId), sourceVersion: numberValue(item.sourceVersion), orderId: item.orderId == null ? null : stringValue(item.orderId), inboundId: item.inboundId == null ? null : stringValue(item.inboundId), containerId: item.containerId == null ? null : stringValue(item.containerId), feeItemName: stringValue(item.feeItemName), amount: numberValue(item.amount), currency: 'CNY', status: stringValue(item.status), createdAt: stringValue(item.createdAt) }
|
2026-08-11 19:35:28 +08:00
|
|
|
})
|
2026-08-11 18:06:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-14 12:50:42 +08:00
|
|
|
export async function listFinanceAccounts(params: { page?: number; size?: number; customerId?: string; status?: string } = {}): Promise<{ records: FinanceAccountSummary[]; total: number; pages: number }> {
|
|
|
|
|
const query = { page: params.page ?? 1, pageSize: params.size ?? 20, customerId: params.customerId || undefined, status: params.status || undefined }
|
2026-08-11 18:43:56 +08:00
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]> & { page?: RawResource }, ApiEnvelope<RawResource[]> & { page?: RawResource }>(`${basePath}/accounts`, { params: query })
|
|
|
|
|
const data = unwrapApiResponse(response)
|
|
|
|
|
const records = data.map((item) => ({ customerId: stringValue(item.customerId), customerCode: stringValue(item.customerCode), customerName: stringValue(item.customerName), contactName: stringValue(item.contactName), contactPhone: stringValue(item.contactPhone), outstandingReceivable: numberValue(item.outstandingReceivable), pendingBilling: numberValue(item.pendingBilling), receivedInPeriod: numberValue(item.receivedInPeriod), outstandingPayable: numberValue(item.outstandingPayable), status: stringValue(item.status), lastChangedAt: stringValue(item.lastChangedAt) }))
|
|
|
|
|
const total = numberValue(response.page?.total ?? records.length)
|
|
|
|
|
const pageSize = numberValue(response.page?.pageSize ?? query.pageSize) || query.pageSize
|
|
|
|
|
return { records, total, pages: Math.max(1, Math.ceil(total / pageSize)) }
|
2026-08-11 18:17:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function listFinanceAccountLedger(customerId: string, params: { page?: number; size?: number; businessType?: string; startDate?: string; endDate?: string } = {}): Promise<{ records: FinanceLedgerEntry[]; total: number; pages: number }> {
|
2026-08-11 18:43:56 +08:00
|
|
|
const query = { page: params.page ?? 1, pageSize: params.size ?? 20, businessType: params.businessType || undefined, startDate: params.startDate || undefined, endDate: params.endDate || undefined }
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]> & { page?: RawResource }, ApiEnvelope<RawResource[]> & { page?: RawResource }>(`${basePath}/accounts/${customerId}/ledger`, { params: query })
|
|
|
|
|
const data = unwrapApiResponse(response)
|
|
|
|
|
const records = data.map((item) => ({ ledgerId: stringValue(item.ledgerId), occurredAt: stringValue(item.occurredAt), businessType: stringValue(item.businessType), documentType: stringValue(item.documentType), documentId: stringValue(item.documentId), documentNo: stringValue(item.documentNo), receivableChange: numberValue(item.receivableChange), payableChange: numberValue(item.payableChange), outstandingReceivableAfter: numberValue(item.outstandingReceivableAfter), operatorName: stringValue(item.operatorName), remark: stringValue(item.remark) }))
|
|
|
|
|
const total = numberValue(response.page?.total ?? records.length)
|
|
|
|
|
const pageSize = numberValue(response.page?.pageSize ?? query.pageSize) || query.pageSize
|
|
|
|
|
return { records, total, pages: Math.max(1, Math.ceil(total / pageSize)) }
|
2026-08-11 18:17:05 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-17 19:52:04 +08:00
|
|
|
export async function listFinanceFeeSources(params: {
|
|
|
|
|
page?: number
|
|
|
|
|
size?: number
|
|
|
|
|
customerId?: string
|
|
|
|
|
direction?: 'RECEIVABLE' | 'PAYABLE'
|
|
|
|
|
status?: string
|
2026-08-19 09:14:35 +08:00
|
|
|
reviewStatus?: 'DRAFT' | 'PENDING' | 'APPROVED' | 'REJECTED'
|
2026-08-17 19:52:04 +08:00
|
|
|
statementGenerated?: boolean
|
|
|
|
|
businessDateFrom?: string
|
|
|
|
|
businessDateTo?: string
|
|
|
|
|
keyword?: string
|
|
|
|
|
} = {}): Promise<{ records: FinanceFeeSourceSummary[]; total: number; pages: number }> {
|
|
|
|
|
const query = {
|
|
|
|
|
page: params.page ?? 1, pageSize: params.size ?? 20, customerId: params.customerId || undefined,
|
|
|
|
|
direction: params.direction || undefined, status: params.status || undefined,
|
2026-08-19 09:14:35 +08:00
|
|
|
reviewStatus: params.reviewStatus || undefined,
|
2026-08-17 19:52:04 +08:00
|
|
|
statementGenerated: params.statementGenerated, businessDateFrom: params.businessDateFrom || undefined,
|
|
|
|
|
businessDateTo: params.businessDateTo || undefined, keyword: params.keyword?.trim() || undefined,
|
|
|
|
|
}
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]> & { page?: RawResource }, ApiEnvelope<RawResource[]> & { page?: RawResource }>(`${basePath}/fee-sources`, { params: query })
|
|
|
|
|
const records = unwrapApiResponse(response).map(normalizeFeeSourceSummary)
|
|
|
|
|
const total = numberValue(response.page?.total ?? records.length)
|
|
|
|
|
const pageSize = numberValue(response.page?.pageSize ?? query.pageSize) || query.pageSize
|
|
|
|
|
return { records, total, pages: Math.max(1, Math.ceil(total / pageSize)) }
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 18:17:05 +08:00
|
|
|
export async function getFinanceFeeSource(feeSourceId: string): Promise<FinanceFeeSourceDetail> {
|
2026-08-11 19:35:28 +08:00
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/fee-sources/${feeSourceId}`)
|
|
|
|
|
return normalizeFeeSourceDetail(unwrapApiResponse(response))
|
2026-08-11 18:17:05 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-17 19:52:04 +08:00
|
|
|
function normalizeFeeSourceSummary(item: RawResource): FinanceFeeSourceSummary {
|
2026-08-11 19:35:28 +08:00
|
|
|
const nullableString = (value: unknown) => value == null ? null : stringValue(value)
|
|
|
|
|
const nullableNumber = (value: unknown) => value == null ? null : numberValue(value)
|
|
|
|
|
return {
|
2026-08-17 19:52:04 +08:00
|
|
|
poolItemId: stringValue(item.feeSourceId), feeSourceId: stringValue(item.feeSourceId), sourceSystem: stringValue(item.sourceSystem),
|
|
|
|
|
sourceType: stringValue(item.sourceType), sourceBusinessId: stringValue(item.sourceBusinessId), sourceId: stringValue(item.sourceFeeId),
|
2026-08-11 19:35:28 +08:00
|
|
|
sourceVersion: numberValue(item.sourceVersion), direction: stringValue(item.direction) as FinanceFeeSourceDetail['direction'],
|
|
|
|
|
customerId: stringValue(item.customerId), customerName: stringValue(item.customerName), supplierId: nullableString(item.supplierId), supplierName: stringValue(item.supplierName),
|
|
|
|
|
orderId: nullableString(item.orderId), inboundId: nullableString(item.inboundId), inboundCode: stringValue(item.inboundCode),
|
|
|
|
|
containerId: nullableString(item.containerId), containerNo: stringValue(item.containerNo), businessDate: stringValue(item.businessDate),
|
|
|
|
|
feeItemName: stringValue(item.feeItemName), quantity: nullableNumber(item.quantity), unitPrice: nullableNumber(item.unitPrice), amount: numberValue(item.amount), currency: 'CNY',
|
2026-08-19 09:14:35 +08:00
|
|
|
reviewStatus: stringValue(item.reviewStatus) as FinanceFeeSourceDetail['reviewStatus'],
|
2026-08-11 19:35:28 +08:00
|
|
|
sourceStatus: stringValue(item.sourceStatus), status: stringValue(item.sourceStatus), statementGenerated: Boolean(item.statementGenerated),
|
2026-08-17 19:52:04 +08:00
|
|
|
createdAt: stringValue(item.businessDate),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeFeeSourceDetail(item: RawResource): FinanceFeeSourceDetail {
|
|
|
|
|
return {
|
|
|
|
|
...normalizeFeeSourceSummary(item), sourceFeeId: stringValue(item.sourceFeeId),
|
2026-08-19 09:14:35 +08:00
|
|
|
reviewReason: stringValue(item.reviewReason) || undefined,
|
2026-08-17 19:52:04 +08:00
|
|
|
sourceHash: stringValue(item.sourceHash) || undefined, operationVersion: numberValue(item.operationVersion),
|
2026-08-11 19:35:28 +08:00
|
|
|
operationLogs: (Array.isArray(item.operationLogs) ? item.operationLogs : []).map((entry) => { const row = entry as RawResource; return { occurredAt: stringValue(row.occurredAt), action: stringValue(row.action), operatorName: stringValue(row.operatorName), result: stringValue(row.result), remark: stringValue(row.remark) || undefined } }),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function reviewFinanceFeeSource(feeSourceId: string, payload: { action: 'APPROVE' | 'REJECT'; reason?: string; expectedVersion: number }): Promise<FinanceFeeSourceDetail> {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/fee-sources/${feeSourceId}/review`, payload, mutationHeaders('fee-source-review'))
|
|
|
|
|
return normalizeFeeSourceDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
2026-08-19 09:14:35 +08:00
|
|
|
export async function editFinanceFeeSource(feeSourceId: string, payload: { amount: number; reason: string; expectedVersion: number }): Promise<FinanceFeeSourceDetail> {
|
|
|
|
|
const response = await http.put<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/fee-sources/${feeSourceId}`, payload, mutationHeaders('fee-source-edit'))
|
|
|
|
|
return normalizeFeeSourceDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function submitFinanceFeeSource(feeSourceId: string, payload: { reason: string; expectedVersion: number }): Promise<FinanceFeeSourceDetail> {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/fee-sources/${feeSourceId}/submit`, payload, mutationHeaders('fee-source-submit'))
|
|
|
|
|
return normalizeFeeSourceDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function adjustFinanceFeeSource(feeSourceId: string, payload: { amount: number; reason: string; expectedVersion: number }): Promise<FinanceFeeSourceDetail> {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/fee-sources/${feeSourceId}/adjust`, payload, mutationHeaders('fee-source-adjust'))
|
|
|
|
|
return normalizeFeeSourceDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function voidFinanceFeeSource(feeSourceId: string, payload: { reason: string; expectedVersion: number }): Promise<FinanceFeeSourceDetail> {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/fee-sources/${feeSourceId}/void`, payload, mutationHeaders('fee-source-void'))
|
2026-08-11 19:35:28 +08:00
|
|
|
return normalizeFeeSourceDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
2026-08-12 09:25:55 +08:00
|
|
|
function normalizeReconciliationDetail(item: RawResource): FinanceReconciliationDetail {
|
|
|
|
|
const lines = (Array.isArray(item.lines) ? item.lines : []).map((entry) => {
|
|
|
|
|
const row = entry as RawResource
|
2026-08-13 12:22:41 +08:00
|
|
|
return {
|
|
|
|
|
statementLineId: stringValue(row.statementLineId), sourceFeeId: stringValue(row.feeSourceId), sourceFeeVersion: numberValue(row.sourceVersion), sourceHash: stringValue(row.sourceHash) || undefined,
|
2026-08-17 19:52:04 +08:00
|
|
|
direction: stringValue(row.direction) as 'RECEIVABLE' | 'PAYABLE', sourceType: stringValue(row.sourceType), sourceNo: stringValue(row.sourceNo),
|
|
|
|
|
orderId: row.orderId == null ? null : stringValue(row.orderId), inboundId: row.inboundId == null ? null : stringValue(row.inboundId), containerId: row.containerId == null ? null : stringValue(row.containerId),
|
|
|
|
|
inboundCode: stringValue(row.inboundCode), containerNo: stringValue(row.containerNo), businessDate: stringValue(row.businessDate),
|
|
|
|
|
quantity: row.quantity == null ? null : numberValue(row.quantity), unitPrice: row.unitPrice == null ? null : numberValue(row.unitPrice),
|
2026-08-13 12:22:41 +08:00
|
|
|
feeItemName: stringValue(row.feeItemName), sourceAmount: numberValue(row.sourceAmount), amount: numberValue(row.statementAmount), adjustmentReason: stringValue(row.adjustmentReason) || undefined,
|
|
|
|
|
}
|
2026-08-12 09:25:55 +08:00
|
|
|
})
|
|
|
|
|
return {
|
|
|
|
|
id: stringValue(item.statementId), billNo: stringValue(item.statementNo), customerId: stringValue(item.customerId), customerName: stringValue(item.customerName),
|
|
|
|
|
billDate: stringValue(item.periodEnd), dueDate: '', periodStart: stringValue(item.periodStart), periodEnd: stringValue(item.periodEnd), creditDays: numberValue(item.creditDays), currency: 'CNY',
|
2026-08-19 09:14:35 +08:00
|
|
|
totalAmount: numberValue(item.totalReceivable), paidAmount: numberValue(item.receivedAmount), outstandingAmount: numberValue(item.outstandingAmount), settled: Boolean(item.settled), status: stringValue(item.status) as FinanceBillResource['status'], version: numberValue(item.version), customerConfirmationStatus: stringValue(item.customerConfirmationStatus) as FinanceBillResource['customerConfirmationStatus'], customerConfirmedBy: stringValue(item.customerConfirmedBy) || undefined, customerConfirmedByName: stringValue(item.customerConfirmedByName) || undefined, customerConfirmedAt: stringValue(item.customerConfirmedAt) || undefined, remark: stringValue(item.remark), createdBy: stringValue(item.createdBy), createdByName: stringValue(item.createdByName), createdAt: stringValue(item.createdAt), updatedBy: stringValue(item.updatedBy), updatedByName: stringValue(item.updatedByName), updatedAt: stringValue(item.updatedAt), lines,
|
2026-08-17 19:52:04 +08:00
|
|
|
paymentHistory: (Array.isArray(item.paymentHistory) ? item.paymentHistory : []).map((entry) => { const row = entry as RawResource; return { transactionId: stringValue(row.transactionId), transactionType: stringValue(row.transactionType), transactionDate: stringValue(row.transactionDate), amount: numberValue(row.amount), method: stringValue(row.method), referenceNo: stringValue(row.referenceNo), voucherFileIds: Array.isArray(row.voucherFileIds) ? row.voucherFileIds.map(stringValue) : [], createdBy: stringValue(row.createdBy), createdByName: stringValue(row.createdByName), occurredAt: stringValue(row.occurredAt ?? row.createdAt), remark: stringValue(row.remark) || undefined } }),
|
2026-08-17 16:45:46 +08:00
|
|
|
revisions: (Array.isArray(item.revisions) ? item.revisions : []).map((entry) => { const row = entry as RawResource; return { revisionNo: numberValue(row.revisionNo), action: stringValue(row.action), beforeAmount: numberValue(row.beforeAmount), afterAmount: numberValue(row.afterAmount), reason: stringValue(row.reason) || undefined, operatorId: stringValue(row.operatorId), operatorName: stringValue(row.operatorName), occurredAt: stringValue(row.occurredAt) } }),
|
2026-08-12 09:25:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeSettlementDetail(item: RawResource): FinanceSettlementDetail {
|
|
|
|
|
const summary = item.summary && typeof item.summary === 'object' ? item.summary as RawResource : item
|
|
|
|
|
return {
|
2026-08-17 19:52:04 +08:00
|
|
|
id: stringValue(summary.settlementId ?? summary.id), settlementNo: stringValue(summary.settlementNo), customerId: stringValue(summary.customerId), customerName: stringValue(summary.customerName), supplierId: summary.supplierId == null ? undefined : stringValue(summary.supplierId), supplierName: stringValue(summary.supplierName), reconciliationStatementId: summary.reconciliationStatementId == null ? undefined : stringValue(summary.reconciliationStatementId), reconciliationStatementNo: stringValue(summary.reconciliationStatementNo) || undefined, businessPeriod: stringValue(summary.businessPeriod), feeItemSummary: stringValue(summary.feeItemSummary) || undefined, generatedDate: stringValue(summary.generatedDate), dueDate: stringValue(summary.dueDate), paymentStatus: stringValue(summary.paymentStatus) as FinanceSettlementDetail['paymentStatus'], direction: stringValue(summary.direction) as FinanceSettlementDetail['direction'],
|
|
|
|
|
amount: numberValue(summary.totalAmount ?? summary.amount), paidAmount: numberValue(summary.paidAmount), outstandingAmount: numberValue(summary.outstandingAmount), currency: 'CNY', status: stringValue(summary.status), createdBy: stringValue(summary.createdBy) || undefined, createdByName: stringValue(summary.createdByName) || undefined, createdAt: stringValue(summary.createdAt) || undefined, updatedAt: stringValue(summary.updatedAt) || undefined, lastPaymentAt: stringValue(summary.lastPaymentAt) || undefined, version: numberValue(summary.version),
|
|
|
|
|
lines: (Array.isArray(item.lines) ? item.lines : []).map((line) => normalizeSettlementLine(line as RawResource)),
|
2026-08-17 16:45:46 +08:00
|
|
|
transactions: (Array.isArray(item.transactions) ? item.transactions : []).map((entry) => { const row = entry as RawResource; return { transactionId: stringValue(row.transactionId), transactionType: stringValue(row.transactionType), transactionDate: stringValue(row.transactionDate), amount: numberValue(row.amount), method: stringValue(row.method), referenceNo: stringValue(row.referenceNo), voucherFileIds: Array.isArray(row.voucherFileIds) ? row.voucherFileIds.map(stringValue) : [], createdBy: stringValue(row.createdBy), createdByName: stringValue(row.createdByName), createdAt: stringValue(row.createdAt), remark: stringValue(row.remark) || undefined } }),
|
|
|
|
|
revisions: (Array.isArray(item.revisions) ? item.revisions : []).map((entry) => { const row = entry as RawResource; return { revisionNo: numberValue(row.revisionNo), action: stringValue(row.action), beforeAmount: numberValue(row.beforeAmount), afterAmount: numberValue(row.afterAmount), reason: stringValue(row.reason) || undefined, operatorId: stringValue(row.operatorId), operatorName: stringValue(row.operatorName), occurredAt: stringValue(row.occurredAt) } }),
|
2026-08-12 09:25:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-17 19:52:04 +08:00
|
|
|
function normalizeSettlementLine(item: RawResource): FinanceSettlementLine {
|
|
|
|
|
const nullableString = (value: unknown) => value == null ? null : stringValue(value)
|
|
|
|
|
const nullableNumber = (value: unknown) => value == null ? null : numberValue(value)
|
|
|
|
|
return {
|
|
|
|
|
feeSourceId: stringValue(item.feeSourceId), sourceType: stringValue(item.sourceType), sourceFeeId: stringValue(item.sourceFeeId), sourceVersion: numberValue(item.sourceVersion),
|
|
|
|
|
direction: stringValue(item.direction) as FinanceSettlementLine['direction'], customerId: stringValue(item.customerId), customerName: stringValue(item.customerName),
|
|
|
|
|
supplierId: nullableString(item.supplierId), supplierName: stringValue(item.supplierName), orderId: nullableString(item.orderId), orderNo: stringValue(item.orderNo),
|
|
|
|
|
inboundId: nullableString(item.inboundId), inboundCode: stringValue(item.inboundCode), containerId: nullableString(item.containerId), containerNo: stringValue(item.containerNo),
|
|
|
|
|
businessDate: stringValue(item.businessDate), feeItemName: stringValue(item.feeItemName), quantity: nullableNumber(item.quantity), unitPrice: nullableNumber(item.unitPrice),
|
2026-08-19 09:14:35 +08:00
|
|
|
amount: numberValue(item.amount), currency: 'CNY', reviewStatus: stringValue(item.reviewStatus) as FinanceSettlementLine['reviewStatus'], sourceStatus: stringValue(item.sourceStatus), statementGenerated: Boolean(item.statementGenerated),
|
2026-08-17 19:52:04 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-12 09:25:55 +08:00
|
|
|
export async function createReconciliationStatement(payload: CreateReconciliationPayload) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/statements`, payload, mutationHeaders('reconciliation-create'))
|
|
|
|
|
return normalizeReconciliationDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function actionReconciliationStatement(statementId: string, payload: ReconciliationActionPayload) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/reconciliation-statements/${statementId}/actions`, payload, mutationHeaders('reconciliation-action'))
|
|
|
|
|
return normalizeReconciliationDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function getReconciliationStatement(statementId: string): Promise<FinanceReconciliationDetail> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/reconciliation-statements/${statementId}`)
|
|
|
|
|
return normalizeReconciliationDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function createReceivableSettlement(payload: CreateReceivableSettlementPayload) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/receivable-settlements`, payload, mutationHeaders('settlement-ar-create'))
|
|
|
|
|
return normalizeSettlementDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function createPayableSettlement(payload: CreatePayableSettlementPayload) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/payable-settlements`, payload, mutationHeaders('settlement-ap-create'))
|
|
|
|
|
return normalizeSettlementDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function getFinanceSettlement(settlementId: string): Promise<FinanceSettlementDetail> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/settlements/${settlementId}`)
|
|
|
|
|
return normalizeSettlementDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
export async function createFinanceSettlementPayment(settlementId: string, payload: CreateSettlementPaymentPayload) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/settlements/${settlementId}/transactions`, payload, mutationHeaders('settlement-payment'))
|
|
|
|
|
return normalizeSettlementDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
2026-08-13 11:57:44 +08:00
|
|
|
export async function actionFinanceSettlement(settlementId: string, payload: import('../types').FinanceSettlementActionPayload) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/settlements/${settlementId}/actions`, payload, mutationHeaders('settlement-action'))
|
|
|
|
|
return normalizeSettlementDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
2026-08-11 18:17:05 +08:00
|
|
|
|
2026-08-12 17:29:35 +08:00
|
|
|
export async function listFinanceStatements(customerId?: string): Promise<FinanceStatementReadModel[]> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]>, ApiEnvelope<RawResource[]>>(`${basePath}/statements`, { params: { customerId: customerId?.trim() || undefined } })
|
2026-08-19 09:14:35 +08:00
|
|
|
return unwrapApiResponse(response).map((item) => ({ id: stringValue(item.statementId ?? item.id), customerId: stringValue(item.customerId), billNo: stringValue(item.statementNo ?? item.billNo), customerName: stringValue(item.customerName), billDate: stringValue(item.periodEnd ?? item.billDate), dueDate: stringValue(item.dueDate), periodStart: stringValue(item.periodStart), periodEnd: stringValue(item.periodEnd), creditDays: item.creditDays == null ? undefined : numberValue(item.creditDays), lineCount: item.lineCount == null ? undefined : numberValue(item.lineCount), sourceCount: item.sourceCount == null ? undefined : numberValue(item.sourceCount), sourceAmount: item.sourceAmount == null ? undefined : numberValue(item.sourceAmount), adjustmentAmount: item.adjustmentAmount == null ? undefined : numberValue(item.adjustmentAmount), totalAmount: numberValue(item.totalReceivable ?? item.totalAmount), receivedAmount: numberValue(item.receivedAmount), outstandingAmount: numberValue(item.outstandingAmount), currency: 'CNY', settled: item.settled == null ? undefined : Boolean(item.settled), lastReceiptAt: stringValue(item.lastReceiptAt), submittedAt: stringValue(item.submittedAt), reviewedAt: stringValue(item.reviewedAt), issuedAt: stringValue(item.issuedAt), status: stringValue(item.status) as FinanceStatementReadModel['status'], version: numberValue(item.version), customerConfirmationStatus: stringValue(item.customerConfirmationStatus) as FinanceStatementReadModel['customerConfirmationStatus'], customerConfirmedBy: stringValue(item.customerConfirmedBy) || undefined, customerConfirmedByName: stringValue(item.customerConfirmedByName) || undefined, customerConfirmedAt: stringValue(item.customerConfirmedAt) || undefined, remark: stringValue(item.remark), createdBy: stringValue(item.createdBy), createdByName: stringValue(item.createdByName), createdAt: stringValue(item.createdAt), updatedBy: stringValue(item.updatedBy), updatedByName: stringValue(item.updatedByName), updatedAt: stringValue(item.updatedAt) }))
|
2026-08-11 18:06:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-12 17:29:35 +08:00
|
|
|
export async function listFinanceReceivableSettlements(customerId?: string): Promise<FinanceReceivableSettlement[]> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]>, ApiEnvelope<RawResource[]>>(`${basePath}/receivable-settlements`, { params: { customerId: customerId?.trim() || undefined } })
|
2026-08-13 11:57:44 +08:00
|
|
|
return unwrapApiResponse(response).map((item) => ({
|
|
|
|
|
id: stringValue(item.settlementId ?? item.id), customerId: stringValue(item.customerId),
|
2026-08-17 19:52:04 +08:00
|
|
|
customerName: stringValue(item.customerName), settlementNo: stringValue(item.settlementNo), reconciliationStatementId: item.reconciliationStatementId == null ? undefined : stringValue(item.reconciliationStatementId), reconciliationStatementNo: stringValue(item.reconciliationStatementNo) || undefined, businessPeriod: stringValue(item.businessPeriod), feeItemSummary: stringValue(item.feeItemSummary) || undefined, generatedDate: stringValue(item.generatedDate), createdBy: stringValue(item.createdBy) || undefined, createdByName: stringValue(item.createdByName) || undefined, createdAt: stringValue(item.createdAt) || undefined, updatedAt: stringValue(item.updatedAt) || undefined, lastPaymentAt: stringValue(item.lastPaymentAt) || undefined, billDate: stringValue(item.generatedDate ?? item.billDate),
|
2026-08-13 11:57:44 +08:00
|
|
|
dueDate: stringValue(item.dueDate), receivableAmount: numberValue(item.totalAmount ?? item.receivableAmount),
|
|
|
|
|
receivedAmount: numberValue(item.paidAmount ?? item.receivedAmount), outstandingAmount: numberValue(item.outstandingAmount),
|
|
|
|
|
paymentStatus: stringValue(item.paymentStatus) as FinanceReceivableSettlement['paymentStatus'], currency: 'CNY', status: stringValue(item.status) as FinanceReceivableSettlement['status'], version: numberValue(item.version),
|
|
|
|
|
}))
|
2026-08-11 18:06:35 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-12 17:29:35 +08:00
|
|
|
export async function listFinancePayableSettlements(customerId?: string): Promise<FinancePayableSettlement[]> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]>, ApiEnvelope<RawResource[]>>(`${basePath}/payable-settlements`, { params: { customerId: customerId?.trim() || undefined } })
|
2026-08-13 11:57:44 +08:00
|
|
|
return unwrapApiResponse(response).map((item) => ({
|
|
|
|
|
id: stringValue(item.settlementId ?? item.id), customerId: stringValue(item.customerId), customerName: stringValue(item.customerName), settlementNo: stringValue(item.settlementNo), supplierId: item.supplierId == null ? undefined : stringValue(item.supplierId),
|
|
|
|
|
costType: 'SINGLE_ORDER', orderId: null, inboundId: null, containerId: null,
|
2026-08-17 19:52:04 +08:00
|
|
|
supplierName: stringValue(item.supplierName), feeItemName: stringValue(item.feeItemSummary), feeItemSummary: stringValue(item.feeItemSummary) || undefined, businessPeriod: stringValue(item.businessPeriod), generatedDate: stringValue(item.generatedDate), dueDate: stringValue(item.dueDate), payableAmount: numberValue(item.totalAmount ?? item.payableAmount),
|
2026-08-13 11:57:44 +08:00
|
|
|
paidAmount: numberValue(item.paidAmount), outstandingAmount: numberValue(item.outstandingAmount), paymentStatus: stringValue(item.paymentStatus) as FinancePayableSettlement['paymentStatus'], currency: 'CNY',
|
2026-08-17 19:52:04 +08:00
|
|
|
status: stringValue(item.status), createdAt: stringValue(item.createdAt), createdBy: stringValue(item.createdBy) || undefined, createdByName: stringValue(item.createdByName) || undefined, updatedAt: stringValue(item.updatedAt) || undefined, lastPaymentAt: stringValue(item.lastPaymentAt) || undefined, version: numberValue(item.version),
|
2026-08-13 11:57:44 +08:00
|
|
|
}))
|
2026-08-11 18:06:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeCustomerTerms(item: RawResource, customerId: string): FinanceCustomerTerms {
|
|
|
|
|
return {
|
|
|
|
|
customerId: stringValue(item.tenant_id ?? item.tenantId ?? customerId),
|
|
|
|
|
customerName: stringValue(item.customer_name_snapshot ?? item.customerName),
|
|
|
|
|
creditDays: numberValue(item.credit_days ?? item.creditDays ?? 30),
|
|
|
|
|
status: stringValue(item.status) || 'ACTIVE',
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getFinanceCustomerTerms(customerId: string) {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/customers/${customerId}/terms`)
|
|
|
|
|
return normalizeCustomerTerms(unwrapApiResponse(response), customerId)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function updateFinanceCustomerTerms(customerId: string, payload: { customerName: string; creditDays: number }) {
|
|
|
|
|
const response = await http.put<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/customers/${customerId}/terms`, payload, mutationHeaders('customer-terms'))
|
|
|
|
|
return normalizeCustomerTerms(unwrapApiResponse(response), customerId)
|
2026-08-11 09:25:48 +08:00
|
|
|
}
|
|
|
|
|
|
2026-08-11 11:41:47 +08:00
|
|
|
export async function getFinanceBill(billId: string) {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/bills/${billId}`)
|
|
|
|
|
return normalizeBillDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 18:06:35 +08:00
|
|
|
export async function listFinanceBillRevisions(billId: string, customerId: string): Promise<FinanceBillRevision[]> {
|
|
|
|
|
const response = await http.get<ApiEnvelope<RawResource[]>, ApiEnvelope<RawResource[]>>(`${basePath}/bills/${billId}/revisions`, { params: { customerId } })
|
|
|
|
|
return unwrapApiResponse(response).map((item) => {
|
|
|
|
|
const bill = jsonRecord(item.bill_snapshot ?? item.billSnapshot)
|
|
|
|
|
const lines = jsonRecords(item.line_snapshot ?? item.lineSnapshot)
|
|
|
|
|
return {
|
|
|
|
|
id: stringValue(item.id), billId: stringValue(item.bill_id ?? item.billId ?? billId), version: numberValue(item.version),
|
|
|
|
|
reason: stringValue(item.reason), createdBy: stringValue(item.created_by ?? item.createdBy), createdAt: stringValue(item.created_at ?? item.createdAt),
|
|
|
|
|
status: stringValue(bill.status) as FinanceBillRevision['status'], totalAmount: numberValue(bill.total_amount ?? bill.totalAmount),
|
|
|
|
|
receivedAmount: numberValue(bill.received_amount ?? bill.receivedAmount), outstandingAmount: numberValue(bill.outstanding_amount ?? bill.outstandingAmount),
|
|
|
|
|
lines: lines.map(normalizeBillLine),
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 11:41:47 +08:00
|
|
|
export async function updateFinanceBill(billId: string, payload: Record<string, unknown>) {
|
|
|
|
|
const response = await http.put<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/bills/${billId}`, payload, mutationHeaders('bill-update'))
|
|
|
|
|
return normalizeBillDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function adjustFinanceBill(billId: string, payload: Record<string, unknown>) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/bills/${billId}/adjust`, payload, mutationHeaders('bill-adjust'))
|
|
|
|
|
return normalizeBillDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function voidFinanceBill(billId: string, expectedVersion: number, reason: string) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/bills/${billId}/void`, { expectedVersion, reason }, mutationHeaders('bill-void'))
|
|
|
|
|
return normalizeBillDetail(unwrapApiResponse(response))
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 10:45:59 +08:00
|
|
|
export async function createFinanceReceipt(payload: Record<string, unknown>, voucherFileIds: string[]) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<FinanceReceiptResource>, ApiEnvelope<FinanceReceiptResource>>(`${basePath}/receipts`, { ...payload, currency: 'CNY', voucherFileIds }, { headers: { 'Idempotency-Key': idempotencyKey('receipt') } })
|
2026-08-11 09:25:48 +08:00
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 13:55:23 +08:00
|
|
|
export async function uploadFinanceVoucher(customerId: string, file: File): Promise<FinanceVoucherResource> {
|
|
|
|
|
const body = new FormData()
|
|
|
|
|
body.append('file', file)
|
|
|
|
|
const response = await http.post<ApiEnvelope<RawResource>, ApiEnvelope<RawResource>>(`${basePath}/vouchers`, body, {
|
|
|
|
|
params: { customerId }, headers: { 'Content-Type': 'multipart/form-data' },
|
|
|
|
|
})
|
|
|
|
|
const item = unwrapApiResponse(response)
|
|
|
|
|
return {
|
|
|
|
|
fileId: stringValue(item.fileId ?? item.file_id), customerId: stringValue(item.tenantId ?? item.customerId ?? customerId),
|
|
|
|
|
fileName: stringValue(item.fileName ?? item.file_name), contentType: stringValue(item.contentType ?? item.content_type),
|
|
|
|
|
sizeBytes: numberValue(item.sizeBytes ?? item.size_bytes),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-11 10:45:59 +08:00
|
|
|
export async function createFinanceCost(payload: Record<string, unknown>, voucherFileIds: string[]) {
|
|
|
|
|
const response = await http.post<ApiEnvelope<FinanceCostResource>, ApiEnvelope<FinanceCostResource>>(`${basePath}/cost-entries`, { ...payload, currency: 'CNY', voucherFileIds }, { headers: { 'Idempotency-Key': idempotencyKey('cost') } })
|
2026-08-11 09:25:48 +08:00
|
|
|
return unwrapApiResponse(response)
|
|
|
|
|
}
|