@taxora/sdk 1.0.7 → 1.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.js CHANGED
@@ -60,37 +60,299 @@ var AuthenticationException = class extends TaxoraException {
60
60
  }
61
61
  };
62
62
 
63
+ // src/exceptions/apiErrorMessage.ts
64
+ var MAX_LENGTH = 500;
65
+ var MAX_PLAIN_LENGTH = 200;
66
+ var REASON_PHRASES = {
67
+ 400: "Bad Request",
68
+ 401: "Unauthorized",
69
+ 403: "Forbidden",
70
+ 404: "Not Found",
71
+ 405: "Method Not Allowed",
72
+ 408: "Request Timeout",
73
+ 409: "Conflict",
74
+ 413: "Payload Too Large",
75
+ 422: "Unprocessable Entity",
76
+ 429: "Too Many Requests",
77
+ 500: "Internal Server Error",
78
+ 502: "Bad Gateway",
79
+ 503: "Service Unavailable",
80
+ 504: "Gateway Timeout"
81
+ };
82
+ function describeApiError(body, statusCode) {
83
+ return jsonErrorMessage(body) ?? plainErrorMessage(body) ?? statusErrorMessage(statusCode);
84
+ }
85
+ function detailApiError(body, statusCode) {
86
+ return jsonErrorMessage(body) ?? plainErrorMessage(body) ?? statusPhrase(statusCode);
87
+ }
88
+ function jsonErrorMessage(body) {
89
+ if (body === null || body === void 0 || body.trim() === "") return null;
90
+ let parsed;
91
+ try {
92
+ parsed = JSON.parse(body);
93
+ } catch {
94
+ return null;
95
+ }
96
+ if (typeof parsed !== "object" || parsed === null) return null;
97
+ const payload = parsed;
98
+ for (const key of ["message", "error"]) {
99
+ const value = payload[key];
100
+ if (typeof value === "string") {
101
+ const message = sanitize(value);
102
+ if (message !== null) return message;
103
+ }
104
+ }
105
+ const errors = payload["errors"];
106
+ if (typeof errors === "object" && errors !== null) {
107
+ return fromErrorBag(errors);
108
+ }
109
+ return null;
110
+ }
111
+ function statusErrorMessage(statusCode) {
112
+ return `Taxora API request failed (${statusPhrase(statusCode)}).`;
113
+ }
114
+ function statusPhrase(statusCode) {
115
+ const phrase = REASON_PHRASES[statusCode];
116
+ return `HTTP ${statusCode}${phrase ? ` ${phrase}` : ""}`;
117
+ }
118
+ function plainErrorMessage(body) {
119
+ if (body === null || body === void 0) return null;
120
+ const value = body.replace(/\s+/g, " ").trim();
121
+ if (value === "" || value.length > MAX_PLAIN_LENGTH) return null;
122
+ if (value.includes("<") || value.includes("{")) return null;
123
+ return value;
124
+ }
125
+ function fromErrorBag(errors) {
126
+ for (const [field, messages] of Object.entries(errors)) {
127
+ for (const message of Array.isArray(messages) ? messages : [messages]) {
128
+ if (typeof message !== "string") continue;
129
+ const sanitized = sanitize(message);
130
+ if (sanitized === null) continue;
131
+ return field !== "" ? `${field}: ${sanitized}` : sanitized;
132
+ }
133
+ }
134
+ return null;
135
+ }
136
+ function sanitize(value) {
137
+ const collapsed = value.replace(/\s+/g, " ").trim();
138
+ if (collapsed === "") return null;
139
+ if (/<!doctype\s+html|<html[\s>]/i.test(collapsed)) return null;
140
+ return collapsed.length > MAX_LENGTH ? `${collapsed.slice(0, MAX_LENGTH - 1)}\u2026` : collapsed;
141
+ }
142
+
63
143
  // src/exceptions/HttpException.ts
64
- var HttpException = class extends TaxoraException {
144
+ var HttpException = class _HttpException extends TaxoraException {
65
145
  statusCode;
66
146
  responseBody;
67
- constructor(message, statusCode, responseBody, context = {}) {
147
+ /** Raw `Retry-After` header of the failed response, when it carried one. */
148
+ retryAfter;
149
+ constructor(message, statusCode, responseBody, context = {}, retryAfter = null) {
68
150
  super(message, context);
69
151
  this.name = "HttpException";
70
152
  this.statusCode = statusCode;
71
153
  this.responseBody = responseBody;
154
+ this.retryAfter = retryAfter;
72
155
  Object.setPrototypeOf(this, new.target.prototype);
73
156
  }
157
+ /**
158
+ * The final failure after the SDK retried a transient gateway error, e.g.
159
+ * "Taxora VAT validation failed after 3 attempts (HTTP 504 Gateway Timeout)."
160
+ */
161
+ static afterAttempts(operation, attempts, last) {
162
+ const detail = detailApiError(last.getResponseBody(), last.getStatusCode());
163
+ return new _HttpException(
164
+ `Taxora ${operation} failed after ${attempts} attempts (${detail}).`,
165
+ last.getStatusCode(),
166
+ last.getResponseBody(),
167
+ { ...last.context, attempts, cause: last },
168
+ last.getRetryAfter()
169
+ );
170
+ }
74
171
  getStatusCode() {
75
172
  return this.statusCode;
76
173
  }
77
174
  getResponseBody() {
78
175
  return this.responseBody;
79
176
  }
177
+ getRetryAfter() {
178
+ return this.retryAfter;
179
+ }
80
180
  };
81
181
 
182
+ // src/http/RetryPolicy.ts
183
+ var DEFAULT_RETRYABLE_STATUS_CODES = [502, 503, 504];
184
+ var RETRY_AFTER_ONLY_STATUS_CODES = [429];
185
+ var NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
186
+ "ECONNRESET",
187
+ "ECONNREFUSED",
188
+ "ECONNABORTED",
189
+ "EPIPE",
190
+ "ETIMEDOUT",
191
+ "EAI_AGAIN",
192
+ "EHOSTUNREACH",
193
+ "ENETUNREACH",
194
+ "ENETDOWN",
195
+ "UND_ERR_CONNECT_TIMEOUT",
196
+ "UND_ERR_HEADERS_TIMEOUT",
197
+ "UND_ERR_BODY_TIMEOUT",
198
+ "UND_ERR_SOCKET"
199
+ ]);
200
+ var RetryPolicy = class _RetryPolicy {
201
+ maxAttempts;
202
+ initialDelayMs;
203
+ backoffMultiplier;
204
+ maxDelayMs;
205
+ retryableStatusCodes;
206
+ respectRetryAfter;
207
+ maxRetryAfterMs;
208
+ retryOnNetworkErrors;
209
+ sleeper;
210
+ constructor(options = {}) {
211
+ this.maxAttempts = options.maxAttempts ?? 3;
212
+ this.initialDelayMs = options.initialDelayMs ?? 500;
213
+ this.backoffMultiplier = options.backoffMultiplier ?? 2;
214
+ this.maxDelayMs = options.maxDelayMs ?? 5e3;
215
+ this.retryableStatusCodes = options.retryableStatusCodes ?? DEFAULT_RETRYABLE_STATUS_CODES;
216
+ this.respectRetryAfter = options.respectRetryAfter ?? true;
217
+ this.maxRetryAfterMs = options.maxRetryAfterMs ?? 1e4;
218
+ this.retryOnNetworkErrors = options.retryOnNetworkErrors ?? true;
219
+ this.sleeper = options.sleeper;
220
+ if (this.maxAttempts < 1) throw new Error("maxAttempts must be at least 1.");
221
+ if (this.initialDelayMs < 0 || this.maxDelayMs < 0) throw new Error("Delays must not be negative.");
222
+ if (this.backoffMultiplier < 1) throw new Error("backoffMultiplier must be at least 1.");
223
+ }
224
+ /** No retrying at all — every failure is surfaced on the first attempt. */
225
+ static disabled() {
226
+ return new _RetryPolicy({ maxAttempts: 1 });
227
+ }
228
+ /** Retry immediately, without waiting (mainly useful in tests). */
229
+ static withoutDelay(maxAttempts = 3) {
230
+ return new _RetryPolicy({ maxAttempts, initialDelayMs: 0, maxDelayMs: 0 });
231
+ }
232
+ isRetryable(statusCode, retryAfterHeader) {
233
+ const retryAfterMs = this.retryAfterMs(retryAfterHeader);
234
+ if (retryAfterMs !== null && retryAfterMs > this.maxRetryAfterMs) return false;
235
+ if (this.retryableStatusCodes.includes(statusCode)) return true;
236
+ return retryAfterMs !== null && RETRY_AFTER_ONLY_STATUS_CODES.includes(statusCode);
237
+ }
238
+ /** @param attempt Number of attempts made so far (1 = the first one just failed). */
239
+ shouldRetry(statusCode, attempt, retryAfterHeader) {
240
+ return attempt < this.maxAttempts && this.isRetryable(statusCode, retryAfterHeader);
241
+ }
242
+ /**
243
+ * A connection-level failure: the request never got an answer, so repeating it
244
+ * is as safe as repeating a gateway timeout.
245
+ */
246
+ isRetryableError(error) {
247
+ if (!this.retryOnNetworkErrors || !(error instanceof Error)) return false;
248
+ if (error.name === "AbortError" || error.name === "TimeoutError") return true;
249
+ const code = error.code ?? error.cause?.code;
250
+ if (typeof code === "string" && NETWORK_ERROR_CODES.has(code)) return true;
251
+ return error instanceof TypeError && /fetch failed|network|failed to fetch/i.test(error.message);
252
+ }
253
+ shouldRetryError(error, attempt) {
254
+ return attempt < this.maxAttempts && this.isRetryableError(error);
255
+ }
256
+ /**
257
+ * The wait the API asked for, in milliseconds — seconds or HTTP-date form — or
258
+ * null when there is none, it cannot be parsed, or the header is ignored.
259
+ */
260
+ retryAfterMs(header, now = /* @__PURE__ */ new Date()) {
261
+ if (!this.respectRetryAfter || header === null || header === void 0) return null;
262
+ const value = header.trim();
263
+ if (value === "") return null;
264
+ if (/^\d+$/.test(value)) return Number(value) * 1e3;
265
+ const date = Date.parse(value);
266
+ if (Number.isNaN(date)) return null;
267
+ return Math.max(0, date - now.getTime());
268
+ }
269
+ /** The wait before the next attempt: what the API asked for, else exponential backoff. */
270
+ delayMsForAttempt(attempt, retryAfterHeader) {
271
+ const retryAfterMs = this.retryAfterMs(retryAfterHeader);
272
+ if (retryAfterMs !== null) return Math.min(retryAfterMs, this.maxRetryAfterMs);
273
+ if (this.initialDelayMs === 0) return 0;
274
+ const delay = Math.round(this.initialDelayMs * this.backoffMultiplier ** Math.max(0, attempt - 1));
275
+ return Math.max(0, Math.min(delay, this.maxDelayMs));
276
+ }
277
+ async sleepBeforeRetry(attempt, retryAfterHeader) {
278
+ const delayMs = this.delayMsForAttempt(attempt, retryAfterHeader);
279
+ if (delayMs <= 0) return;
280
+ if (this.sleeper) {
281
+ await this.sleeper(delayMs);
282
+ return;
283
+ }
284
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
285
+ }
286
+ };
287
+
288
+ // src/http/withRetries.ts
289
+ async function withRetries(policy, operation, request) {
290
+ let attempt = 1;
291
+ for (; ; ) {
292
+ try {
293
+ return await request();
294
+ } catch (error) {
295
+ if (!(error instanceof HttpException)) {
296
+ if (!policy.shouldRetryError(error, attempt)) throw error;
297
+ await policy.sleepBeforeRetry(attempt);
298
+ attempt++;
299
+ continue;
300
+ }
301
+ const retryAfter = error.getRetryAfter();
302
+ if (!policy.shouldRetry(error.getStatusCode(), attempt, retryAfter)) {
303
+ throw attempt > 1 && policy.isRetryable(error.getStatusCode(), retryAfter) ? HttpException.afterAttempts(operation, attempt, error) : error;
304
+ }
305
+ await policy.sleepBeforeRetry(attempt, retryAfter);
306
+ attempt++;
307
+ }
308
+ }
309
+ }
310
+ async function withResponseRetries(policy, operation, send) {
311
+ let attempt = 1;
312
+ for (; ; ) {
313
+ let response;
314
+ try {
315
+ response = await send();
316
+ } catch (error) {
317
+ if (!policy.shouldRetryError(error, attempt)) throw error;
318
+ await policy.sleepBeforeRetry(attempt);
319
+ attempt++;
320
+ continue;
321
+ }
322
+ const retryAfter = response.headers.get("retry-after");
323
+ if (!policy.shouldRetry(response.status, attempt, retryAfter)) {
324
+ if (attempt > 1 && policy.isRetryable(response.status, retryAfter)) {
325
+ const body = await response.text();
326
+ const last = new HttpException(
327
+ describeApiError(body, response.status),
328
+ response.status,
329
+ body,
330
+ {},
331
+ retryAfter
332
+ );
333
+ throw HttpException.afterAttempts(operation, attempt, last);
334
+ }
335
+ return response;
336
+ }
337
+ await policy.sleepBeforeRetry(attempt, retryAfter);
338
+ attempt++;
339
+ }
340
+ }
341
+
82
342
  // src/endpoints/AuthEndpoint.ts
