@happyvertical/accounting 0.80.0 → 0.80.2

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.
@@ -0,0 +1,773 @@
1
+ import { createHmac } from "node:crypto";
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esmMin = (fn, res, err) => () => {
10
+ if (err) throw err[0];
11
+ try {
12
+ return fn && (res = fn(fn = 0)), res;
13
+ } catch (e) {
14
+ throw err = [e], e;
15
+ }
16
+ };
17
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
18
+ var __exportAll = (all, no_symbols) => {
19
+ let target = {};
20
+ for (var name in all) __defProp(target, name, {
21
+ get: all[name],
22
+ enumerable: true
23
+ });
24
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
25
+ return target;
26
+ };
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
29
+ key = keys[i];
30
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
31
+ get: ((k) => from[k]).bind(null, key),
32
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
33
+ });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
38
+ value: mod,
39
+ enumerable: true
40
+ }) : target, mod));
41
+ var __toCommonJS = (mod) => __hasOwnProp.call(mod, "module.exports") ? mod["module.exports"] : __copyProps(__defProp({}, "__esModule", { value: true }), mod);
42
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
43
+ if (typeof require !== "undefined") return require.apply(this, arguments);
44
+ throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
45
+ });
46
+ //#endregion
47
+ //#region src/providers/quickbooks/index.ts
48
+ /**
49
+ * QuickBooks Online Provider
50
+ *
51
+ * Implements the AccountingProvider interface for QuickBooks Online.
52
+ * Uses the Intuit OAuth2 library for authentication and the QBO API for operations.
53
+ */
54
+ /**
55
+ * Format a Date to YYYY-MM-DD in local timezone (not UTC)
56
+ * This prevents timezone conversion issues where dates shift by a day
57
+ */
58
+ function formatLocalDate(date) {
59
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
60
+ }
61
+ /**
62
+ * Sleep for a given number of milliseconds
63
+ */
64
+ function sleep(ms) {
65
+ return new Promise((resolve) => setTimeout(resolve, ms));
66
+ }
67
+ /**
68
+ * Compare two values for equality (handles dates, objects, etc.)
69
+ */
70
+ function valuesEqual(a, b) {
71
+ if (a === b) return true;
72
+ if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
73
+ if (typeof a === "number" && typeof b === "number") return Math.abs(a - b) < .01;
74
+ return false;
75
+ }
76
+ /**
77
+ * QuickBooks Online accounting provider
78
+ */
79
+ var QuickBooksProvider = class {
80
+ type = "quickbooks";
81
+ options;
82
+ accessToken = null;
83
+ tokenExpiresAt = null;
84
+ customers;
85
+ invoices;
86
+ vendors;
87
+ bills;
88
+ payments;
89
+ audit;
90
+ webhooks;
91
+ constructor(options) {
92
+ this.options = {
93
+ timeout: 3e4,
94
+ maxRetries: 3,
95
+ ...options
96
+ };
97
+ this.customers = new QuickBooksCustomerOperations(this);
98
+ this.invoices = new QuickBooksInvoiceOperations(this);
99
+ this.vendors = new QuickBooksVendorOperations(this);
100
+ this.bills = new QuickBooksBillOperations(this);
101
+ this.payments = new QuickBooksPaymentOperations(this);
102
+ this.audit = new QuickBooksAuditOperations(this);
103
+ this.webhooks = new QuickBooksWebhookOperations(this.options);
104
+ }
105
+ /**
106
+ * Get the QBO API base URL
107
+ */
108
+ get baseUrl() {
109
+ return this.options.environment === "production" ? "https://quickbooks.api.intuit.com" : "https://sandbox-quickbooks.api.intuit.com";
110
+ }
111
+ /**
112
+ * Get the company/realm ID
113
+ */
114
+ get realmId() {
115
+ return this.options.realmId;
116
+ }
117
+ /**
118
+ * Ensure we have a valid access token, refreshing if needed
119
+ */
120
+ async ensureAccessToken() {
121
+ if (this.accessToken && this.tokenExpiresAt) {
122
+ if ((/* @__PURE__ */ new Date()).getTime() < this.tokenExpiresAt.getTime() - 300 * 1e3) return this.accessToken;
123
+ }
124
+ const tokens = await this.refreshAccessToken();
125
+ this.accessToken = tokens.accessToken;
126
+ this.tokenExpiresAt = tokens.expiresAt;
127
+ this.options.refreshToken = tokens.refreshToken;
128
+ if (this.options.onTokenRefresh) await this.options.onTokenRefresh(tokens);
129
+ return this.accessToken;
130
+ }
131
+ /**
132
+ * Refresh the OAuth access token
133
+ */
134
+ async refreshAccessToken() {
135
+ const OAuthClient = (await import("./OAuthClient-Bv-lzpx8.js").then((m) => /* @__PURE__ */ __toESM(m.default, 1))).default;
136
+ const redirectUri = this.options.redirectUri || "https://developer.intuit.com/v2/OAuth2Playground/RedirectUrl";
137
+ const oauthClient = new OAuthClient({
138
+ clientId: this.options.clientId,
139
+ clientSecret: this.options.clientSecret,
140
+ environment: this.options.environment === "production" ? "production" : "sandbox",
141
+ redirectUri
142
+ });
143
+ oauthClient.setToken({
144
+ refresh_token: this.options.refreshToken,
145
+ access_token: this.accessToken || ""
146
+ });
147
+ const token = (await oauthClient.refresh()).getJson();
148
+ return {
149
+ accessToken: token.access_token,
150
+ refreshToken: token.refresh_token,
151
+ expiresAt: new Date(Date.now() + token.expires_in * 1e3),
152
+ tokenType: token.token_type
153
+ };
154
+ }
155
+ /**
156
+ * Make an authenticated request to the QBO API with timeout and retry support
157
+ */
158
+ async request(method, endpoint, body) {
159
+ const accessToken = await this.ensureAccessToken();
160
+ const url = `${this.baseUrl}/v3/company/${this.realmId}/${endpoint}`;
161
+ const timeout = this.options.timeout || 3e4;
162
+ const maxRetries = this.options.maxRetries || 3;
163
+ let lastError = null;
164
+ for (let attempt = 0; attempt <= maxRetries; attempt++) try {
165
+ const controller = new AbortController();
166
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
167
+ try {
168
+ const response = await fetch(url, {
169
+ method,
170
+ headers: {
171
+ Authorization: `Bearer ${accessToken}`,
172
+ Accept: "application/json",
173
+ "Content-Type": "application/json"
174
+ },
175
+ body: body ? JSON.stringify(body) : void 0,
176
+ signal: controller.signal
177
+ });
178
+ clearTimeout(timeoutId);
179
+ if (!response.ok) {
180
+ const errorText = await response.text();
181
+ if (response.status >= 400 && response.status < 500 && response.status !== 429) throw new Error(`QBO API error (${response.status}): ${errorText}`);
182
+ throw new Error(`QBO API error (${response.status}): ${errorText}`);
183
+ }
184
+ return await response.json();
185
+ } finally {
186
+ clearTimeout(timeoutId);
187
+ }
188
+ } catch (error) {
189
+ lastError = error instanceof Error ? error : new Error(String(error));
190
+ const statusMatch = lastError.message.match(/QBO API error \((\d+)\)/);
191
+ if (statusMatch) {
192
+ const status = Number.parseInt(statusMatch[1], 10);
193
+ if (status >= 400 && status < 500 && status !== 429) throw lastError;
194
+ }
195
+ if (attempt < maxRetries) await sleep(2 ** attempt * 1e3);
196
+ }
197
+ throw lastError || /* @__PURE__ */ new Error("Request failed after retries");
198
+ }
199
+ /**
200
+ * Execute a QBO query with pagination support
201
+ */
202
+ async queryAll(entity, whereClause, maxResults = 1e3) {
203
+ const results = [];
204
+ let startPosition = 1;
205
+ const pageSize = 100;
206
+ while (results.length < maxResults) {
207
+ let query = `SELECT * FROM ${entity}`;
208
+ if (whereClause) query += ` WHERE ${whereClause}`;
209
+ query += ` MAXRESULTS ${pageSize} STARTPOSITION ${startPosition}`;
210
+ const items = (await this.request("GET", `query?query=${encodeURIComponent(query)}`)).QueryResponse[entity] || [];
211
+ results.push(...items);
212
+ if (items.length < pageSize) break;
213
+ startPosition += pageSize;
214
+ }
215
+ return results;
216
+ }
217
+ };
218
+ var QuickBooksCustomerOperations = class {
219
+ provider;
220
+ constructor(provider) {
221
+ this.provider = provider;
222
+ }
223
+ async push(customer) {
224
+ const qboCustomer = mapCustomerToQBO(customer);
225
+ return {
226
+ action: "created",
227
+ externalId: (await this.provider.request("POST", "customer", qboCustomer)).Customer.Id,
228
+ syncedAt: /* @__PURE__ */ new Date()
229
+ };
230
+ }
231
+ async pull(externalId) {
232
+ return mapQBOToCustomer((await this.provider.request("GET", `customer/${externalId}`)).Customer);
233
+ }
234
+ async list(options) {
235
+ const limit = options?.limit || 1e3;
236
+ return (await this.provider.queryAll("Customer", void 0, limit)).map(mapQBOToCustomer);
237
+ }
238
+ async sync(customer) {
239
+ if (customer.externalId) {
240
+ const existing = await this.provider.request("GET", `customer/${customer.externalId}`);
241
+ const qboCustomer = {
242
+ ...mapCustomerToQBO(customer),
243
+ Id: customer.externalId,
244
+ SyncToken: existing.Customer.SyncToken
245
+ };
246
+ await this.provider.request("POST", "customer", qboCustomer);
247
+ return {
248
+ action: "updated",
249
+ externalId: customer.externalId,
250
+ syncedAt: /* @__PURE__ */ new Date()
251
+ };
252
+ }
253
+ return this.push(customer);
254
+ }
255
+ };
256
+ var QuickBooksInvoiceOperations = class {
257
+ provider;
258
+ constructor(provider) {
259
+ this.provider = provider;
260
+ }
261
+ async push(invoice) {
262
+ const qboInvoice = mapInvoiceToQBO(invoice);
263
+ return {
264
+ action: "created",
265
+ externalId: (await this.provider.request("POST", "invoice", qboInvoice)).Invoice.Id,
266
+ syncedAt: /* @__PURE__ */ new Date()
267
+ };
268
+ }
269
+ async pull(externalId) {
270
+ return mapQBOToInvoice((await this.provider.request("GET", `invoice/${externalId}`)).Invoice);
271
+ }
272
+ async list(options) {
273
+ const limit = options?.limit || 1e3;
274
+ let whereClause;
275
+ if (options?.startDate) {
276
+ whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;
277
+ if (options?.endDate) whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;
278
+ }
279
+ return (await this.provider.queryAll("Invoice", whereClause, limit)).map(mapQBOToInvoice);
280
+ }
281
+ async sync(invoice) {
282
+ if (invoice.externalId) {
283
+ const existing = await this.provider.request("GET", `invoice/${invoice.externalId}`);
284
+ const qboInvoice = {
285
+ ...mapInvoiceToQBO(invoice),
286
+ Id: invoice.externalId,
287
+ SyncToken: existing.Invoice.SyncToken
288
+ };
289
+ await this.provider.request("POST", "invoice", qboInvoice);
290
+ return {
291
+ action: "updated",
292
+ externalId: invoice.externalId,
293
+ syncedAt: /* @__PURE__ */ new Date()
294
+ };
295
+ }
296
+ return this.push(invoice);
297
+ }
298
+ async send(externalId) {
299
+ await this.provider.request("POST", `invoice/${externalId}/send`);
300
+ }
301
+ async void(externalId) {
302
+ const existing = await this.provider.request("GET", `invoice/${externalId}`);
303
+ await this.provider.request("POST", "invoice", {
304
+ Id: externalId,
305
+ SyncToken: existing.Invoice.SyncToken,
306
+ sparse: true,
307
+ PrivateNote: "VOIDED"
308
+ });
309
+ }
310
+ };
311
+ var QuickBooksVendorOperations = class {
312
+ provider;
313
+ constructor(provider) {
314
+ this.provider = provider;
315
+ }
316
+ async push(vendor) {
317
+ const qboVendor = mapVendorToQBO(vendor);
318
+ return {
319
+ action: "created",
320
+ externalId: (await this.provider.request("POST", "vendor", qboVendor)).Vendor.Id,
321
+ syncedAt: /* @__PURE__ */ new Date()
322
+ };
323
+ }
324
+ async pull(externalId) {
325
+ return mapQBOToVendor((await this.provider.request("GET", `vendor/${externalId}`)).Vendor);
326
+ }
327
+ async list(options) {
328
+ const limit = options?.limit || 1e3;
329
+ return (await this.provider.queryAll("Vendor", void 0, limit)).map(mapQBOToVendor);
330
+ }
331
+ async sync(vendor) {
332
+ if (vendor.externalId) {
333
+ const existing = await this.provider.request("GET", `vendor/${vendor.externalId}`);
334
+ const qboVendor = {
335
+ ...mapVendorToQBO(vendor),
336
+ Id: vendor.externalId,
337
+ SyncToken: existing.Vendor.SyncToken
338
+ };
339
+ await this.provider.request("POST", "vendor", qboVendor);
340
+ return {
341
+ action: "updated",
342
+ externalId: vendor.externalId,
343
+ syncedAt: /* @__PURE__ */ new Date()
344
+ };
345
+ }
346
+ return this.push(vendor);
347
+ }
348
+ };
349
+ var QuickBooksBillOperations = class {
350
+ provider;
351
+ constructor(provider) {
352
+ this.provider = provider;
353
+ }
354
+ async push(bill) {
355
+ const qboBill = mapBillToQBO(bill);
356
+ return {
357
+ action: "created",
358
+ externalId: (await this.provider.request("POST", "bill", qboBill)).Bill.Id,
359
+ syncedAt: /* @__PURE__ */ new Date()
360
+ };
361
+ }
362
+ async pull(externalId) {
363
+ return mapQBOToBill((await this.provider.request("GET", `bill/${externalId}`)).Bill);
364
+ }
365
+ async list(options) {
366
+ const limit = options?.limit || 1e3;
367
+ let whereClause;
368
+ if (options?.startDate) {
369
+ whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;
370
+ if (options?.endDate) whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;
371
+ }
372
+ return (await this.provider.queryAll("Bill", whereClause, limit)).map(mapQBOToBill);
373
+ }
374
+ async sync(bill) {
375
+ if (bill.externalId) {
376
+ const existing = await this.provider.request("GET", `bill/${bill.externalId}`);
377
+ const qboBill = {
378
+ ...mapBillToQBO(bill),
379
+ Id: bill.externalId,
380
+ SyncToken: existing.Bill.SyncToken
381
+ };
382
+ await this.provider.request("POST", "bill", qboBill);
383
+ return {
384
+ action: "updated",
385
+ externalId: bill.externalId,
386
+ syncedAt: /* @__PURE__ */ new Date()
387
+ };
388
+ }
389
+ return this.push(bill);
390
+ }
391
+ };
392
+ var QuickBooksPaymentOperations = class {
393
+ provider;
394
+ constructor(provider) {
395
+ this.provider = provider;
396
+ }
397
+ async pull(externalId) {
398
+ return mapQBOToPayment((await this.provider.request("GET", `payment/${externalId}`)).Payment);
399
+ }
400
+ async list(options) {
401
+ const limit = options?.limit || 1e3;
402
+ let whereClause;
403
+ if (options?.startDate) {
404
+ whereClause = `TxnDate >= '${formatLocalDate(options.startDate)}'`;
405
+ if (options?.endDate) whereClause += ` AND TxnDate <= '${formatLocalDate(options.endDate)}'`;
406
+ }
407
+ return (await this.provider.queryAll("Payment", whereClause, limit)).map(mapQBOToPayment);
408
+ }
409
+ };
410
+ var QuickBooksAuditOperations = class {
411
+ provider;
412
+ constructor(provider) {
413
+ this.provider = provider;
414
+ }
415
+ async reconcileCustomers(locals) {
416
+ return reconcileRecords(locals, await this.provider.customers.list({ limit: 1e4 }), (l) => l.externalId, (e) => e.externalId, compareCustomer);
417
+ }
418
+ async reconcileInvoices(locals, dateRange) {
419
+ return reconcileRecords(locals, await this.provider.invoices.list({
420
+ limit: 1e4,
421
+ startDate: dateRange?.start,
422
+ endDate: dateRange?.end
423
+ }), (l) => l.externalId, (e) => e.externalId, compareInvoice);
424
+ }
425
+ async reconcileVendors(locals) {
426
+ return reconcileRecords(locals, await this.provider.vendors.list({ limit: 1e4 }), (l) => l.externalId, (e) => e.externalId, compareVendor);
427
+ }
428
+ async reconcileBills(locals, dateRange) {
429
+ return reconcileRecords(locals, await this.provider.bills.list({
430
+ limit: 1e4,
431
+ startDate: dateRange?.start,
432
+ endDate: dateRange?.end
433
+ }), (l) => l.externalId, (e) => e.externalId, compareBill);
434
+ }
435
+ async reconcilePayments(locals, dateRange) {
436
+ return reconcileRecords(locals, await this.provider.payments.list({
437
+ limit: 1e4,
438
+ startDate: dateRange?.start,
439
+ endDate: dateRange?.end
440
+ }), (l) => l.externalId, (e) => e.externalId, comparePayment);
441
+ }
442
+ };
443
+ /**
444
+ * Generic reconciliation function with field comparison
445
+ */
446
+ function reconcileRecords(locals, externals, getLocalExternalId, getExternalId, compareFields) {
447
+ const externalMap = new Map(externals.map((e) => [getExternalId(e), e]));
448
+ const matchedExternalIds = /* @__PURE__ */ new Set();
449
+ const matched = [];
450
+ const localOnly = [];
451
+ const discrepancies = [];
452
+ for (const local of locals) {
453
+ const externalId = getLocalExternalId(local);
454
+ if (externalId && externalMap.has(externalId)) {
455
+ const external = externalMap.get(externalId);
456
+ matchedExternalIds.add(externalId);
457
+ const differences = compareFields(local, external);
458
+ if (differences.length === 0) matched.push({
459
+ local,
460
+ external,
461
+ status: "identical"
462
+ });
463
+ else discrepancies.push({
464
+ local,
465
+ external,
466
+ differences
467
+ });
468
+ } else localOnly.push(local);
469
+ }
470
+ const externalOnly = externals.filter((e) => !matchedExternalIds.has(getExternalId(e)));
471
+ return {
472
+ provider: "quickbooks",
473
+ auditedAt: /* @__PURE__ */ new Date(),
474
+ matched,
475
+ localOnly,
476
+ externalOnly,
477
+ discrepancies,
478
+ summary: {
479
+ total: locals.length + externalOnly.length,
480
+ matched: matched.length,
481
+ localOnly: localOnly.length,
482
+ externalOnly: externalOnly.length,
483
+ discrepancies: discrepancies.length
484
+ }
485
+ };
486
+ }
487
+ function compareCustomer(local, external) {
488
+ const diffs = [];
489
+ if (local.name !== external.name) diffs.push({
490
+ field: "name",
491
+ localValue: local.name,
492
+ externalValue: external.name
493
+ });
494
+ if (local.email !== external.email) diffs.push({
495
+ field: "email",
496
+ localValue: local.email,
497
+ externalValue: external.email
498
+ });
499
+ return diffs;
500
+ }
501
+ function compareInvoice(local, external) {
502
+ const diffs = [];
503
+ if (!valuesEqual(local.totalAmount, external.totalAmount)) diffs.push({
504
+ field: "totalAmount",
505
+ localValue: local.totalAmount,
506
+ externalValue: external.totalAmount
507
+ });
508
+ if (local.invoiceNumber !== external.invoiceNumber) diffs.push({
509
+ field: "invoiceNumber",
510
+ localValue: local.invoiceNumber,
511
+ externalValue: external.invoiceNumber
512
+ });
513
+ return diffs;
514
+ }
515
+ function compareVendor(local, external) {
516
+ const diffs = [];
517
+ if (local.name !== external.name) diffs.push({
518
+ field: "name",
519
+ localValue: local.name,
520
+ externalValue: external.name
521
+ });
522
+ if (local.email !== external.email) diffs.push({
523
+ field: "email",
524
+ localValue: local.email,
525
+ externalValue: external.email
526
+ });
527
+ return diffs;
528
+ }
529
+ function compareBill(local, external) {
530
+ const diffs = [];
531
+ if (!valuesEqual(local.totalAmount, external.totalAmount)) diffs.push({
532
+ field: "totalAmount",
533
+ localValue: local.totalAmount,
534
+ externalValue: external.totalAmount
535
+ });
536
+ return diffs;
537
+ }
538
+ function comparePayment(local, external) {
539
+ const diffs = [];
540
+ if (!valuesEqual(local.amount, external.amount)) diffs.push({
541
+ field: "amount",
542
+ localValue: local.amount,
543
+ externalValue: external.amount
544
+ });
545
+ return diffs;
546
+ }
547
+ var QuickBooksWebhookOperations = class {
548
+ options;
549
+ constructor(options) {
550
+ this.options = options;
551
+ }
552
+ /**
553
+ * Verify webhook signature using HMAC-SHA256
554
+ * @see https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks/verify-webhooks
555
+ */
556
+ verify(payload, signature, secret) {
557
+ const verifierToken = secret || this.options.webhookVerifierToken;
558
+ if (!verifierToken) throw new Error("Webhook verifier token not provided. Pass it as the secret parameter or configure webhookVerifierToken in options.");
559
+ const hmac = createHmac("sha256", verifierToken);
560
+ hmac.update(payload);
561
+ const expectedSignature = hmac.digest("base64");
562
+ if (signature.length !== expectedSignature.length) return false;
563
+ let result = 0;
564
+ for (let i = 0; i < signature.length; i++) result |= signature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);
565
+ return result === 0;
566
+ }
567
+ parse(payload) {
568
+ let data;
569
+ try {
570
+ data = JSON.parse(payload);
571
+ } catch (error) {
572
+ const message = error instanceof Error ? error.message : "Unknown JSON parse error";
573
+ throw new Error(`Invalid QuickBooks webhook payload: ${message}`);
574
+ }
575
+ const firstEvent = (data.eventNotifications || [])[0]?.dataChangeEvent?.entities?.[0];
576
+ return {
577
+ type: firstEvent?.operation || "unknown",
578
+ provider: "quickbooks",
579
+ timestamp: /* @__PURE__ */ new Date(),
580
+ payload: data,
581
+ resourceType: mapQBOResourceType(firstEvent?.name),
582
+ resourceId: firstEvent?.id
583
+ };
584
+ }
585
+ };
586
+ function mapQBOResourceType(name) {
587
+ switch (name?.toLowerCase()) {
588
+ case "customer": return "customer";
589
+ case "invoice": return "invoice";
590
+ case "payment": return "payment";
591
+ case "vendor": return "vendor";
592
+ case "bill": return "bill";
593
+ default: return;
594
+ }
595
+ }
596
+ function mapCustomerToQBO(customer) {
597
+ return {
598
+ DisplayName: customer.name,
599
+ PrimaryEmailAddr: customer.email ? { Address: customer.email } : void 0,
600
+ PrimaryPhone: customer.phone ? { FreeFormNumber: customer.phone } : void 0,
601
+ BillAddr: mapAddressToQBO(customer.billingAddress),
602
+ ShipAddr: mapAddressToQBO(customer.shippingAddress),
603
+ CurrencyRef: customer.currency ? { value: customer.currency } : void 0
604
+ };
605
+ }
606
+ function mapQBOToCustomer(qbo) {
607
+ return {
608
+ externalId: qbo.Id,
609
+ provider: "quickbooks",
610
+ syncedAt: /* @__PURE__ */ new Date(),
611
+ name: qbo.DisplayName,
612
+ email: qbo.PrimaryEmailAddr?.Address,
613
+ phone: qbo.PrimaryPhone?.FreeFormNumber,
614
+ billingAddress: mapQBOToAddress(qbo.BillAddr),
615
+ balance: qbo.Balance,
616
+ currency: qbo.CurrencyRef?.value,
617
+ raw: qbo
618
+ };
619
+ }
620
+ function mapInvoiceToQBO(invoice) {
621
+ return {
622
+ CustomerRef: { value: invoice.customerExternalId || invoice.customerId },
623
+ DocNumber: invoice.invoiceNumber,
624
+ TxnDate: formatLocalDate(invoice.issueDate),
625
+ DueDate: formatLocalDate(invoice.dueDate),
626
+ Line: invoice.lineItems.map((item, idx) => ({
627
+ LineNum: idx + 1,
628
+ Description: item.description,
629
+ Amount: item.amount ?? item.quantity * item.unitPrice,
630
+ DetailType: "SalesItemLineDetail",
631
+ SalesItemLineDetail: {
632
+ Qty: item.quantity,
633
+ UnitPrice: item.unitPrice
634
+ }
635
+ })),
636
+ CurrencyRef: invoice.currency ? { value: invoice.currency } : void 0,
637
+ CustomerMemo: invoice.memo ? { value: invoice.memo } : void 0
638
+ };
639
+ }
640
+ function mapQBOToInvoice(qbo) {
641
+ const balance = qbo.Balance ?? 0;
642
+ const totalAmount = qbo.TotalAmt ?? 0;
643
+ return {
644
+ externalId: qbo.Id,
645
+ provider: "quickbooks",
646
+ syncedAt: /* @__PURE__ */ new Date(),
647
+ invoiceNumber: qbo.DocNumber || "",
648
+ customerExternalId: qbo.CustomerRef?.value || "",
649
+ issueDate: new Date(qbo.TxnDate),
650
+ dueDate: new Date(qbo.DueDate),
651
+ subtotal: totalAmount - (qbo.TxnTaxDetail?.TotalTax ?? 0),
652
+ taxAmount: qbo.TxnTaxDetail?.TotalTax ?? 0,
653
+ totalAmount,
654
+ amountPaid: totalAmount - balance,
655
+ balance,
656
+ status: mapInvoiceStatus(balance, totalAmount, qbo.DueDate),
657
+ currency: qbo.CurrencyRef?.value || "USD",
658
+ raw: qbo
659
+ };
660
+ }
661
+ function mapInvoiceStatus(balance, total, dueDate) {
662
+ if (balance === 0) return "paid";
663
+ if (balance < total) return "viewed";
664
+ if (new Date(dueDate) < /* @__PURE__ */ new Date()) return "overdue";
665
+ return "sent";
666
+ }
667
+ function mapVendorToQBO(vendor) {
668
+ return {
669
+ DisplayName: vendor.name,
670
+ PrimaryEmailAddr: vendor.email ? { Address: vendor.email } : void 0,
671
+ PrimaryPhone: vendor.phone ? { FreeFormNumber: vendor.phone } : void 0,
672
+ BillAddr: mapAddressToQBO(vendor.address),
673
+ TaxIdentifier: vendor.taxId,
674
+ CurrencyRef: vendor.currency ? { value: vendor.currency } : void 0
675
+ };
676
+ }
677
+ function mapQBOToVendor(qbo) {
678
+ return {
679
+ externalId: qbo.Id,
680
+ provider: "quickbooks",
681
+ syncedAt: /* @__PURE__ */ new Date(),
682
+ name: qbo.DisplayName,
683
+ email: qbo.PrimaryEmailAddr?.Address,
684
+ phone: qbo.PrimaryPhone?.FreeFormNumber,
685
+ address: mapQBOToAddress(qbo.BillAddr),
686
+ balance: qbo.Balance,
687
+ currency: qbo.CurrencyRef?.value,
688
+ raw: qbo
689
+ };
690
+ }
691
+ function mapBillToQBO(bill) {
692
+ return {
693
+ VendorRef: { value: bill.vendorExternalId || bill.vendorId },
694
+ DocNumber: bill.billNumber,
695
+ TxnDate: formatLocalDate(bill.billDate),
696
+ DueDate: formatLocalDate(bill.dueDate),
697
+ Line: bill.lineItems.map((item, idx) => ({
698
+ LineNum: idx + 1,
699
+ Description: item.description,
700
+ Amount: item.amount ?? item.quantity * item.unitPrice,
701
+ DetailType: "AccountBasedExpenseLineDetail",
702
+ AccountBasedExpenseLineDetail: { AccountRef: item.accountCode ? { value: item.accountCode } : void 0 }
703
+ })),
704
+ CurrencyRef: bill.currency ? { value: bill.currency } : void 0
705
+ };
706
+ }
707
+ function mapQBOToBill(qbo) {
708
+ const balance = qbo.Balance ?? 0;
709
+ const totalAmount = qbo.TotalAmt ?? 0;
710
+ return {
711
+ externalId: qbo.Id,
712
+ provider: "quickbooks",
713
+ syncedAt: /* @__PURE__ */ new Date(),
714
+ billNumber: qbo.DocNumber,
715
+ vendorExternalId: qbo.VendorRef?.value || "",
716
+ billDate: new Date(qbo.TxnDate),
717
+ dueDate: new Date(qbo.DueDate),
718
+ subtotal: totalAmount - (qbo.TxnTaxDetail?.TotalTax ?? 0),
719
+ taxAmount: qbo.TxnTaxDetail?.TotalTax ?? 0,
720
+ totalAmount,
721
+ amountPaid: totalAmount - balance,
722
+ balance,
723
+ status: mapBillStatus(balance, totalAmount, qbo.DueDate),
724
+ currency: qbo.CurrencyRef?.value || "USD",
725
+ raw: qbo
726
+ };
727
+ }
728
+ function mapBillStatus(balance, total, dueDate) {
729
+ if (balance === 0) return "paid";
730
+ if (new Date(dueDate) < /* @__PURE__ */ new Date()) return "overdue";
731
+ return "pending";
732
+ }
733
+ function mapQBOToPayment(qbo) {
734
+ return {
735
+ externalId: qbo.Id,
736
+ provider: "quickbooks",
737
+ syncedAt: /* @__PURE__ */ new Date(),
738
+ amount: qbo.TotalAmt ?? 0,
739
+ currency: qbo.CurrencyRef?.value || "USD",
740
+ paidAt: new Date(qbo.TxnDate),
741
+ method: qbo.PaymentMethodRef?.name,
742
+ transactionId: qbo.PaymentRefNum,
743
+ invoiceExternalIds: qbo.Line?.filter((l) => l.LinkedTxn).flatMap((l) => l.LinkedTxn?.map((t) => t.TxnId) || []),
744
+ status: "completed",
745
+ raw: qbo
746
+ };
747
+ }
748
+ function mapAddressToQBO(address) {
749
+ if (!address) return void 0;
750
+ return {
751
+ Line1: address.street1,
752
+ Line2: address.street2,
753
+ City: address.city,
754
+ CountrySubDivisionCode: address.state,
755
+ PostalCode: address.postalCode,
756
+ Country: address.country
757
+ };
758
+ }
759
+ function mapQBOToAddress(qbo) {
760
+ if (!qbo) return void 0;
761
+ return {
762
+ street1: qbo.Line1,
763
+ street2: qbo.Line2,
764
+ city: qbo.City,
765
+ state: qbo.CountrySubDivisionCode,
766
+ postalCode: qbo.PostalCode,
767
+ country: qbo.Country
768
+ };
769
+ }
770
+ //#endregion
771
+ export { QuickBooksProvider, __toCommonJS as a, __require as i, __esmMin as n, __exportAll as r, __commonJSMin as t };
772
+
773
+ //# sourceMappingURL=quickbooks-BF3GrJBw.js.map