UNPKG

@accounter/server

Version:

394 lines (363 loc) • 14 kB
import { format } from 'date-fns'; import { GraphQLError } from 'graphql'; import { ChargesProvider } from '@modules/charges/providers/charges.provider.js'; import { LedgerProvider } from '@modules/ledger/providers/ledger.provider.js'; import { Currency } from '@shared/enums'; import type { Resolvers } from '@shared/gql-types'; import { formatFinancialAmount } from '@shared/helpers'; import type { BusinessTransactionProto, RawBusinessTransactionsSum, TimelessDateString, } from '@shared/types'; import { handleBusinessLedgerRecord, handleBusinessTransaction, } from '../helpers/business-transactions.helper.js'; import { FinancialEntitiesProvider } from '../providers/financial-entities.provider.js'; import type { FinancialEntitiesModule, IGetBusinessesByIdsResult, IGetFinancialEntitiesByIdsResult, } from '../types.js'; export const businessTransactionsResolvers: FinancialEntitiesModule.Resolvers & Pick< Resolvers, 'BusinessTransactionsSumFromLedgerRecordsResult' | 'BusinessTransactionsFromLedgerRecordsResult' > = { Query: { businessTransactionsSumFromLedgerRecords: async (_, { filters }, context, _info) => { const injector = context.injector; const { ownerIds, businessIDs, fromDate, toDate } = filters || {}; const financialEntities = await injector .get(FinancialEntitiesProvider) .getFinancialEntityByIdLoader.loadMany(businessIDs ?? []); const isFilteredByFinancialEntities = !!businessIDs?.length; const financialEntitiesIDs = financialEntities ?.filter(fe => fe && 'id' in fe) .map(fe => (fe as IGetFinancialEntitiesByIdsResult).id); try { const charges = await injector.get(ChargesProvider).getChargesByFilters({ ownerIds: ownerIds ?? undefined, fromAnyDate: fromDate, toAnyDate: toDate, }); const ledgerRecordSets = await Promise.all( charges.map(charge => injector.get(LedgerProvider).getLedgerRecordsByChargesIdLoader.load(charge.id), ), ); const ledgerRecords = ledgerRecordSets.flat(); const rawRes: Record<string, RawBusinessTransactionsSum> = {}; for (const ledger of ledgerRecords) { // re-filter ledger records by date (to prevent charge's out-of-range dates from affecting the sum) if (!!fromDate && format(ledger.invoice_date, 'yyyy-MM-dd') < fromDate) { continue; } if (!!toDate && format(ledger.invoice_date, 'yyyy-MM-dd') > toDate) { continue; } if ( ledger.credit_entity1 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.credit_entity1)) ) { handleBusinessLedgerRecord( rawRes, ledger.credit_entity1, ledger.currency as Currency, true, ledger.credit_local_amount1, ledger.credit_foreign_amount1, ); } if ( ledger.credit_entity2 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.credit_entity2)) ) { handleBusinessLedgerRecord( rawRes, ledger.credit_entity2, ledger.currency as Currency, true, ledger.credit_local_amount2, ledger.credit_foreign_amount2, ); } if ( ledger.debit_entity1 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.debit_entity1)) ) { handleBusinessLedgerRecord( rawRes, ledger.debit_entity1, ledger.currency as Currency, false, ledger.debit_local_amount1, ledger.debit_foreign_amount1, ); } if ( ledger.debit_entity2 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(ledger.debit_entity2)) ) { handleBusinessLedgerRecord( rawRes, ledger.debit_entity2, ledger.currency as Currency, false, ledger.debit_local_amount2, ledger.debit_foreign_amount2, ); } } return { __typename: 'BusinessTransactionsSumFromLedgerRecordsSuccessfulResult', businessTransactionsSum: Object.values(rawRes), }; } catch (e) { console.error(e); return { __typename: 'CommonError', message: 'Error fetching business transactions summary from ledger records', }; } }, businessTransactionsFromLedgerRecords: async (_, { filters }, context, _info) => { const injector = context.injector; const { ownerIds, businessIDs, fromDate, toDate } = filters || {}; const isFilteredByFinancialEntities = !!filters?.businessIDs?.length; const financialEntities = await injector .get(FinancialEntitiesProvider) .getFinancialEntityByIdLoader.loadMany(businessIDs ?? []); const financialEntitiesIDs = financialEntities ?.filter(business => business && 'id' in business) .map(business => (business as IGetBusinessesByIdsResult).id); try { const charges = await injector.get(ChargesProvider).getChargesByFilters({ ownerIds: ownerIds ?? undefined, fromAnyDate: fromDate, toAnyDate: toDate, }); const ledgerRecordSets = await Promise.all( charges.map(charge => injector.get(LedgerProvider).getLedgerRecordsByChargesIdLoader.load(charge.id), ), ); const ledgerRecords = ledgerRecordSets.flat(); const rawTransactions: BusinessTransactionProto[] = []; for (const record of ledgerRecords) { // re-filter ledger records by date (to prevent charge's out-of-range dates from affecting the sum) if (!!fromDate && format(record.invoice_date, 'yyyy-MM-dd') < fromDate) { continue; } if (!!toDate && format(record.invoice_date, 'yyyy-MM-dd') > toDate) { continue; } if ( record.credit_entity1 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(record.credit_entity1)) ) { const transaction = handleBusinessTransaction( record, record.credit_entity1, record.debit_entity1, true, record.credit_local_amount1, record.credit_foreign_amount1, ); rawTransactions.push(transaction); } if ( record.credit_entity2 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(record.credit_entity2)) ) { const transaction = handleBusinessTransaction( record, record.credit_entity2, record.debit_entity2 ?? record.debit_entity2, true, record.credit_local_amount2, record.credit_foreign_amount2, ); rawTransactions.push(transaction); } if ( record.debit_entity2 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(record.debit_entity2)) ) { const transaction = handleBusinessTransaction( record, record.debit_entity2, record.credit_entity1, false, record.debit_local_amount1, record.debit_foreign_amount1, ); rawTransactions.push(transaction); } if ( record.debit_entity2 && (!isFilteredByFinancialEntities || financialEntitiesIDs.includes(record.debit_entity2)) ) { const transaction = handleBusinessTransaction( record, record.debit_entity2, record.credit_entity2 ?? record.credit_entity1, false, record.debit_local_amount2, record.debit_foreign_amount2, ); rawTransactions.push(transaction); } } return { businessTransactions: rawTransactions.sort((a, b) => { const chargeA = charges.find(charge => charge.id === a.chargeId); const chargeB = charges.find(charge => charge.id === b.chargeId); const dateA = Math.min( ...([ chargeA?.documents_min_date?.getTime(), chargeA?.transactions_min_event_date?.getTime(), ].filter(Boolean) as number[]), ); const dateB = Math.min( ...([ chargeB?.documents_min_date?.getTime(), chargeB?.transactions_min_event_date?.getTime(), ].filter(Boolean) as number[]), ); if (dateA < dateB) return -1; if (dateA > dateB) return 1; if (a.chargeId < b.chargeId) return -1; if (a.chargeId > b.chargeId) return 1; if (a.date.getTime() < b.date.getTime()) return -1; if (a.date.getTime() > b.date.getTime()) return 1; return 0; }), }; } catch (e) { console.error(e); return { __typename: 'CommonError', message: 'Error fetching business transactions from ledger records', }; } }, }, BusinessTransactionsSumFromLedgerRecordsResult: { __resolveType: (obj, _context, _info) => { if ('__typename' in obj && obj.__typename === 'CommonError') return 'CommonError'; return 'BusinessTransactionsSumFromLedgerRecordsSuccessfulResult'; }, }, BusinessTransactionSum: { business: (rawSum, _, { injector }) => injector .get(FinancialEntitiesProvider) .getFinancialEntityByIdLoader.load(rawSum.businessId) .then(res => { if (!res) { throw new GraphQLError(`Business with id ${rawSum.businessId} not found`); } return res; }), credit: rawSum => formatFinancialAmount(rawSum.ils.credit, Currency.Ils), debit: rawSum => formatFinancialAmount(rawSum.ils.debit, Currency.Ils), total: rawSum => formatFinancialAmount(rawSum.ils.total, Currency.Ils), eurSum: rawSum => rawSum.eur.credit || rawSum.eur.debit ? { credit: formatFinancialAmount(rawSum.eur.credit, Currency.Eur), debit: formatFinancialAmount(rawSum.eur.debit, Currency.Eur), total: formatFinancialAmount(rawSum.eur.total, Currency.Eur), } : null, gbpSum: rawSum => rawSum.gbp.credit || rawSum.gbp.debit ? { credit: formatFinancialAmount(rawSum.gbp.credit, Currency.Gbp), debit: formatFinancialAmount(rawSum.gbp.debit, Currency.Gbp), total: formatFinancialAmount(rawSum.gbp.total, Currency.Gbp), } : null, usdSum: rawSum => rawSum.usd.credit | rawSum.usd.debit ? { credit: formatFinancialAmount(rawSum.usd.credit, Currency.Usd), debit: formatFinancialAmount(rawSum.usd.debit, Currency.Usd), total: formatFinancialAmount(rawSum.usd.total, Currency.Usd), } : null, }, BusinessTransactionsFromLedgerRecordsResult: { __resolveType: parent => 'businessTransactions' in parent ? 'BusinessTransactionsFromLedgerRecordsSuccessfulResult' : 'CommonError', }, BusinessTransaction: { __isTypeOf: parent => !!parent.businessId, amount: parent => formatFinancialAmount( Number.isNaN(parent.foreignAmount) ? parent.amount : Number(parent.amount) * (parent.isCredit ? 1 : -1), Currency.Ils, ), business: (parent, _, { injector }) => injector .get(FinancialEntitiesProvider) .getFinancialEntityByIdLoader.load(parent.businessId) .then(res => { if (!res) { throw new GraphQLError(`Financial entity with id ${parent.businessId} not found`); } return res; }), eurAmount: parent => parent.currency === Currency.Eur ? formatFinancialAmount( Number.isNaN(parent.foreignAmount) ? parent.foreignAmount : Number(parent.foreignAmount) * (parent.isCredit ? 1 : -1), Currency.Eur, ) : null, gbpAmount: parent => parent.currency === Currency.Gbp ? formatFinancialAmount( Number.isNaN(parent.foreignAmount) ? parent.foreignAmount : Number(parent.foreignAmount) * (parent.isCredit ? 1 : -1), Currency.Gbp, ) : null, usdAmount: parent => parent.currency === Currency.Usd ? formatFinancialAmount(parent.foreignAmount * (parent.isCredit ? 1 : -1), Currency.Usd) : null, invoiceDate: parent => format(parent.date!, 'yyyy-MM-dd') as TimelessDateString, reference1: parent => parent.reference1 ?? null, reference2: _ => null, details: parent => parent.details ?? null, counterAccount: (parent, _, { injector }) => parent.counterAccountId ? injector .get(FinancialEntitiesProvider) .getFinancialEntityByIdLoader.load(parent.counterAccountId) .then(res => { if (!res) { throw new GraphQLError( `Financial entity with id ${parent.counterAccountId} not found`, ); } return res; }) : null, }, TaxCategory: { __isTypeOf: parent => 'hashavshevet_name' in parent, id: parent => parent.id, name: parent => parent.name, }, };