Skip to content

Commit db03216

Browse files
waleedlatif1claude
andcommitted
fix(quickbooks): align purchasing and accounting tools with Intuit's model
Validated against Intuit's live machine-readable model files (EntityJsonObject_v1.json, CodesModelsJsonObjects_v2.json). - CurrencyRef is "Conditionally required" on all seven purchasing and accounting create models ("This must be defined if multicurrency is enabled for the company") and was never written, so every create failed on a multicurrency company. Creates now accept an ISO 4217 currencyCode. - GlobalTaxCalculation is "Conditionally required" on Bill, VendorCredit, PurchaseOrder, JournalEntry, and Deposit, and Optional on Purchase ("Not applicable to US companies; required for non-US companies"). Those six creates now accept it; JournalEntry accepts only the two values Intuit documents for it. BillPayment has no such property and is left alone. - Add void_bill_payment, the documented POST /billpayment?operation=update&include=void operation. - itembasedexpenselinedetail.Required is [] and ItemRef is Optional, so an item line no longer requires itemId. - The Deposit sparse update leaves "missing elements untouched", so depositAccountId is no longer forced on every update. - BillPayment DocNumber and APAccountRef are Optional writable fields and were unreachable; PurchaseOrder DueDate was emitted by the body builder but had no parameter. - Correct the maxResults range in the two read tools to 1-1000, matching validateQuickBooksPagination. - Record the unresolved BillPayment "Line [0..n]" / requiredFlag Required contradiction as a TSDoc note; behavior unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RB5M2gk7zZgGomnQqr7WRB
1 parent 9a91e4b commit db03216

19 files changed

Lines changed: 637 additions & 15 deletions
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, expect, it } from 'vitest'
2+
import {
3+
buildQuickBooksCreateDepositBody,
4+
buildQuickBooksCreateJournalEntryBody,
5+
buildQuickBooksUpdateDepositBody,
6+
} from '@/tools/quickbooks/accounting_utils'
7+
import type {
8+
QuickBooksCreateDepositParams,
9+
QuickBooksCreateJournalEntryParams,
10+
} from '@/tools/quickbooks/types'
11+
12+
const DEPOSIT: QuickBooksCreateDepositParams = {
13+
accessToken: 'token',
14+
realmId: '123',
15+
quickBooksEnvironment: 'sandbox',
16+
depositAccountId: 'bank-1',
17+
lines: [{ amount: 20, accountId: 'account-1' }],
18+
}
19+
20+
const JOURNAL_ENTRY: QuickBooksCreateJournalEntryParams = {
21+
accessToken: 'token',
22+
realmId: '123',
23+
quickBooksEnvironment: 'sandbox',
24+
confirmPosting: true,
25+
lines: [
26+
{ postingType: 'debit', amount: 20, accountId: 'account-1' },
27+
{ postingType: 'credit', amount: 20, accountId: 'account-2' },
28+
],
29+
}
30+
31+
describe('QuickBooks multicurrency and non-US tax fields on accounting creates', () => {
32+
it('emits CurrencyRef and GlobalTaxCalculation on a Deposit', () => {
33+
expect(
34+
buildQuickBooksCreateDepositBody({
35+
...DEPOSIT,
36+
currencyCode: 'aud',
37+
globalTaxCalculation: 'NotApplicable',
38+
})
39+
).toMatchObject({
40+
CurrencyRef: { value: 'AUD' },
41+
GlobalTaxCalculation: 'NotApplicable',
42+
})
43+
})
44+
45+
it('emits CurrencyRef and GlobalTaxCalculation on a Journal Entry', () => {
46+
expect(
47+
buildQuickBooksCreateJournalEntryBody({
48+
...JOURNAL_ENTRY,
49+
currencyCode: 'CAD',
50+
globalTaxCalculation: 'TaxExcluded',
51+
})
52+
).toMatchObject({
53+
CurrencyRef: { value: 'CAD' },
54+
GlobalTaxCalculation: 'TaxExcluded',
55+
})
56+
})
57+
58+
it('rejects NotApplicable on a Journal Entry, which Intuit does not document for it', () => {
59+
expect(() =>
60+
buildQuickBooksCreateJournalEntryBody({
61+
...JOURNAL_ENTRY,
62+
globalTaxCalculation: 'NotApplicable',
63+
})
64+
).toThrow('globalTaxCalculation must be one of TaxExcluded, TaxInclusive')
65+
})
66+
67+
it('omits both fields when they are not supplied', () => {
68+
const body = buildQuickBooksCreateDepositBody(DEPOSIT)
69+
70+
expect(body).not.toHaveProperty('CurrencyRef')
71+
expect(body).not.toHaveProperty('GlobalTaxCalculation')
72+
})
73+
})
74+
75+
describe('QuickBooks Deposit sparse update', () => {
76+
it('updates a single header field without restating the destination account', () => {
77+
expect(
78+
buildQuickBooksUpdateDepositBody({
79+
accessToken: 'token',
80+
realmId: '123',
81+
quickBooksEnvironment: 'sandbox',
82+
depositId: 'deposit-1',
83+
syncToken: '2',
84+
privateNote: 'Updated deposit',
85+
})
86+
).toEqual({
87+
Id: 'deposit-1',
88+
SyncToken: '2',
89+
sparse: true,
90+
PrivateNote: 'Updated deposit',
91+
})
92+
})
93+
94+
it('sends DepositToAccountRef only when a replacement account is supplied', () => {
95+
expect(
96+
buildQuickBooksUpdateDepositBody({
97+
accessToken: 'token',
98+
realmId: '123',
99+
quickBooksEnvironment: 'sandbox',
100+
depositId: 'deposit-1',
101+
syncToken: '2',
102+
depositAccountId: 'bank-2',
103+
})
104+
).toMatchObject({ DepositToAccountRef: { value: 'bank-2' } })
105+
})
106+
107+
it('still refuses an update that would change nothing', () => {
108+
expect(() =>
109+
buildQuickBooksUpdateDepositBody({
110+
accessToken: 'token',
111+
realmId: '123',
112+
quickBooksEnvironment: 'sandbox',
113+
depositId: 'deposit-1',
114+
syncToken: '2',
115+
})
116+
).toThrow('Provide at least one field to update')
117+
})
118+
})

