@accounter/server
Version:
Accounter GraphQL server
279 lines (242 loc) • 9.03 kB
text/typescript
import { Injector } from 'graphql-modules';
import type { FinancialAmount } from '../../../__generated__/types.js';
import { Currency, DocumentType } from '../../../shared/enums.js';
import { formatFinancialAmount } from '../../../shared/helpers/index.js';
import { isInvoice, isReceipt } from '../../documents/helpers/common.helper.js';
import { basicDocumentValidation } from '../../documents/helpers/validate-document.helper.js';
import { DocumentsProvider } from '../../documents/providers/documents.provider.js';
import { TaxCategoriesProvider } from '../../financial-entities/providers/tax-categories.provider.js';
import { getLedgerMeta } from '../../ledger/helpers/common.helper.js';
import { LedgerProvider } from '../../ledger/providers/ledger.provider.js';
import { MiscExpensesProvider } from '../../misc-expenses/providers/misc-expenses.provider.js';
import { getTransactionsMeta } from '../../transactions/helpers/common.helper.js';
import { TransactionsProvider } from '../../transactions/providers/transactions.provider.js';
import { ChargesProvider } from '../providers/charges.provider.js';
export async function calculateTotalAmount(
chargeId: string,
injector: Injector,
defaultLocalCurrency: Currency,
): Promise<FinancialAmount | null> {
try {
const [
charge,
{ transactionsAmount, transactionsCurrency },
{ documentsCurrency, documentsAmount },
] = await Promise.all([
injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId),
getChargeTransactionsMeta(chargeId, injector),
getChargeDocumentsMeta(chargeId, injector),
]);
if (!charge) {
throw new Error(`Charge ID="${chargeId}" not found`);
}
if (charge.type === 'PAYROLL' && transactionsAmount) {
return formatFinancialAmount(transactionsAmount, defaultLocalCurrency);
}
if (documentsAmount && documentsCurrency) {
return formatFinancialAmount(documentsAmount, documentsCurrency);
}
if (transactionsAmount && transactionsCurrency) {
return formatFinancialAmount(transactionsAmount, transactionsCurrency);
}
return null;
} catch (error) {
console.error('Error calculating total amount for charge:', error);
throw new Error('Failed to calculate total amount for charge', { cause: error });
}
}
export async function getChargeBusinesses(chargeId: string, injector: Injector) {
const [charge, transactions, documents, ledgerRecords, miscExpenses] = await Promise.all([
injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId),
injector.get(TransactionsProvider).transactionsByChargeIDLoader.load(chargeId),
injector.get(DocumentsProvider).getDocumentsByChargeIdLoader.load(chargeId),
injector.get(LedgerProvider).getLedgerRecordsByChargesIdLoader.load(chargeId),
injector.get(MiscExpensesProvider).getExpensesByChargeIdLoader.load(chargeId),
]);
if (!charge) {
throw new Error(`Charge ID="${chargeId}" not found`);
}
const allBusinessIdsSet = new Set<string>();
const mainBusinessIdsSet = new Set<string>();
transactions.map(t => {
if (t.business_id) {
allBusinessIdsSet.add(t.business_id);
if (!t.is_fee) {
mainBusinessIdsSet.add(t.business_id);
}
}
});
documents.map(d => {
[d.creditor_id, d.debtor_id].map(id => {
if (id && id !== charge.owner_id) {
allBusinessIdsSet.add(id);
mainBusinessIdsSet.add(id);
}
});
});
ledgerRecords.map(lr => {
[lr.credit_entity1, lr.credit_entity2, lr.debit_entity1, lr.debit_entity2].map(id => {
if (id && id !== charge.owner_id) {
allBusinessIdsSet.add(id);
}
});
});
miscExpenses.map(me => {
[me.creditor_id, me.debtor_id].map(id => {
if (id && id !== charge.owner_id) {
allBusinessIdsSet.add(id);
}
});
});
const allBusinessIds = Array.from(allBusinessIdsSet);
const mainBusinessIds = Array.from(mainBusinessIdsSet);
let mainBusinessId: string | null = null;
if (mainBusinessIds.length === 1) {
mainBusinessId = mainBusinessIds[0];
}
return {
allBusinessIds,
mainBusinessId,
};
}
export async function getChargeDocumentsMeta(chargeId: string, injector: Injector) {
const [charge, documents] = await Promise.all([
injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId),
injector.get(DocumentsProvider).getDocumentsByChargeIdLoader.load(chargeId),
]);
if (!charge) {
throw new Error(`Charge ID="${chargeId}" not found`);
}
let invalidDocuments = false;
let receiptAmount = 0;
let receiptVatAmount: number | null = null;
let receiptCount = 0;
let invoiceAmount = 0;
let invoiceVatAmount: number | null = null;
let invoiceCount = 0;
let proformaAmount: number | null = null;
const currenciesSet = new Set<Currency>();
const proformaCurrencySet = new Set<Currency>();
let documentsMinAccountancyDate: Date | null = null;
let documentsMinAnyDate: Date | null = null;
let documentsMaxAccountancyDate: Date | null = null;
let documentsMaxDate: Date | null = null;
documents.map(d => {
const amount = d.total_amount ?? 0;
let factor = 1;
if (d.debtor_id === charge.owner_id) {
factor *= -1;
}
if (d.date) {
documentsMinAnyDate ??= d.date;
if (documentsMinAnyDate > d.date) {
documentsMinAnyDate = d.date;
}
documentsMaxDate ??= d.date;
if (documentsMaxDate < d.date) {
documentsMaxDate = d.date;
}
}
if (isInvoice(d.type)) {
invoiceCount++;
invoiceAmount += amount * factor;
if (d.vat_amount != null) {
invoiceVatAmount ??= 0;
invoiceVatAmount += (d.vat_amount ?? 0) * factor;
}
if (d.date) {
documentsMinAccountancyDate ??= d.date;
if (documentsMinAccountancyDate > d.date) {
documentsMinAccountancyDate = d.date;
}
documentsMaxAccountancyDate ??= d.date;
if (documentsMaxAccountancyDate < d.date) {
documentsMaxAccountancyDate = d.date;
}
}
if (d.currency_code) {
currenciesSet.add(d.currency_code as Currency);
}
}
if (isReceipt(d.type)) {
receiptCount++;
receiptAmount += amount * factor;
if (d.vat_amount != null) {
receiptVatAmount ??= 0;
receiptVatAmount += (d.vat_amount ?? 0) * factor;
}
if (d.date) {
documentsMinAccountancyDate ??= d.date;
if (documentsMinAccountancyDate > d.date) {
documentsMinAccountancyDate = d.date;
}
documentsMaxAccountancyDate ??= d.date;
if (documentsMaxAccountancyDate < d.date) {
documentsMaxAccountancyDate = d.date;
}
}
if (d.currency_code) {
currenciesSet.add(d.currency_code as Currency);
}
}
if (d.type === DocumentType.Proforma) {
proformaAmount ??= 0;
proformaAmount += amount * factor;
if (d.currency_code) {
proformaCurrencySet.add(d.currency_code as Currency);
}
}
if (!basicDocumentValidation(d)) {
invalidDocuments = true;
}
});
const currencies =
currenciesSet.size > 0 ? Array.from(currenciesSet) : Array.from(proformaCurrencySet);
const documentsAmount =
invoiceCount > 0 ? invoiceAmount : receiptCount > 0 ? receiptAmount : proformaAmount;
return {
receiptAmount,
receiptCount,
invoiceAmount,
invoiceCount,
documentsAmount,
documentsVatAmount: invoiceVatAmount == null ? receiptVatAmount : invoiceVatAmount,
documentsCount: documents.length,
documentsCurrency: currencies.length === 1 ? currencies[0] : null,
invalidDocuments,
documentsMinDate: documentsMinAccountancyDate ?? (documentsMinAnyDate as Date | null),
documentsMaxDate: documentsMaxAccountancyDate ?? (documentsMaxDate as Date | null),
};
}
export async function getChargeTransactionsMeta(chargeId: string, injector: Injector) {
const transactions = await injector
.get(TransactionsProvider)
.transactionsByChargeIDLoader.load(chargeId);
return getTransactionsMeta(transactions);
}
export async function getChargeLedgerMeta(chargeId: string, injector: Injector) {
const ledgerRecords = await injector
.get(LedgerProvider)
.getLedgerRecordsByChargesIdLoader.load(chargeId);
return getLedgerMeta(ledgerRecords);
}
export async function getChargeTaxCategoryId(
chargeId: string,
injector: Injector,
): Promise<string | null> {
const charge = await injector.get(ChargesProvider).getChargeByIdLoader.load(chargeId);
if (!charge) {
throw new Error(`Charge ID="${chargeId}" not found`);
}
if (charge.tax_category_id) {
return charge.tax_category_id;
}
const { mainBusinessId } = await getChargeBusinesses(chargeId, injector);
if (!mainBusinessId) {
return null;
}
const taxCategory = await injector
.get(TaxCategoriesProvider)
.taxCategoryByBusinessIDsLoader.load(mainBusinessId);
return taxCategory?.id ?? null;
}