Skip to content

Commit e9e6598

Browse files
committed
Merge branch 'fix/qb-block-wiring' into integration/quickbooks-doc-alignment
2 parents 5ee1614 + 1524160 commit e9e6598

14 files changed

Lines changed: 837 additions & 249 deletions

File tree

apps/docs/content/docs/integrations/quickbooks.mdx

Lines changed: 152 additions & 13 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/quickbooks.ts

Lines changed: 287 additions & 163 deletions
Large diffs are not rendered by default.

apps/sim/tools/generated/tool-ids.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/generated/tool-metadata.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/generated/tool-outputs.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('QuickBooks credential authority', () => {
5151
'params' in value
5252
)
5353

54-
expect(tools).toHaveLength(47)
54+
expect(tools).toHaveLength(49)
5555
for (const tool of tools) {
5656
expect(tool.oauth?.authoritativeParams, tool.id).toContain('realmId')
5757
expect(tool.oauth?.authoritativeParams, tool.id).toContain('quickBooksEnvironment')

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

Lines changed: 175 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, it } from 'vitest'
22
import { QuickBooksBlock } from '@/blocks/blocks/quickbooks'
33
import * as quickBooksTools from '@/tools/quickbooks'
4+
import { QUICKBOOKS_REPORTS } from '@/tools/quickbooks/report-metadata'
45

56
function requiredCondition(fieldId: string, values: Record<string, unknown>) {
67
const field = QuickBooksBlock.subBlocks.find((subBlock) => subBlock.id === fieldId)
@@ -66,7 +67,7 @@ describe('QuickBooks block operation coverage', () => {
6667
const optionIds = operation.options.map((option) => option.id).sort()
6768
const accessIds = [...(QuickBooksBlock.tools?.access ?? [])].sort()
6869

69-
expect(toolIds).toHaveLength(47)
70+
expect(toolIds).toHaveLength(49)
7071
expect(optionIds).toEqual(toolIds)
7172
expect(accessIds).toEqual(toolIds)
7273
for (const toolId of toolIds) {
@@ -79,3 +80,176 @@ describe('QuickBooks block operation coverage', () => {
7980
expect(new Set(ids).size).toBe(ids.length)
8081
})
8182
})
83+
84+
function blockParams(values: Record<string, unknown>): Record<string, unknown> {
85+
const params = QuickBooksBlock.tools?.config?.params
86+
if (!params) throw new Error('QuickBooks block does not define a params mapper')
87+
return params(values) as Record<string, unknown>
88+
}
89+
90+
function staticRequiredValue(fieldId: string): unknown {
91+
const field = QuickBooksBlock.subBlocks.find((subBlock) => subBlock.id === fieldId)
92+
if (!field || typeof field.required !== 'object') {
93+
throw new Error(`${fieldId} does not define a static required condition`)
94+
}
95+
return field.required.value
96+
}
97+
98+
describe('QuickBooks block documented requirements', () => {
99+
it('requires a customer only where Intuit lists CustomerRef on the request model', () => {
100+
const required = staticRequiredValue('customerId') as string[]
101+
expect(required).toContain('quickbooks_create_invoice')
102+
expect(required).toContain('quickbooks_create_estimate')
103+
expect(required).toContain('quickbooks_create_credit_memo')
104+
expect(required).not.toContain('quickbooks_create_sales_receipt')
105+
expect(required).not.toContain('quickbooks_create_refund_receipt')
106+
})
107+
108+
it('accepts the documented maximum page size of 1000', () => {
109+
expect(
110+
blockParams({
111+
operation: 'quickbooks_read_master_data',
112+
readMode: 'list',
113+
maxResults: '1000',
114+
}).maxResults
115+
).toBe(1000)
116+
expect(() =>
117+
blockParams({
118+
operation: 'quickbooks_read_master_data',
119+
readMode: 'list',
120+
maxResults: '1001',
121+
})
122+
).toThrow('maxResults must be an integer from 1 through 1000')
123+
})
124+
125+
it('omits pagination from every by-ID read', () => {
126+
for (const [operation, extra] of [
127+
['quickbooks_read_master_data', { recordType: 'customer' }],
128+
['quickbooks_read_sales_transactions', { transactionType: 'invoice' }],
129+
['quickbooks_read_purchasing_transactions', { purchasingTransactionType: 'bill' }],
130+
['quickbooks_read_accounting_transactions', { accountingTransactionType: 'deposit' }],
131+
['quickbooks_read_attachments', {}],
132+
] as const) {
133+
const mapped = blockParams({
134+
operation,
135+
readMode: 'by_id',
136+
startPosition: '5',
137+
maxResults: '10',
138+
...extra,
139+
})
140+
expect(mapped, operation).not.toHaveProperty('startPosition')
141+
expect(mapped, operation).not.toHaveProperty('maxResults')
142+
}
143+
})
144+
})
145+
146+
describe('QuickBooks block wiring for documented tool parameters', () => {
147+
it('sends the multicurrency and tax-treatment fields on the creates that document them', () => {
148+
const bill = blockParams({
149+
operation: 'quickbooks_create_bill',
150+
currencyCode: 'EUR',
151+
globalTaxCalculation: 'TaxInclusive',
152+
})
153+
expect(bill.currencyCode).toBe('EUR')
154+
expect(bill.globalTaxCalculation).toBe('TaxInclusive')
155+
156+
const billPayment = blockParams({
157+
operation: 'quickbooks_create_bill_payment',
158+
currencyCode: 'EUR',
159+
globalTaxCalculation: 'TaxInclusive',
160+
apAccountId: 'ap-1',
161+
documentNumber: 'BP-1',
162+
})
163+
expect(billPayment.currencyCode).toBe('EUR')
164+
expect(billPayment.globalTaxCalculation).toBeUndefined()
165+
expect(billPayment.apAccountId).toBe('ap-1')
166+
expect(billPayment.documentNumber).toBe('BP-1')
167+
168+
const journalEntry = blockParams({
169+
operation: 'quickbooks_create_journal_entry',
170+
currencyCode: 'GBP',
171+
globalTaxCalculation: 'default',
172+
})
173+
expect(journalEntry.currencyCode).toBe('GBP')
174+
expect(journalEntry.globalTaxCalculation).toBeUndefined()
175+
})
176+
177+
it('sends a purchase-order due date', () => {
178+
expect(
179+
blockParams({ operation: 'quickbooks_create_purchase_order', dueDate: '2026-09-30' }).dueDate
180+
).toBe('2026-09-30')
181+
expect(
182+
blockParams({ operation: 'quickbooks_update_purchase_order', dueDate: '2026-09-30' }).dueDate
183+
).toBe('2026-09-30')
184+
})
185+
186+
it('coerces the quick-zoom switch to the boolean the report tool requires', () => {
187+
expect(
188+
blockParams({
189+
operation: 'quickbooks_run_financial_report',
190+
reportType: 'balance_sheet',
191+
reportQuickZoomUrl: 'true',
192+
}).quickZoomUrl
193+
).toBe(true)
194+
expect(
195+
blockParams({
196+
operation: 'quickbooks_run_financial_report',
197+
reportType: 'cash_flow',
198+
reportQuickZoomUrl: 'true',
199+
}).quickZoomUrl
200+
).toBeUndefined()
201+
})
202+
203+
it('sends the report date macro and the employee filter', () => {
204+
expect(
205+
blockParams({
206+
operation: 'quickbooks_run_financial_report',
207+
reportType: 'balance_sheet',
208+
reportDateMacro: 'last_fiscal_year',
209+
}).dateMacro
210+
).toBe('last_fiscal_year')
211+
expect(
212+
blockParams({
213+
operation: 'quickbooks_run_financial_report',
214+
reportType: 'profit_and_loss_detail',
215+
reportEmployeeId: 'employee-1',
216+
}).employeeId
217+
).toBe('employee-1')
218+
})
219+
220+
it('offers every documented report in the report dropdown', () => {
221+
const reportType = QuickBooksBlock.subBlocks.find((subBlock) => subBlock.id === 'reportType')
222+
if (!reportType || !('options' in reportType) || !Array.isArray(reportType.options)) {
223+
throw new Error('QuickBooks report dropdown is missing')
224+
}
225+
expect(reportType.options.map((option) => option.id).sort()).toEqual(
226+
Object.keys(QUICKBOOKS_REPORTS).sort()
227+
)
228+
})
229+
230+
it('keeps the upload and download attachment file names as separate fields', () => {
231+
expect(
232+
blockParams({
233+
operation: 'quickbooks_download_attachment',
234+
attachmentId: 'attachment-1',
235+
downloadAttachmentFileName: 'saved.pdf',
236+
}).fileName
237+
).toBe('saved.pdf')
238+
expect(
239+
blockParams({
240+
operation: 'quickbooks_add_attachment',
241+
attachmentKind: 'note',
242+
attachmentFileName: 'ignored.pdf',
243+
}).fileName
244+
).toBeUndefined()
245+
246+
const uploadName = QuickBooksBlock.subBlocks.find(
247+
(subBlock) => subBlock.id === 'attachmentFileName'
248+
)
249+
expect(uploadName?.condition).toEqual({
250+
field: 'operation',
251+
value: 'quickbooks_add_attachment',
252+
and: { field: 'attachmentKind', value: 'file' },
253+
})
254+
})
255+
})

apps/sim/tools/quickbooks/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,7 @@ export { quickbooksUpdateRefundReceiptTool } from '@/tools/quickbooks/update_ref
4444
export { quickbooksUpdateSalesReceiptTool } from '@/tools/quickbooks/update_sales_receipt'
4545
export { quickbooksUpdateVendorTool } from '@/tools/quickbooks/update_vendor'
4646
export { quickbooksUpdateVendorCreditTool } from '@/tools/quickbooks/update_vendor_credit'
47+
export { quickbooksVoidBillPaymentTool } from '@/tools/quickbooks/void_bill_payment'
4748
export { quickbooksVoidCustomerPaymentTool } from '@/tools/quickbooks/void_customer_payment'
4849
export { quickbooksVoidInvoiceTool } from '@/tools/quickbooks/void_invoice'
50+
export { quickbooksVoidSalesReceiptTool } from '@/tools/quickbooks/void_sales_receipt'

0 commit comments

Comments
 (0)