@accounter/server
Version:
Accounter GraphQL server
226 lines • 9.3 kB
JavaScript
import { startOfDay } from 'date-fns';
import { EMPTY_UUID } from '../../../shared/constants.js';
import { formatCurrency } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { LedgerProvider } from '../providers/ledger.provider.js';
const comparisonKeys = [
'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 = ['value_date', 'invoice_date'];
export function ledgerRecordsGenerationFullMatchComparison(storageRecords, newRecords) {
// compare existing records with new records
const unmatchedStorageRecords = [...storageRecords];
const unmatchedNewRecords = [];
const fullMatches = new Map();
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, newRecords) {
const matches = new Map();
const unmatchedNewRecords = [...newRecords];
storageRecords.map(storageRecord => {
let maxScore = 0;
let bestMatch = 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 = {
...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, newRecord) {
return comparisonKeys.reduce((isMatch, key) => {
if (!isMatch) {
return false;
}
if (dateKeys.includes(key)) {
const newDate = newRecord[key] ? startOfDay(new Date(newRecord[key])) : undefined;
return storageRecord[key]?.getTime() === newDate?.getTime();
}
if (typeof storageRecord[key] === 'string' && !Number.isNaN(Number(storageRecord[key]))) {
return Math.abs(Number(storageRecord[key]) - Number(newRecord[key])) < 0.005;
}
return storageRecord[key] === newRecord[key];
}, true);
}
export function convertToStorageInputRecord(record, ownerId) {
return {
chargeId: record.chargeId,
ownerId: record.ownerId ?? ownerId,
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,
reference: record.reference,
valueDate: record.valueDate,
};
}
function getMatchScore(storageRecord, newRecord) {
return comparisonKeys.reduce((cumulativeScore, key) => {
const factor = storageRecord[key] ? 1 : 0.5;
let scoreDirection;
if (dateKeys.includes(key)) {
scoreDirection =
storageRecord[key]?.getTime() === newRecord[key]?.getTime() ? 1 : -1;
}
else {
scoreDirection = storageRecord[key] === newRecord[key] ? 1 : -1;
}
const addedScore = factor * scoreDirection;
return cumulativeScore + addedScore;
}, 0);
}
export function convertLedgerRecordToProto(record, ownerId) {
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,
reference: record.reference1 ?? undefined,
valueDate: record.value_date,
currency: formatCurrency(record.currency),
isCreditorCounterparty: false, // redundant value
ownerId: record.owner_id ?? ownerId,
currencyRate: undefined,
chargeId: record.charge_id,
};
}
export function convertLedgerRecordToInput(record, ownerId) {
return {
chargeId: record.charge_id ?? undefined,
ownerId: record.owner_id ?? ownerId,
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,
reference: record.reference1 ?? undefined,
valueDate: record.value_date,
currency: formatCurrency(record.currency),
ledgerId: record.id,
};
}
export async function storeInitialGeneratedRecords(chargeId, records, injector) {
const ledgerRecords = await injector
.get(LedgerProvider)
.getLedgerRecordsByChargesIdLoader.load(chargeId);
if (ledgerRecords.length === 0) {
const { ownerId } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const ledgerRecords = records.map(record => convertToStorageInputRecord(record, ownerId));
if (ledgerRecords.length) {
return injector.get(LedgerProvider).insertLedgerRecords({ ledgerRecords });
}
}
return void 0;
}
//# sourceMappingURL=ledgrer-storage.helper.js.map