149 lines
4.3 KiB
TypeScript
149 lines
4.3 KiB
TypeScript
import type {
|
|
FinanceBillDetail,
|
|
FinanceBillLine,
|
|
FinanceBillStatus,
|
|
FinanceCustomerConfirmationStatus,
|
|
FinanceBillSummary,
|
|
FinanceReceiptRecord,
|
|
} from '../contracts'
|
|
|
|
type WireScalar = string | number
|
|
|
|
export interface FinanceBillWire {
|
|
id: WireScalar
|
|
customerId: WireScalar
|
|
billNo: string
|
|
customerName: string
|
|
currency: string
|
|
billDate: string | null
|
|
dueDate: string | null
|
|
status: FinanceBillStatus
|
|
totalAmount: WireScalar
|
|
receivedAmount: WireScalar
|
|
outstandingAmount: WireScalar
|
|
version: number
|
|
customerConfirmationStatus: FinanceCustomerConfirmationStatus
|
|
customerConfirmedByName: string | null
|
|
customerConfirmedAt: string | null
|
|
}
|
|
|
|
export interface FinanceBillLineWire {
|
|
id: WireScalar
|
|
sourceType: string
|
|
sourceId: WireScalar
|
|
sourceVersion: number
|
|
orderId: WireScalar | null
|
|
inboundId: WireScalar | null
|
|
inboundCode: string | null
|
|
feeItemCode: string
|
|
feeItemName: string
|
|
amount: WireScalar
|
|
currency: string
|
|
}
|
|
|
|
export interface FinanceBillDetailWire {
|
|
statement: FinanceBillWire
|
|
lines: FinanceBillLineWire[]
|
|
}
|
|
|
|
export interface FinanceReceiptAllocationWire {
|
|
billId: WireScalar
|
|
billNo: string
|
|
appliedAmount: WireScalar
|
|
}
|
|
|
|
export interface FinanceReceiptWire {
|
|
id: WireScalar
|
|
receiptNo: string
|
|
receivedAmount: WireScalar
|
|
receivedAt: string
|
|
paymentMethod: string | null
|
|
currency: string
|
|
status: string
|
|
allocations: FinanceReceiptAllocationWire[]
|
|
}
|
|
|
|
function text(value: WireScalar) {
|
|
return String(value)
|
|
}
|
|
|
|
function mapBillSummary(bill: FinanceBillWire): FinanceBillSummary {
|
|
return {
|
|
id: text(bill.id),
|
|
billNo: bill.billNo,
|
|
customerName: bill.customerName,
|
|
currencyCode: 'CNY',
|
|
totalAmount: text(bill.totalAmount),
|
|
receivedAmount: text(bill.receivedAmount),
|
|
outstandingAmount: text(bill.outstandingAmount),
|
|
issueDate: bill.billDate,
|
|
dueDate: bill.dueDate,
|
|
status: bill.status,
|
|
version: bill.version ?? 0,
|
|
customerConfirmationStatus: bill.customerConfirmationStatus ?? 'NOT_REQUIRED',
|
|
customerConfirmedByName: bill.customerConfirmedByName ?? null,
|
|
customerConfirmedAt: bill.customerConfirmedAt ?? null,
|
|
}
|
|
}
|
|
|
|
function mapBillLine(line: FinanceBillLineWire): FinanceBillLine {
|
|
return {
|
|
id: text(line.id),
|
|
orderId: line.orderId == null ? null : text(line.orderId),
|
|
inboundCode: line.inboundCode?.trim() || null,
|
|
feeNo: `${line.sourceType}:${text(line.sourceId)}/v${line.sourceVersion}`,
|
|
feeName: line.feeItemName,
|
|
amount: text(line.amount),
|
|
currencyCode: 'CNY',
|
|
}
|
|
}
|
|
|
|
export function mapFinanceBills(bills: FinanceBillWire[]) {
|
|
return bills
|
|
.filter((bill) => bill.status === 'ISSUED' || bill.status === 'PARTIALLY_PAID' || bill.status === 'PAID')
|
|
.map(mapBillSummary)
|
|
}
|
|
|
|
export function mapFinanceBillDetail(detail: FinanceBillDetailWire): FinanceBillDetail {
|
|
return {
|
|
...mapBillSummary(detail.statement),
|
|
lines: (detail.lines ?? []).map(mapBillLine),
|
|
receipts: [],
|
|
}
|
|
}
|
|
|
|
export function mapFinanceReceipts(receipts: FinanceReceiptWire[], billId: string): FinanceReceiptRecord[] {
|
|
return receipts.flatMap((receipt) => receipt.allocations
|
|
.filter((allocation) => text(allocation.billId) === billId)
|
|
.map((allocation) => ({
|
|
id: text(receipt.id),
|
|
receiptNo: receipt.receiptNo,
|
|
receivedAt: receipt.receivedAt,
|
|
amount: text(receipt.receivedAmount),
|
|
currencyCode: 'CNY' as const,
|
|
method: receipt.paymentMethod,
|
|
allocatedAmount: text(allocation.appliedAmount),
|
|
})))
|
|
}
|
|
|
|
export function sumDecimalStrings(values: string[]) {
|
|
let scale = 0
|
|
const parsed = values.map((value) => {
|
|
const normalized = value.trim() || '0'
|
|
const [integer = '0', fraction = ''] = normalized.split('.')
|
|
scale = Math.max(scale, fraction.length)
|
|
return { integer, fraction }
|
|
})
|
|
const total = parsed.reduce((sum, value) => {
|
|
const sign = value.integer.startsWith('-') ? -1n : 1n
|
|
const integer = value.integer.replace(/^[+-]/, '') || '0'
|
|
const digits = `${integer}${value.fraction.padEnd(scale, '0')}`
|
|
return sum + sign * BigInt(digits || '0')
|
|
}, 0n)
|
|
if (scale === 0) return total.toString()
|
|
const negative = total < 0n
|
|
const absolute = (negative ? -total : total).toString().padStart(scale + 1, '0')
|
|
const result = `${absolute.slice(0, -scale)}.${absolute.slice(-scale)}`
|
|
return negative ? `-${result}` : result
|
|
}
|