Skip to content

Commit 187e7d2

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(quickbooks): address final integration review
1 parent e334bbc commit 187e7d2

11 files changed

Lines changed: 189 additions & 23 deletions

File tree

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,8 @@ List or read one account, customer, vendor, item, or employee
188188
|`InvStartDate` | string | Inventory tracking start date |
189189
|`PrimaryAddr` | json | Employee primary address |
190190
|`BillableTime` | boolean | Whether employee time is billable |
191-
| `startPosition` | number | One-based position of the first record in this page |
192-
| `maxResults` | number | Actual number of records returned in this page |
191+
| `startPosition` | number | One-based position of the first item in this response |
192+
| `maxResults` | number | Actual number of items reported for this response |
193193
| `nextStartPosition` | number | Position to use when explicitly requesting the next page |
194194
| `hasMore` | boolean | Conservative indication that another page may exist |
195195
| `time` | string | QuickBooks response timestamp |
@@ -211,6 +211,7 @@ Create a customer in the connected QuickBooks Online company
211211
| `billingAddress` | json | No | Customer billing address |
212212
| `shippingAddress` | json | No | Customer shipping address |
213213
| `taxable` | boolean | No | Whether sales to this customer are taxable |
214+
| `requestId` | string | No | Optional Intuit idempotency request ID, up to 50 characters |
214215

215216
#### Output
216217

@@ -307,6 +308,7 @@ Create a vendor in the connected QuickBooks Online company
307308
| `printOnCheckName` | string | No | Name to print on checks |
308309
| `accountNumber` | string | No | Vendor account number |
309310
| `vendor1099` | boolean | No | Whether the vendor is tracked for 1099 reporting |
311+
| `requestId` | string | No | Optional Intuit idempotency request ID, up to 50 characters |
310312

311313
#### Output
312314

@@ -405,6 +407,7 @@ Create a Service or Non-inventory item in QuickBooks Online
405407
| `purchaseCost` | number | No | Purchase cost per unit |
406408
| `expenseAccountId` | string | No | Expense account ID used when the item is purchased |
407409
| `taxable` | boolean | No | Whether the item is taxable |
410+
| `requestId` | string | No | Optional Intuit idempotency request ID, up to 50 characters |
408411

409412
#### Output
410413

@@ -446,14 +449,15 @@ Create a Service or Non-inventory item in QuickBooks Online
446449

447450
### `quickbooks_update_item`
448451

449-
Sparse-update supported fields on an item without changing its type
452+
Sparse-update a Service or Non-inventory item in QuickBooks Online
450453

451454
#### Input
452455

453456
| Parameter | Type | Required | Description |
454457
| --------- | ---- | -------- | ----------- |
455458
| `itemId` | string | Yes | ID of the item to update |
456459
| `syncToken` | string | Yes | Current item sync token |
460+
| `itemType` | string | Yes | Current item type: service or non_inventory |
457461
| `name` | string | No | Replacement item name |
458462
| `incomeAccountId` | string | No | Replacement income account ID |
459463
| `description` | string | No | Replacement sales description |
@@ -590,7 +594,6 @@ List or read one estimate, invoice, sales receipt, payment, credit memo, or refu
590594
|`MetaData` | json | Transaction creation and update timestamps |
591595
|`CreateTime` | string | Entity creation timestamp |
592596
|`LastUpdatedTime` | string | Entity last-updated timestamp |
593-
594597
### `quickbooks_create_estimate`
595598

596599
Create an estimate with bounded item and description lines

