@accounter/server
Version:
Accounter GraphQL server
276 lines • 13.1 kB
JavaScript
import { DECREASED_VAT_RATIO } from '../../../shared/constants.js';
import { dateToTimelessDateString, formatCurrency } from '../../../shared/helpers/index.js';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { validateDocumentAllocation, validateDocumentVat, } from '../../documents/helpers/validate-document.helper.js';
import { ExchangeProvider } from '../../exchange-rates/providers/exchange.provider.js';
import { FinancialAccountsProvider } from '../../financial-accounts/providers/financial-accounts.provider.js';
import { VatProvider } from '../../vat/providers/vat.provider.js';
import { getFinancialAccountTaxCategoryId, LedgerError, validateTransactionBasicVariables, } from './utils.helper.js';
export async function ledgerEntryFromDocument(injector, document, charge, ownerId, taxCategoryId) {
if (!document.date) {
throw new LedgerError(`Document serial "${document.serial_number}" is missing the date`);
}
if (!document.debtor_id) {
throw new LedgerError(`Document serial "${document.serial_number}" is missing the debtor`);
}
if (!document.creditor_id) {
throw new LedgerError(`Document serial "${document.serial_number}" is missing the creditor`);
}
if (!document.total_amount) {
throw new LedgerError(`Document serial "${document.serial_number}" is missing amount`);
}
if (!document.currency_code) {
throw new LedgerError(`Document serial "${document.serial_number}" is missing currency code`);
}
const { defaultLocalCurrency, authorities: { inputVatTaxCategoryId, outputVatTaxCategoryId, propertyOutputVatTaxCategoryId }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
let totalAmount = Math.abs(document.total_amount);
const isCreditorCounterparty = document.debtor_id === ownerId;
const counterpartyId = isCreditorCounterparty ? document.creditor_id : document.debtor_id;
const debitAccountID1 = isCreditorCounterparty ? taxCategoryId : counterpartyId;
const creditAccountID1 = isCreditorCounterparty ? counterpartyId : taxCategoryId;
const currency = formatCurrency(document.currency_code);
let foreignTotalAmount = null;
let amountWithoutVat = totalAmount;
let foreignAmountWithoutVat = null;
let vatAmount = document.vat_amount == null ? null : Math.abs(document.vat_amount);
let foreignVatAmount = null;
let vatTaxCategory = null;
if (vatAmount != null && charge.is_property) {
vatAmount = vatAmount * DECREASED_VAT_RATIO; // Adjust VAT for property charges
}
const isCreditInvoice = document.type === 'CREDIT_INVOICE';
if (vatAmount) {
amountWithoutVat = amountWithoutVat - vatAmount;
const adjustedOutputVatTaxCategoryId = charge.is_property
? propertyOutputVatTaxCategoryId
: outputVatTaxCategoryId;
vatTaxCategory =
isCreditorCounterparty === isCreditInvoice
? inputVatTaxCategoryId
: adjustedOutputVatTaxCategoryId;
const vatValue = await injector
.get(VatProvider)
.getVatValueByDateLoader.load(dateToTimelessDateString(document.date));
if (!vatValue) {
throw new LedgerError(`VAT value is missing for document ID=${document.id}`);
}
validateDocumentVat(document, vatValue, message => {
throw new LedgerError(message);
});
await validateDocumentAllocation(document, injector).then(valid => {
if (!valid) {
throw new LedgerError(`Allocation number missing for document [${document.serial_number}]`);
}
});
}
// handle non-local currencies
if (document.currency_code !== defaultLocalCurrency) {
let exchangeRate;
if (document.exchange_rate_override) {
exchangeRate = Number(document.exchange_rate_override);
}
else {
exchangeRate = await injector
.get(ExchangeProvider)
.getExchangeRates(currency, defaultLocalCurrency, document.date);
}
// Set foreign amounts
foreignTotalAmount = totalAmount;
foreignAmountWithoutVat = amountWithoutVat;
// calculate amounts in ILS
totalAmount = exchangeRate * totalAmount;
amountWithoutVat = exchangeRate * amountWithoutVat;
if (vatAmount && vatAmount > 0) {
foreignVatAmount = vatAmount;
vatAmount = exchangeRate * vatAmount;
}
}
let creditAccountID2 = null;
let debitAccountID2 = null;
let creditAmount1;
let localCurrencyCreditAmount1;
let debitAmount1;
let localCurrencyDebitAmount1;
let creditAmount2 = null;
let localCurrencyCreditAmount2 = null;
let debitAmount2 = null;
let localCurrencyDebitAmount2 = null;
if (isCreditorCounterparty) {
localCurrencyCreditAmount1 = totalAmount;
creditAmount1 = foreignTotalAmount;
localCurrencyDebitAmount1 = amountWithoutVat;
debitAmount1 = foreignAmountWithoutVat;
if (vatAmount && vatAmount > 0) {
// add vat to debtor2
debitAmount2 = foreignVatAmount;
localCurrencyDebitAmount2 = vatAmount;
debitAccountID2 = vatTaxCategory;
}
}
else {
localCurrencyDebitAmount1 = totalAmount;
debitAmount1 = foreignTotalAmount;
localCurrencyCreditAmount1 = amountWithoutVat;
creditAmount1 = foreignAmountWithoutVat;
if (vatAmount && vatAmount > 0) {
// add vat to creditor2
creditAmount2 = foreignVatAmount;
localCurrencyCreditAmount2 = vatAmount;
creditAccountID2 = vatTaxCategory;
}
}
const ledgerEntry = {
id: document.id,
invoiceDate: document.date,
valueDate: document.date,
currency,
creditAccountID1,
creditAmount1: creditAmount1 ?? undefined,
localCurrencyCreditAmount1,
debitAccountID1,
debitAmount1: debitAmount1 ?? undefined,
localCurrencyDebitAmount1,
creditAccountID2: creditAccountID2 ?? undefined,
creditAmount2: creditAmount2 ?? undefined,
localCurrencyCreditAmount2: localCurrencyCreditAmount2 ?? undefined,
debitAccountID2: debitAccountID2 ?? undefined,
debitAmount2: debitAmount2 ?? undefined,
localCurrencyDebitAmount2: localCurrencyDebitAmount2 ?? undefined,
description: undefined,
reference: document.serial_number ?? undefined,
isCreditorCounterparty,
isCreditInvoice,
ownerId,
chargeId: charge.id,
};
return ledgerEntry;
}
function getCreditCardAccountByTransactionReferenceAndDescription(sourceReference, sourceDescription) {
if (sourceReference === '8547') {
return '6fed9073-f571-402e-9da0-ac0669f658d2'; // 9200
}
if (sourceReference === '13795') {
if (sourceDescription?.includes('2260')) {
return '0e86514c-9ddb-4c82-b731-6107f7293b1f'; //2260
}
if (sourceDescription?.includes('4333')) {
return '782070ce-ec7e-4be0-ab69-46c89528bf04'; //4333
}
}
if (sourceReference === '22200' && sourceDescription?.includes('מסטרקארד')) {
return '782070ce-ec7e-4be0-ab69-46c89528bf04'; //4333
}
if (sourceReference === '11200' && sourceDescription?.includes('כ.א.ל')) {
return '6fed9073-f571-402e-9da0-ac0669f658d2'; // 9200
}
return null;
}
export async function ledgerEntryFromMainTransaction(transaction, injector, chargeId, ownerId, businessId, gotRelevantDocuments = false) {
const { currency, valueDate, transactionBusinessId } = validateTransactionBasicVariables(transaction);
let mainAccountId = transactionBusinessId;
const { defaultLocalCurrency, financialAccounts: { internalWalletsIds }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
if (!gotRelevantDocuments &&
transaction.source_reference &&
businessId &&
internalWalletsIds.includes(businessId)) {
const account = await injector
.get(FinancialAccountsProvider)
.getFinancialAccountByAccountNumberLoader.load(transaction.source_reference);
let accountId = account?.id ?? null;
accountId ??= getCreditCardAccountByTransactionReferenceAndDescription(transaction.source_reference, transaction.source_description);
if (!accountId) {
throw new LedgerError(`Transaction reference "${transaction.source_reference}" is missing account`);
}
mainAccountId = await getFinancialAccountTaxCategoryId(injector, {
...transaction,
account_id: accountId,
});
}
let amount = Number(transaction.amount);
let foreignAmount = undefined;
if (currency !== defaultLocalCurrency) {
// get exchange rate for currency
const exchangeRate = await injector
.get(ExchangeProvider)
.getExchangeRates(currency, defaultLocalCurrency, valueDate);
foreignAmount = amount;
// calculate amounts in ILS
amount = exchangeRate * amount;
}
const accountTaxCategoryId = await getFinancialAccountTaxCategoryId(injector, transaction);
const isCreditorCounterparty = amount > 0;
const ledgerEntry = {
id: transaction.id,
invoiceDate: transaction.event_date,
valueDate,
currency,
creditAccountID1: isCreditorCounterparty ? mainAccountId : accountTaxCategoryId,
creditAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
localCurrencyCreditAmount1: Math.abs(amount),
debitAccountID1: isCreditorCounterparty ? accountTaxCategoryId : mainAccountId,
debitAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
localCurrencyDebitAmount1: Math.abs(amount),
description: transaction.source_description ?? undefined,
reference: transaction.origin_key,
isCreditorCounterparty,
ownerId,
currencyRate: transaction.currency_rate ? Number(transaction.currency_rate) : undefined,
chargeId,
};
return ledgerEntry;
}
export async function ledgerEntryFromBalanceCancellation(balanceCancellation, ledgerBalance, financialAccountLedgerEntries, chargeId, ownerId, injector) {
const { defaultLocalCurrency, general: { taxCategories: { balanceCancellationTaxCategoryId }, }, } = await injector.get(AdminContextProvider).getVerifiedAdminContext();
const entityBalance = ledgerBalance.get(balanceCancellation.business_id);
if (!entityBalance) {
throw new LedgerError(`Balance cancellation for business ${balanceCancellation.business_id} redundant - already balanced`);
}
const { amount, entityId } = entityBalance;
const financialAccountEntry = financialAccountLedgerEntries.find(entry => [
entry.creditAccountID1,
entry.creditAccountID2,
entry.debitAccountID1,
entry.debitAccountID2,
].includes(balanceCancellation.business_id));
if (!financialAccountEntry) {
throw new LedgerError(`Balance cancellation for business ${balanceCancellation.business_id} failed - no financial account entry found`);
}
let foreignAmount = undefined;
if (financialAccountEntry.currency !== defaultLocalCurrency &&
financialAccountEntry.currencyRate) {
foreignAmount = financialAccountEntry.currencyRate * amount;
}
const isCreditorCounterparty = amount > 0;
const ledgerEntry = {
id: balanceCancellation.charge_id,
invoiceDate: financialAccountEntry.invoiceDate,
valueDate: financialAccountEntry.valueDate,
currency: financialAccountEntry.currency,
creditAccountID1: isCreditorCounterparty ? balanceCancellationTaxCategoryId : entityId,
creditAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
localCurrencyCreditAmount1: Math.abs(amount),
debitAccountID1: isCreditorCounterparty ? entityId : balanceCancellationTaxCategoryId,
debitAmount1: foreignAmount ? Math.abs(foreignAmount) : undefined,
localCurrencyDebitAmount1: Math.abs(amount),
description: balanceCancellation.description ?? undefined,
reference: financialAccountEntry.reference,
isCreditorCounterparty,
ownerId,
currencyRate: financialAccountEntry.currencyRate,
chargeId,
};
return ledgerEntry;
}
export function isRefundCharge(description) {
if (!description) {
return false;
}
const normalizedDescription = description.toLocaleLowerCase();
return (normalizedDescription.includes('refund') || normalizedDescription.includes('reimbursement'));
}
export function getExchangeDates(financialAccountLedgerEntries) {
const timeValue = Math.max(...financialAccountLedgerEntries.map(entry => entry.valueDate.getTime()));
const date = new Date(timeValue);
return date;
}
//# sourceMappingURL=common-charge-ledger.helper.js.map