UNPKG

@accounter/server

Version:

The test suite is split into three Vitest projects for efficiency and isolation:

239 lines • 11.8 kB
import { GraphQLError } from 'graphql'; import { dateToTimelessDateString, formatFinancialAmount } from '../../../shared/helpers/index.js'; import { identifyInterestTransactionIds } from '../../ledger/helpers/bank-deposit-ledger-generation.helper.js'; import { BankDepositChargesProvider } from '../providers/bank-deposit-charges.provider.js'; export const bankDepositChargesResolvers = { Query: { deposit: async (_, { depositId }, { injector, adminContext: { defaultLocalCurrency } }) => { try { const transactions = await injector .get(BankDepositChargesProvider) .getTransactionsByBankDepositLoader.load(depositId); // Identify interest transactions via shared helper const interestTransactionIds = identifyInterestTransactionIds(transactions, { getId: t => t.id, getChargeId: t => t.charge_id, getAmount: t => Number(t.amount ?? 0), }); let currentBalance = 0; let totalInterest = 0; let totalDeposit = 0; for (const tx of transactions) { const amount = Number(tx.amount); if (interestTransactionIds.has(tx.id)) { totalInterest += amount; } else { currentBalance += amount; if (amount > 0) { totalDeposit += amount; } } } const currency = (transactions[0]?.currency ?? defaultLocalCurrency); const sortedByDebit = [...transactions].sort((a, b) => { const ad = a.debit_date ?? a.event_date; const bd = b.debit_date ?? b.event_date; return ad.getTime() - bd.getTime(); }); const openDate = sortedByDebit.length > 0 ? dateToTimelessDateString((sortedByDebit[0].debit_date ?? sortedByDebit[0].event_date)) : dateToTimelessDateString(new Date()); // Find closeDate (last transaction where running balance reached zero) let runningBalance = 0; let closeDate = null; for (const tx of sortedByDebit) { if (!interestTransactionIds.has(tx.id)) { runningBalance += Number(tx.amount); } if (Math.abs(runningBalance) < 0.005) { closeDate = dateToTimelessDateString((tx.debit_date ?? tx.event_date)); } } // If final balance is not closed, ensure closeDate is null if (Math.abs(currentBalance) >= 0.005) { closeDate = null; } // Check for currency conflicts const currencies = new Set(transactions.map(tx => tx.currency)); const currencyError = currencies.size > 1 ? transactions.filter(tx => tx.currency !== currency).map(tx => tx.id) : []; return { id: depositId, currency, openDate, closeDate, currentBalance: formatFinancialAmount(currentBalance), totalInterest: formatFinancialAmount(totalInterest), totalDeposit: formatFinancialAmount(totalDeposit), isOpen: Math.abs(currentBalance) >= 0.005, currencyError, transactions: transactions.map(tx => tx.id), balance: formatFinancialAmount(currentBalance), }; } catch (e) { if (e instanceof GraphQLError) throw e; throw new GraphQLError('Error fetching bank deposit'); } }, depositByCharge: async (_, { chargeId }, { injector, adminContext: { defaultLocalCurrency } }) => { try { const transactions = await injector .get(BankDepositChargesProvider) .getDepositTransactionsByChargeId(chargeId, true); // Identify interest transactions via shared helper const interestTransactionIds = identifyInterestTransactionIds(transactions, { getId: t => t.id, getChargeId: t => t.charge_id, getAmount: t => Number(t.amount ?? 0), }); let currentBalance = 0; let totalInterest = 0; let totalDeposit = 0; for (const tx of transactions) { const amount = Number(tx.amount); if (interestTransactionIds.has(tx.id)) { totalInterest += amount; } else { currentBalance += amount; if (amount > 0) { totalDeposit += amount; } } } const depositIds = new Set(transactions.map(tx => tx.deposit_id).filter(Boolean)); if (depositIds.size === 0) { return null; } if (depositIds.size > 1) { throw new GraphQLError('Multiple deposits found'); } const id = [...depositIds][0]; const currency = (transactions[0]?.currency ?? defaultLocalCurrency); const sortedByDebit = [...transactions].sort((a, b) => { const ad = a.debit_date ?? a.event_date; const bd = b.debit_date ?? b.event_date; return ad.getTime() - bd.getTime(); }); const openDate = sortedByDebit.length > 0 ? dateToTimelessDateString((sortedByDebit[0].debit_date ?? sortedByDebit[0].event_date)) : dateToTimelessDateString(new Date()); // Find closeDate let runningBalance = 0; let closeDate = null; for (const tx of sortedByDebit) { if (!interestTransactionIds.has(tx.id)) { runningBalance += Number(tx.amount); } if (Math.abs(runningBalance) < 0.005) { closeDate = dateToTimelessDateString((tx.debit_date ?? tx.event_date)); } } // If final balance is not closed, ensure closeDate is null if (Math.abs(currentBalance) >= 0.005) { closeDate = null; } const currencies = new Set(transactions.map(tx => tx.currency)); const currencyError = currencies.size > 1 ? transactions.filter(tx => tx.currency !== currency).map(tx => tx.id) : []; return { id, currency, openDate, closeDate, currentBalance: formatFinancialAmount(currentBalance), totalInterest: formatFinancialAmount(totalInterest), totalDeposit: formatFinancialAmount(totalDeposit), isOpen: Math.abs(currentBalance) >= 0.005, currencyError, transactions: transactions.map(tx => tx.id), balance: formatFinancialAmount(currentBalance), }; } catch (e) { if (e instanceof GraphQLError) throw e; throw new GraphQLError('Error fetching bank deposit'); } }, allDeposits: async (_, __, { injector, adminContext: { defaultLocalCurrency } }) => { try { const deposits = await injector .get(BankDepositChargesProvider) .getAllDepositsWithMetadata(); return deposits.map(deposit => ({ id: deposit.id, currency: deposit.currency ?? defaultLocalCurrency, openDate: deposit.openDate ?? dateToTimelessDateString(new Date()), closeDate: deposit.closeDate, currentBalance: formatFinancialAmount(deposit.currentBalance, deposit.currency), totalDeposit: formatFinancialAmount(deposit.totalDeposit, deposit.currency), totalInterest: formatFinancialAmount(deposit.totalInterest, deposit.currency), isOpen: Math.abs(deposit.currentBalance) >= 0.005, currencyError: deposit.currencyError, transactions: deposit.transactionIds, balance: formatFinancialAmount(deposit.currentBalance, deposit.currency), })); } catch (e) { if (e instanceof GraphQLError) throw e; throw new GraphQLError('Error fetching all deposits'); } }, }, Mutation: { createDeposit: async (_, { currency }, { injector }) => { try { const deposit = await injector.get(BankDepositChargesProvider).createDeposit(currency); return { id: deposit.id, currency: currency, openDate: dateToTimelessDateString(new Date()), closeDate: null, currentBalance: formatFinancialAmount(0), totalInterest: formatFinancialAmount(0), totalDeposit: formatFinancialAmount(0), isOpen: false, currencyError: [], transactions: [], balance: formatFinancialAmount(0), }; } catch (e) { if (e instanceof GraphQLError) throw e; throw new GraphQLError('Error creating deposit'); } }, assignChargeToDeposit: async (_, { chargeId, depositId }, { injector, adminContext: { defaultLocalCurrency } }) => { try { const updatedDeposit = await injector .get(BankDepositChargesProvider) .assignChargeToDeposit(chargeId, depositId); return { id: updatedDeposit.id, currency: updatedDeposit.currency ?? defaultLocalCurrency, openDate: updatedDeposit.openDate ?? dateToTimelessDateString(new Date()), closeDate: updatedDeposit.closeDate, currentBalance: formatFinancialAmount(updatedDeposit.currentBalance), totalInterest: formatFinancialAmount(updatedDeposit.totalInterest), totalDeposit: formatFinancialAmount(updatedDeposit.totalDeposit), isOpen: Math.abs(updatedDeposit.currentBalance) >= 0.005, currencyError: updatedDeposit.currencyError, transactions: updatedDeposit.transactionIds, balance: formatFinancialAmount(updatedDeposit.currentBalance), }; } catch (e) { throw new GraphQLError(e.message || 'Error assigning transaction to deposit'); } }, }, }; //# sourceMappingURL=bank-deposit-charges.resolver.js.map