apps/sim/blocks/blocks/quickbooks.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ const SALES_READ_OPERATION = 'quickbooks_read_sales_transactions'
1414
const CUSTOMER_OPERATIONS = ['quickbooks_create_customer', 'quickbooks_update_customer'] as const
1515
const VENDOR_OPERATIONS = ['quickbooks_create_vendor', 'quickbooks_update_vendor'] as const
1616
const ITEM_OPERATIONS = ['quickbooks_create_item', 'quickbooks_update_item'] as const
17+
const MASTER_DATA_CREATE_OPERATIONS = [
18+
'quickbooks_create_customer',
19+
'quickbooks_create_item',
20+
'quickbooks_create_vendor',
21+
] as const
1722
const SALES_DOCUMENT_CREATE_OPERATIONS = [
1823
'quickbooks_create_estimate',
1924
'quickbooks_create_invoice',
@@ -40,6 +45,7 @@ const SALES_CREATE_OPERATIONS = [
4045
...SALES_DOCUMENT_CREATE_OPERATIONS,
4146
'quickbooks_create_customer_payment',
4247
] as const
48+
const CREATE_OPERATIONS = [...MASTER_DATA_CREATE_OPERATIONS, ...SALES_CREATE_OPERATIONS] as const
4349
const SALES_UPDATE_OPERATIONS = [
4450
...SALES_DOCUMENT_UPDATE_OPERATIONS,
4551
'quickbooks_update_customer_payment',
@@ -710,7 +716,7 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
710716
title: 'Request ID',
711717
type: 'short-input',
712718
placeholder: 'Optional idempotency key (max 50 characters)',
713-
condition: { field: 'operation', value: [...SALES_CREATE_OPERATIONS] },
719+
condition: { field: 'operation', value: [...CREATE_OPERATIONS] },
714720
mode: 'advanced',
715721
},
716722
{
@@ -876,6 +882,7 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
876882
operation === 'quickbooks_create_customer' ||
877883
operation === 'quickbooks_update_customer'
878884
) {
885+
const isCreate = operation === 'quickbooks_create_customer'
879886
return {
880887
credential: oauthCredentialValue,
881888
customerId: optionalValue(params.customerId),
@@ -890,9 +897,11 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
890897
shippingAddress: parseQuickBooksAddress(params.shippingAddress, 'shippingAddress'),
891898
taxable: parseTriStateBoolean(params.taxable, 'taxable'),
892899
activeStatus: params.activeStatus ?? 'unchanged',
900+
requestId: isCreate ? optionalValue(params.requestId) : undefined,
893901
}
894902
}
895903
if (operation === 'quickbooks_create_vendor' || operation === 'quickbooks_update_vendor') {
904+
const isCreate = operation === 'quickbooks_create_vendor'
896905
return {
897906
credential: oauthCredentialValue,
898907
vendorId: optionalValue(params.vendorId),
@@ -908,9 +917,11 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
908917
accountNumber: optionalValue(params.accountNumber),
909918
vendor1099: parseTriStateBoolean(params.vendor1099, 'vendor1099'),
910919
activeStatus: params.activeStatus ?? 'unchanged',
920+
requestId: isCreate ? optionalValue(params.requestId) : undefined,
911921
}
912922
}
913923
if (operation === 'quickbooks_create_item' || operation === 'quickbooks_update_item') {
924+
const isCreate = operation === 'quickbooks_create_item'
914925
return {
915926
credential: oauthCredentialValue,
916927
itemId: optionalValue(params.itemId),
@@ -926,6 +937,7 @@ export const QuickBooksBlock: BlockConfig<QuickBooksResponse> = {
926937
expenseAccountId: optionalValue(params.expenseAccountId),
927938
taxable: parseTriStateBoolean(params.taxable, 'taxable'),
928939
activeStatus: params.activeStatus ?? 'unchanged',
940+
requestId: isCreate ? optionalValue(params.requestId) : undefined,
929941
}
930942
}
931943
return { credential: oauthCredentialValue }

apps/sim/tools/quickbooks/client.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ export interface QuickBooksCompanyInfoEnvelope {
7777
time?: string
7878
}
7979

80+
export function assertQuickBooksCompanyInfo<T extends { Id?: unknown }>(candidate: unknown): T {
81+
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
82+
throw new Error('QuickBooks CompanyInfo response is missing a valid CompanyInfo object')
83+
}
84+
const id = (candidate as { Id?: unknown }).Id
85+
if (typeof id !== 'string' || !id.trim()) {
86+
throw new Error('QuickBooks CompanyInfo response is missing a valid company Id')
87+
}
88+
return candidate as T
89+
}
90+
8091
function getQuickBooksTrackingId(headers: Headers): string | null {
8192
return (
8293
headers.get('intuit_tid') ?? headers.get('intuit-tid') ?? headers.get('x-request-id') ?? null
@@ -167,9 +178,7 @@ export async function fetchValidatedQuickBooksCompanyInfo(
167178
)
168179
}
169180

170-
if (!data.CompanyInfo || typeof data.CompanyInfo !== 'object') {
171-
throw new Error('QuickBooks company validation response is missing CompanyInfo')
172-
}
181+
assertQuickBooksCompanyInfo(data.CompanyInfo)
173182

174183
return data
175184
}

apps/sim/tools/quickbooks/create_customer.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
QUICKBOOKS_MUTATION_OUTPUTS,
1212
} from '@/tools/quickbooks/types'
1313
import {
14+
addQuickBooksRequestId,
1415
buildQuickBooksEntityUrl,
1516
getQuickBooksToolHeaders,
1617
optionalQuickBooksString,
@@ -96,6 +97,12 @@ export const quickbooksCreateCustomerTool: ToolConfig<
9697
visibility: 'user-or-llm',
9798
description: 'Whether sales to this customer are taxable',
9899
},
100+
requestId: {
101+
type: 'string',
102+
required: false,
103+
visibility: 'user-or-llm',
104+
description: 'Optional Intuit idempotency request ID, up to 50 characters',
105+
},
99106
},
100107
oauth: {
101108
required: true,
@@ -104,7 +111,11 @@ export const quickbooksCreateCustomerTool: ToolConfig<
104111
},
105112
errorExtractor: ErrorExtractorId.QUICKBOOKS_FAULT,
106113
request: {
107-
url: (params) => buildQuickBooksEntityUrl(params.realmId, 'customer').toString(),
114+
url: (params) =>
115+
addQuickBooksRequestId(
116+
buildQuickBooksEntityUrl(params.realmId, 'customer'),
117+
params.requestId
118+
).toString(),
108119
method: 'POST',
109120
headers: (params) => getQuickBooksToolHeaders(params.accessToken, 'application/json'),
110121
body: (params) =>

apps/sim/tools/quickbooks/create_item.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
} from '@/tools/quickbooks/types'
99
import { QUICKBOOKS_ITEM_PROPERTIES, QUICKBOOKS_MUTATION_OUTPUTS } from '@/tools/quickbooks/types'
1010
import {
11+
addQuickBooksRequestId,
1112
buildQuickBooksEntityUrl,
1213
getQuickBooksToolHeaders,
1314
optionalQuickBooksString,
@@ -94,6 +95,12 @@ export const quickbooksCreateItemTool: ToolConfig<
9495
visibility: 'user-or-llm',
9596
description: 'Whether the item is taxable',
9697
},
98+
requestId: {
99+
type: 'string',
100+
required: false,
101+
visibility: 'user-or-llm',
102+
description: 'Optional Intuit idempotency request ID, up to 50 characters',
103+
},
97104
},
98105
oauth: {
99106
required: true,
@@ -102,7 +109,11 @@ export const quickbooksCreateItemTool: ToolConfig<
102109
},
103110
errorExtractor: ErrorExtractorId.QUICKBOOKS_FAULT,
104111
request: {
105-
url: (params) => buildQuickBooksEntityUrl(params.realmId, 'item').toString(),
112+
url: (params) =>
113+
addQuickBooksRequestId(
114+
buildQuickBooksEntityUrl(params.realmId, 'item'),
115+
params.requestId
116+
).toString(),
106117
method: 'POST',
107118
headers: (params) => getQuickBooksToolHeaders(params.accessToken, 'application/json'),
108119
body: (params) => {

apps/sim/tools/quickbooks/create_vendor.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
} from '@/tools/quickbooks/types'
99
import { QUICKBOOKS_MUTATION_OUTPUTS, QUICKBOOKS_VENDOR_PROPERTIES } from '@/tools/quickbooks/types'
1010
import {
11+
addQuickBooksRequestId,
1112
buildQuickBooksEntityUrl,
1213
getQuickBooksToolHeaders,
1314
optionalQuickBooksString,
@@ -100,6 +101,12 @@ export const quickbooksCreateVendorTool: ToolConfig<
100101
visibility: 'user-or-llm',
101102
description: 'Whether the vendor is tracked for 1099 reporting',
102103
},
104+
requestId: {
105+
type: 'string',
106+
required: false,
107+
visibility: 'user-or-llm',
108+
description: 'Optional Intuit idempotency request ID, up to 50 characters',
109+
},
103110
},
104111
oauth: {
105112
required: true,
@@ -108,7 +115,11 @@ export const quickbooksCreateVendorTool: ToolConfig<
108115
},
109116
errorExtractor: ErrorExtractorId.QUICKBOOKS_FAULT,
110117
request: {
111-
url: (params) => buildQuickBooksEntityUrl(params.realmId, 'vendor').toString(),
118+
url: (params) =>
119+
addQuickBooksRequestId(
120+
buildQuickBooksEntityUrl(params.realmId, 'vendor'),
121+
params.requestId
122+
).toString(),
112123
method: 'POST',
113124
headers: (params) => getQuickBooksToolHeaders(params.accessToken, 'application/json'),
114125
body: (params) =>

apps/sim/tools/quickbooks/get_company_info.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ErrorExtractorId } from '@/tools/error-extractors'
22
import {
3+
assertQuickBooksCompanyInfo,
34
buildQuickBooksCompanyUrl,
45
normalizeQuickBooksRealmId,
56
QUICKBOOKS_MAX_RESPONSE_BYTES,
@@ -65,13 +66,11 @@ export const quickbooksGetCompanyInfoTool: ToolConfig<
6566
response,
6667
'QuickBooks CompanyInfo response'
6768
)
68-
if (!data.CompanyInfo || typeof data.CompanyInfo !== 'object') {
69-
throw new Error('QuickBooks CompanyInfo response is missing CompanyInfo')
70-
}
69+
const company = assertQuickBooksCompanyInfo<QuickBooksCompanyInfo>(data.CompanyInfo)
7170
return {
7271
success: true,
7372
output: {
74-
company: data.CompanyInfo,
73+
company,
7574
time: typeof data.time === 'string' ? data.time : null,
7675
},
7776
}

0 commit comments

Comments
 (0)