Skip to content

Commit 7ddfdbe

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(quickbooks): balance journal entries in exact cents
1 parent 17e6eab commit 7ddfdbe

2 files changed

Lines changed: 21 additions & 9 deletions

File tree

apps/sim/tools/quickbooks/accounting.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,13 @@ describe('QuickBooks accounting line validation', () => {
181181
{ ...journalLines[1], amount: '90071992547409.91' },
182182
])
183183
).toThrow('safely supported amount range')
184+
expect(() =>
185+
parseQuickBooksJournalLines([
186+
{ ...journalLines[0], amount: '1e30' },
187+
{ ...journalLines[0], amount: 1 },
188+
{ ...journalLines[1], amount: '1e30' },
189+
])
190+
).toThrow('safely supported amount range')
184191
expect(() =>
185192
parseQuickBooksJournalLines(
186193
Array.from({ length: 102 }, (_, index) => ({

apps/sim/tools/quickbooks/accounting_utils.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function rejectUnknownKeys(
6262
function positiveQuickBooksAmount(
6363
value: unknown,
6464
fieldName: string
65-
): { decimal: Decimal; number: number } {
65+
): { cents: bigint; number: number } {
6666
if (typeof value !== 'number' && typeof value !== 'string') {
6767
throw new Error(`${fieldName} must be a positive finite number`)
6868
}
@@ -82,11 +82,16 @@ function positiveQuickBooksAmount(
8282
throw new Error(`${fieldName} cannot have more than two decimal places`)
8383
}
8484

85+
const centsNumber = decimal.times(100).toNumber()
86+
if (!Number.isSafeInteger(centsNumber)) {
87+
throw new Error(`${fieldName} is outside the safely supported amount range`)
88+
}
89+
8590
const number = decimal.toNumber()
8691
if (!Number.isFinite(number) || !new Decimal(number).equals(decimal)) {
8792
throw new Error(`${fieldName} is outside the safely supported amount range`)
8893
}
89-
return { decimal, number }
94+
return { cents: BigInt(centsNumber), number }
9095
}
9196

9297
function stringValue(value: unknown, fieldName: string): string {
@@ -119,7 +124,7 @@ export function parseQuickBooksJournalLines(
119124
if (!parsed) return undefined
120125
validateLineCount(parsed, fieldName, 2)
121126

122-
const decimalAmounts: Decimal[] = []
127+
const centAmounts: bigint[] = []
123128
const lines = parsed.map((rawLine, index) => {
124129
const itemName = `${fieldName}[${index}]`
125130
const line = requireObject(rawLine, itemName)
@@ -141,7 +146,7 @@ export function parseQuickBooksJournalLines(
141146
throw new Error(`${itemName}.entityType and entityId must be supplied together`)
142147
}
143148
const amount = positiveQuickBooksAmount(line.amount, `${itemName}.amount`)
144-
decimalAmounts.push(amount.decimal)
149+
centAmounts.push(amount.cents)
145150
return {
146151
postingType,
147152
amount: amount.number,
@@ -156,14 +161,14 @@ export function parseQuickBooksJournalLines(
156161
})
157162

158163
const debitTotal = lines.reduce(
159-
(sum, line, index) => (line.postingType === 'debit' ? sum.plus(decimalAmounts[index]) : sum),
160-
new Decimal(0)
164+
(sum, line, index) => (line.postingType === 'debit' ? sum + centAmounts[index] : sum),
165+
0n
161166
)
162167
const creditTotal = lines.reduce(
163-
(sum, line, index) => (line.postingType === 'credit' ? sum.plus(decimalAmounts[index]) : sum),
164-
new Decimal(0)
168+
(sum, line, index) => (line.postingType === 'credit' ? sum + centAmounts[index] : sum),
169+
0n
165170
)
166-
if (!debitTotal.equals(creditTotal)) {
171+
if (debitTotal !== creditTotal) {
167172
throw new Error('Journal entry debit and credit totals must balance')
168173
}
169174
return lines

0 commit comments

Comments
 (0)