Skip to content

Commit

Permalink
feat(backend): update balance calculation to use psql query
Browse files Browse the repository at this point in the history
  • Loading branch information
mkurapov committed Aug 21, 2024
1 parent c5eca52 commit 2a61215
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 30 deletions.
21 changes: 21 additions & 0 deletions packages/backend/src/accounting/psql/balance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ describe('Balances', (): void => {
})
})

test('ignores expired pending transfers', async (): Promise<void> => {
await createLedgerTransfer(
{
ledger: account.ledger,
creditAccountId: account.id,
debitAccountId: peerAccount.id,
state: LedgerTransferState.PENDING,
expiresAt: new Date(Date.now() - 1),
amount: 10n
},
knex
)

await expect(getAccountBalances(serviceDeps, account)).resolves.toEqual({
creditsPosted: 0n,
creditsPending: 0n,
debitsPosted: 0n,
debitsPending: 0n
})
})

describe('calculates balances for single transfers', (): void => {
const amounts = {
credit: {
Expand Down
65 changes: 35 additions & 30 deletions packages/backend/src/accounting/psql/balance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { LedgerAccount } from './ledger-account/model'
import { LedgerTransferState } from '../service'
import { ServiceDependencies } from './service'
import { getAccountTransfers } from './ledger-transfer'
import { TransactionOrKnex } from 'objection'

export interface AccountBalance {
Expand All @@ -16,38 +14,45 @@ export async function getAccountBalances(
account: LedgerAccount,
trx?: TransactionOrKnex
): Promise<AccountBalance> {
const { credits, debits } = await getAccountTransfers(
deps,
account.id,
undefined, // No limit for balances
trx
)
try {
const queryResult = await (trx ?? deps.knex).raw(
`
SELECT
COALESCE(SUM("amount") FILTER(WHERE "creditAccountId" = :accountId AND "state" = 'POSTED'), 0) AS "creditsPosted",
COALESCE(SUM("amount") FILTER(WHERE "creditAccountId" = :accountId AND "state" = 'PENDING'), 0) AS "creditsPending",
COALESCE(SUM("amount") FILTER(WHERE "debitAccountId" = :accountId AND "state" = 'POSTED'), 0) AS "debitsPosted",
COALESCE(SUM("amount") FILTER(WHERE "debitAccountId" = :accountId AND "state" = 'PENDING'), 0) AS "debitsPending"
FROM "ledgerTransfers"
WHERE ("creditAccountId" = :accountId OR "debitAccountId" = :accountId)
AND ("state" = 'POSTED' OR ("state" = 'PENDING' AND "expiresAt" > NOW()));
`,
{ accountId: account.id }
)

let creditsPosted = 0n
let creditsPending = 0n
let debitsPosted = 0n
let debitsPending = 0n

for (const credit of credits) {
if (credit.state === LedgerTransferState.POSTED) {
creditsPosted += credit.amount
} else if (credit.state === LedgerTransferState.PENDING) {
creditsPending += credit.amount
if (queryResult?.rows < 1) {
throw new Error('No results when fetching balance for account')
}
}

for (const debit of debits) {
if (debit.state === LedgerTransferState.POSTED) {
debitsPosted += debit.amount
} else if (debit.state === LedgerTransferState.PENDING) {
debitsPending += debit.amount
const creditsPosted = BigInt(queryResult.rows[0].creditsPosted)
const creditsPending = BigInt(queryResult.rows[0].creditsPending)
const debitsPosted = BigInt(queryResult.rows[0].debitsPosted)
const debitsPending = BigInt(queryResult.rows[0].debitsPending)

return {
creditsPosted,
creditsPending,
debitsPosted,
debitsPending
}
}
} catch (err) {
deps.logger.error(
{
err,
accountId: account.id
},
'Could not fetch balances for account'
)

return {
creditsPosted,
creditsPending,
debitsPosted,
debitsPending
throw err
}
}

0 comments on commit 2a61215

Please sign in to comment.