UNPKG

@accounter/server

Version:

271 lines (248 loc) • 9.52 kB
import { startOfDay } from 'date-fns'; import { Injector } from 'graphql-modules'; import { IGetChargesByIdsResult } from '@modules/charges/types.js'; import { DEFAULT_FINANCIAL_ENTITY_ID, EMPTY_UUID } from '@shared/constants'; import { formatCurrency } from '@shared/helpers'; import type { LedgerProto } from '@shared/types'; import { LedgerProvider } from '../providers/ledger.provider.js'; import type { IGetLedgerRecordsByChargesIdsResult, IInsertLedgerRecordsParams, IUpdateLedgerRecordParams, } from '../types.js'; type LedgerRecordInput = IInsertLedgerRecordsParams['ledgerRecords'][number]; const comparisonKeys: Array<keyof IGetLedgerRecordsByChargesIdsResult> = [ 'credit_entity1', 'credit_entity2', 'credit_foreign_amount1', 'credit_foreign_amount2', 'credit_local_amount1', 'credit_local_amount2', 'currency', 'debit_entity1', 'debit_entity2', 'debit_foreign_amount1', 'debit_foreign_amount2', 'debit_local_amount1', 'debit_local_amount2', 'description', 'invoice_date', 'reference1', 'value_date', ]; const dateKeys: Array<keyof IGetLedgerRecordsByChargesIdsResult> = ['value_date', 'invoice_date']; export function ledgerRecordsGenerationFullMatchComparison( storageRecords: IGetLedgerRecordsByChargesIdsResult[], newRecords: readonly IGetLedgerRecordsByChargesIdsResult[], ) { // compare existing records with new records const unmatchedStorageRecords = [...storageRecords]; const unmatchedNewRecords: IGetLedgerRecordsByChargesIdsResult[] = []; const fullMatches = new Map<number, string | undefined>(); newRecords.map((newRecord, index) => { const matchIndex = unmatchedStorageRecords.findIndex(storageRecord => { return isExactMatch(storageRecord, newRecord); }); if (matchIndex !== -1) { fullMatches.set(index, unmatchedStorageRecords[matchIndex].id); unmatchedStorageRecords.splice(matchIndex, 1); return; } unmatchedNewRecords.push(newRecord); }); return { unmatchedStorageRecords, unmatchedNewRecords, fullMatches, isFullyMatched: storageRecords.length === newRecords.length && newRecords.length === fullMatches.size, }; } export function ledgerRecordsGenerationPartialMatchComparison( storageRecords: IGetLedgerRecordsByChargesIdsResult[], newRecords: IGetLedgerRecordsByChargesIdsResult[], ) { const matches = new Map<string, IGetLedgerRecordsByChargesIdsResult>(); const unmatchedNewRecords = [...newRecords]; storageRecords.map(storageRecord => { let maxScore = 0; let bestMatch: IGetLedgerRecordsByChargesIdsResult | undefined = undefined; unmatchedNewRecords.map(newRecord => { const score = getMatchScore(storageRecord, newRecord); if (score < 0) { return; } if (score > maxScore) { maxScore = score; bestMatch = newRecord; } }); if (bestMatch) { matches.set(storageRecord.id, bestMatch); unmatchedNewRecords.splice(unmatchedNewRecords.indexOf(bestMatch), 1); } }); const diffs = Array.from(matches.entries()).map(([storageId, newRecord]) => { const storageRecord = storageRecords.find(record => record.id === storageId); if (!storageRecord) { throw new Error('Storage record not found'); } const recordDiffs: IGetLedgerRecordsByChargesIdsResult = { ...newRecord, id: storageRecord.id, }; return recordDiffs; }); return { toUpdate: [ ...diffs, ...unmatchedNewRecords.map(record => ({ ...record, id: EMPTY_UUID, })), ], toRemove: storageRecords.filter(record => !diffs.find(diff => diff.id === record.id)), }; } function isExactMatch( storageRecord: IGetLedgerRecordsByChargesIdsResult, newRecord: IGetLedgerRecordsByChargesIdsResult, ): boolean { return comparisonKeys.reduce((isMatch, key) => { if (!isMatch) { return false; } if (dateKeys.includes(key)) { const newDate = newRecord[key] ? startOfDay(new Date(storageRecord[key] as Date)) : undefined; return (storageRecord[key] as Date)?.getTime() === newDate?.getTime(); } if (typeof storageRecord[key] === 'string' && !Number.isNaN(Number(storageRecord[key]))) { return Number(storageRecord[key]).toFixed(2) === Number(newRecord[key]).toFixed(2); } return storageRecord[key] === newRecord[key]; }, true as boolean); } export function convertToStorageInputRecord(record: LedgerProto): LedgerRecordInput { return { chargeId: record.chargeId, ownerId: record.ownerId ?? DEFAULT_FINANCIAL_ENTITY_ID, creditEntity1: record.creditAccountID1, creditEntity2: record.creditAccountID2, creditForeignAmount1: record.creditAmount1, creditForeignAmount2: record.creditAmount2, creditLocalAmount1: record.localCurrencyCreditAmount1, creditLocalAmount2: record.localCurrencyCreditAmount2, currency: record.currency, debitEntity1: record.debitAccountID1, debitEntity2: record.debitAccountID2, debitForeignAmount1: record.debitAmount1, debitForeignAmount2: record.debitAmount2, debitLocalAmount1: record.localCurrencyDebitAmount1, debitLocalAmount2: record.localCurrencyDebitAmount2, description: record.description, invoiceDate: record.invoiceDate, reference1: record.reference1, valueDate: record.valueDate, }; } function getMatchScore( storageRecord: IGetLedgerRecordsByChargesIdsResult, newRecord: IGetLedgerRecordsByChargesIdsResult, ): number { return comparisonKeys.reduce((cumulativeScore, key) => { const factor = storageRecord[key] ? 1 : 0.5; let scoreDirection: number; if (dateKeys.includes(key)) { scoreDirection = (storageRecord[key] as Date)?.getTime() === (newRecord[key] as Date)?.getTime() ? 1 : -1; } else { scoreDirection = storageRecord[key] === newRecord[key] ? 1 : -1; } const addedScore = factor * scoreDirection; return cumulativeScore + addedScore; }, 0); } export function convertLedgerRecordToProto( record: IGetLedgerRecordsByChargesIdsResult, ): LedgerProto { return { id: record.id, creditAccountID1: record.credit_entity1 ?? undefined, creditAccountID2: record.credit_entity2 ?? undefined, debitAccountID1: record.debit_entity1 ?? undefined, debitAccountID2: record.debit_entity2 ?? undefined, creditAmount1: record.credit_foreign_amount1 ? Number(record.credit_foreign_amount1) : undefined, creditAmount2: record.credit_foreign_amount2 ? Number(record.credit_foreign_amount2) : undefined, debitAmount1: record.debit_foreign_amount1 ? Number(record.debit_foreign_amount1) : undefined, debitAmount2: record.debit_foreign_amount2 ? Number(record.debit_foreign_amount2) : undefined, localCurrencyCreditAmount1: Number(record.credit_local_amount1), localCurrencyCreditAmount2: record.credit_local_amount2 ? Number(record.credit_local_amount2) : undefined, localCurrencyDebitAmount1: Number(record.debit_local_amount1), localCurrencyDebitAmount2: record.debit_local_amount2 ? Number(record.debit_local_amount2) : undefined, description: record.description ?? undefined, invoiceDate: record.invoice_date, reference1: record.reference1 ?? undefined, valueDate: record.value_date, currency: formatCurrency(record.currency), isCreditorCounterparty: false, // redundant value ownerId: record.owner_id ?? DEFAULT_FINANCIAL_ENTITY_ID, currencyRate: undefined, chargeId: record.charge_id, }; } export function convertLedgerRecordToInput( record: IGetLedgerRecordsByChargesIdsResult, ): LedgerRecordInput | IUpdateLedgerRecordParams { return { chargeId: record.charge_id ?? undefined, ownerId: record.owner_id ?? DEFAULT_FINANCIAL_ENTITY_ID, creditEntity1: record.credit_entity1 ?? undefined, creditEntity2: record.credit_entity2 ?? undefined, debitEntity1: record.debit_entity1 ?? undefined, debitEntity2: record.debit_entity2 ?? undefined, creditForeignAmount1: record.credit_foreign_amount1 ? Number(record.credit_foreign_amount1) : undefined, creditForeignAmount2: record.credit_foreign_amount2 ? Number(record.credit_foreign_amount2) : undefined, debitForeignAmount1: record.debit_foreign_amount1 ? Number(record.debit_foreign_amount1) : undefined, debitForeignAmount2: record.debit_foreign_amount2 ? Number(record.debit_foreign_amount2) : undefined, creditLocalAmount1: Number(record.credit_local_amount1), creditLocalAmount2: record.credit_local_amount2 ? Number(record.credit_local_amount2) : undefined, debitLocalAmount1: Number(record.debit_local_amount1), debitLocalAmount2: record.debit_local_amount2 ? Number(record.debit_local_amount2) : undefined, description: record.description ?? undefined, invoiceDate: record.invoice_date, reference1: record.reference1 ?? undefined, valueDate: record.value_date, currency: formatCurrency(record.currency), ledgerId: record.id, }; } export async function storeInitialGeneratedRecords( charge: IGetChargesByIdsResult, records: LedgerProto[], injector: Injector, ) { if (!charge.ledger_count || Number(charge.ledger_count) === 0) { const ledgerRecords: IInsertLedgerRecordsParams['ledgerRecords'] = records.map( convertToStorageInputRecord, ); await injector.get(LedgerProvider).insertLedgerRecords({ ledgerRecords }); } }