83
343
  var AuthEndpoint = class {
84
- constructor(baseUrl, apiKey, tokenStorage, httpClient) {
344
+ constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
85
345
  this.baseUrl = baseUrl;
86
346
  this.apiKey = apiKey;
87
347
  this.tokenStorage = tokenStorage;
88
348
  this.httpClient = httpClient;
349
+ this.retryPolicy = retryPolicy;
89
350
  }
90
351
  baseUrl;
91
352
  apiKey;
92
353
  tokenStorage;
93
354
  httpClient;
355
+ retryPolicy;
94
356
  async login(identifier, password, device, loginIdentifier = LoginIdentifier.EMAIL) {
95
357
  const deviceName = device ?? hostname();
96
358
  const body = {
@@ -103,62 +365,1296 @@ var AuthEndpoint = class {
103
365
  } else {
104
366
  body["email"] = identifier;
105
367
  }
106
- const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/login`, {
107
- headers: {
108
- "Content-Type": "application/json",
109
- "x-api-key": this.apiKey
110
- },
111
- body: JSON.stringify(body)
368
+ const data = await withRetries(this.retryPolicy, "login", async () => {
369
+ const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/login`, {
370
+ headers: {
371
+ "Content-Type": "application/json",
372
+ "x-api-key": this.apiKey
373
+ },
374
+ body: JSON.stringify(body)
375
+ });
376
+ const responseText = await response.text();
377
+ if (response.status === 401) {
378
+ throw new AuthenticationException("Authentication failed: invalid credentials", {
379
+ status: 401,
380
+ body: responseText
381
+ });
382
+ }
383
+ if (!response.ok) {
384
+ throw new HttpException(
385
+ describeApiError(responseText, response.status),
386
+ response.status,
387
+ responseText,
388
+ {},
389
+ response.headers.get("retry-after")
390
+ );
391
+ }
392
+ return this.parseResponse(responseText, response.status);
393
+ });
394
+ const token = Token.fromArray(data);
395
+ this.tokenStorage.set(token);
396
+ return token;
397
+ }
398
+ async loginWithClientId(clientId, password, device) {
399
+ return this.login(clientId, password, device, LoginIdentifier.CLIENT_ID);
400
+ }
401
+ async refresh() {
402
+ const storedToken = this.tokenStorage.get();
403
+ const headers = {
404
+ "Content-Type": "application/json",
405
+ "x-api-key": this.apiKey
406
+ };
407
+ if (storedToken) {
408
+ headers["Authorization"] = `Bearer ${storedToken.accessToken}`;
409
+ }
410
+ const data = await withRetries(this.retryPolicy, "token refresh", async () => {
411
+ const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/refresh`, { headers });
412
+ const responseText = await response.text();
413
+ if (response.status === 401) {
414
+ throw new AuthenticationException("Token refresh failed: unauthorized", { status: 401, body: responseText });
415
+ }
416
+ if (!response.ok) {
417
+ throw new HttpException(
418
+ describeApiError(responseText, response.status),
419
+ response.status,
420
+ responseText,
421
+ {},
422
+ response.headers.get("retry-after")
423
+ );
424
+ }
425
+ return this.parseResponse(responseText, response.status);
426
+ });
427
+ const token = Token.fromArray(data);
428
+ this.tokenStorage.set(token);
429
+ return token;
430
+ }
431
+ parseResponse(responseText, statusCode) {
432
+ let parsed;
433
+ try {
434
+ parsed = JSON.parse(responseText);
435
+ } catch {
436
+ throw new HttpException("Invalid JSON response", statusCode, responseText);
437
+ }
438
+ if (typeof parsed !== "object" || parsed === null) {
439
+ throw new HttpException("Unexpected response format", statusCode, responseText);
440
+ }
441
+ const obj = parsed;
442
+ if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
443
+ return obj["data"];
444
+ }
445
+ return obj;
446
+ }
447
+ };
448
+
449
+ // src/dto/CompanyResource.ts
450
+ function normalizeCompanyResource(data) {
451
+ const apiRateLimit = typeof data["api_rate_limit"] === "number" ? data["api_rate_limit"] : void 0;
452
+ const vatRateLimit = typeof data["vat_rate_limit"] === "number" ? data["vat_rate_limit"] : void 0;
453
+ const legacyRateLimit = typeof data["rate_limit"] === "number" ? data["rate_limit"] : void 0;
454
+ return {
455
+ ...data,
456
+ api_rate_limit: apiRateLimit ?? legacyRateLimit ?? 0,
457
+ vat_rate_limit: vatRateLimit ?? legacyRateLimit ?? 0
458
+ };
459
+ }
460
+
461
+ // src/endpoints/CompanyEndpoint.ts
462
+ var CompanyEndpoint = class {
463
+ constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
464
+ this.baseUrl = baseUrl;
465
+ this.apiKey = apiKey;
466
+ this.tokenStorage = tokenStorage;
467
+ this.httpClient = httpClient;
468
+ this.retryPolicy = retryPolicy;
469
+ }
470
+ baseUrl;
471
+ apiKey;
472
+ tokenStorage;
473
+ httpClient;
474
+ retryPolicy;
475
+ async get() {
476
+ const headers = {
477
+ "Content-Type": "application/json",
478
+ "x-api-key": this.apiKey
479
+ };
480
+ const token = this.tokenStorage.get();
481
+ if (token) {
482
+ headers["Authorization"] = `Bearer ${token.accessToken}`;
483
+ }
484
+ return withRetries(this.retryPolicy, "company request", async () => {
485
+ const response = await this.httpClient.request("GET", `${this.baseUrl}/company`, { headers });
486
+ const responseText = await response.text();
487
+ if (!response.ok) {
488
+ throw new HttpException(
489
+ describeApiError(responseText, response.status),
490
+ response.status,
491
+ responseText,
492
+ {},
493
+ response.headers.get("retry-after")
494
+ );
495
+ }
496
+ let parsed;
497
+ try {
498
+ parsed = JSON.parse(responseText);
499
+ } catch {
500
+ throw new HttpException("Invalid JSON response", response.status, responseText);
501
+ }
502
+ if (typeof parsed !== "object" || parsed === null) {
503
+ throw new HttpException("Unexpected response format", response.status, responseText);
504
+ }
505
+ const obj = parsed;
506
+ if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
507
+ return normalizeCompanyResource(obj["data"]);
508
+ }
509
+ return normalizeCompanyResource(obj);
510
+ });
511
+ }
512
+ };
513
+
514
+ // src/enums/ComplianceEnrollmentStatus.ts
515
+ var ComplianceEnrollmentStatus = {
516
+ PENDING_DATA: "pending_data",
517
+ ACCOUNT_CREATED: "account_created",
518
+ REGIME_ACTIVATED: "regime_activated",
519
+ READY: "ready",
520
+ SUSPENDED: "suspended",
521
+ ERROR_ACCOUNT: "error_account",
522
+ ERROR_ACTIVATION: "error_activation",
523
+ UNKNOWN: "unknown"
524
+ };
525
+ var VALUES = Object.values(ComplianceEnrollmentStatus);
526
+ function toComplianceEnrollmentStatus(value) {
527
+ if (typeof value === "string") {
528
+ const normalized = value.trim().toLowerCase();
529
+ if (VALUES.includes(normalized)) {
530
+ return normalized;
531
+ }
532
+ }
533
+ return ComplianceEnrollmentStatus.UNKNOWN;
534
+ }
535
+ var DESCRIPTIONS = {
536
+ [ComplianceEnrollmentStatus.PENDING_DATA]: "Awaiting merchant data before provisioning can start.",
537
+ [ComplianceEnrollmentStatus.ACCOUNT_CREATED]: "Provider account exists. Regime not yet activated.",
538
+ [ComplianceEnrollmentStatus.REGIME_ACTIVATED]: "Compliance regime activated at provider. Ready to submit transactions.",
539
+ [ComplianceEnrollmentStatus.READY]: "Enrollment fully active and reporting.",
540
+ [ComplianceEnrollmentStatus.SUSPENDED]: "Enrollment temporarily suspended.",
541
+ [ComplianceEnrollmentStatus.ERROR_ACCOUNT]: "Provider account creation failed.",
542
+ [ComplianceEnrollmentStatus.ERROR_ACTIVATION]: "Regime activation at provider failed.",
543
+ [ComplianceEnrollmentStatus.UNKNOWN]: "Unknown status."
544
+ };
545
+ function describeComplianceEnrollmentStatus(status) {
546
+ return DESCRIPTIONS[status];
547
+ }
548
+
549
+ // src/dto/ComplianceEnrollment.ts
550
+ function asString(value, fallback = "") {
551
+ return typeof value === "string" ? value : fallback;
552
+ }
553
+ function asNullableString(value) {
554
+ return typeof value === "string" ? value : null;
555
+ }
556
+ function asNumber(value) {
557
+ return typeof value === "number" ? value : Number(value ?? 0) || 0;
558
+ }
559
+ var ComplianceEnrollment = class _ComplianceEnrollment {
560
+ constructor(id, companyId, country, regime, provider, status, statusLabel, statusError, providerAccountId, taxId, companyRegisterId, companyRegisterScheme, regimeConfig, reportingStartDate, notificationEmail, autoSend, createdAt, updatedAt) {
561
+ this.id = id;
562
+ this.companyId = companyId;
563
+ this.country = country;
564
+ this.regime = regime;
565
+ this.provider = provider;
566
+ this.status = status;
567
+ this.statusLabel = statusLabel;
568
+ this.statusError = statusError;
569
+ this.providerAccountId = providerAccountId;
570
+ this.taxId = taxId;
571
+ this.companyRegisterId = companyRegisterId;
572
+ this.companyRegisterScheme = companyRegisterScheme;
573
+ this.regimeConfig = regimeConfig;
574
+ this.reportingStartDate = reportingStartDate;
575
+ this.notificationEmail = notificationEmail;
576
+ this.autoSend = autoSend;
577
+ this.createdAt = createdAt;
578
+ this.updatedAt = updatedAt;
579
+ }
580
+ id;
581
+ companyId;
582
+ country;
583
+ regime;
584
+ provider;
585
+ status;
586
+ statusLabel;
587
+ statusError;
588
+ providerAccountId;
589
+ taxId;
590
+ companyRegisterId;
591
+ companyRegisterScheme;
592
+ regimeConfig;
593
+ reportingStartDate;
594
+ notificationEmail;
595
+ autoSend;
596
+ createdAt;
597
+ updatedAt;
598
+ static fromArray(data) {
599
+ const regimeConfig = typeof data["regime_config"] === "object" && data["regime_config"] !== null && !Array.isArray(data["regime_config"]) ? data["regime_config"] : null;
600
+ return new _ComplianceEnrollment(
601
+ asNumber(data["id"]),
602
+ asNumber(data["company_id"]),
603
+ asString(data["country"]),
604
+ asString(data["regime"]),
605
+ asString(data["provider"]),
606
+ toComplianceEnrollmentStatus(data["status"]),
607
+ asString(data["status_label"]),
608
+ asNullableString(data["status_error"]),
609
+ asNullableString(data["provider_account_id"]),
610
+ asNullableString(data["tax_id"]),
611
+ asNullableString(data["company_register_id"]),
612
+ asNullableString(data["company_register_scheme"]),
613
+ regimeConfig,
614
+ asNullableString(data["reporting_start_date"]),
615
+ asNullableString(data["notification_email"]),
616
+ data["auto_send"] === true,
617
+ asNullableString(data["created_at"]),
618
+ asNullableString(data["updated_at"])
619
+ );
620
+ }
621
+ };
622
+
623
+ // src/dto/ComplianceEnrollmentPage.ts
624
+ var ComplianceEnrollmentPage = class _ComplianceEnrollmentPage {
625
+ constructor(rows, currentPage, perPage, total, lastPage) {
626
+ this.rows = rows;
627
+ this.currentPage = currentPage;
628
+ this.perPage = perPage;
629
+ this.total = total;
630
+ this.lastPage = lastPage;
631
+ }
632
+ rows;
633
+ currentPage;
634
+ perPage;
635
+ total;
636
+ lastPage;
637
+ static fromResponse(payload) {
638
+ const obj = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
639
+ const rows = Array.isArray(obj["data"]) ? obj["data"].filter((row) => typeof row === "object" && row !== null).map(ComplianceEnrollment.fromArray) : [];
640
+ const meta = obj["meta"] !== null && typeof obj["meta"] === "object" && !Array.isArray(obj["meta"]) ? obj["meta"] : {};
641
+ const num = (value, fallback) => typeof value === "number" ? value : fallback;
642
+ return new _ComplianceEnrollmentPage(
643
+ rows,
644
+ num(meta["current_page"], 1),
645
+ num(meta["per_page"], rows.length),
646
+ num(meta["total"], rows.length),
647
+ num(meta["last_page"], 1)
648
+ );
649
+ }
650
+ [Symbol.iterator]() {
651
+ return this.rows[Symbol.iterator]();
652
+ }
653
+ get length() {
654
+ return this.rows.length;
655
+ }
656
+ };
657
+
658
+ // src/enums/ComplianceTaxReportState.ts
659
+ var ComplianceTaxReportState = {
660
+ NEW: "new",
661
+ SENT: "sent",
662
+ ACKNOWLEDGED: "acknowledged",
663
+ REGISTERED: "registered",
664
+ REFUSED: "refused",
665
+ ERROR: "error",
666
+ UNKNOWN: "unknown"
667
+ };
668
+ var VALUES2 = Object.values(ComplianceTaxReportState);
669
+ function toComplianceTaxReportState(value) {
670
+ if (typeof value === "string") {
671
+ const normalized = value.trim().toLowerCase();
672
+ if (VALUES2.includes(normalized)) {
673
+ return normalized;
674
+ }
675
+ }
676
+ return ComplianceTaxReportState.UNKNOWN;
677
+ }
678
+ var DESCRIPTIONS2 = {
679
+ [ComplianceTaxReportState.NEW]: "Created at provider, waiting for the daily ledger batch.",
680
+ [ComplianceTaxReportState.SENT]: "Ledger transmitted to PPF.",
681
+ [ComplianceTaxReportState.ACKNOWLEDGED]: "PPF acknowledged receipt of the ledger.",
682
+ [ComplianceTaxReportState.REGISTERED]: "Accepted by DGFiP (CDV 300).",
683
+ [ComplianceTaxReportState.REFUSED]: "Rejected by DGFiP (CDV 301).",
684
+ [ComplianceTaxReportState.ERROR]: "Transmission or PPF error.",
685
+ [ComplianceTaxReportState.UNKNOWN]: "Unknown state."
686
+ };
687
+ function describeComplianceTaxReportState(state) {
688
+ return DESCRIPTIONS2[state];
689
+ }
690
+
691
+ // src/dto/ComplianceTaxReport.ts
692
+ function asString2(value, fallback = "") {
693
+ return typeof value === "string" ? value : fallback;
694
+ }
695
+ function asNullableString2(value) {
696
+ return typeof value === "string" ? value : null;
697
+ }
698
+ function asNumber2(value) {
699
+ return typeof value === "number" ? value : Number(value ?? 0) || 0;
700
+ }
701
+ function asNullableNumber(value) {
702
+ return typeof value === "number" ? value : null;
703
+ }
704
+ function asMoney(value) {
705
+ if (typeof value === "string") return value;
706
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
707
+ return "0.00";
708
+ }
709
+ var ComplianceTaxReport = class _ComplianceTaxReport {
710
+ constructor(id, companyId, complianceTransactionId, ledgerId, country, regime, state, stateLabel, isTerminal, providerTaxReportId, providerLedgerId, baseAmount, taxAmount, totalAmount, refusalReason, registeredAt, refusedAt, createdAt, updatedAt) {
711
+ this.id = id;
712
+ this.companyId = companyId;
713
+ this.complianceTransactionId = complianceTransactionId;
714
+ this.ledgerId = ledgerId;
715
+ this.country = country;
716
+ this.regime = regime;
717
+ this.state = state;
718
+ this.stateLabel = stateLabel;
719
+ this.isTerminal = isTerminal;
720
+ this.providerTaxReportId = providerTaxReportId;
721
+ this.providerLedgerId = providerLedgerId;
722
+ this.baseAmount = baseAmount;
723
+ this.taxAmount = taxAmount;
724
+ this.totalAmount = totalAmount;
725
+ this.refusalReason = refusalReason;
726
+ this.registeredAt = registeredAt;
727
+ this.refusedAt = refusedAt;
728
+ this.createdAt = createdAt;
729
+ this.updatedAt = updatedAt;
730
+ }
731
+ id;
732
+ companyId;
733
+ complianceTransactionId;
734
+ ledgerId;
735
+ country;
736
+ regime;
737
+ state;
738
+ stateLabel;
739
+ isTerminal;
740
+ providerTaxReportId;
741
+ providerLedgerId;
742
+ baseAmount;
743
+ taxAmount;
744
+ totalAmount;
745
+ refusalReason;
746
+ registeredAt;
747
+ refusedAt;
748
+ createdAt;
749
+ updatedAt;
750
+ static fromArray(data) {
751
+ return new _ComplianceTaxReport(
752
+ asNumber2(data["id"]),
753
+ asNumber2(data["company_id"]),
754
+ asNullableNumber(data["compliance_transaction_id"]),
755
+ asNullableNumber(data["ledger_id"]),
756
+ asString2(data["country"]),
757
+ asString2(data["regime"]),
758
+ toComplianceTaxReportState(data["state"]),
759
+ asString2(data["state_label"]),
760
+ data["is_terminal"] === true,
761
+ asNullableString2(data["provider_tax_report_id"]),
762
+ asNullableString2(data["provider_ledger_id"]),
763
+ asMoney(data["base_amount"]),
764
+ asMoney(data["tax_amount"]),
765
+ asMoney(data["total_amount"]),
766
+ asNullableString2(data["refusal_reason"]),
767
+ asNullableString2(data["registered_at"]),
768
+ asNullableString2(data["refused_at"]),
769
+ asNullableString2(data["created_at"]),
770
+ asNullableString2(data["updated_at"])
771
+ );
772
+ }
773
+ };
774
+
775
+ // src/dto/ComplianceTaxReportPage.ts
776
+ var ComplianceTaxReportPage = class _ComplianceTaxReportPage {
777
+ constructor(rows, currentPage, perPage, total, lastPage) {
778
+ this.rows = rows;
779
+ this.currentPage = currentPage;
780
+ this.perPage = perPage;
781
+ this.total = total;
782
+ this.lastPage = lastPage;
783
+ }
784
+ rows;
785
+ currentPage;
786
+ perPage;
787
+ total;
788
+ lastPage;
789
+ static fromResponse(payload) {
790
+ const obj = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
791
+ const rows = Array.isArray(obj["data"]) ? obj["data"].filter((row) => typeof row === "object" && row !== null).map(ComplianceTaxReport.fromArray) : [];
792
+ const meta = obj["meta"] !== null && typeof obj["meta"] === "object" && !Array.isArray(obj["meta"]) ? obj["meta"] : {};
793
+ const num = (value, fallback) => typeof value === "number" ? value : fallback;
794
+ return new _ComplianceTaxReportPage(
795
+ rows,
796
+ num(meta["current_page"], 1),
797
+ num(meta["per_page"], rows.length),
798
+ num(meta["total"], rows.length),
799
+ num(meta["last_page"], 1)
800
+ );
801
+ }
802
+ [Symbol.iterator]() {
803
+ return this.rows[Symbol.iterator]();
804
+ }
805
+ get length() {
806
+ return this.rows.length;
807
+ }
808
+ };
809
+
810
+ // src/enums/ComplianceTransactionState.ts
811
+ var ComplianceTransactionState = {
812
+ PENDING: "pending",
813
+ SENDING: "sending",
814
+ SUBMITTED: "submitted",
815
+ ERROR: "error",
816
+ UNKNOWN: "unknown"
817
+ };
818
+ var VALUES3 = Object.values(ComplianceTransactionState);
819
+ function toComplianceTransactionState(value) {
820
+ if (typeof value === "string") {
821
+ const normalized = value.trim().toLowerCase();
822
+ if (VALUES3.includes(normalized)) {
823
+ return normalized;
824
+ }
825
+ }
826
+ return ComplianceTransactionState.UNKNOWN;
827
+ }
828
+ var DESCRIPTIONS3 = {
829
+ [ComplianceTransactionState.PENDING]: "Recorded locally, not yet submitted to the provider.",
830
+ [ComplianceTransactionState.SENDING]: "Submission to the provider in progress.",
831
+ [ComplianceTransactionState.SUBMITTED]: "Successfully submitted to the provider.",
832
+ [ComplianceTransactionState.ERROR]: "Submission failed. See submissionError.",
833
+ [ComplianceTransactionState.UNKNOWN]: "Unknown state."
834
+ };
835
+ function describeComplianceTransactionState(state) {
836
+ return DESCRIPTIONS3[state];
837
+ }
838
+
839
+ // src/enums/ComplianceTransactionType.ts
840
+ var ComplianceTransactionType = {
841
+ B2C_OUTBOUND: "b2c_outbound",
842
+ B2B_DOMESTIC_OUTBOUND: "b2b_domestic_outbound",
843
+ B2B_DOMESTIC_INBOUND: "b2b_domestic_inbound",
844
+ CROSSBORDER_OUTBOUND: "crossborder_outbound",
845
+ CROSSBORDER_INBOUND: "crossborder_inbound",
846
+ UNKNOWN: "unknown"
847
+ };
848
+ var VALUES4 = Object.values(ComplianceTransactionType);
849
+ function toComplianceTransactionType(value) {
850
+ if (typeof value === "string") {
851
+ const normalized = value.trim().toLowerCase();
852
+ if (VALUES4.includes(normalized)) {
853
+ return normalized;
854
+ }
855
+ }
856
+ return ComplianceTransactionType.UNKNOWN;
857
+ }
858
+ var DESCRIPTIONS4 = {
859
+ [ComplianceTransactionType.B2C_OUTBOUND]: "Sale to a private person in the merchant country (DGFiP Flux 10.3).",
860
+ [ComplianceTransactionType.B2B_DOMESTIC_OUTBOUND]: "Sale to a domestic business (DGFiP Flux 1, Phase 2).",
861
+ [ComplianceTransactionType.B2B_DOMESTIC_INBOUND]: "Purchase from a domestic business (DGFiP Flux 1, Phase 2).",
862
+ [ComplianceTransactionType.CROSSBORDER_OUTBOUND]: "Sale to a non-domestic business (DGFiP Flux 10.1).",
863
+ [ComplianceTransactionType.CROSSBORDER_INBOUND]: "Purchase from a non-domestic supplier (DGFiP Flux 10.1).",
864
+ [ComplianceTransactionType.UNKNOWN]: "Unknown transaction type."
865
+ };
866
+ function describeComplianceTransactionType(type) {
867
+ return DESCRIPTIONS4[type];
868
+ }
869
+
870
+ // src/dto/ComplianceTransaction.ts
871
+ function asString3(value, fallback = "") {
872
+ return typeof value === "string" ? value : fallback;
873
+ }
874
+ function asNullableString3(value) {
875
+ return typeof value === "string" ? value : null;
876
+ }
877
+ function asNumber3(value) {
878
+ return typeof value === "number" ? value : Number(value ?? 0) || 0;
879
+ }
880
+ function asMoney2(value) {
881
+ if (typeof value === "string") return value;
882
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
883
+ return "0.00";
884
+ }
885
+ var ComplianceTransaction = class _ComplianceTransaction {
886
+ constructor(id, companyId, complianceEnrollmentId, country, regime, transactionType, transactionTypeLabel, state, stateLabel, invoiceNumber, invoiceDate, dueDate, currency, subtotal, taxAmount, total, counterpartyName, counterpartyCountry, counterpartyVatNumber, isPaid, paidAt, providerInvoiceId, submissionError, reportedAt, taxReport, createdAt, updatedAt) {
887
+ this.id = id;
888
+ this.companyId = companyId;
889
+ this.complianceEnrollmentId = complianceEnrollmentId;
890
+ this.country = country;
891
+ this.regime = regime;
892
+ this.transactionType = transactionType;
893
+ this.transactionTypeLabel = transactionTypeLabel;
894
+ this.state = state;
895
+ this.stateLabel = stateLabel;
896
+ this.invoiceNumber = invoiceNumber;
897
+ this.invoiceDate = invoiceDate;
898
+ this.dueDate = dueDate;
899
+ this.currency = currency;
900
+ this.subtotal = subtotal;
901
+ this.taxAmount = taxAmount;
902
+ this.total = total;
903
+ this.counterpartyName = counterpartyName;
904
+ this.counterpartyCountry = counterpartyCountry;
905
+ this.counterpartyVatNumber = counterpartyVatNumber;
906
+ this.isPaid = isPaid;
907
+ this.paidAt = paidAt;
908
+ this.providerInvoiceId = providerInvoiceId;
909
+ this.submissionError = submissionError;
910
+ this.reportedAt = reportedAt;
911
+ this.taxReport = taxReport;
912
+ this.createdAt = createdAt;
913
+ this.updatedAt = updatedAt;
914
+ }
915
+ id;
916
+ companyId;
917
+ complianceEnrollmentId;
918
+ country;
919
+ regime;
920
+ transactionType;
921
+ transactionTypeLabel;
922
+ state;
923
+ stateLabel;
924
+ invoiceNumber;
925
+ invoiceDate;
926
+ dueDate;
927
+ currency;
928
+ subtotal;
929
+ taxAmount;
930
+ total;
931
+ counterpartyName;
932
+ counterpartyCountry;
933
+ counterpartyVatNumber;
934
+ isPaid;
935
+ paidAt;
936
+ providerInvoiceId;
937
+ submissionError;
938
+ reportedAt;
939
+ taxReport;
940
+ createdAt;
941
+ updatedAt;
942
+ static fromArray(data) {
943
+ const taxReport = typeof data["tax_report"] === "object" && data["tax_report"] !== null && !Array.isArray(data["tax_report"]) ? ComplianceTaxReport.fromArray(data["tax_report"]) : void 0;
944
+ return new _ComplianceTransaction(
945
+ asNumber3(data["id"]),
946
+ asNumber3(data["company_id"]),
947
+ asNumber3(data["compliance_enrollment_id"]),
948
+ asString3(data["country"]),
949
+ asString3(data["regime"]),
950
+ toComplianceTransactionType(data["transaction_type"]),
951
+ asString3(data["transaction_type_label"]),
952
+ toComplianceTransactionState(data["state"]),
953
+ asString3(data["state_label"]),
954
+ asString3(data["invoice_number"]),
955
+ asNullableString3(data["invoice_date"]),
956
+ asNullableString3(data["due_date"]),
957
+ asString3(data["currency"], "EUR"),
958
+ asMoney2(data["subtotal"]),
959
+ asMoney2(data["tax_amount"]),
960
+ asMoney2(data["total"]),
961
+ asNullableString3(data["counterparty_name"]),
962
+ asNullableString3(data["counterparty_country"]),
963
+ asNullableString3(data["counterparty_vat_number"]),
964
+ data["is_paid"] === true,
965
+ asNullableString3(data["paid_at"]),
966
+ asNullableString3(data["provider_invoice_id"]),
967
+ asNullableString3(data["submission_error"]),
968
+ asNullableString3(data["reported_at"]),
969
+ taxReport,
970
+ asNullableString3(data["created_at"]),
971
+ asNullableString3(data["updated_at"])
972
+ );
973
+ }
974
+ };
975
+
976
+ // src/dto/ComplianceTransactionPage.ts
977
+ var ComplianceTransactionPage = class _ComplianceTransactionPage {
978
+ constructor(rows, currentPage, perPage, total, lastPage) {
979
+ this.rows = rows;
980
+ this.currentPage = currentPage;
981
+ this.perPage = perPage;
982
+ this.total = total;
983
+ this.lastPage = lastPage;
984
+ }
985
+ rows;
986
+ currentPage;
987
+ perPage;
988
+ total;
989
+ lastPage;
990
+ static fromResponse(payload) {
991
+ const obj = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
992
+ const rows = Array.isArray(obj["data"]) ? obj["data"].filter((row) => typeof row === "object" && row !== null).map(ComplianceTransaction.fromArray) : [];
993
+ const meta = obj["meta"] !== null && typeof obj["meta"] === "object" && !Array.isArray(obj["meta"]) ? obj["meta"] : {};
994
+ const num = (value, fallback) => typeof value === "number" ? value : fallback;
995
+ return new _ComplianceTransactionPage(
996
+ rows,
997
+ num(meta["current_page"], 1),
998
+ num(meta["per_page"], rows.length),
999
+ num(meta["total"], rows.length),
1000
+ num(meta["last_page"], 1)
1001
+ );
1002
+ }
1003
+ [Symbol.iterator]() {
1004
+ return this.rows[Symbol.iterator]();
1005
+ }
1006
+ get length() {
1007
+ return this.rows.length;
1008
+ }
1009
+ };
1010
+
1011
+ // src/dto/ImportResult.ts
1012
+ var ImportResult = class _ImportResult {
1013
+ constructor(created, skippedDuplicates, errors) {
1014
+ this.created = created;
1015
+ this.skippedDuplicates = skippedDuplicates;
1016
+ this.errors = errors;
1017
+ }
1018
+ created;
1019
+ skippedDuplicates;
1020
+ errors;
1021
+ static fromArray(data) {
1022
+ const errors = (Array.isArray(data["errors"]) ? data["errors"] : []).filter(
1023
+ (row) => typeof row === "string"
1024
+ );
1025
+ const num = (value) => typeof value === "number" ? value : 0;
1026
+ return new _ImportResult(num(data["created"]), num(data["skipped_duplicates"]), errors);
1027
+ }
1028
+ };
1029
+
1030
+ // src/dto/RevenueStatistics.ts
1031
+ function asString4(value, fallback = "") {
1032
+ return typeof value === "string" ? value : fallback;
1033
+ }
1034
+ function asNumber4(value) {
1035
+ return typeof value === "number" ? value : Number(value ?? 0) || 0;
1036
+ }
1037
+ function asMoney3(value) {
1038
+ return typeof value === "string" ? value : "0.0000";
1039
+ }
1040
+ function asRecords(value) {
1041
+ if (!Array.isArray(value)) return [];
1042
+ return value.filter((row) => typeof row === "object" && row !== null);
1043
+ }
1044
+ function toTotals(data) {
1045
+ return {
1046
+ count: asNumber4(data["count"]),
1047
+ subtotal: asMoney3(data["subtotal"]),
1048
+ taxAmount: asMoney3(data["tax_amount"]),
1049
+ total: asMoney3(data["total"])
1050
+ };
1051
+ }
1052
+ var RevenueStatistics = class _RevenueStatistics {
1053
+ constructor(dateFrom, dateTo, interval, primaryCurrency, isMultiCurrency, totals, timeSeries, byType, byCountry, byState, byCurrency) {
1054
+ this.dateFrom = dateFrom;
1055
+ this.dateTo = dateTo;
1056
+ this.interval = interval;
1057
+ this.primaryCurrency = primaryCurrency;
1058
+ this.isMultiCurrency = isMultiCurrency;
1059
+ this.totals = totals;
1060
+ this.timeSeries = timeSeries;
1061
+ this.byType = byType;
1062
+ this.byCountry = byCountry;
1063
+ this.byState = byState;
1064
+ this.byCurrency = byCurrency;
1065
+ }
1066
+ dateFrom;
1067
+ dateTo;
1068
+ interval;
1069
+ primaryCurrency;
1070
+ isMultiCurrency;
1071
+ totals;
1072
+ timeSeries;
1073
+ byType;
1074
+ byCountry;
1075
+ byState;
1076
+ byCurrency;
1077
+ static fromArray(data) {
1078
+ const range = typeof data["range"] === "object" && data["range"] !== null ? data["range"] : {};
1079
+ const totals = toTotals(
1080
+ typeof data["totals"] === "object" && data["totals"] !== null ? data["totals"] : {}
1081
+ );
1082
+ const timeSeries = asRecords(data["time_series"]).map((row) => ({
1083
+ bucket: asString4(row["bucket"]),
1084
+ ...toTotals(row)
1085
+ }));
1086
+ const byType = asRecords(data["by_type"]).map((row) => ({
1087
+ type: asString4(row["type"]),
1088
+ typeLabel: asString4(row["type_label"]),
1089
+ count: asNumber4(row["count"]),
1090
+ total: asMoney3(row["total"])
1091
+ }));
1092
+ const byCountry = asRecords(data["by_country"]).map((row) => ({
1093
+ country: asString4(row["country"], "unknown"),
1094
+ count: asNumber4(row["count"]),
1095
+ total: asMoney3(row["total"])
1096
+ }));
1097
+ const byState = asRecords(data["by_state"]).map((row) => ({
1098
+ state: asString4(row["state"]),
1099
+ stateLabel: asString4(row["state_label"]),
1100
+ count: asNumber4(row["count"]),
1101
+ total: asMoney3(row["total"])
1102
+ }));
1103
+ const byCurrency = asRecords(data["by_currency"]).map((row) => ({
1104
+ currency: asString4(row["currency"]),
1105
+ ...toTotals(row)
1106
+ }));
1107
+ return new _RevenueStatistics(
1108
+ asString4(range["date_from"]) || void 0,
1109
+ asString4(range["date_to"]) || void 0,
1110
+ asString4(range["interval"]) || void 0,
1111
+ asString4(data["primary_currency"], "EUR"),
1112
+ data["is_multi_currency"] === true,
1113
+ totals,
1114
+ timeSeries,
1115
+ byType,
1116
+ byCountry,
1117
+ byState,
1118
+ byCurrency
1119
+ );
1120
+ }
1121
+ };
1122
+
1123
+ // src/dto/SireneLookupResult.ts
1124
+ function asString5(value, fallback = "") {
1125
+ return typeof value === "string" ? value : fallback;
1126
+ }
1127
+ function asNullableString4(value) {
1128
+ return typeof value === "string" ? value : null;
1129
+ }
1130
+ var SireneLookupResult = class _SireneLookupResult {
1131
+ constructor(companyName, siren, siret, nafCode, nafFull, enterpriseSize, address, city, postalcode) {
1132
+ this.companyName = companyName;
1133
+ this.siren = siren;
1134
+ this.siret = siret;
1135
+ this.nafCode = nafCode;
1136
+ this.nafFull = nafFull;
1137
+ this.enterpriseSize = enterpriseSize;
1138
+ this.address = address;
1139
+ this.city = city;
1140
+ this.postalcode = postalcode;
1141
+ }
1142
+ companyName;
1143
+ siren;
1144
+ siret;
1145
+ nafCode;
1146
+ nafFull;
1147
+ enterpriseSize;
1148
+ address;
1149
+ city;
1150
+ postalcode;
1151
+ static fromArray(data) {
1152
+ return new _SireneLookupResult(
1153
+ asString5(data["company_name"]),
1154
+ asString5(data["siren"]),
1155
+ asNullableString4(data["siret"]),
1156
+ asNullableString4(data["naf_code"]),
1157
+ asNullableString4(data["naf_full"]),
1158
+ asNullableString4(data["enterprise_size"]),
1159
+ asNullableString4(data["address"]),
1160
+ asNullableString4(data["city"]),
1161
+ asNullableString4(data["postalcode"])
1162
+ );
1163
+ }
1164
+ };
1165
+
1166
+ // src/dto/VatRates.ts
1167
+ function asString6(value, fallback = "") {
1168
+ return typeof value === "string" ? value : fallback;
1169
+ }
1170
+ function asNumber5(value) {
1171
+ return typeof value === "number" ? value : Number(value ?? 0) || 0;
1172
+ }
1173
+ var VatRates = class _VatRates {
1174
+ constructor(country, currency, rates) {
1175
+ this.country = country;
1176
+ this.currency = currency;
1177
+ this.rates = rates;
1178
+ }
1179
+ country;
1180
+ currency;
1181
+ rates;
1182
+ static fromArray(data) {
1183
+ const rates = (Array.isArray(data["rates"]) ? data["rates"] : []).filter((row) => typeof row === "object" && row !== null).map((row) => ({
1184
+ percent: asNumber5(row["percent"]),
1185
+ category: asString6(row["category"]),
1186
+ labelKey: asString6(row["label_key"]),
1187
+ taxName: asString6(row["tax_name"]),
1188
+ requiresVatex: row["requires_vatex"] === true
1189
+ }));
1190
+ return new _VatRates(asString6(data["country"]), asString6(data["currency"], "EUR"), rates);
1191
+ }
1192
+ };
1193
+
1194
+ // src/exceptions/ValidationException.ts
1195
+ var ValidationException = class extends TaxoraException {
1196
+ statusCode = 422;
1197
+ errors;
1198
+ constructor(message, errors, context = {}) {
1199
+ super(message, context);
1200
+ this.name = "ValidationException";
1201
+ this.errors = errors;
1202
+ Object.setPrototypeOf(this, new.target.prototype);
1203
+ }
1204
+ getErrors() {
1205
+ return this.errors;
1206
+ }
1207
+ };
1208
+
1209
+ // src/endpoints/EReportingEndpoint.ts
1210
+ var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
1211
+ var NAF_CODE_REGEX = /^\d{2}$/;
1212
+ var INTERVALS = ["day", "week", "month"];
1213
+ var ENTERPRISE_SIZES = ["micro", "pme", "eti", "ge"];
1214
+ var TYPE_OPERATIONS = ["services", "goods", "mixed"];
1215
+ var TRANSACTION_TYPES = [
1216
+ "b2c_outbound",
1217
+ "b2b_domestic_outbound",
1218
+ "b2b_domestic_inbound",
1219
+ "crossborder_outbound",
1220
+ "crossborder_inbound"
1221
+ ];
1222
+ var EReportingEndpoint = class {
1223
+ constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
1224
+ this.baseUrl = baseUrl;
1225
+ this.apiKey = apiKey;
1226
+ this.tokenStorage = tokenStorage;
1227
+ this.httpClient = httpClient;
1228
+ this.retryPolicy = retryPolicy;
1229
+ }
1230
+ baseUrl;
1231
+ apiKey;
1232
+ tokenStorage;
1233
+ httpClient;
1234
+ retryPolicy;
1235
+ // ---------------------------------------------------------------------
1236
+ // Enrollments
1237
+ // ---------------------------------------------------------------------
1238
+ /**
1239
+ * Create a compliance enrollment and provision the provider account. By
1240
+ * default the DGFiP regime is activated right away; pass `autoActivate: false`
1241
+ * to only create the account.
1242
+ *
1243
+ * On provider failure the API responds 502 (mapped to an HttpException) and
1244
+ * the enrollment is persisted in `error_account`/`error_activation` state so
1245
+ * provisioning can be retried.
1246
+ */
1247
+ async createEnrollment(input) {
1248
+ this.validateEnrollmentInput(input);
1249
+ const body = {
1250
+ email: input.email,
1251
+ naf_code: input.nafCode,
1252
+ enterprise_size: input.enterpriseSize,
1253
+ type_operation: input.typeOperation,
1254
+ reporting_start_date: this.formatDate(input.reportingStartDate)
1255
+ };
1256
+ if (input.country !== void 0) body["country"] = input.country;
1257
+ if (input.regime !== void 0) body["regime"] = input.regime;
1258
+ if (input.siret !== void 0) body["siret"] = input.siret;
1259
+ if (input.siren !== void 0) body["siren"] = input.siren;
1260
+ if (input.vatNumber !== void 0) body["vat_number"] = input.vatNumber;
1261
+ if (input.companyName !== void 0) body["company_name"] = input.companyName;
1262
+ if (input.address !== void 0) body["address"] = input.address;
1263
+ if (input.city !== void 0) body["city"] = input.city;
1264
+ if (input.postalcode !== void 0) body["postalcode"] = input.postalcode;
1265
+ if (input.province !== void 0) body["province"] = input.province;
1266
+ if (input.autoActivate !== void 0) body["auto_activate"] = input.autoActivate;
1267
+ const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/enrollments`, body);
1268
+ const data = await this.parseJsonResponse(response);
1269
+ return ComplianceEnrollment.fromArray(data);
1270
+ }
1271
+ /** Paginated list of the company's enrollments (newest first). `perPage` is capped at 100 server-side. */
1272
+ async listEnrollments(page = 1, perPage = 25) {
1273
+ const params = new URLSearchParams();
1274
+ params.set("page", String(page));
1275
+ params.set("per_page", String(perPage));
1276
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/enrollments?${params.toString()}`);
1277
+ return ComplianceEnrollmentPage.fromResponse(await this.parseJsonResponse(response));
1278
+ }
1279
+ /** Get a single enrollment by id. */
1280
+ async getEnrollment(id) {
1281
+ this.validateId(id, "id");
1282
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/enrollments/${id}`);
1283
+ const data = await this.parseJsonResponse(response);
1284
+ return ComplianceEnrollment.fromArray(data);
1285
+ }
1286
+ /**
1287
+ * Look up company data by SIRET, SIREN or French VAT number via the French
1288
+ * SIRENE registry — ideal for prefilling createEnrollment().
1289
+ *
1290
+ * Rate-limited to 3 requests/minute per user (protects the external French
1291
+ * government API); exceeding it yields a 429 (mapped to an HttpException).
1292
+ */
1293
+ async sireneLookup(q) {
1294
+ if (q.trim() === "") {
1295
+ throw new TaxoraException("q must not be empty.");
1296
+ }
1297
+ const params = new URLSearchParams();
1298
+ params.set("q", q);
1299
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/sirene-lookup?${params.toString()}`);
1300
+ const data = await this.parseJsonResponse(response);
1301
+ return SireneLookupResult.fromArray(data);
1302
+ }
1303
+ // ---------------------------------------------------------------------
1304
+ // Transactions
1305
+ // ---------------------------------------------------------------------
1306
+ /**
1307
+ * Record and (by default) submit a Flux 10 transaction. Pass `submitNow: false`
1308
+ * to only record it — submission can be triggered later via submitTransaction().
1309
+ *
1310
+ * Immediate per-invoice submission requires the e-invoicing feature on the
1311
+ * account; in pure e-reporting mode the transaction is only recorded and
1312
+ * reported via the aggregated daily ledgers.
1313
+ *
1314
+ * Creation is idempotent on the invoice's natural key (enrollment, type,
1315
+ * invoice number, invoice date, counterparty VAT): a re-sent create returns
1316
+ * the existing transaction with HTTP 200 instead of creating a duplicate.
1317
+ *
1318
+ * On provider failure the API responds 502 (mapped to an HttpException).
1319
+ */
1320
+ async createTransaction(input) {
1321
+ this.validateId(input.complianceEnrollmentId, "complianceEnrollmentId");
1322
+ this.validateTransactionType(input.transactionType);
1323
+ this.validateInvoiceNumber(input.invoiceNumber);
1324
+ this.validateInvoiceLines(input.invoiceLines);
1325
+ this.validateOptionalCodes(input);
1326
+ const body = {
1327
+ compliance_enrollment_id: input.complianceEnrollmentId,
1328
+ transaction_type: input.transactionType,
1329
+ invoice_number: input.invoiceNumber,
1330
+ invoice_date: this.formatDate(input.invoiceDate),
1331
+ subtotal: input.subtotal,
1332
+ total: input.total,
1333
+ invoice_lines_attributes: this.buildInvoiceLines(input.invoiceLines)
1334
+ };
1335
+ this.applyOptionalTransactionFields(body, input);
1336
+ if (input.submitNow !== void 0) body["submit_now"] = input.submitNow;
1337
+ const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/transactions`, body);
1338
+ const data = await this.parseJsonResponse(response);
1339
+ return ComplianceTransaction.fromArray(data);
1340
+ }
1341
+ /** Paginated list of the company's transactions (newest invoice date first), filterable by date / state / type. */
1342
+ async listTransactions(filters = {}) {
1343
+ const params = new URLSearchParams();
1344
+ if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
1345
+ if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
1346
+ if (filters.state !== void 0) params.set("state", filters.state);
1347
+ if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
1348
+ if (filters.page !== void 0) params.set("page", String(filters.page));
1349
+ if (filters.perPage !== void 0) params.set("per_page", String(filters.perPage));
1350
+ const queryString = params.toString();
1351
+ const url = `${this.baseUrl}/compliance/transactions${queryString ? `?${queryString}` : ""}`;
1352
+ const response = await this.sendRequest("GET", url);
1353
+ return ComplianceTransactionPage.fromResponse(await this.parseJsonResponse(response));
1354
+ }
1355
+ /** Get a single transaction by id (includes its tax report when one exists). */
1356
+ async getTransaction(id) {
1357
+ this.validateId(id, "id");
1358
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/transactions/${id}`);
1359
+ const data = await this.parseJsonResponse(response);
1360
+ return ComplianceTransaction.fromArray(data);
1361
+ }
1362
+ /** Update a still-pending transaction. Only the provided fields are sent; already-submitted transactions are rejected with 422. */
1363
+ async updateTransaction(id, input) {
1364
+ this.validateId(id, "id");
1365
+ if (input.transactionType !== void 0) this.validateTransactionType(input.transactionType);
1366
+ if (input.invoiceNumber !== void 0) this.validateInvoiceNumber(input.invoiceNumber);
1367
+ if (input.invoiceLines !== void 0) this.validateInvoiceLines(input.invoiceLines);
1368
+ this.validateOptionalCodes(input);
1369
+ const body = {};
1370
+ if (input.transactionType !== void 0) body["transaction_type"] = input.transactionType;
1371
+ if (input.invoiceNumber !== void 0) body["invoice_number"] = input.invoiceNumber;
1372
+ if (input.invoiceDate !== void 0) body["invoice_date"] = this.formatDate(input.invoiceDate);
1373
+ if (input.subtotal !== void 0) body["subtotal"] = input.subtotal;
1374
+ if (input.total !== void 0) body["total"] = input.total;
1375
+ if (input.invoiceLines !== void 0) body["invoice_lines_attributes"] = this.buildInvoiceLines(input.invoiceLines);
1376
+ this.applyOptionalTransactionFields(body, input);
1377
+ const response = await this.sendRequest("PUT", `${this.baseUrl}/compliance/transactions/${id}`, body);
1378
+ const data = await this.parseJsonResponse(response);
1379
+ return ComplianceTransaction.fromArray(data);
1380
+ }
1381
+ /** Delete a still-pending transaction. Already-submitted transactions are rejected with 422. */
1382
+ async deleteTransaction(id) {
1383
+ this.validateId(id, "id");
1384
+ const response = await this.sendRequest("DELETE", `${this.baseUrl}/compliance/transactions/${id}`);
1385
+ await this.assertNoContentResponse(response);
1386
+ }
1387
+ /**
1388
+ * Submit (or retry) an existing `pending`/`error` transaction to the provider.
1389
+ * Replays the invoice lines stored at creation, so they don't have to be re-sent.
1390
+ *
1391
+ * Requires the e-invoicing feature on the account — pure e-reporting customers
1392
+ * get a 422 (transactions are reported via the aggregated daily ledgers instead).
1393
+ *
1394
+ * On provider failure the API responds 502 (mapped to an HttpException);
1395
+ * already-submitted transactions are rejected with 422.
1396
+ */
1397
+ async submitTransaction(id) {
1398
+ this.validateId(id, "id");
1399
+ const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/transactions/${id}/submit`);
1400
+ const data = await this.parseJsonResponse(response);
1401
+ return ComplianceTransaction.fromArray(data);
1402
+ }
1403
+ /**
1404
+ * Bulk-import transactions from a semicolon-separated CSV (one row per invoice
1405
+ * line; rows sharing an invoice_number are grouped into one transaction).
1406
+ * Required columns: invoice_number, invoice_date, transaction_type, subtotal,
1407
+ * total, line_description, line_quantity, line_price, tax_name, tax_percent,
1408
+ * tax_category.
1409
+ *
1410
+ * The import is idempotent: re-uploading the same file skips rows whose
1411
+ * invoice already exists (reported via `skippedDuplicates`).
1412
+ */
1413
+ async importTransactions(enrollmentId, csvContent, filename = "transactions.csv") {
1414
+ this.validateId(enrollmentId, "enrollmentId");
1415
+ if (csvContent.trim() === "") {
1416
+ throw new TaxoraException("csvContent must not be empty.");
1417
+ }
1418
+ const form = new FormData();
1419
+ form.set("compliance_enrollment_id", String(enrollmentId));
1420
+ form.set("file", new Blob([csvContent], { type: "text/csv" }), filename);
1421
+ const response = await this.sendMultipartRequest("POST", `${this.baseUrl}/compliance/transactions/import`, form);
1422
+ const data = await this.parseJsonResponse(response);
1423
+ return ImportResult.fromArray(data);
1424
+ }
1425
+ // ---------------------------------------------------------------------
1426
+ // Analytics, configuration & reports
1427
+ // ---------------------------------------------------------------------
1428
+ /**
1429
+ * Aggregated turnover statistics for the authenticated company's e-reporting
1430
+ * transactions. All arguments are optional — the API defaults to the last 12
1431
+ * months and a monthly interval.
1432
+ */
1433
+ async getRevenueStatistics(filters = {}) {
1434
+ if (filters.interval !== void 0 && !INTERVALS.includes(filters.interval)) {
1435
+ throw new TaxoraException(
1436
+ `Invalid statistics interval "${String(filters.interval)}". Expected one of: ${INTERVALS.join(", ")}.`
1437
+ );
1438
+ }
1439
+ const params = new URLSearchParams();
1440
+ if (filters.dateFrom !== void 0) params.set("date_from", this.formatDate(filters.dateFrom));
1441
+ if (filters.dateTo !== void 0) params.set("date_to", this.formatDate(filters.dateTo));
1442
+ if (filters.interval !== void 0) params.set("interval", filters.interval);
1443
+ if (filters.transactionType !== void 0) params.set("transaction_type", filters.transactionType);
1444
+ if (filters.state !== void 0) params.set("state", filters.state);
1445
+ const queryString = params.toString();
1446
+ const url = `${this.baseUrl}/compliance/revenue-statistics${queryString ? `?${queryString}` : ""}`;
1447
+ const response = await this.sendRequest("GET", url);
1448
+ const data = await this.parseJsonResponse(response);
1449
+ return RevenueStatistics.fromArray(data);
1450
+ }
1451
+ /**
1452
+ * The canonical VAT rates & DGFiP tax categories for a reporting country
1453
+ * (the seller's reporting-country rates, not the counterparty's). Falls back
1454
+ * to the configured default country (FR) when none/unknown is given.
1455
+ */
1456
+ async getVatRates(country) {
1457
+ const params = new URLSearchParams();
1458
+ if (country !== void 0) params.set("country", country);
1459
+ const queryString = params.toString();
1460
+ const url = `${this.baseUrl}/compliance/vat-rates${queryString ? `?${queryString}` : ""}`;
1461
+ const response = await this.sendRequest("GET", url);
1462
+ const data = await this.parseJsonResponse(response);
1463
+ return VatRates.fromArray(data);
1464
+ }
1465
+ /** Paginated list of the company's tax reports (newest first), optionally filtered by state. */
1466
+ async listTaxReports(page = 1, perPage = 25, state) {
1467
+ const params = new URLSearchParams();
1468
+ params.set("page", String(page));
1469
+ params.set("per_page", String(perPage));
1470
+ if (state !== void 0) params.set("state", state);
1471
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/tax-reports?${params.toString()}`);
1472
+ return ComplianceTaxReportPage.fromResponse(await this.parseJsonResponse(response));
1473
+ }
1474
+ /** Get a single tax report by id. */
1475
+ async getTaxReport(id) {
1476
+ this.validateId(id, "id");
1477
+ const response = await this.sendRequest("GET", `${this.baseUrl}/compliance/tax-reports/${id}`);
1478
+ const data = await this.parseJsonResponse(response);
1479
+ return ComplianceTaxReport.fromArray(data);
1480
+ }
1481
+ /**
1482
+ * Request activation of the E-Reporting feature for the account. This route
1483
+ * is deliberately NOT gated by compliance access — it exists precisely for
1484
+ * accounts that do not have the feature yet. The authenticated user's
1485
+ * name/email take precedence over the form values server-side.
1486
+ *
1487
+ * Note: unlike the other compliance routes, validation failures here are
1488
+ * returned as HTTP 400 (surfaced as HttpException), not 422.
1489
+ */
1490
+ async requestEReportingAccess(input = {}) {
1491
+ const body = {};
1492
+ if (input.name !== void 0) body["name"] = input.name;
1493
+ if (input.email !== void 0) body["email"] = input.email;
1494
+ if (input.company !== void 0) body["company"] = input.company;
1495
+ if (input.phone !== void 0) body["phone"] = input.phone;
1496
+ if (input.message !== void 0) body["message"] = input.message;
1497
+ if (input.language !== void 0) body["language"] = input.language;
1498
+ const response = await this.sendRequest("POST", `${this.baseUrl}/compliance/e-reporting-request`, body);
1499
+ await this.parseJsonResponse(response);
1500
+ }
1501
+ // ---------------------------------------------------------------------
1502
+ // Input validation (fail fast, before any request is sent)
1503
+ // ---------------------------------------------------------------------
1504
+ validateEnrollmentInput(input) {
1505
+ if (input.siret === void 0 && input.siren === void 0) {
1506
+ throw new TaxoraException("Either siret or siren is required.");
1507
+ }
1508
+ if (input.siret !== void 0 && input.siret.length !== 14) {
1509
+ throw new TaxoraException("siret must be exactly 14 characters.");
1510
+ }
1511
+ if (input.siren !== void 0 && input.siren.length !== 9) {
1512
+ throw new TaxoraException("siren must be exactly 9 characters.");
1513
+ }
1514
+ if (input.email.trim() === "" || !input.email.includes("@")) {
1515
+ throw new TaxoraException("email must be a valid email address.");
1516
+ }
1517
+ if (!NAF_CODE_REGEX.test(input.nafCode)) {
1518
+ throw new TaxoraException('nafCode must be exactly 2 digits, e.g. "47".');
1519
+ }
1520
+ if (!ENTERPRISE_SIZES.includes(input.enterpriseSize)) {
1521
+ throw new TaxoraException(
1522
+ `Invalid enterpriseSize "${String(input.enterpriseSize)}". Expected one of: ${ENTERPRISE_SIZES.join(", ")}.`
1523
+ );
1524
+ }
1525
+ if (!TYPE_OPERATIONS.includes(input.typeOperation)) {
1526
+ throw new TaxoraException(
1527
+ `Invalid typeOperation "${String(input.typeOperation)}". Expected one of: ${TYPE_OPERATIONS.join(", ")}.`
1528
+ );
1529
+ }
1530
+ if (input.country !== void 0 && input.country.length !== 2) {
1531
+ throw new TaxoraException("country must be a 2-character ISO 3166-1 alpha-2 code.");
1532
+ }
1533
+ }
1534
+ validateId(id, name) {
1535
+ if (!Number.isInteger(id) || id <= 0) {
1536
+ throw new TaxoraException(`${name} must be a positive integer.`);
1537
+ }
1538
+ }
1539
+ validateTransactionType(type) {
1540
+ if (!TRANSACTION_TYPES.includes(type)) {
1541
+ throw new TaxoraException(
1542
+ `Invalid transactionType "${String(type)}". Expected one of: ${TRANSACTION_TYPES.join(", ")}.`
1543
+ );
1544
+ }
1545
+ }
1546
+ validateInvoiceNumber(invoiceNumber) {
1547
+ if (invoiceNumber.trim() === "") {
1548
+ throw new TaxoraException("invoiceNumber must not be empty.");
1549
+ }
1550
+ if (invoiceNumber.length > 50) {
1551
+ throw new TaxoraException("invoiceNumber must not exceed 50 characters.");
1552
+ }
1553
+ }
1554
+ validateInvoiceLines(lines) {
1555
+ if (lines.length === 0) {
1556
+ throw new TaxoraException("invoiceLines must contain at least one line.");
1557
+ }
1558
+ lines.forEach((line, index) => {
1559
+ if (line.taxes.length === 0) {
1560
+ throw new TaxoraException(`invoiceLines[${index}].taxes must contain at least one tax.`);
1561
+ }
112
1562
  });
113
- const responseText = await response.text();
114
- if (response.status === 401) {
115
- throw new AuthenticationException("Authentication failed: invalid credentials", {
116
- status: 401,
117
- body: responseText
118
- });
1563
+ }
1564
+ validateOptionalCodes(input) {
1565
+ if (input.currency !== void 0 && input.currency.length !== 3) {
1566
+ throw new TaxoraException("currency must be a 3-character ISO 4217 code.");
119
1567
  }
120
- if (!response.ok) {
121
- throw new HttpException(`HTTP error ${response.status}`, response.status, responseText);
1568
+ if (input.counterpartyCountry !== void 0 && input.counterpartyCountry.length !== 2) {
1569
+ throw new TaxoraException("counterpartyCountry must be a 2-character ISO 3166-1 alpha-2 code.");
122
1570
  }
123
- const data = this.parseResponse(responseText);
124
- const token = Token.fromArray(data);
125
- this.tokenStorage.set(token);
126
- return token;
127
1571
  }
128
- async loginWithClientId(clientId, password, device) {
129
- return this.login(clientId, password, device, LoginIdentifier.CLIENT_ID);
1572
+ // ---------------------------------------------------------------------
1573
+ // Body building (camelCase input → snake_case wire format)
1574
+ // ---------------------------------------------------------------------
1575
+ buildInvoiceLines(lines) {
1576
+ return lines.map((line) => {
1577
+ const built = {
1578
+ description: line.description,
1579
+ quantity: line.quantity,
1580
+ price: line.price,
1581
+ taxes_attributes: line.taxes.map((tax) => {
1582
+ const taxBuilt = {
1583
+ name: tax.name,
1584
+ percent: tax.percent,
1585
+ category: tax.category
1586
+ };
1587
+ if (tax.comment !== void 0) taxBuilt["comment"] = tax.comment;
1588
+ return taxBuilt;
1589
+ })
1590
+ };
1591
+ if (line.unit !== void 0) built["unit"] = line.unit;
1592
+ return built;
1593
+ });
130
1594
  }
131
- async refresh() {
132
- const storedToken = this.tokenStorage.get();
1595
+ /** Applies the optional scalar fields shared by create and update. */
1596
+ applyOptionalTransactionFields(body, input) {
1597
+ if (input.dueDate !== void 0) body["due_date"] = this.formatDate(input.dueDate);
1598
+ if (input.currency !== void 0) body["currency"] = input.currency;
1599
+ if (input.taxAmount !== void 0) body["tax_amount"] = input.taxAmount;
1600
+ if (input.counterpartyName !== void 0) body["counterparty_name"] = input.counterpartyName;
1601
+ if (input.counterpartyCountry !== void 0) body["counterparty_country"] = input.counterpartyCountry;
1602
+ if (input.counterpartyVatNumber !== void 0) body["counterparty_vat_number"] = input.counterpartyVatNumber;
1603
+ if (input.providerContactId !== void 0) body["provider_contact_id"] = input.providerContactId;
1604
+ if (input.paymentMethod !== void 0) body["payment_method"] = input.paymentMethod;
1605
+ if (input.remittanceInformation !== void 0) body["remittance_information"] = input.remittanceInformation;
1606
+ if (input.paymentMethodText !== void 0) body["payment_method_text"] = input.paymentMethodText;
1607
+ if (input.paymentTerms !== void 0) body["payment_terms"] = input.paymentTerms;
1608
+ }
1609
+ // ---------------------------------------------------------------------
1610
+ // HTTP plumbing (kept in sync with SmartEnrichmentEndpoint)
1611
+ // ---------------------------------------------------------------------
1612
+ buildHeaders(withJsonContentType = true) {
133
1613
  const headers = {
134
- "Content-Type": "application/json",
135
1614
  "x-api-key": this.apiKey
136
1615
  };
137
- if (storedToken) {
138
- headers["Authorization"] = `Bearer ${storedToken.accessToken}`;
1616
+ if (withJsonContentType) {
1617
+ headers["Content-Type"] = "application/json";
1618
+ }
1619
+ const token = this.tokenStorage.get();
1620
+ if (token) {
1621
+ headers["Authorization"] = `Bearer ${token.accessToken}`;
139
1622
  }
140
- const response = await this.httpClient.request("POST", `${this.baseUrl}/auth/refresh`, { headers });
1623
+ return headers;
1624
+ }
1625
+ async sendRequest(method, url, body) {
1626
+ const options = { headers: this.buildHeaders() };
1627
+ if (body) options.body = JSON.stringify(body);
1628
+ const send = () => this.httpClient.request(method, url, options);
1629
+ return method === "GET" ? withResponseRetries(this.retryPolicy, "e-reporting request", send) : send();
1630
+ }
1631
+ /** Multipart upload: Content-Type is left to fetch so the boundary is set correctly. */
1632
+ async sendMultipartRequest(method, url, form) {
1633
+ return this.httpClient.request(method, url, { headers: this.buildHeaders(false), body: form });
1634
+ }
1635
+ /** Accepts any 2xx response (e.g. 201 on create, 200 on idempotent replay). */
1636
+ async parseJsonResponse(response) {
141
1637
  const responseText = await response.text();
142
- if (response.status === 401) {
143
- throw new AuthenticationException("Token refresh failed: unauthorized", { status: 401, body: responseText });
1638
+ if (response.status === 422) {
1639
+ throw this.buildValidationException(responseText);
144
1640
  }
145
1641
  if (!response.ok) {
146
- throw new HttpException(`HTTP error ${response.status}`, response.status, responseText);
1642
+ throw new HttpException(
1643
+ describeApiError(responseText, response.status),
1644
+ response.status,
1645
+ responseText,
1646
+ {},
1647
+ response.headers.get("retry-after")
1648
+ );
147
1649
  }
148
- const data = this.parseResponse(responseText);
149
- const token = Token.fromArray(data);
150
- this.tokenStorage.set(token);
151
- return token;
152
- }
153
- parseResponse(responseText) {
154
1650
  let parsed;
155
1651
  try {
156
1652
  parsed = JSON.parse(responseText);
157
1653
  } catch {
158
- throw new HttpException("Invalid JSON response", 0, responseText);
1654
+ throw new HttpException("Invalid JSON response", response.status, responseText);
159
1655
  }
160
1656
  if (typeof parsed !== "object" || parsed === null) {
161
- throw new HttpException("Unexpected response format", 0, responseText);
1657
+ throw new HttpException("Unexpected response format", response.status, responseText);
162
1658
  }
163
1659
  const obj = parsed;
164
1660
  if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
@@ -166,33 +1662,589 @@ var AuthEndpoint = class {
166
1662
  }
167
1663
  return obj;
168
1664
  }
1665
+ /** For the 204 delete: keeps the shared 422/HTTP error mapping but never parses a body. */
1666
+ async assertNoContentResponse(response) {
1667
+ if (response.status === 204) {
1668
+ return;
1669
+ }
1670
+ const responseText = await response.text();
1671
+ if (response.status === 422) {
1672
+ throw this.buildValidationException(responseText);
1673
+ }
1674
+ if (!response.ok) {
1675
+ throw new HttpException(
1676
+ describeApiError(responseText, response.status),
1677
+ response.status,
1678
+ responseText,
1679
+ {},
1680
+ response.headers.get("retry-after")
1681
+ );
1682
+ }
1683
+ }
1684
+ /**
1685
+ * The backend emits two 422 shapes: Laravel FormRequest failures as
1686
+ * `{message, errors: {field: [...]}}` and state-guard errors via returnError()
1687
+ * as `{error: "reason"}`. Preserve the reason in both cases.
1688
+ */
1689
+ buildValidationException(responseText) {
1690
+ let message = "Validation failed";
1691
+ let errors = {};
1692
+ try {
1693
+ const parsed = JSON.parse(responseText);
1694
+ if (parsed["errors"] !== null && typeof parsed["errors"] === "object" && !Array.isArray(parsed["errors"])) {
1695
+ errors = parsed["errors"];
1696
+ }
1697
+ if (typeof parsed["message"] === "string" && parsed["message"] !== "") {
1698
+ message = parsed["message"];
1699
+ } else if (typeof parsed["error"] === "string" && parsed["error"] !== "") {
1700
+ message = parsed["error"];
1701
+ }
1702
+ } catch {
1703
+ }
1704
+ return new ValidationException(message, errors, { responseBody: responseText });
1705
+ }
1706
+ /**
1707
+ * Date objects are serialized from their LOCAL date parts — construct them as
1708
+ * `new Date('2026-09-01T00:00:00')` (local midnight); a bare `new Date('2026-09-01')`
1709
+ * is UTC midnight and shifts a day back in UTC-negative timezones.
1710
+ */
1711
+ formatDate(date) {
1712
+ if (date instanceof Date) {
1713
+ const year = date.getFullYear();
1714
+ const month = String(date.getMonth() + 1).padStart(2, "0");
1715
+ const day = String(date.getDate()).padStart(2, "0");
1716
+ return `${year}-${month}-${day}`;
1717
+ }
1718
+ if (!DATE_REGEX.test(date)) {
1719
+ throw new TaxoraException(`Invalid date format: "${date}". Expected YYYY-MM-DD.`);
1720
+ }
1721
+ return date;
1722
+ }
169
1723
  };
170
1724
 
171
- // src/dto/CompanyResource.ts
172
- function normalizeCompanyResource(data) {
173
- const apiRateLimit = typeof data["api_rate_limit"] === "number" ? data["api_rate_limit"] : void 0;
174
- const vatRateLimit = typeof data["vat_rate_limit"] === "number" ? data["vat_rate_limit"] : void 0;
175
- const legacyRateLimit = typeof data["rate_limit"] === "number" ? data["rate_limit"] : void 0;
176
- return {
177
- ...data,
178
- api_rate_limit: apiRateLimit ?? legacyRateLimit ?? 0,
179
- vat_rate_limit: vatRateLimit ?? legacyRateLimit ?? 0
180
- };
1725
+ // src/enums/SmartEnrichmentMode.ts
1726
+ var SmartEnrichmentMode = {
1727
+ DEFAULT: "default",
1728
+ COMPLEX: "complex"
1729
+ };
1730
+ var VALUES5 = Object.values(SmartEnrichmentMode);
1731
+ function toSmartEnrichmentMode(value) {
1732
+ if (typeof value === "string") {
1733
+ const normalized = value.trim().toLowerCase();
1734
+ if (VALUES5.includes(normalized)) {
1735
+ return normalized;
1736
+ }
1737
+ }
1738
+ return void 0;
1739
+ }
1740
+ var DESCRIPTIONS5 = {
1741
+ [SmartEnrichmentMode.DEFAULT]: "One provider searches; a second is consulted only if it finds nothing.",
1742
+ [SmartEnrichmentMode.COMPLEX]: "Two providers search independently and are cross-checked against the tax authority. Costs more."
1743
+ };
1744
+ function describeSmartEnrichmentMode(mode) {
1745
+ return DESCRIPTIONS5[mode];
181
1746
  }
182
1747
 
183
- // src/endpoints/CompanyEndpoint.ts
184
- var CompanyEndpoint = class {
185
- constructor(baseUrl, apiKey, tokenStorage, httpClient) {
1748
+ // src/enums/SmartEnrichmentStatus.ts
1749
+ var SmartEnrichmentStatus = {
1750
+ FOUND: "found",
1751
+ NO_VAT_EXISTS: "no_vat_exists",
1752
+ NOT_FOUND: "not_found",
1753
+ PROCESSING: "processing",
1754
+ QUEUED: "queued",
1755
+ COMPLETED: "completed",
1756
+ FAILED: "failed",
1757
+ UNKNOWN: "unknown"
1758
+ };
1759
+ var VALUES6 = Object.values(SmartEnrichmentStatus);
1760
+ function toSmartEnrichmentStatus(value) {
1761
+ if (typeof value === "string") {
1762
+ const normalized = value.trim().toLowerCase();
1763
+ if (VALUES6.includes(normalized)) {
1764
+ return normalized;
1765
+ }
1766
+ }
1767
+ return SmartEnrichmentStatus.UNKNOWN;
1768
+ }
1769
+ var DESCRIPTIONS6 = {
1770
+ [SmartEnrichmentStatus.FOUND]: "A VAT number was resolved and confirmed.",
1771
+ [SmartEnrichmentStatus.NO_VAT_EXISTS]: "The company was identified but has no VAT number.",
1772
+ [SmartEnrichmentStatus.NOT_FOUND]: "No sufficiently-confident match could be resolved.",
1773
+ [SmartEnrichmentStatus.PROCESSING]: "The lookup is still being processed.",
1774
+ [SmartEnrichmentStatus.QUEUED]: "The job is queued.",
1775
+ [SmartEnrichmentStatus.COMPLETED]: "The job has completed.",
1776
+ [SmartEnrichmentStatus.FAILED]: "The job failed.",
1777
+ [SmartEnrichmentStatus.UNKNOWN]: "Unknown status."
1778
+ };
1779
+ function describeSmartEnrichmentStatus(status) {
1780
+ return DESCRIPTIONS6[status];
1781
+ }
1782
+
1783
+ // src/dto/SmartEnrichmentResource.ts
1784
+ var SmartEnrichmentResource = class _SmartEnrichmentResource {
1785
+ constructor(status, vatNumber, vatType, confidence, matchedCompanyName, matchedAddress, country, source, mode = void 0, providerVerdicts = [], addressQuality = void 0) {
1786
+ this.status = status;
1787
+ this.vatNumber = vatNumber;
1788
+ this.vatType = vatType;
1789
+ this.confidence = confidence;
1790
+ this.matchedCompanyName = matchedCompanyName;
1791
+ this.matchedAddress = matchedAddress;
1792
+ this.country = country;
1793
+ this.source = source;
1794
+ this.mode = mode;
1795
+ this.providerVerdicts = providerVerdicts;
1796
+ this.addressQuality = addressQuality;
1797
+ }
1798
+ status;
1799
+ vatNumber;
1800
+ vatType;
1801
+ confidence;
1802
+ matchedCompanyName;
1803
+ matchedAddress;
1804
+ country;
1805
+ source;
1806
+ mode;
1807
+ providerVerdicts;
1808
+ addressQuality;
1809
+ static fromArray(data) {
1810
+ const str = (key) => {
1811
+ const v = data[key];
1812
+ return typeof v === "string" ? v : void 0;
1813
+ };
1814
+ const num = (key) => {
1815
+ const v = data[key];
1816
+ return typeof v === "number" ? v : void 0;
1817
+ };
1818
+ const verdicts = data["providerVerdicts"];
1819
+ const quality = data["addressQuality"];
1820
+ return new _SmartEnrichmentResource(
1821
+ toSmartEnrichmentStatus(data["status"]),
1822
+ str("vatNumber"),
1823
+ str("vatType"),
1824
+ num("confidence"),
1825
+ str("matchedCompanyName"),
1826
+ str("matchedAddress"),
1827
+ str("country"),
1828
+ str("source"),
1829
+ toSmartEnrichmentMode(data["mode"]),
1830
+ Array.isArray(verdicts) ? verdicts.filter((v) => typeof v === "object" && v !== null) : [],
1831
+ typeof quality === "object" && quality !== null ? quality : void 0
1832
+ );
1833
+ }
1834
+ toArray() {
1835
+ return {
1836
+ status: this.status,
1837
+ vatNumber: this.vatNumber ?? null,
1838
+ vatType: this.vatType ?? null,
1839
+ confidence: this.confidence ?? null,
1840
+ matchedCompanyName: this.matchedCompanyName ?? null,
1841
+ matchedAddress: this.matchedAddress ?? null,
1842
+ country: this.country ?? null,
1843
+ source: this.source ?? null,
1844
+ mode: this.mode ?? null,
1845
+ providerVerdicts: this.providerVerdicts,
1846
+ addressQuality: this.addressQuality ?? null
1847
+ };
1848
+ }
1849
+ };
1850
+
1851
+ // src/dto/SmartEnrichmentJob.ts
1852
+ var SmartEnrichmentJob = class _SmartEnrichmentJob {
1853
+ constructor(jobId, status, itemCount, completedCount, foundCount, results) {
1854
+ this.jobId = jobId;
1855
+ this.status = status;
1856
+ this.itemCount = itemCount;
1857
+ this.completedCount = completedCount;
1858
+ this.foundCount = foundCount;
1859
+ this.results = results;
1860
+ }
1861
+ jobId;
1862
+ status;
1863
+ itemCount;
1864
+ completedCount;
1865
+ foundCount;
1866
+ results;
1867
+ static fromArray(data) {
1868
+ const num = (key) => {
1869
+ const v = data[key];
1870
+ return typeof v === "number" ? v : void 0;
1871
+ };
1872
+ let results = [];
1873
+ if (Array.isArray(data["results"])) {
1874
+ results = data["results"].filter((row) => typeof row === "object" && row !== null).map(SmartEnrichmentResource.fromArray);
1875
+ } else if (data["vatNumber"] !== void 0 || _SmartEnrichmentJob.looksLikeItem(data["status"])) {
1876
+ results = [SmartEnrichmentResource.fromArray(data)];
1877
+ }
1878
+ return new _SmartEnrichmentJob(
1879
+ typeof data["jobId"] === "string" ? data["jobId"] : "",
1880
+ toSmartEnrichmentStatus(data["status"]),
1881
+ num("itemCount"),
1882
+ num("completedCount"),
1883
+ num("foundCount"),
1884
+ results
1885
+ );
1886
+ }
1887
+ isProcessing() {
1888
+ return this.status === SmartEnrichmentStatus.PROCESSING || this.status === SmartEnrichmentStatus.QUEUED;
1889
+ }
1890
+ /** Convenience for single lookups: the first (or only) result, if any. */
1891
+ result() {
1892
+ return this.results[0];
1893
+ }
1894
+ static looksLikeItem(status) {
1895
+ const s = toSmartEnrichmentStatus(status);
1896
+ return s === SmartEnrichmentStatus.FOUND || s === SmartEnrichmentStatus.NO_VAT_EXISTS || s === SmartEnrichmentStatus.NOT_FOUND;
1897
+ }
1898
+ };
1899
+
1900
+ // src/dto/SmartEnrichmentHistoryRow.ts
1901
+ var SmartEnrichmentHistoryRow = class _SmartEnrichmentHistoryRow {
1902
+ constructor(jobId, status, isBulk, itemCount, foundCount, queryCompanyName, queryCountry, queryCity, input, result, createdAt, finishedAt) {
1903
+ this.jobId = jobId;
1904
+ this.status = status;
1905
+ this.isBulk = isBulk;
1906
+ this.itemCount = itemCount;
1907
+ this.foundCount = foundCount;
1908
+ this.queryCompanyName = queryCompanyName;
1909
+ this.queryCountry = queryCountry;
1910
+ this.queryCity = queryCity;
1911
+ this.input = input;
1912
+ this.result = result;
1913
+ this.createdAt = createdAt;
1914
+ this.finishedAt = finishedAt;
1915
+ }
1916
+ jobId;
1917
+ status;
1918
+ isBulk;
1919
+ itemCount;
1920
+ foundCount;
1921
+ queryCompanyName;
1922
+ queryCountry;
1923
+ queryCity;
1924
+ input;
1925
+ result;
1926
+ createdAt;
1927
+ finishedAt;
1928
+ static fromArray(data) {
1929
+ const str = (value) => typeof value === "string" ? value : void 0;
1930
+ const query = data["query"] !== null && typeof data["query"] === "object" && !Array.isArray(data["query"]) ? data["query"] : {};
1931
+ const inputRaw = data["input"] !== null && typeof data["input"] === "object" && !Array.isArray(data["input"]) ? data["input"] : void 0;
1932
+ const input = inputRaw ? {
1933
+ companyName: str(inputRaw["companyName"]),
1934
+ street: str(inputRaw["street"]),
1935
+ postalCode: str(inputRaw["postalCode"]),
1936
+ city: str(inputRaw["city"]),
1937
+ country: str(inputRaw["country"])
1938
+ } : void 0;
1939
+ const result = data["result"] !== null && typeof data["result"] === "object" && !Array.isArray(data["result"]) ? SmartEnrichmentResource.fromArray(data["result"]) : void 0;
1940
+ return new _SmartEnrichmentHistoryRow(
1941
+ str(data["jobId"]) ?? "",
1942
+ toSmartEnrichmentStatus(data["status"]),
1943
+ data["isBulk"] === true,
1944
+ typeof data["itemCount"] === "number" ? data["itemCount"] : 0,
1945
+ typeof data["foundCount"] === "number" ? data["foundCount"] : 0,
1946
+ str(query["companyName"]),
1947
+ str(query["country"]),
1948
+ str(query["city"]),
1949
+ input,
1950
+ result,
1951
+ str(data["createdAt"]),
1952
+ str(data["finishedAt"])
1953
+ );
1954
+ }
1955
+ };
1956
+
1957
+ // src/dto/SmartEnrichmentHistoryPage.ts
1958
+ var SmartEnrichmentHistoryPage = class _SmartEnrichmentHistoryPage {
1959
+ constructor(rows, currentPage, perPage, total, lastPage, stats) {
1960
+ this.rows = rows;
1961
+ this.currentPage = currentPage;
1962
+ this.perPage = perPage;
1963
+ this.total = total;
1964
+ this.lastPage = lastPage;
1965
+ this.stats = stats;
1966
+ }
1967
+ rows;
1968
+ currentPage;
1969
+ perPage;
1970
+ total;
1971
+ lastPage;
1972
+ stats;
1973
+ static fromResponse(payload) {
1974
+ const obj = payload !== null && typeof payload === "object" && !Array.isArray(payload) ? payload : {};
1975
+ const rows = Array.isArray(obj["data"]) ? obj["data"].filter((row) => typeof row === "object" && row !== null).map(SmartEnrichmentHistoryRow.fromArray) : [];
1976
+ const meta = obj["meta"] !== null && typeof obj["meta"] === "object" && !Array.isArray(obj["meta"]) ? obj["meta"] : {};
1977
+ const num = (value, fallback) => typeof value === "number" ? value : fallback;
1978
+ const statsRaw = obj["stats"] !== null && typeof obj["stats"] === "object" && !Array.isArray(obj["stats"]) ? obj["stats"] : void 0;
1979
+ const stats = statsRaw ? { total: num(statsRaw["total"], 0), found: num(statsRaw["found"], 0) } : void 0;
1980
+ return new _SmartEnrichmentHistoryPage(
1981
+ rows,
1982
+ num(meta["currentPage"], 1),
1983
+ num(meta["perPage"], rows.length),
1984
+ num(meta["total"], rows.length),
1985
+ num(meta["lastPage"], 1),
1986
+ stats
1987
+ );
1988
+ }
1989
+ [Symbol.iterator]() {
1990
+ return this.rows[Symbol.iterator]();
1991
+ }
1992
+ get length() {
1993
+ return this.rows.length;
1994
+ }
1995
+ };
1996
+
1997
+ // src/dto/SmartEnrichmentStatistics.ts
1998
+ function asString7(value, fallback = "") {
1999
+ return typeof value === "string" ? value : fallback;
2000
+ }
2001
+ function asNumber6(value) {
2002
+ return typeof value === "number" ? value : Number(value ?? 0) || 0;
2003
+ }
2004
+ function asRecord(value) {
2005
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
2006
+ }
2007
+ function asRecords2(value) {
2008
+ if (!Array.isArray(value)) return [];
2009
+ return value.filter((row) => typeof row === "object" && row !== null);
2010
+ }
2011
+ var SmartEnrichmentStatistics = class _SmartEnrichmentStatistics {
2012
+ constructor(range, totals, timeSeries, bySource, byOutcome, confidenceBuckets) {
2013
+ this.range = range;
2014
+ this.totals = totals;
2015
+ this.timeSeries = timeSeries;
2016
+ this.bySource = bySource;
2017
+ this.byOutcome = byOutcome;
2018
+ this.confidenceBuckets = confidenceBuckets;
2019
+ }
2020
+ range;
2021
+ totals;
2022
+ timeSeries;
2023
+ bySource;
2024
+ byOutcome;
2025
+ confidenceBuckets;
2026
+ static fromArray(data) {
2027
+ const range = asRecord(data["range"]);
2028
+ const totals = asRecord(data["totals"]);
2029
+ const timeSeries = asRecords2(data["time_series"]).map((row) => ({
2030
+ bucket: asString7(row["bucket"]),
2031
+ jobs: asNumber6(row["jobs"]),
2032
+ items: asNumber6(row["items"]),
2033
+ found: asNumber6(row["found"])
2034
+ }));
2035
+ const bySource = asRecords2(data["by_source"]).map((row) => ({
2036
+ source: asString7(row["source"]),
2037
+ jobs: asNumber6(row["jobs"]),
2038
+ found: asNumber6(row["found"])
2039
+ }));
2040
+ const byOutcome = asRecords2(data["by_outcome"]).map((row) => ({
2041
+ outcome: asString7(row["outcome"]),
2042
+ count: asNumber6(row["count"])
2043
+ }));
2044
+ const confidenceBuckets = asRecords2(data["confidence_buckets"]).map((row) => ({
2045
+ bucket: asString7(row["bucket"]),
2046
+ count: asNumber6(row["count"])
2047
+ }));
2048
+ return new _SmartEnrichmentStatistics(
2049
+ {
2050
+ dateFrom: asString7(range["date_from"]),
2051
+ dateTo: asString7(range["date_to"]),
2052
+ interval: asString7(range["interval"])
2053
+ },
2054
+ {
2055
+ jobs: asNumber6(totals["jobs"]),
2056
+ items: asNumber6(totals["items"]),
2057
+ found: asNumber6(totals["found"]),
2058
+ foundRate: asNumber6(totals["found_rate"]),
2059
+ avgConfidence: asNumber6(totals["avg_confidence"])
2060
+ },
2061
+ timeSeries,
2062
+ bySource,
2063
+ byOutcome,
2064
+ confidenceBuckets
2065
+ );
2066
+ }
2067
+ };
2068
+
2069
+ // src/endpoints/SmartEnrichmentEndpoint.ts
2070
+ var STATISTICS_INTERVALS = ["day", "week", "month"];
2071
+ var DEFAULT_POLL_INTERVAL_MS = 2e3;
2072
+ var DEFAULT_POLL_TIMEOUT_MS = 12e4;
2073
+ var SmartEnrichmentEndpoint = class {
2074
+ constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
186
2075
  this.baseUrl = baseUrl;
187
2076
  this.apiKey = apiKey;
188
2077
  this.tokenStorage = tokenStorage;
189
2078
  this.httpClient = httpClient;
2079
+ this.retryPolicy = retryPolicy;
190
2080
  }
191
2081
  baseUrl;
192
2082
  apiKey;
193
2083
  tokenStorage;
194
2084
  httpClient;
195
- async get() {
2085
+ retryPolicy;
2086
+ /**
2087
+ * Single reverse lookup. Returns a confident result synchronously, or a `processing`
2088
+ * job (poll with get(job.jobId) or waitForCompletion(job.jobId)) when deeper resolution
2089
+ * is needed.
2090
+ */
2091
+ async lookup(input) {
2092
+ this.validateInput(input);
2093
+ const body = {
2094
+ companyName: input.companyName,
2095
+ country: input.country
2096
+ };
2097
+ if (input.street !== void 0) body["street"] = input.street;
2098
+ if (input.postalCode !== void 0) body["postalCode"] = input.postalCode;
2099
+ if (input.city !== void 0) body["city"] = input.city;
2100
+ if (input.mode !== void 0) body["mode"] = input.mode;
2101
+ const response = await this.sendRequest("POST", `${this.baseUrl}/smart-enrichment`, body);
2102
+ const data = await this.parseJsonResponse(response, [200, 202]);
2103
+ return SmartEnrichmentJob.fromArray(data);
2104
+ }
2105
+ /**
2106
+ * Bulk batch — always async. Returns a `processing` job; results arrive via the
2107
+ * `enrichment.completed` webhook and are retrievable via get(job.jobId).
2108
+ *
2109
+ * @param mode Search depth for every row in the batch. A row carrying its own `mode` overrides
2110
+ * it, so a caller can pay for `complex` on just the rows that need it.
2111
+ */
2112
+ async bulkLookup(items, mode) {
2113
+ if (items.length === 0) {
2114
+ throw new TaxoraException("bulkLookup requires at least one item.");
2115
+ }
2116
+ items.forEach((item, index) => this.validateInput(item, `items[${index}]`));
2117
+ const body = { items };
2118
+ if (mode !== void 0) body["mode"] = mode;
2119
+ const response = await this.sendRequest("POST", `${this.baseUrl}/smart-enrichment/bulk`, body);
2120
+ const data = await this.parseJsonResponse(response, [200, 202]);
2121
+ return SmartEnrichmentJob.fromArray(data);
2122
+ }
2123
+ /** Poll a job (single or bulk) by its id. */
2124
+ async get(jobId) {
2125
+ this.validateJobId(jobId);
2126
+ const response = await this.sendRequest("GET", `${this.baseUrl}/smart-enrichment/${encodeURIComponent(jobId)}`);
2127
+ const data = await this.parseJsonResponse(response);
2128
+ return SmartEnrichmentJob.fromArray(data);
2129
+ }
2130
+ /**
2131
+ * Poll get(jobId) until the job is no longer processing (completed, failed or resolved).
2132
+ * Throws a TaxoraException when the job does not finish within `timeoutMs`.
2133
+ */
2134
+ async waitForCompletion(jobId, options = {}) {
2135
+ this.validateJobId(jobId);
2136
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2137
+ const timeoutMs = options.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
2138
+ if (!Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
2139
+ throw new TaxoraException("pollIntervalMs must be greater than 0.");
2140
+ }
2141
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
2142
+ throw new TaxoraException("timeoutMs must be greater than 0.");
2143
+ }
2144
+ const deadline = Date.now() + timeoutMs;
2145
+ for (; ; ) {
2146
+ const job = await this.get(jobId);
2147
+ if (!job.isProcessing()) {
2148
+ return job;
2149
+ }
2150
+ if (Date.now() + pollIntervalMs > deadline) {
2151
+ throw new TaxoraException(`Smart Enrichment job "${jobId}" did not complete within ${timeoutMs}ms.`, {
2152
+ jobId,
2153
+ timeoutMs
2154
+ });
2155
+ }
2156
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
2157
+ }
2158
+ }
2159
+ /**
2160
+ * Paginated lookup history (newest first). `perPage` is capped at 100 server-side.
2161
+ * `search` filters (free-text) over the queried company name and the resolved
2162
+ * VAT number / matched company name.
2163
+ */
2164
+ async history(page = 1, perPage = 25, search) {
2165
+ const params = new URLSearchParams();
2166
+ params.set("page", String(page));
2167
+ params.set("perPage", String(perPage));
2168
+ if (search !== void 0) params.set("search", search);
2169
+ const response = await this.sendRequest("GET", `${this.baseUrl}/smart-enrichment/history?${params.toString()}`);
2170
+ return SmartEnrichmentHistoryPage.fromResponse(await this.parseRawJson(response));
2171
+ }
2172
+ /** Current billing-period quota usage, plus recent monthly billing entries. */
2173
+ async usage() {
2174
+ const response = await this.sendRequest("GET", `${this.baseUrl}/smart-enrichment/usage`);
2175
+ const data = await this.parseJsonResponse(response);
2176
+ return {
2177
+ period: typeof data["period"] === "string" ? data["period"] : "",
2178
+ used: typeof data["used"] === "number" ? data["used"] : 0,
2179
+ included: typeof data["included"] === "number" ? data["included"] : 0,
2180
+ remaining: typeof data["remaining"] === "number" ? data["remaining"] : 0,
2181
+ overageCount: typeof data["overageCount"] === "number" ? data["overageCount"] : 0,
2182
+ overagePrice: typeof data["overagePrice"] === "number" ? data["overagePrice"] : 0,
2183
+ overageAmount: typeof data["overageAmount"] === "number" ? data["overageAmount"] : 0,
2184
+ hardCap: typeof data["hardCap"] === "number" ? data["hardCap"] : null,
2185
+ history: this.parseUsageHistory(data["history"])
2186
+ };
2187
+ }
2188
+ /**
2189
+ * Export the company's lookups as CSV (bulk-input shape plus the resolved VAT columns;
2190
+ * bulk jobs are flattened to one line per input row). Returns the raw CSV text
2191
+ * (UTF-8, BOM included by the API so Excel opens umlauts correctly).
2192
+ */
2193
+ async export(options = {}) {
2194
+ const params = new URLSearchParams();
2195
+ if (options.dateFrom !== void 0) params.set("date_from", options.dateFrom);
2196
+ if (options.dateTo !== void 0) params.set("date_to", options.dateTo);
2197
+ if (options.minConfidence !== void 0) params.set("min_confidence", String(options.minConfidence));
2198
+ for (const status of options.status ?? []) {
2199
+ params.append("status[]", status);
2200
+ }
2201
+ if (options.onlyFound !== void 0) params.set("only_found", options.onlyFound ? "1" : "0");
2202
+ const queryString = params.toString();
2203
+ const url = `${this.baseUrl}/smart-enrichment/export${queryString ? `?${queryString}` : ""}`;
2204
+ const response = await this.sendRequest("GET", url);
2205
+ return this.parseCsvResponse(response);
2206
+ }
2207
+ /**
2208
+ * Aggregated lookup statistics: headline totals, a time series and breakdowns by
2209
+ * source, outcome and confidence band. The API defaults to the last 12 months
2210
+ * and a monthly interval.
2211
+ */
2212
+ async statistics(options = {}) {
2213
+ if (options.interval !== void 0 && !STATISTICS_INTERVALS.includes(options.interval)) {
2214
+ throw new TaxoraException(
2215
+ `Invalid statistics interval "${String(options.interval)}". Expected one of: ${STATISTICS_INTERVALS.join(", ")}.`
2216
+ );
2217
+ }
2218
+ const params = new URLSearchParams();
2219
+ if (options.dateFrom !== void 0) params.set("date_from", options.dateFrom);
2220
+ if (options.dateTo !== void 0) params.set("date_to", options.dateTo);
2221
+ if (options.interval !== void 0) params.set("interval", options.interval);
2222
+ const queryString = params.toString();
2223
+ const url = `${this.baseUrl}/smart-enrichment/statistics${queryString ? `?${queryString}` : ""}`;
2224
+ const response = await this.sendRequest("GET", url);
2225
+ return SmartEnrichmentStatistics.fromArray(await this.parseRawJson(response));
2226
+ }
2227
+ // ---------------------------------------------------------------------
2228
+ // Input validation (fail fast, before any request is sent)
2229
+ // ---------------------------------------------------------------------
2230
+ validateInput(input, context) {
2231
+ const where = context ? ` (${context})` : "";
2232
+ if (input.companyName.trim() === "") {
2233
+ throw new TaxoraException(`companyName must not be empty${where}.`);
2234
+ }
2235
+ if (input.country.length !== 2) {
2236
+ throw new TaxoraException(`country must be a 2-character ISO 3166-1 alpha-2 code${where}.`);
2237
+ }
2238
+ }
2239
+ validateJobId(jobId) {
2240
+ if (jobId.trim() === "") {
2241
+ throw new TaxoraException("jobId must not be empty.");
2242
+ }
2243
+ }
2244
+ // ---------------------------------------------------------------------
2245
+ // HTTP plumbing (kept in sync with VatEndpoint)
2246
+ // ---------------------------------------------------------------------
2247
+ buildHeaders() {
196
2248
  const headers = {
197
2249
  "Content-Type": "application/json",
198
2250
  "x-api-key": this.apiKey
@@ -201,25 +2253,113 @@ var CompanyEndpoint = class {
201
2253
  if (token) {
202
2254
  headers["Authorization"] = `Bearer ${token.accessToken}`;
203
2255
  }
204
- const response = await this.httpClient.request("GET", `${this.baseUrl}/company`, { headers });
2256
+ return headers;
2257
+ }
2258
+ async sendRequest(method, url, body) {
2259
+ const options = { headers: this.buildHeaders() };
2260
+ if (body) options.body = JSON.stringify(body);
2261
+ const send = () => this.httpClient.request(method, url, options);
2262
+ return method === "GET" ? withResponseRetries(this.retryPolicy, "Smart Enrichment request", send) : send();
2263
+ }
2264
+ async parseJsonResponse(response, acceptedStatuses = [200]) {
205
2265
  const responseText = await response.text();
206
- if (!response.ok) {
207
- throw new HttpException(`HTTP error ${response.status}`, response.status, responseText);
2266
+ if (response.status === 422) {
2267
+ throw new ValidationException(jsonErrorMessage(responseText) ?? "Validation failed", this.parseValidationErrors(responseText));
2268
+ }
2269
+ if (!response.ok && !acceptedStatuses.includes(response.status)) {
2270
+ throw new HttpException(
2271
+ describeApiError(responseText, response.status),
2272
+ response.status,
2273
+ responseText,
2274
+ {},
2275
+ response.headers.get("retry-after")
2276
+ );
208
2277
  }
209
2278
  let parsed;
210
2279
  try {
211
2280
  parsed = JSON.parse(responseText);
212
2281
  } catch {
213
- throw new HttpException("Invalid JSON response", 0, responseText);
2282
+ throw new HttpException("Invalid JSON response", response.status, responseText);
214
2283
  }
215
2284
  if (typeof parsed !== "object" || parsed === null) {
216
- throw new HttpException("Unexpected response format", 0, responseText);
2285
+ throw new HttpException("Unexpected response format", response.status, responseText);
217
2286
  }
218
2287
  const obj = parsed;
219
- if (obj["data"] !== void 0 && typeof obj["data"] === "object" && obj["data"] !== null) {
220
- return normalizeCompanyResource(obj["data"]);
2288
+ if (obj["data"] !== void 0 && (typeof obj["data"] === "object" || Array.isArray(obj["data"]))) {
2289
+ return obj["data"];
2290
+ }
2291
+ return obj;
2292
+ }
2293
+ /**
2294
+ * Like parseJsonResponse but returns the whole object WITHOUT unwrapping `data` — used for
2295
+ * the paginated history response (where `data` is the row array and `meta`/`stats` must be
2296
+ * preserved) and the statistics response.
2297
+ */
2298
+ async parseRawJson(response) {
2299
+ const responseText = await response.text();
2300
+ if (response.status === 422) {
2301
+ throw new ValidationException(jsonErrorMessage(responseText) ?? "Validation failed", this.parseValidationErrors(responseText));
2302
+ }
2303
+ if (!response.ok) {
2304
+ throw new HttpException(
2305
+ describeApiError(responseText, response.status),
2306
+ response.status,
2307
+ responseText,
2308
+ {},
2309
+ response.headers.get("retry-after")
2310
+ );
2311
+ }
2312
+ let parsed;
2313
+ try {
2314
+ parsed = JSON.parse(responseText);
2315
+ } catch {
2316
+ throw new HttpException("Invalid JSON response", response.status, responseText);
2317
+ }
2318
+ if (typeof parsed !== "object" || parsed === null) {
2319
+ throw new HttpException("Unexpected response format", response.status, responseText);
2320
+ }
2321
+ return parsed;
2322
+ }
2323
+ /**
2324
+ * For the CSV export: keeps the shared 422/HTTP error mapping but returns the body
2325
+ * verbatim (text/csv) instead of parsing JSON.
2326
+ */
2327
+ async parseCsvResponse(response) {
2328
+ const responseText = await response.text();
2329
+ if (response.status === 422) {
2330
+ throw new ValidationException(jsonErrorMessage(responseText) ?? "Validation failed", this.parseValidationErrors(responseText));
2331
+ }
2332
+ if (!response.ok) {
2333
+ throw new HttpException(
2334
+ describeApiError(responseText, response.status),
2335
+ response.status,
2336
+ responseText,
2337
+ {},
2338
+ response.headers.get("retry-after")
2339
+ );
2340
+ }
2341
+ return responseText;
2342
+ }
2343
+ parseValidationErrors(responseText) {
2344
+ try {
2345
+ const parsed = JSON.parse(responseText);
2346
+ if (parsed["errors"] !== null && typeof parsed["errors"] === "object" && !Array.isArray(parsed["errors"])) {
2347
+ return parsed["errors"];
2348
+ }
2349
+ } catch {
221
2350
  }
222
- return normalizeCompanyResource(obj);
2351
+ return {};
2352
+ }
2353
+ parseUsageHistory(value) {
2354
+ if (!Array.isArray(value)) return [];
2355
+ return value.filter((row) => typeof row === "object" && row !== null).map((row) => ({
2356
+ period: typeof row["period"] === "string" ? row["period"] : "",
2357
+ matches: typeof row["matches"] === "number" ? row["matches"] : 0,
2358
+ amount: typeof row["amount"] === "string" ? row["amount"] : "0.00",
2359
+ state: row["state"] === "billed" ? "billed" : "pending",
2360
+ billingType: typeof row["billingType"] === "string" ? row["billingType"] : null,
2361
+ manualReference: typeof row["manualReference"] === "string" ? row["manualReference"] : null
2362
+ }));
223
2363
  }
224
2364
  };
225
2365
 
@@ -233,14 +2373,14 @@ var VatState = {
233
2373
  function getFailedVatStates() {
234
2374
  return [VatState.INVALID, VatState.FRAUD];
235
2375
  }
236
- var DESCRIPTIONS = {
2376
+ var DESCRIPTIONS7 = {
237
2377
  [VatState.VALID]: "The VAT number is valid and active.",
238
2378
  [VatState.INVALID]: "The VAT number is invalid or not registered.",
239
2379
  [VatState.FRAUD]: "The VAT number has been flagged as potentially fraudulent.",
240
2380
  [VatState.UNKNOWN]: "The VAT number state could not be determined."
241
2381
  };
242
2382
  function describeVatState(state) {
243
- return DESCRIPTIONS[state];
2383
+ return DESCRIPTIONS7[state];
244
2384
  }
245
2385
 
246
2386
  // src/dto/CompanyAddress.ts
@@ -702,34 +2842,21 @@ var VatValidationAddressInput = class _VatValidationAddressInput {
702
2842
  }
703
2843
  };
704
2844
 
705
- // src/exceptions/ValidationException.ts
706
- var ValidationException = class extends TaxoraException {
707
- statusCode = 422;
708
- errors;
709
- constructor(message, errors, context = {}) {
710
- super(message, context);
711
- this.name = "ValidationException";
712
- this.errors = errors;
713
- Object.setPrototypeOf(this, new.target.prototype);
714
- }
715
- getErrors() {
716
- return this.errors;
717
- }
718
- };
719
-
720
2845
  // src/endpoints/VatEndpoint.ts
721
- var DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
2846
+ var DATE_REGEX2 = /^\d{4}-\d{2}-\d{2}$/;
722
2847
  var VatEndpoint = class {
723
- constructor(baseUrl, apiKey, tokenStorage, httpClient) {
2848
+ constructor(baseUrl, apiKey, tokenStorage, httpClient, retryPolicy = new RetryPolicy()) {
724
2849
  this.baseUrl = baseUrl;
725
2850
  this.apiKey = apiKey;
726
2851
  this.tokenStorage = tokenStorage;
727
2852
  this.httpClient = httpClient;
2853
+ this.retryPolicy = retryPolicy;
728
2854
  }
729
2855
  baseUrl;
730
2856
  apiKey;
731
2857
  tokenStorage;
732
2858
  httpClient;
2859
+ retryPolicy;
733
2860
  async validate(vatUid, companyName, provider, addressInput) {
734
2861
  const body = { vat_uid: vatUid };
735
2862
  if (companyName) body["company_name"] = companyName;
@@ -737,44 +2864,44 @@ var VatEndpoint = class {
737
2864
  if (addressInput) {
738
2865
  body["address_input"] = addressInput instanceof VatValidationAddressInput ? addressInput.toArray() : addressInput;
739
2866
  }
740
- return this.validateWithGatewayRetry(body, 2);
741
- }
742
- async validateWithGatewayRetry(body, attemptsLeft) {
743
- const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate`, body);
744
- if (response.status === 504) {
745
- if (attemptsLeft <= 1) {
746
- const text = await response.text();
747
- throw new HttpException("VAT validation timed out after multiple attempts", 504, text);
748
- }
749
- return this.validateWithGatewayRetry(body, attemptsLeft - 1);
750
- }
751
- const data = await this.parseJsonResponse(response);
752
- return VatResource.fromArray(data);
2867
+ return withRetries(this.retryPolicy, "VAT validation", async () => {
2868
+ const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate`, body);
2869
+ const data = await this.parseJsonResponse(response);
2870
+ return VatResource.fromArray(data);
2871
+ });
753
2872
  }
754
2873
  async validateMultiple(vatUids, companyNames, provider) {
755
2874
  const body = { vat_uids: vatUids };
756
2875
  if (companyNames) body["company_names"] = companyNames;
757
2876
  if (provider) body["source"] = provider;
758
- const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/multiple`, body);
759
- const data = await this.parseJsonResponse(response);
760
- return VatCollection.fromResponse(data);
2877
+ return withRetries(this.retryPolicy, "VAT validation", async () => {
2878
+ const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/multiple`, body);
2879
+ const data = await this.parseJsonResponse(response);
2880
+ return VatCollection.fromResponse(data);
2881
+ });
761
2882
  }
762
2883
  async validateSchema(vatUid) {
763
2884
  const body = { vat_uid: vatUid };
764
- const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/schema`, body);
765
- return this.parseJsonResponse(response);
2885
+ return withRetries(this.retryPolicy, "VAT schema check", async () => {
2886
+ const response = await this.sendRequest("POST", `${this.baseUrl}/vat/validate/schema`, body);
2887
+ return this.parseJsonResponse(response);
2888
+ });
766
2889
  }
767
2890
  async state(vatUid) {
768
- const response = await this.sendRequest("GET", `${this.baseUrl}/vat/state/${encodeURIComponent(vatUid)}`);
769
- const data = await this.parseJsonResponse(response);
770
- return VatResource.fromArray(data);
2891
+ return withRetries(this.retryPolicy, "VAT state lookup", async () => {
2892
+ const response = await this.sendRequest("GET", `${this.baseUrl}/vat/state/${encodeURIComponent(vatUid)}`);
2893
+ const data = await this.parseJsonResponse(response);
2894
+ return VatResource.fromArray(data);
2895
+ });
771
2896
  }
772
2897
  async history(vatUid) {
773
2898
  let url = `${this.baseUrl}/vat/history`;
774
2899
  if (vatUid) url += `?vat_uid=${encodeURIComponent(vatUid)}`;
775
- const response = await this.sendRequest("GET", url);
776
- const data = await this.parseJsonResponse(response);
777
- return VatCollection.fromResponse(data);
2900
+ return withRetries(this.retryPolicy, "VAT history request", async () => {
2901
+ const response = await this.sendRequest("GET", url);
2902
+ const data = await this.parseJsonResponse(response);
2903
+ return VatCollection.fromResponse(data);
2904
+ });
778
2905
  }
779
2906
  async search(term, perPage) {
780
2907
  const params = new URLSearchParams();
@@ -782,16 +2909,20 @@ var VatEndpoint = class {
782
2909
  if (perPage !== void 0) params.set("per_page", String(perPage));
783
2910
  const queryString = params.toString();
784
2911
  const url = `${this.baseUrl}/vat/search${queryString ? `?${queryString}` : ""}`;
785
- const response = await this.sendRequest("GET", url);
786
- const data = await this.parseJsonResponse(response);
787
- return VatCollection.fromResponse(data);
2912
+ return withRetries(this.retryPolicy, "VAT search", async () => {
2913
+ const response = await this.sendRequest("GET", url);
2914
+ const data = await this.parseJsonResponse(response);
2915
+ return VatCollection.fromResponse(data);
2916
+ });
788
2917
  }
789
2918
  async certificate(uuid, lang) {
790
2919
  let url = `${this.baseUrl}/vat/certificate/${encodeURIComponent(uuid)}`;
791
2920
  if (lang) url += `?lang=${lang}`;
792
- const response = await this.sendBinaryRequest("GET", url);
793
- const buffer = await response.arrayBuffer();
794
- return new Uint8Array(buffer);
2921
+ return withRetries(this.retryPolicy, "VAT certificate download", async () => {
2922
+ const response = await this.sendBinaryRequest("GET", url);
2923
+ const buffer = await response.arrayBuffer();
2924
+ return new Uint8Array(buffer);
2925
+ });
795
2926
  }
796
2927
  async certificatesBulkExport(fromDate, toDate, countries, lang) {
797
2928
  return this.exportRequest(`${this.baseUrl}/vat/certificates/bulk-export`, fromDate, toDate, countries, lang);
@@ -815,9 +2946,11 @@ var VatEndpoint = class {
815
2946
  }
816
2947
  async downloadBulkExport(exportId) {
817
2948
  const url = `${this.baseUrl}/vat/certificates/bulk-export/${encodeURIComponent(exportId)}/download`;
818
- const response = await this.sendBinaryRequest("GET", url);
819
- const buffer = await response.arrayBuffer();
820
- return new Uint8Array(buffer);
2949
+ return withRetries(this.retryPolicy, "VAT export download", async () => {
2950
+ const response = await this.sendBinaryRequest("GET", url);
2951
+ const buffer = await response.arrayBuffer();
2952
+ return new Uint8Array(buffer);
2953
+ });
821
2954
  }
822
2955
  buildHeaders() {
823
2956
  const headers = {
@@ -841,7 +2974,13 @@ var VatEndpoint = class {
841
2974
  const response = await this.httpClient.request(method, url, { headers });
842
2975
  if (!response.ok) {
843
2976
  const text = await response.text();
844
- throw new HttpException(`HTTP error ${response.status}`, response.status, text);
2977
+ throw new HttpException(
2978
+ describeApiError(text, response.status),
2979
+ response.status,
2980
+ text,
2981
+ {},
2982
+ response.headers.get("retry-after")
2983
+ );
845
2984
  }
846
2985
  return response;
847
2986
  }
@@ -856,19 +2995,25 @@ var VatEndpoint = class {
856
2995
  }
857
2996
  } catch {
858
2997
  }
859
- throw new ValidationException("Validation failed", errors);
2998
+ throw new ValidationException(jsonErrorMessage(responseText) ?? "Validation failed", errors);
860
2999
  }
861
3000
  if (!response.ok && !acceptedStatuses.includes(response.status)) {
862
- throw new HttpException(`HTTP error ${response.status}`, response.status, responseText);
3001
+ throw new HttpException(
3002
+ describeApiError(responseText, response.status),
3003
+ response.status,
3004
+ responseText,
3005
+ {},
3006
+ response.headers.get("retry-after")
3007
+ );
863
3008
  }
864
3009
  let parsed;
865
3010
  try {
866
3011
  parsed = JSON.parse(responseText);
867
3012
  } catch {
868
- throw new HttpException("Invalid JSON response", 0, responseText);
3013
+ throw new HttpException("Invalid JSON response", response.status, responseText);
869
3014
  }
870
3015
  if (typeof parsed !== "object" || parsed === null) {
871
- throw new HttpException("Unexpected response format", 0, responseText);
3016
+ throw new HttpException("Unexpected response format", response.status, responseText);
872
3017
  }
873
3018
  const obj = parsed;
874
3019
  if (obj["data"] !== void 0 && (typeof obj["data"] === "object" || Array.isArray(obj["data"]))) {
@@ -883,7 +3028,7 @@ var VatEndpoint = class {
883
3028
  const day = String(date.getDate()).padStart(2, "0");
884
3029
  return `${year}-${month}-${day}`;
885
3030
  }
886
- if (!DATE_REGEX.test(date)) {
3031
+ if (!DATE_REGEX2.test(date)) {
887
3032
  throw new TaxoraException(`Invalid date format: "${date}". Expected YYYY-MM-DD.`);
888
3033
  }
889
3034
  return date;
@@ -966,21 +3111,48 @@ var AuthRetryHttpClient = class {
966
3111
  }
967
3112
  };
968
3113
 
3114
+ // src/version.ts
3115
+ var SDK_VERSION = "1.2.0";
3116
+
3117
+ // src/http/SdkVersionHttpClient.ts
3118
+ var SDK_VERSION_HEADER = "X-Taxora-SDK-Version";
3119
+ var SDK_VERSION_VALUE = `taxora-node/${SDK_VERSION}`;
3120
+ var SdkVersionHttpClient = class {
3121
+ constructor(inner) {
3122
+ this.inner = inner;
3123
+ }
3124
+ inner;
3125
+ async request(method, url, options) {
3126
+ const existing = options?.headers ?? {};
3127
+ const headers = {
3128
+ ...existing,
3129
+ [SDK_VERSION_HEADER]: SDK_VERSION_VALUE
3130
+ };
3131
+ return this.inner.request(method, url, { ...options ?? {}, headers });
3132
+ }
3133
+ };
3134
+
969
3135
  // src/client/TaxoraClient.ts
970
3136
  var TaxoraClient = class {
971
3137
  auth;
972
3138
  vat;
973
3139
  company;
3140
+ smartEnrichment;
3141
+ eReporting;
974
3142
  baseUrl;
975
3143
  constructor(options) {
976
3144
  const environment = options.environment ?? Environment.SANDBOX;
977
3145
  const tokenStorage = options.tokenStorage ?? new InMemoryTokenStorage();
978
3146
  const httpClient = options.httpClient ?? new FetchHttpClient();
3147
+ const retryPolicy = options.retryPolicy ?? new RetryPolicy();
3148
+ const versionedClient = new SdkVersionHttpClient(httpClient);
979
3149
  this.baseUrl = getBaseUrl(environment);
980
- this.auth = new AuthEndpoint(this.baseUrl, options.apiKey, tokenStorage, httpClient);
981
- const retryClient = new AuthRetryHttpClient(httpClient, tokenStorage, () => this.auth.refresh());
982
- this.vat = new VatEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient);
983
- this.company = new CompanyEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient);
3150
+ this.auth = new AuthEndpoint(this.baseUrl, options.apiKey, tokenStorage, versionedClient, retryPolicy);
3151
+ const retryClient = new AuthRetryHttpClient(versionedClient, tokenStorage, () => this.auth.refresh());
3152
+ this.vat = new VatEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
3153
+ this.company = new CompanyEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
3154
+ this.smartEnrichment = new SmartEnrichmentEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
3155
+ this.eReporting = new EReportingEndpoint(this.baseUrl, options.apiKey, tokenStorage, retryClient, retryPolicy);
984
3156
  }
985
3157
  };
986
3158
 
@@ -1008,15 +3180,38 @@ export {
1008
3180
  AuthenticationException,
1009
3181
  CompanyAddress,
1010
3182
  CompanyEndpoint,
3183
+ ComplianceEnrollment,
3184
+ ComplianceEnrollmentPage,
3185
+ ComplianceEnrollmentStatus,
3186
+ ComplianceTaxReport,
3187
+ ComplianceTaxReportPage,
3188
+ ComplianceTaxReportState,
3189
+ ComplianceTransaction,
3190
+ ComplianceTransactionPage,
3191
+ ComplianceTransactionState,
3192
+ ComplianceTransactionType,
3193
+ EReportingEndpoint,
1011
3194
  Environment,
1012
3195
  FetchHttpClient,
1013
3196
  HttpException,
3197
+ ImportResult,
1014
3198
  InMemoryTokenStorage,
1015
3199
  Language,
1016
3200
  LoginIdentifier,
1017
3201
  ProviderDocument,
1018
3202
  ProviderDocumentLine,
3203
+ RetryPolicy,
3204
+ RevenueStatistics,
1019
3205
  ScoreBreakdown,
3206
+ SireneLookupResult,
3207
+ SmartEnrichmentEndpoint,
3208
+ SmartEnrichmentHistoryPage,
3209
+ SmartEnrichmentHistoryRow,
3210
+ SmartEnrichmentJob,
3211
+ SmartEnrichmentMode,
3212
+ SmartEnrichmentResource,
3213
+ SmartEnrichmentStatistics,
3214
+ SmartEnrichmentStatus,
1020
3215
  TaxoraClient,
1021
3216
  TaxoraClientFactory,
1022
3217
  TaxoraException,
@@ -1025,11 +3220,25 @@ export {
1025
3220
  VatCertificateExport,
1026
3221
  VatCollection,
1027
3222
  VatEndpoint,
3223
+ VatRates,
1028
3224
  VatResource,
1029
3225
  VatState,
1030
3226
  VatValidationAddressInput,
3227
+ describeApiError,
3228
+ describeComplianceEnrollmentStatus,
3229
+ describeComplianceTaxReportState,
3230
+ describeComplianceTransactionState,
3231
+ describeComplianceTransactionType,
3232
+ describeSmartEnrichmentMode,
3233
+ describeSmartEnrichmentStatus,
1031
3234
  describeVatState,
1032
3235
  getBaseUrl,
1033
- getFailedVatStates
3236
+ getFailedVatStates,
3237
+ toComplianceEnrollmentStatus,
3238
+ toComplianceTaxReportState,
3239
+ toComplianceTransactionState,
3240
+ toComplianceTransactionType,
3241
+ toSmartEnrichmentMode,
3242
+ toSmartEnrichmentStatus
1034
3243
  };
1035
3244
  //# sourceMappingURL=index.js.map