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