Skip to content

Commit 970c092

Browse files
committed
Merge branch 'fix/qb-purchasing' into integration/quickbooks-doc-alignment
2 parents eb8fc44 + db03216 commit 970c092

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)