‎apps/sim/tools/quickbooks/accounting_utils.ts‎

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
QuickBooksCreateDepositParams,
55
QuickBooksCreateJournalEntryParams,
66
QuickBooksDepositLineInput,
7+
QuickBooksGlobalTaxCalculation,
78
QuickBooksJournalEntityType,
89
QuickBooksJournalLineInput,
910
QuickBooksJournalPostingType,
@@ -13,6 +14,8 @@ import type {
1314
import {
1415
assertQuickBooksSparseUpdate,
1516
optionalQuickBooksString,
17+
quickBooksCurrencyRef,
18+
quickBooksGlobalTaxCalculation,
1619
quickBooksReference,
1720
requiredQuickBooksString,
1821
validateQuickBooksDate,
@@ -29,6 +32,16 @@ const JOURNAL_LINE_KEYS = new Set([
2932
])
3033
const DEPOSIT_LINE_KEYS = new Set(['amount', 'accountId', 'description'])
3134

35+
/**
36+
* JournalEntry is the one entity in this module whose `GlobalTaxCalculation`
37+
* Intuit documents without `NotApplicable`: "Allowed values are: TaxExcluded,
38+
* TaxInclusive."
39+
*/
40+
const JOURNAL_ENTRY_GLOBAL_TAX_CALCULATIONS: readonly QuickBooksGlobalTaxCalculation[] = [
41+
'TaxExcluded',
42+
'TaxInclusive',
43+
]
44+
3245
function parseJsonArray(value: unknown, fieldName: string): unknown[] | undefined {
3346
if (value == null || value === '') return undefined
3447
let parsed = value
@@ -241,11 +254,13 @@ export function buildQuickBooksDepositLines(lines: QuickBooksDepositLineInput[])
241254
}
242255

243256
function transactionHeader(params: {
257+
currencyCode?: string
244258
transactionDate?: string
245259
documentNumber?: string
246260
privateNote?: string
247261
}): Record<string, unknown> {
248262
return filterUndefined({
263+
CurrencyRef: quickBooksCurrencyRef(params.currencyCode),
249264
TxnDate: validateQuickBooksDate(params.transactionDate, 'transactionDate'),
250265
DocNumber: optionalQuickBooksString(params.documentNumber),
251266
PrivateNote: optionalQuickBooksString(params.privateNote),
@@ -262,10 +277,14 @@ export function buildQuickBooksCreateJournalEntryBody(
262277
params: QuickBooksCreateJournalEntryParams
263278
): Record<string, unknown> {
264279
requirePostingConfirmation(params.confirmPosting)
265-
return {
280+
return filterUndefined({
266281
...transactionHeader(params),
282+
GlobalTaxCalculation: quickBooksGlobalTaxCalculation(
283+
params.globalTaxCalculation,
284+
JOURNAL_ENTRY_GLOBAL_TAX_CALCULATIONS
285+
),
267286
Line: buildQuickBooksJournalLines(params.lines),
268-
}
287+
})
269288
}
270289

271290
export function buildQuickBooksUpdateJournalEntryBody(
@@ -285,23 +304,31 @@ export function buildQuickBooksUpdateJournalEntryBody(
285304
export function buildQuickBooksCreateDepositBody(
286305
params: QuickBooksCreateDepositParams
287306
): Record<string, unknown> {
288-
return {
307+
return filterUndefined({
289308
DepositToAccountRef: quickBooksReference(params.depositAccountId, 'depositAccountId'),
290309
...transactionHeader(params),
310+
GlobalTaxCalculation: quickBooksGlobalTaxCalculation(params.globalTaxCalculation),
291311
Line: buildQuickBooksDepositLines(params.lines),
292-
}
312+
})
293313
}
294314

315+
/**
316+
* Intuit documents the Deposit sparse update as "only elements specified in the
317+
* request are updated. Missing elements are left untouched", so
318+
* `DepositToAccountRef` is sent only when the caller is actually changing it.
319+
*/
295320
export function buildQuickBooksUpdateDepositBody(
296321
params: QuickBooksUpdateDepositParams
297322
): Record<string, unknown> {
298-
const body = {
323+
const body = filterUndefined({
299324
Id: requiredQuickBooksString(params.depositId, 'depositId'),
300325
SyncToken: requiredQuickBooksString(params.syncToken, 'syncToken'),
301326
sparse: true,
302-
DepositToAccountRef: quickBooksReference(params.depositAccountId, 'depositAccountId'),
327+
DepositToAccountRef: params.depositAccountId
328+
? quickBooksReference(params.depositAccountId, 'depositAccountId')
329+
: undefined,
303330
...transactionHeader(params),
304-
}
305-
assertQuickBooksSparseUpdate(body, 4)
331+
}) as Record<string, unknown>
332+
assertQuickBooksSparseUpdate(body)
306333
return body
307334
}

‎apps/sim/tools/quickbooks/create_bill.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,20 @@ export const quickbooksCreateBillTool: ToolConfig<
9191
visibility: 'user-or-llm',
9292
description: 'Internal bill note',
9393
},
94+
currencyCode: {
95+
type: 'string',
96+
required: false,
97+
visibility: 'user-or-llm',
98+
description:
99+
'Three-letter ISO 4217 currency code, required when multicurrency is enabled for the company',
100+
},
101+
globalTaxCalculation: {
102+
type: 'string',
103+
required: false,
104+
visibility: 'user-or-llm',
105+
description:
106+
'Tax treatment required for non-US companies: TaxExcluded, TaxInclusive, or NotApplicable',
107+
},
94108
requestId: {
95109
type: 'string',
96110
required: false,

‎apps/sim/tools/quickbooks/create_bill_payment.ts‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,25 @@ export const quickbooksCreateBillPaymentTool: InternalToolConfig<
8181
visibility: 'user-or-llm',
8282
description: 'Internal payment note',
8383
},
84+
apAccountId: {
85+
type: 'string',
86+
required: false,
87+
visibility: 'user-or-llm',
88+
description: 'Optional accounts-payable account the payment is credited to',
89+
},
90+
documentNumber: {
91+
type: 'string',
92+
required: false,
93+
visibility: 'user-or-llm',
94+
description: 'Optional reference number for the payment',
95+
},
96+
currencyCode: {
97+
type: 'string',
98+
required: false,
99+
visibility: 'user-or-llm',
100+
description:
101+
'Three-letter ISO 4217 currency code, required when multicurrency is enabled for the company',
102+
},
84103
requestId: {
85104
type: 'string',
86105
required: false,

‎apps/sim/tools/quickbooks/create_deposit.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ export const quickbooksCreateDepositTool: ToolConfig<
6868
visibility: 'user-or-llm',
6969
description: 'Internal deposit note',
7070
},
71+
currencyCode: {
72+
type: 'string',
73+
required: false,
74+
visibility: 'user-or-llm',
75+
description:
76+
'Three-letter ISO 4217 currency code, required when multicurrency is enabled for the company',
77+
},
78+
globalTaxCalculation: {
79+
type: 'string',
80+
required: false,
81+
visibility: 'user-or-llm',
82+
description:
83+
'Tax treatment required for non-US companies: TaxExcluded, TaxInclusive, or NotApplicable',
84+
},
7185
requestId: {
7286
type: 'string',
7387
required: false,

‎apps/sim/tools/quickbooks/create_journal_entry.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ export const quickbooksCreateJournalEntryTool: ToolConfig<
7474
visibility: 'user-or-llm',
7575
description: 'Internal journal-entry note',
7676
},
77+
currencyCode: {
78+
type: 'string',
79+
required: false,
80+
visibility: 'user-or-llm',
81+
description:
82+
'Three-letter ISO 4217 currency code, required when multicurrency is enabled for the company',
83+
},
84+
globalTaxCalculation: {
85+
type: 'string',
86+
required: false,
87+
visibility: 'user-or-llm',
88+
description: 'Tax treatment required for non-US companies: TaxExcluded or TaxInclusive',
89+
},
7790
requestId: {
7891
type: 'string',
7992
required: false,

‎apps/sim/tools/quickbooks/create_purchase.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ export const quickbooksCreatePurchaseTool: ToolConfig<
8787
visibility: 'user-or-llm',
8888
description: 'Internal purchase note',
8989
},
90+
currencyCode: {
91+
type: 'string',
92+
required: false,
93+
visibility: 'user-or-llm',
94+
description:
95+
'Three-letter ISO 4217 currency code, required when multicurrency is enabled for the company',
96+
},
97+
globalTaxCalculation: {
98+
type: 'string',
99+
required: false,
100+
visibility: 'user-or-llm',
101+
description:
102+
'Tax treatment required for non-US companies: TaxExcluded, TaxInclusive, or NotApplicable',
103+
},
90104
requestId: {
91105
type: 'string',
92106
required: false,

‎apps/sim/tools/quickbooks/create_purchase_order.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ export const quickbooksCreatePurchaseOrderTool: ToolConfig<
8080
visibility: 'user-or-llm',
8181
description: 'Internal purchase-order note',
8282
},
83+
currencyCode: {
84+
type: 'string',
85+
required: false,
86+
visibility: 'user-or-llm',
87+
description:
88+
'Three-letter ISO 4217 currency code, required when multicurrency is enabled for the company',
89+
},
90+
globalTaxCalculation: {
91+
type: 'string',
92+
required: false,
93+
visibility: 'user-or-llm',
94+
description:
95+
'Tax treatment required for non-US companies: TaxExcluded, TaxInclusive, or NotApplicable',
96+
},
97+
dueDate: {
98+
type: 'string',
99+
required: false,
100+
visibility: 'user-or-llm',
101+
description: 'Date the payment is due in YYYY-MM-DD format',
102+
},
83103
requestId: {
84104
type: 'string',
85105
required: false,

‎apps/sim/tools/quickbooks/create_vendor_credit.ts‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ export const quickbooksCreateVendorCreditTool: ToolConfig<
8080
visibility: 'user-or-llm',
8181
description: 'Internal vendor-credit note',
8282
},
83+
currencyCode: {
84+
type: 'string',
85+
required: false,
86+
visibility: 'user-or-llm',
87+
description:
88+
'Three-letter ISO 4217 currency code, required when multicurrency is enabled for the company',
89+
},
90+
globalTaxCalculation: {
91+
type: 'string',
92+
required: false,
93+
visibility: 'user-or-llm',
94+
description:
95+
'Tax treatment required for non-US companies: TaxExcluded, TaxInclusive, or NotApplicable',
96+
},
8397
requestId: {
8498
type: 'string',
8599
required: false,

0 commit comments

Comments
 (0)