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