@panoptic-it-solutions/quickbooks-client 0.1.4 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +343 -89
- package/dist/index.d.ts +343 -89
- package/dist/index.js +376 -36
- package/dist/index.mjs +376 -36
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -105,7 +105,9 @@ function generateAuthUrl(config, state) {
|
|
|
105
105
|
}
|
|
106
106
|
async function exchangeCodeForTokens(config, code, realmId) {
|
|
107
107
|
const env = config.environment || "production";
|
|
108
|
-
const credentials = Buffer.from(
|
|
108
|
+
const credentials = Buffer.from(
|
|
109
|
+
`${config.clientId}:${config.clientSecret}`
|
|
110
|
+
).toString("base64");
|
|
109
111
|
const response = await fetch(ENDPOINTS[env].token, {
|
|
110
112
|
method: "POST",
|
|
111
113
|
headers: {
|
|
@@ -139,7 +141,9 @@ async function exchangeCodeForTokens(config, code, realmId) {
|
|
|
139
141
|
}
|
|
140
142
|
async function refreshTokens(config, refreshToken, realmId) {
|
|
141
143
|
const env = config.environment || "production";
|
|
142
|
-
const credentials = Buffer.from(
|
|
144
|
+
const credentials = Buffer.from(
|
|
145
|
+
`${config.clientId}:${config.clientSecret}`
|
|
146
|
+
).toString("base64");
|
|
143
147
|
const response = await fetch(ENDPOINTS[env].token, {
|
|
144
148
|
method: "POST",
|
|
145
149
|
headers: {
|
|
@@ -172,7 +176,9 @@ async function refreshTokens(config, refreshToken, realmId) {
|
|
|
172
176
|
}
|
|
173
177
|
async function revokeTokens(config, token) {
|
|
174
178
|
const env = config.environment || "production";
|
|
175
|
-
const credentials = Buffer.from(
|
|
179
|
+
const credentials = Buffer.from(
|
|
180
|
+
`${config.clientId}:${config.clientSecret}`
|
|
181
|
+
).toString("base64");
|
|
176
182
|
const response = await fetch(ENDPOINTS[env].revoke, {
|
|
177
183
|
method: "POST",
|
|
178
184
|
headers: {
|
|
@@ -202,7 +208,9 @@ function isTokenExpired(expiresAt, bufferSeconds = 300) {
|
|
|
202
208
|
function generateState() {
|
|
203
209
|
const array = new Uint8Array(16);
|
|
204
210
|
crypto.getRandomValues(array);
|
|
205
|
-
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
211
|
+
return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
|
|
212
|
+
""
|
|
213
|
+
);
|
|
206
214
|
}
|
|
207
215
|
|
|
208
216
|
// src/client.ts
|
|
@@ -219,24 +227,38 @@ var QuickBooksClient = class {
|
|
|
219
227
|
tokenStore;
|
|
220
228
|
requestTimestamps = [];
|
|
221
229
|
onLog;
|
|
230
|
+
minorVersion;
|
|
222
231
|
constructor(options) {
|
|
223
232
|
this.validateConfig(options);
|
|
224
233
|
this.config = options;
|
|
225
234
|
this.tokenStore = options.tokenStore;
|
|
226
235
|
this.onLog = options.onLog;
|
|
236
|
+
this.minorVersion = options.minorVersion;
|
|
227
237
|
}
|
|
228
238
|
validateConfig(options) {
|
|
229
239
|
if (!options.clientId) {
|
|
230
|
-
throw new QuickBooksError(
|
|
240
|
+
throw new QuickBooksError(
|
|
241
|
+
"clientId is required",
|
|
242
|
+
QB_ERROR_CODES.INVALID_CONFIG
|
|
243
|
+
);
|
|
231
244
|
}
|
|
232
245
|
if (!options.clientSecret) {
|
|
233
|
-
throw new QuickBooksError(
|
|
246
|
+
throw new QuickBooksError(
|
|
247
|
+
"clientSecret is required",
|
|
248
|
+
QB_ERROR_CODES.INVALID_CONFIG
|
|
249
|
+
);
|
|
234
250
|
}
|
|
235
251
|
if (!options.redirectUri) {
|
|
236
|
-
throw new QuickBooksError(
|
|
252
|
+
throw new QuickBooksError(
|
|
253
|
+
"redirectUri is required",
|
|
254
|
+
QB_ERROR_CODES.INVALID_CONFIG
|
|
255
|
+
);
|
|
237
256
|
}
|
|
238
257
|
if (!options.tokenStore) {
|
|
239
|
-
throw new QuickBooksError(
|
|
258
|
+
throw new QuickBooksError(
|
|
259
|
+
"tokenStore is required",
|
|
260
|
+
QB_ERROR_CODES.INVALID_CONFIG
|
|
261
|
+
);
|
|
240
262
|
}
|
|
241
263
|
}
|
|
242
264
|
log(level, message, data) {
|
|
@@ -244,6 +266,14 @@ var QuickBooksClient = class {
|
|
|
244
266
|
this.onLog(level, message, data);
|
|
245
267
|
}
|
|
246
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Append minorversion query param to a URL if configured
|
|
271
|
+
*/
|
|
272
|
+
appendMinorVersion(url) {
|
|
273
|
+
if (this.minorVersion == null) return url;
|
|
274
|
+
const separator = url.includes("?") ? "&" : "?";
|
|
275
|
+
return `${url}${separator}minorversion=${this.minorVersion}`;
|
|
276
|
+
}
|
|
247
277
|
/**
|
|
248
278
|
* Rate limiting - ensures we don't exceed 500 requests/minute
|
|
249
279
|
*/
|
|
@@ -300,7 +330,9 @@ var QuickBooksClient = class {
|
|
|
300
330
|
const tokens = await this.getValidTokens();
|
|
301
331
|
const env = this.config.environment || "production";
|
|
302
332
|
const baseUrl = API_BASE[env];
|
|
303
|
-
const url =
|
|
333
|
+
const url = this.appendMinorVersion(
|
|
334
|
+
`${baseUrl}/v3/company/${tokens.realm_id}${endpoint}`
|
|
335
|
+
);
|
|
304
336
|
this.log("debug", `${method} ${endpoint}`, { body });
|
|
305
337
|
const headers = {
|
|
306
338
|
Authorization: `Bearer ${tokens.access_token}`,
|
|
@@ -317,8 +349,11 @@ var QuickBooksClient = class {
|
|
|
317
349
|
});
|
|
318
350
|
if (response.status === 429 && retryCount < MAX_RETRIES) {
|
|
319
351
|
const retryAfter = response.headers.get("Retry-After");
|
|
320
|
-
const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : INITIAL_RETRY_DELAY_MS *
|
|
321
|
-
this.log(
|
|
352
|
+
const delay = retryAfter ? parseInt(retryAfter, 10) * 1e3 : INITIAL_RETRY_DELAY_MS * 2 ** retryCount;
|
|
353
|
+
this.log(
|
|
354
|
+
"warn",
|
|
355
|
+
`Rate limited, retrying in ${delay}ms (attempt ${retryCount + 1})`
|
|
356
|
+
);
|
|
322
357
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
323
358
|
return this.request(method, endpoint, body, retryCount + 1);
|
|
324
359
|
}
|
|
@@ -351,7 +386,9 @@ var QuickBooksClient = class {
|
|
|
351
386
|
const tokens = await this.getValidTokens();
|
|
352
387
|
const env = this.config.environment || "production";
|
|
353
388
|
const baseUrl = API_BASE[env];
|
|
354
|
-
const url =
|
|
389
|
+
const url = this.appendMinorVersion(
|
|
390
|
+
`${baseUrl}/v3/company/${tokens.realm_id}/query`
|
|
391
|
+
);
|
|
355
392
|
await this.checkRateLimit();
|
|
356
393
|
const fetchResponse = await fetch(url, {
|
|
357
394
|
method: "POST",
|
|
@@ -364,7 +401,10 @@ var QuickBooksClient = class {
|
|
|
364
401
|
});
|
|
365
402
|
if (!fetchResponse.ok) {
|
|
366
403
|
const errorData = await fetchResponse.json().catch(() => ({}));
|
|
367
|
-
throw handleQuickBooksError({
|
|
404
|
+
throw handleQuickBooksError({
|
|
405
|
+
status: fetchResponse.status,
|
|
406
|
+
...errorData
|
|
407
|
+
});
|
|
368
408
|
}
|
|
369
409
|
const data = await fetchResponse.json();
|
|
370
410
|
const keys = Object.keys(data.QueryResponse).filter(
|
|
@@ -387,7 +427,9 @@ var QuickBooksClient = class {
|
|
|
387
427
|
const tokens = await this.getValidTokens();
|
|
388
428
|
const env = this.config.environment || "production";
|
|
389
429
|
const baseUrl = API_BASE[env];
|
|
390
|
-
const url =
|
|
430
|
+
const url = this.appendMinorVersion(
|
|
431
|
+
`${baseUrl}/v3/company/${tokens.realm_id}/query`
|
|
432
|
+
);
|
|
391
433
|
await this.checkRateLimit();
|
|
392
434
|
const fetchResponse = await fetch(url, {
|
|
393
435
|
method: "POST",
|
|
@@ -400,7 +442,10 @@ var QuickBooksClient = class {
|
|
|
400
442
|
});
|
|
401
443
|
if (!fetchResponse.ok) {
|
|
402
444
|
const errorData = await fetchResponse.json().catch(() => ({}));
|
|
403
|
-
throw handleQuickBooksError({
|
|
445
|
+
throw handleQuickBooksError({
|
|
446
|
+
status: fetchResponse.status,
|
|
447
|
+
...errorData
|
|
448
|
+
});
|
|
404
449
|
}
|
|
405
450
|
const data = await fetchResponse.json();
|
|
406
451
|
const keys = Object.keys(data.QueryResponse).filter(
|
|
@@ -409,7 +454,10 @@ var QuickBooksClient = class {
|
|
|
409
454
|
const entityKey = keys[0];
|
|
410
455
|
const pageResults = entityKey ? data.QueryResponse[entityKey] : [];
|
|
411
456
|
allResults.push(...pageResults);
|
|
412
|
-
this.log(
|
|
457
|
+
this.log(
|
|
458
|
+
"debug",
|
|
459
|
+
`Fetched page at position ${startPosition}, got ${pageResults.length} results (total: ${allResults.length})`
|
|
460
|
+
);
|
|
413
461
|
if (pageResults.length < maxResults) {
|
|
414
462
|
break;
|
|
415
463
|
}
|
|
@@ -421,23 +469,34 @@ var QuickBooksClient = class {
|
|
|
421
469
|
// Invoice Methods
|
|
422
470
|
// ============================================
|
|
423
471
|
async getInvoice(id) {
|
|
424
|
-
const response = await this.request(
|
|
472
|
+
const response = await this.request(
|
|
473
|
+
"GET",
|
|
474
|
+
`/invoice/${id}`
|
|
475
|
+
);
|
|
425
476
|
return response.Invoice;
|
|
426
477
|
}
|
|
427
478
|
async getInvoices(where) {
|
|
428
479
|
const sql = where ? `SELECT * FROM Invoice WHERE ${where}` : "SELECT * FROM Invoice";
|
|
429
|
-
return this.
|
|
480
|
+
return this.queryAll(sql);
|
|
430
481
|
}
|
|
431
482
|
async createInvoice(invoice) {
|
|
432
|
-
const response = await this.request(
|
|
483
|
+
const response = await this.request(
|
|
484
|
+
"POST",
|
|
485
|
+
"/invoice",
|
|
486
|
+
invoice
|
|
487
|
+
);
|
|
433
488
|
return response.Invoice;
|
|
434
489
|
}
|
|
435
490
|
async updateInvoice(invoice) {
|
|
436
|
-
const response = await this.request(
|
|
491
|
+
const response = await this.request(
|
|
492
|
+
"POST",
|
|
493
|
+
"/invoice",
|
|
494
|
+
invoice
|
|
495
|
+
);
|
|
437
496
|
return response.Invoice;
|
|
438
497
|
}
|
|
439
498
|
async deleteInvoice(id, syncToken) {
|
|
440
|
-
await this.request("POST", "/invoice", {
|
|
499
|
+
await this.request("POST", "/invoice?operation=delete", {
|
|
441
500
|
Id: id,
|
|
442
501
|
SyncToken: syncToken
|
|
443
502
|
});
|
|
@@ -446,64 +505,96 @@ var QuickBooksClient = class {
|
|
|
446
505
|
// Customer Methods
|
|
447
506
|
// ============================================
|
|
448
507
|
async getCustomer(id) {
|
|
449
|
-
const response = await this.request(
|
|
508
|
+
const response = await this.request(
|
|
509
|
+
"GET",
|
|
510
|
+
`/customer/${id}`
|
|
511
|
+
);
|
|
450
512
|
return response.Customer;
|
|
451
513
|
}
|
|
452
514
|
async getCustomers(where) {
|
|
453
515
|
const sql = where ? `SELECT * FROM Customer WHERE ${where}` : "SELECT * FROM Customer";
|
|
454
|
-
return this.
|
|
516
|
+
return this.queryAll(sql);
|
|
455
517
|
}
|
|
456
518
|
async createCustomer(customer) {
|
|
457
|
-
const response = await this.request(
|
|
519
|
+
const response = await this.request(
|
|
520
|
+
"POST",
|
|
521
|
+
"/customer",
|
|
522
|
+
customer
|
|
523
|
+
);
|
|
458
524
|
return response.Customer;
|
|
459
525
|
}
|
|
460
526
|
async updateCustomer(customer) {
|
|
461
|
-
const response = await this.request(
|
|
527
|
+
const response = await this.request(
|
|
528
|
+
"POST",
|
|
529
|
+
"/customer",
|
|
530
|
+
customer
|
|
531
|
+
);
|
|
462
532
|
return response.Customer;
|
|
463
533
|
}
|
|
464
534
|
// ============================================
|
|
465
535
|
// Payment Methods
|
|
466
536
|
// ============================================
|
|
467
537
|
async getPayment(id) {
|
|
468
|
-
const response = await this.request(
|
|
538
|
+
const response = await this.request(
|
|
539
|
+
"GET",
|
|
540
|
+
`/payment/${id}`
|
|
541
|
+
);
|
|
469
542
|
return response.Payment;
|
|
470
543
|
}
|
|
471
544
|
async getPayments(where) {
|
|
472
545
|
const sql = where ? `SELECT * FROM Payment WHERE ${where}` : "SELECT * FROM Payment";
|
|
473
|
-
return this.
|
|
546
|
+
return this.queryAll(sql);
|
|
474
547
|
}
|
|
475
548
|
async createPayment(payment) {
|
|
476
|
-
const response = await this.request(
|
|
549
|
+
const response = await this.request(
|
|
550
|
+
"POST",
|
|
551
|
+
"/payment",
|
|
552
|
+
payment
|
|
553
|
+
);
|
|
477
554
|
return response.Payment;
|
|
478
555
|
}
|
|
479
556
|
// ============================================
|
|
480
557
|
// Account Methods
|
|
481
558
|
// ============================================
|
|
482
559
|
async getAccount(id) {
|
|
483
|
-
const response = await this.request(
|
|
560
|
+
const response = await this.request(
|
|
561
|
+
"GET",
|
|
562
|
+
`/account/${id}`
|
|
563
|
+
);
|
|
484
564
|
return response.Account;
|
|
485
565
|
}
|
|
486
566
|
async getAccounts(where) {
|
|
487
567
|
const sql = where ? `SELECT * FROM Account WHERE ${where}` : "SELECT * FROM Account WHERE Active = true";
|
|
488
|
-
return this.
|
|
568
|
+
return this.queryAll(sql);
|
|
489
569
|
}
|
|
490
570
|
// ============================================
|
|
491
571
|
// Vendor Methods
|
|
492
572
|
// ============================================
|
|
493
573
|
async getVendor(id) {
|
|
494
|
-
const response = await this.request(
|
|
574
|
+
const response = await this.request(
|
|
575
|
+
"GET",
|
|
576
|
+
`/vendor/${id}`
|
|
577
|
+
);
|
|
495
578
|
return response.Vendor;
|
|
496
579
|
}
|
|
497
580
|
async getVendors(where) {
|
|
498
581
|
const sql = where ? `SELECT * FROM Vendor WHERE ${where}` : "SELECT * FROM Vendor";
|
|
499
|
-
return this.
|
|
582
|
+
return this.queryAll(sql);
|
|
500
583
|
}
|
|
501
584
|
async createVendor(vendor) {
|
|
502
|
-
const response = await this.request(
|
|
585
|
+
const response = await this.request(
|
|
586
|
+
"POST",
|
|
587
|
+
"/vendor",
|
|
588
|
+
vendor
|
|
589
|
+
);
|
|
503
590
|
return response.Vendor;
|
|
504
591
|
}
|
|
505
592
|
async updateVendor(vendor) {
|
|
506
|
-
const response = await this.request(
|
|
593
|
+
const response = await this.request(
|
|
594
|
+
"POST",
|
|
595
|
+
"/vendor",
|
|
596
|
+
vendor
|
|
597
|
+
);
|
|
507
598
|
return response.Vendor;
|
|
508
599
|
}
|
|
509
600
|
// ============================================
|
|
@@ -515,7 +606,7 @@ var QuickBooksClient = class {
|
|
|
515
606
|
}
|
|
516
607
|
async getBills(where) {
|
|
517
608
|
const sql = where ? `SELECT * FROM Bill WHERE ${where}` : "SELECT * FROM Bill";
|
|
518
|
-
return this.
|
|
609
|
+
return this.queryAll(sql);
|
|
519
610
|
}
|
|
520
611
|
async createBill(bill) {
|
|
521
612
|
const response = await this.request("POST", "/bill", bill);
|
|
@@ -525,6 +616,148 @@ var QuickBooksClient = class {
|
|
|
525
616
|
const response = await this.request("POST", "/bill", bill);
|
|
526
617
|
return response.Bill;
|
|
527
618
|
}
|
|
619
|
+
async deleteBill(id, syncToken) {
|
|
620
|
+
await this.request("POST", "/bill?operation=delete", {
|
|
621
|
+
Id: id,
|
|
622
|
+
SyncToken: syncToken
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
// ============================================
|
|
626
|
+
// BillPayment Methods
|
|
627
|
+
// ============================================
|
|
628
|
+
async getBillPayment(id) {
|
|
629
|
+
const response = await this.request(
|
|
630
|
+
"GET",
|
|
631
|
+
`/billpayment/${id}`
|
|
632
|
+
);
|
|
633
|
+
return response.BillPayment;
|
|
634
|
+
}
|
|
635
|
+
async getBillPayments(where) {
|
|
636
|
+
const sql = where ? `SELECT * FROM BillPayment WHERE ${where}` : "SELECT * FROM BillPayment";
|
|
637
|
+
return this.queryAll(sql);
|
|
638
|
+
}
|
|
639
|
+
async createBillPayment(billPayment) {
|
|
640
|
+
const response = await this.request(
|
|
641
|
+
"POST",
|
|
642
|
+
"/billpayment",
|
|
643
|
+
billPayment
|
|
644
|
+
);
|
|
645
|
+
return response.BillPayment;
|
|
646
|
+
}
|
|
647
|
+
async updateBillPayment(billPayment) {
|
|
648
|
+
const response = await this.request(
|
|
649
|
+
"POST",
|
|
650
|
+
"/billpayment",
|
|
651
|
+
billPayment
|
|
652
|
+
);
|
|
653
|
+
return response.BillPayment;
|
|
654
|
+
}
|
|
655
|
+
async deleteBillPayment(id, syncToken) {
|
|
656
|
+
await this.request("POST", "/billpayment?operation=delete", {
|
|
657
|
+
Id: id,
|
|
658
|
+
SyncToken: syncToken
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
// ============================================
|
|
662
|
+
// CreditMemo Methods (customer-facing credit notes)
|
|
663
|
+
// ============================================
|
|
664
|
+
async getCreditMemo(id) {
|
|
665
|
+
const response = await this.request(
|
|
666
|
+
"GET",
|
|
667
|
+
`/creditmemo/${id}`
|
|
668
|
+
);
|
|
669
|
+
return response.CreditMemo;
|
|
670
|
+
}
|
|
671
|
+
async getCreditMemos(where) {
|
|
672
|
+
const sql = where ? `SELECT * FROM CreditMemo WHERE ${where}` : "SELECT * FROM CreditMemo";
|
|
673
|
+
return this.queryAll(sql);
|
|
674
|
+
}
|
|
675
|
+
async createCreditMemo(creditMemo) {
|
|
676
|
+
const response = await this.request(
|
|
677
|
+
"POST",
|
|
678
|
+
"/creditmemo",
|
|
679
|
+
creditMemo
|
|
680
|
+
);
|
|
681
|
+
return response.CreditMemo;
|
|
682
|
+
}
|
|
683
|
+
async updateCreditMemo(creditMemo) {
|
|
684
|
+
const response = await this.request(
|
|
685
|
+
"POST",
|
|
686
|
+
"/creditmemo",
|
|
687
|
+
creditMemo
|
|
688
|
+
);
|
|
689
|
+
return response.CreditMemo;
|
|
690
|
+
}
|
|
691
|
+
async deleteCreditMemo(id, syncToken) {
|
|
692
|
+
await this.request("POST", "/creditmemo?operation=delete", {
|
|
693
|
+
Id: id,
|
|
694
|
+
SyncToken: syncToken
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
// ============================================
|
|
698
|
+
// VendorCredit Methods (supplier-side credit notes)
|
|
699
|
+
// ============================================
|
|
700
|
+
async getVendorCredit(id) {
|
|
701
|
+
const response = await this.request(
|
|
702
|
+
"GET",
|
|
703
|
+
`/vendorcredit/${id}`
|
|
704
|
+
);
|
|
705
|
+
return response.VendorCredit;
|
|
706
|
+
}
|
|
707
|
+
async getVendorCredits(where) {
|
|
708
|
+
const sql = where ? `SELECT * FROM VendorCredit WHERE ${where}` : "SELECT * FROM VendorCredit";
|
|
709
|
+
return this.queryAll(sql);
|
|
710
|
+
}
|
|
711
|
+
async createVendorCredit(vendorCredit) {
|
|
712
|
+
const response = await this.request(
|
|
713
|
+
"POST",
|
|
714
|
+
"/vendorcredit",
|
|
715
|
+
vendorCredit
|
|
716
|
+
);
|
|
717
|
+
return response.VendorCredit;
|
|
718
|
+
}
|
|
719
|
+
async updateVendorCredit(vendorCredit) {
|
|
720
|
+
const response = await this.request(
|
|
721
|
+
"POST",
|
|
722
|
+
"/vendorcredit",
|
|
723
|
+
vendorCredit
|
|
724
|
+
);
|
|
725
|
+
return response.VendorCredit;
|
|
726
|
+
}
|
|
727
|
+
async deleteVendorCredit(id, syncToken) {
|
|
728
|
+
await this.request("POST", "/vendorcredit?operation=delete", {
|
|
729
|
+
Id: id,
|
|
730
|
+
SyncToken: syncToken
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
// ============================================
|
|
734
|
+
// TaxCode Methods (read-only in QBO API)
|
|
735
|
+
// ============================================
|
|
736
|
+
async getTaxCode(id) {
|
|
737
|
+
const response = await this.request(
|
|
738
|
+
"GET",
|
|
739
|
+
`/taxcode/${id}`
|
|
740
|
+
);
|
|
741
|
+
return response.TaxCode;
|
|
742
|
+
}
|
|
743
|
+
async getTaxCodes(where) {
|
|
744
|
+
const sql = where ? `SELECT * FROM TaxCode WHERE ${where}` : "SELECT * FROM TaxCode";
|
|
745
|
+
return this.queryAll(sql);
|
|
746
|
+
}
|
|
747
|
+
// ============================================
|
|
748
|
+
// TaxRate Methods (read-only in QBO API)
|
|
749
|
+
// ============================================
|
|
750
|
+
async getTaxRate(id) {
|
|
751
|
+
const response = await this.request(
|
|
752
|
+
"GET",
|
|
753
|
+
`/taxrate/${id}`
|
|
754
|
+
);
|
|
755
|
+
return response.TaxRate;
|
|
756
|
+
}
|
|
757
|
+
async getTaxRates(where) {
|
|
758
|
+
const sql = where ? `SELECT * FROM TaxRate WHERE ${where}` : "SELECT * FROM TaxRate";
|
|
759
|
+
return this.queryAll(sql);
|
|
760
|
+
}
|
|
528
761
|
// ============================================
|
|
529
762
|
// Item Methods
|
|
530
763
|
// ============================================
|
|
@@ -534,7 +767,7 @@ var QuickBooksClient = class {
|
|
|
534
767
|
}
|
|
535
768
|
async getItems(where) {
|
|
536
769
|
const sql = where ? `SELECT * FROM Item WHERE ${where}` : "SELECT * FROM Item WHERE Active = true";
|
|
537
|
-
return this.
|
|
770
|
+
return this.queryAll(sql);
|
|
538
771
|
}
|
|
539
772
|
async createItem(item) {
|
|
540
773
|
const response = await this.request("POST", "/item", item);
|
|
@@ -545,6 +778,113 @@ var QuickBooksClient = class {
|
|
|
545
778
|
return response.Item;
|
|
546
779
|
}
|
|
547
780
|
// ============================================
|
|
781
|
+
// Attachable Methods
|
|
782
|
+
// ============================================
|
|
783
|
+
async getAttachable(id) {
|
|
784
|
+
const response = await this.request(
|
|
785
|
+
"GET",
|
|
786
|
+
`/attachable/${id}`
|
|
787
|
+
);
|
|
788
|
+
return response.Attachable;
|
|
789
|
+
}
|
|
790
|
+
async getAttachables(where) {
|
|
791
|
+
const sql = where ? `SELECT * FROM Attachable WHERE ${where}` : "SELECT * FROM Attachable";
|
|
792
|
+
return this.queryAll(sql);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Upload a file and attach it to an entity.
|
|
796
|
+
* Uses multipart/form-data — the QBO upload endpoint differs from standard CRUD.
|
|
797
|
+
*/
|
|
798
|
+
async uploadAttachable(file, fileName, contentType, attachTo) {
|
|
799
|
+
await this.checkRateLimit();
|
|
800
|
+
const tokens = await this.getValidTokens();
|
|
801
|
+
const env = this.config.environment || "production";
|
|
802
|
+
const baseUrl = API_BASE[env];
|
|
803
|
+
const url = this.appendMinorVersion(
|
|
804
|
+
`${baseUrl}/v3/company/${tokens.realm_id}/upload`
|
|
805
|
+
);
|
|
806
|
+
const metadata = {
|
|
807
|
+
FileName: fileName,
|
|
808
|
+
ContentType: contentType
|
|
809
|
+
};
|
|
810
|
+
if (attachTo) {
|
|
811
|
+
metadata.AttachableRef = [
|
|
812
|
+
{
|
|
813
|
+
EntityRef: {
|
|
814
|
+
value: attachTo.entityId,
|
|
815
|
+
name: attachTo.entityType
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
];
|
|
819
|
+
}
|
|
820
|
+
const formData = new FormData();
|
|
821
|
+
formData.append(
|
|
822
|
+
"file_metadata_0",
|
|
823
|
+
new Blob([JSON.stringify(metadata)], { type: "application/json" })
|
|
824
|
+
);
|
|
825
|
+
const fileBlob = file instanceof Buffer ? new Blob([file], { type: contentType }) : file;
|
|
826
|
+
formData.append("file_content_0", fileBlob, fileName);
|
|
827
|
+
try {
|
|
828
|
+
const response = await fetch(url, {
|
|
829
|
+
method: "POST",
|
|
830
|
+
headers: {
|
|
831
|
+
Authorization: `Bearer ${tokens.access_token}`,
|
|
832
|
+
Accept: "application/json"
|
|
833
|
+
},
|
|
834
|
+
body: formData
|
|
835
|
+
});
|
|
836
|
+
if (!response.ok) {
|
|
837
|
+
const errorData = await response.json().catch(() => ({}));
|
|
838
|
+
throw { status: response.status, ...errorData };
|
|
839
|
+
}
|
|
840
|
+
const data = await response.json();
|
|
841
|
+
return data.AttachableResponse[0].Attachable;
|
|
842
|
+
} catch (error) {
|
|
843
|
+
throw handleQuickBooksError(error);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
async updateAttachable(attachable) {
|
|
847
|
+
const response = await this.request(
|
|
848
|
+
"POST",
|
|
849
|
+
"/attachable",
|
|
850
|
+
attachable
|
|
851
|
+
);
|
|
852
|
+
return response.Attachable;
|
|
853
|
+
}
|
|
854
|
+
async deleteAttachable(id, syncToken) {
|
|
855
|
+
await this.request("POST", "/attachable?operation=delete", {
|
|
856
|
+
Id: id,
|
|
857
|
+
SyncToken: syncToken
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
// ============================================
|
|
861
|
+
// Batch Operations
|
|
862
|
+
// ============================================
|
|
863
|
+
/**
|
|
864
|
+
* Execute a batch of up to 30 operations in a single API call.
|
|
865
|
+
* Each item needs a unique bId and the entity payload.
|
|
866
|
+
*
|
|
867
|
+
* @example
|
|
868
|
+
* ```ts
|
|
869
|
+
* const results = await client.batch([
|
|
870
|
+
* { bId: "1", operation: "create", Bill: { VendorRef: { value: "1" }, Line: [...] } },
|
|
871
|
+
* { bId: "2", operation: "query", optionsData: "SELECT * FROM Vendor WHERE Id = '1'" },
|
|
872
|
+
* ]);
|
|
873
|
+
* ```
|
|
874
|
+
*/
|
|
875
|
+
async batch(items) {
|
|
876
|
+
if (items.length > 30) {
|
|
877
|
+
throw new QuickBooksError(
|
|
878
|
+
"Batch operations are limited to 30 items per request",
|
|
879
|
+
QB_ERROR_CODES.INVALID_CONFIG
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
const response = await this.request("POST", "/batch", {
|
|
883
|
+
BatchItemRequest: items
|
|
884
|
+
});
|
|
885
|
+
return response;
|
|
886
|
+
}
|
|
887
|
+
// ============================================
|
|
548
888
|
// Utility Methods
|
|
549
889
|
// ============================================
|
|
550
890
|
/**
|
package/package.json
CHANGED