@voyant-travel/plugin-smartbill 0.119.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +324 -0
- package/dist/artifacts.d.ts +80 -0
- package/dist/artifacts.d.ts.map +1 -0
- package/dist/artifacts.js +295 -0
- package/dist/client/errors.d.ts +28 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +32 -0
- package/dist/client/fetch.d.ts +4 -0
- package/dist/client/fetch.d.ts.map +1 -0
- package/dist/client/fetch.js +32 -0
- package/dist/client/rate-limit.d.ts +8 -0
- package/dist/client/rate-limit.d.ts.map +1 -0
- package/dist/client/rate-limit.js +54 -0
- package/dist/client/resilience.d.ts +36 -0
- package/dist/client/resilience.d.ts.map +1 -0
- package/dist/client/resilience.js +23 -0
- package/dist/client.d.ts +67 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +234 -0
- package/dist/hono.d.ts +199 -0
- package/dist/hono.d.ts.map +1 -0
- package/dist/hono.js +77 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/invoice-ui-data.d.ts +46 -0
- package/dist/invoice-ui-data.d.ts.map +1 -0
- package/dist/invoice-ui-data.js +62 -0
- package/dist/invoice-ui.d.ts +438 -0
- package/dist/invoice-ui.d.ts.map +1 -0
- package/dist/invoice-ui.js +97 -0
- package/dist/mapping.d.ts +50 -0
- package/dist/mapping.d.ts.map +1 -0
- package/dist/mapping.js +219 -0
- package/dist/mock/node.d.ts +4 -0
- package/dist/mock/node.d.ts.map +1 -0
- package/dist/mock/node.js +14 -0
- package/dist/mock/pdf.d.ts +7 -0
- package/dist/mock/pdf.d.ts.map +1 -0
- package/dist/mock/pdf.js +44 -0
- package/dist/mock/types.d.ts +94 -0
- package/dist/mock/types.d.ts.map +1 -0
- package/dist/mock/types.js +1 -0
- package/dist/mock.d.ts +4 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +431 -0
- package/dist/plugin.d.ts +55 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +192 -0
- package/dist/runtime.d.ts +25 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +42 -0
- package/dist/settlement.d.ts +33 -0
- package/dist/settlement.d.ts.map +1 -0
- package/dist/settlement.js +65 -0
- package/dist/sync/events.d.ts +5 -0
- package/dist/sync/events.d.ts.map +1 -0
- package/dist/sync/events.js +96 -0
- package/dist/sync/helpers.d.ts +66 -0
- package/dist/sync/helpers.d.ts.map +1 -0
- package/dist/sync/helpers.js +353 -0
- package/dist/sync/invoice.d.ts +3 -0
- package/dist/sync/invoice.d.ts.map +1 -0
- package/dist/sync/invoice.js +25 -0
- package/dist/sync/types.d.ts +71 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +1 -0
- package/dist/sync.d.ts +4 -0
- package/dist/sync.d.ts.map +1 -0
- package/dist/sync.js +2 -0
- package/dist/types.d.ts +189 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/validation.d.ts +39 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +120 -0
- package/dist/workflow-candidates.d.ts +36 -0
- package/dist/workflow-candidates.d.ts.map +1 -0
- package/dist/workflow-candidates.js +182 -0
- package/dist/workflow-remote-discovery.d.ts +6 -0
- package/dist/workflow-remote-discovery.d.ts.map +1 -0
- package/dist/workflow-remote-discovery.js +83 -0
- package/dist/workflows/refs.d.ts +13 -0
- package/dist/workflows/refs.d.ts.map +1 -0
- package/dist/workflows/refs.js +51 -0
- package/dist/workflows/spaced-client.d.ts +5 -0
- package/dist/workflows/spaced-client.d.ts.map +1 -0
- package/dist/workflows/spaced-client.js +44 -0
- package/dist/workflows/types.d.ts +142 -0
- package/dist/workflows/types.d.ts.map +1 -0
- package/dist/workflows/types.js +1 -0
- package/dist/workflows.d.ts +5 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +229 -0
- package/package.json +129 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// agent-quality: file-size exception -- owner: plugins; existing module stays co-located until a dedicated split preserves behavior and tests.
|
|
2
|
+
import { financeService } from "@voyant-travel/finance";
|
|
3
|
+
export class SmartbillPdfPersistError extends Error {
|
|
4
|
+
stage;
|
|
5
|
+
originalError;
|
|
6
|
+
constructor(stage, error) {
|
|
7
|
+
super(`SmartBill PDF persist failed at ${stage}: ${errorMessage(error)}`);
|
|
8
|
+
this.name = "SmartbillPdfPersistError";
|
|
9
|
+
this.stage = stage;
|
|
10
|
+
this.originalError = error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class SmartbillPdfPersistMetadataUpdateError extends Error {
|
|
14
|
+
originalError;
|
|
15
|
+
constructor(error) {
|
|
16
|
+
super(`SmartBill PDF persist metadata update failed: ${errorMessage(error)}`);
|
|
17
|
+
this.name = "SmartbillPdfPersistMetadataUpdateError";
|
|
18
|
+
this.originalError = error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function isSmartbillPdfPersistMetadataUpdateError(error) {
|
|
22
|
+
return error instanceof SmartbillPdfPersistMetadataUpdateError;
|
|
23
|
+
}
|
|
24
|
+
const SMARTBILL_ATTACHMENT_KIND = "smartbill_pdf";
|
|
25
|
+
function isResolver(value) {
|
|
26
|
+
return typeof value === "function";
|
|
27
|
+
}
|
|
28
|
+
async function resolveMaybe(value, context) {
|
|
29
|
+
return isResolver(value) ? await value(context) : value;
|
|
30
|
+
}
|
|
31
|
+
function sanitizeKeyPart(value) {
|
|
32
|
+
return value.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "document";
|
|
33
|
+
}
|
|
34
|
+
async function sha256(bytes) {
|
|
35
|
+
const crypto = globalThis.crypto;
|
|
36
|
+
if (!crypto?.subtle)
|
|
37
|
+
return null;
|
|
38
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
39
|
+
new Uint8Array(buffer).set(bytes);
|
|
40
|
+
const digest = await crypto.subtle.digest("SHA-256", buffer);
|
|
41
|
+
return `sha256:${Array.from(new Uint8Array(digest))
|
|
42
|
+
.map((byte) => byte.toString(16).padStart(2, "0"))
|
|
43
|
+
.join("")}`;
|
|
44
|
+
}
|
|
45
|
+
function smartbillAttachmentName(documentType, seriesName, number) {
|
|
46
|
+
return `SmartBill ${documentType} ${seriesName}-${number}.pdf`;
|
|
47
|
+
}
|
|
48
|
+
function coerceMetadata(value) {
|
|
49
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
50
|
+
? value
|
|
51
|
+
: null;
|
|
52
|
+
}
|
|
53
|
+
function metadataString(metadata, key) {
|
|
54
|
+
const value = metadata?.[key];
|
|
55
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
56
|
+
}
|
|
57
|
+
function errorMessage(error) {
|
|
58
|
+
if (error instanceof SmartbillPdfPersistError)
|
|
59
|
+
return errorMessage(error.originalError);
|
|
60
|
+
if (error instanceof Error)
|
|
61
|
+
return error.message;
|
|
62
|
+
return String(error);
|
|
63
|
+
}
|
|
64
|
+
function errorStage(error) {
|
|
65
|
+
return error instanceof SmartbillPdfPersistError ? error.stage : "unknown";
|
|
66
|
+
}
|
|
67
|
+
async function withPdfPersistStage(stage, operation) {
|
|
68
|
+
try {
|
|
69
|
+
return await operation();
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
throw new SmartbillPdfPersistError(stage, error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function buildSmartbillExternalRefMetadata({ body, result, documentType, extra, }) {
|
|
76
|
+
return {
|
|
77
|
+
companyVatCode: body.companyVatCode,
|
|
78
|
+
seriesName: body.seriesName,
|
|
79
|
+
series: result.series ?? body.seriesName,
|
|
80
|
+
number: result.number ?? null,
|
|
81
|
+
documentType,
|
|
82
|
+
...extra,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async function registerSmartbillExternalRef(db, invoiceId, body, result, documentType, metadataExtra) {
|
|
86
|
+
const number = result.number;
|
|
87
|
+
return financeService.registerInvoiceExternalRef(db, invoiceId, {
|
|
88
|
+
provider: "smartbill",
|
|
89
|
+
externalId: number ?? null,
|
|
90
|
+
externalNumber: number ?? null,
|
|
91
|
+
externalUrl: result.url ?? null,
|
|
92
|
+
status: result.errorText ? "error" : "issued",
|
|
93
|
+
syncedAt: new Date().toISOString(),
|
|
94
|
+
syncError: result.errorText ?? null,
|
|
95
|
+
metadata: buildSmartbillExternalRefMetadata({
|
|
96
|
+
body,
|
|
97
|
+
result,
|
|
98
|
+
documentType,
|
|
99
|
+
extra: metadataExtra,
|
|
100
|
+
}),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async function recordSmartbillInvoiceArtifactReady(db, invoiceId, body, result, documentType, storageKey, existingMetadata) {
|
|
104
|
+
try {
|
|
105
|
+
await registerSmartbillExternalRef(db, invoiceId, body, result, documentType, {
|
|
106
|
+
...(existingMetadata ?? {}),
|
|
107
|
+
pdfPersistStatus: "ready",
|
|
108
|
+
pdfPersistedAt: new Date().toISOString(),
|
|
109
|
+
pdfStorageKey: storageKey,
|
|
110
|
+
pdfPersistError: null,
|
|
111
|
+
pdfPersistStage: null,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw new SmartbillPdfPersistMetadataUpdateError(error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
export async function persistSmartbillInvoiceArtifact({ runtime, client, event, documentType, body, result, }) {
|
|
119
|
+
if (!runtime.db)
|
|
120
|
+
return { status: "skipped", reason: "missing_db" };
|
|
121
|
+
const context = { event, documentType, body, result };
|
|
122
|
+
const db = await resolveMaybe(runtime.db, context);
|
|
123
|
+
if (!db)
|
|
124
|
+
return { status: "skipped", reason: "missing_db" };
|
|
125
|
+
const seriesName = result.series ?? body.seriesName;
|
|
126
|
+
const number = result.number;
|
|
127
|
+
const externalRef = await registerSmartbillExternalRef(db, event.id, body, result, documentType);
|
|
128
|
+
if (!externalRef)
|
|
129
|
+
return { status: "skipped", reason: "missing_invoice" };
|
|
130
|
+
const storage = await resolveMaybe(runtime.documentStorage, context);
|
|
131
|
+
if (!storage)
|
|
132
|
+
return { status: "registered_ref" };
|
|
133
|
+
if (!number)
|
|
134
|
+
return { status: "registered_ref", reason: "missing_number" };
|
|
135
|
+
const keyPrefix = await resolveMaybe(runtime.documentStorageKeyPrefix, context);
|
|
136
|
+
const persisted = await persistSmartbillPdfArtifact({
|
|
137
|
+
db,
|
|
138
|
+
storage,
|
|
139
|
+
client,
|
|
140
|
+
invoiceId: event.id,
|
|
141
|
+
documentType,
|
|
142
|
+
companyVatCode: body.companyVatCode,
|
|
143
|
+
seriesName,
|
|
144
|
+
number,
|
|
145
|
+
language: body.language ?? null,
|
|
146
|
+
keyPrefix,
|
|
147
|
+
});
|
|
148
|
+
if (persisted.status === "persisted" || persisted.status === "already_exists") {
|
|
149
|
+
await recordSmartbillInvoiceArtifactReady(db, event.id, body, result, documentType, persisted.attachment?.storageKey ?? null);
|
|
150
|
+
}
|
|
151
|
+
return persisted;
|
|
152
|
+
}
|
|
153
|
+
export async function recordSmartbillInvoiceArtifactFailure({ runtime, event, documentType, body, result, error, }) {
|
|
154
|
+
if (!runtime.db)
|
|
155
|
+
return { status: "skipped", reason: "missing_db" };
|
|
156
|
+
const context = { event, documentType, body, result };
|
|
157
|
+
const db = await resolveMaybe(runtime.db, context);
|
|
158
|
+
if (!db)
|
|
159
|
+
return { status: "skipped", reason: "missing_db" };
|
|
160
|
+
const externalRef = await registerSmartbillExternalRef(db, event.id, body, result, documentType, {
|
|
161
|
+
pdfPersistStatus: "failed",
|
|
162
|
+
pdfPersistError: errorMessage(error),
|
|
163
|
+
pdfPersistStage: errorStage(error),
|
|
164
|
+
pdfPersistedAt: null,
|
|
165
|
+
});
|
|
166
|
+
if (!externalRef)
|
|
167
|
+
return { status: "skipped", reason: "missing_invoice" };
|
|
168
|
+
return { status: "registered_ref" };
|
|
169
|
+
}
|
|
170
|
+
export async function retrySmartbillInvoiceArtifact({ runtime, client, externalRef, documentType, }) {
|
|
171
|
+
if (!runtime.db)
|
|
172
|
+
return { status: "skipped", reason: "missing_db" };
|
|
173
|
+
const metadata = coerceMetadata(externalRef.metadata);
|
|
174
|
+
const companyVatCode = metadataString(metadata, "companyVatCode") ?? metadataString(metadata, "vatCode");
|
|
175
|
+
const seriesName = metadataString(metadata, "series") ?? metadataString(metadata, "seriesName");
|
|
176
|
+
const number = metadataString(metadata, "number") ??
|
|
177
|
+
externalRef.externalNumber ??
|
|
178
|
+
externalRef.externalId ??
|
|
179
|
+
null;
|
|
180
|
+
if (!companyVatCode || !seriesName || !number) {
|
|
181
|
+
return { status: "skipped", reason: "missing_smartbill_reference" };
|
|
182
|
+
}
|
|
183
|
+
const context = {
|
|
184
|
+
event: { id: externalRef.invoiceId },
|
|
185
|
+
documentType,
|
|
186
|
+
body: {
|
|
187
|
+
companyVatCode,
|
|
188
|
+
client: { name: "Client" },
|
|
189
|
+
seriesName,
|
|
190
|
+
currency: "RON",
|
|
191
|
+
language: metadataString(metadata, "language") ?? undefined,
|
|
192
|
+
products: [],
|
|
193
|
+
},
|
|
194
|
+
result: {
|
|
195
|
+
number,
|
|
196
|
+
series: seriesName,
|
|
197
|
+
url: externalRef.externalUrl ?? undefined,
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
const db = await resolveMaybe(runtime.db, context);
|
|
201
|
+
if (!db)
|
|
202
|
+
return { status: "skipped", reason: "missing_db" };
|
|
203
|
+
const storage = await resolveMaybe(runtime.documentStorage, context);
|
|
204
|
+
if (!storage)
|
|
205
|
+
return { status: "skipped", reason: "missing_document_storage" };
|
|
206
|
+
const keyPrefix = await resolveMaybe(runtime.documentStorageKeyPrefix, context);
|
|
207
|
+
const result = context.result;
|
|
208
|
+
let persisted;
|
|
209
|
+
try {
|
|
210
|
+
persisted = await persistSmartbillPdfArtifact({
|
|
211
|
+
db,
|
|
212
|
+
storage,
|
|
213
|
+
client,
|
|
214
|
+
invoiceId: externalRef.invoiceId,
|
|
215
|
+
documentType,
|
|
216
|
+
companyVatCode,
|
|
217
|
+
seriesName,
|
|
218
|
+
number,
|
|
219
|
+
language: metadataString(metadata, "language"),
|
|
220
|
+
keyPrefix,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
await registerSmartbillExternalRef(db, externalRef.invoiceId, context.body, result, documentType, {
|
|
225
|
+
...(metadata ?? {}),
|
|
226
|
+
pdfPersistStatus: "failed",
|
|
227
|
+
pdfPersistError: errorMessage(error),
|
|
228
|
+
pdfPersistStage: errorStage(error),
|
|
229
|
+
pdfPersistedAt: null,
|
|
230
|
+
});
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
if (persisted.status === "persisted" || persisted.status === "already_exists") {
|
|
234
|
+
await recordSmartbillInvoiceArtifactReady(db, externalRef.invoiceId, context.body, result, documentType, persisted.attachment?.storageKey ?? null, metadata);
|
|
235
|
+
}
|
|
236
|
+
return persisted;
|
|
237
|
+
}
|
|
238
|
+
async function persistSmartbillPdfArtifact({ db, storage, client, invoiceId, documentType, companyVatCode, seriesName, number, language, keyPrefix, }) {
|
|
239
|
+
const existingAttachments = await withPdfPersistStage("attachment_lookup", () => financeService.listInvoiceAttachments(db, invoiceId));
|
|
240
|
+
const existingSmartbillAttachment = existingAttachments.find((attachment) => attachment.kind === SMARTBILL_ATTACHMENT_KIND);
|
|
241
|
+
if (existingSmartbillAttachment) {
|
|
242
|
+
return { status: "already_exists", attachment: existingSmartbillAttachment };
|
|
243
|
+
}
|
|
244
|
+
const pdf = documentType === "proforma"
|
|
245
|
+
? await withPdfPersistStage("viewEstimatePdf", () => client.viewEstimatePdf(companyVatCode, seriesName, number))
|
|
246
|
+
: await withPdfPersistStage("viewInvoicePdf", () => client.viewInvoicePdf(companyVatCode, seriesName, number));
|
|
247
|
+
const defaultPrefix = `invoices/${invoiceId}/smartbill`;
|
|
248
|
+
const resolvedKeyPrefix = keyPrefix ?? defaultPrefix;
|
|
249
|
+
const key = `${resolvedKeyPrefix.replace(/\/$/, "")}/${documentType}-${sanitizeKeyPart(seriesName)}-${sanitizeKeyPart(number)}.pdf`;
|
|
250
|
+
const contentType = pdf.contentType || "application/pdf";
|
|
251
|
+
const checksum = await sha256(pdf.bytes);
|
|
252
|
+
const uploaded = await withPdfPersistStage("storage_upload", () => storage.upload(pdf.bytes, {
|
|
253
|
+
key,
|
|
254
|
+
contentType,
|
|
255
|
+
metadata: {
|
|
256
|
+
provider: "smartbill",
|
|
257
|
+
documentType,
|
|
258
|
+
invoiceId,
|
|
259
|
+
seriesName,
|
|
260
|
+
number,
|
|
261
|
+
},
|
|
262
|
+
}));
|
|
263
|
+
const commonMetadata = {
|
|
264
|
+
provider: "smartbill",
|
|
265
|
+
documentType,
|
|
266
|
+
companyVatCode,
|
|
267
|
+
seriesName,
|
|
268
|
+
number,
|
|
269
|
+
storageProvider: storage.name,
|
|
270
|
+
...(uploaded.url ? { url: uploaded.url } : {}),
|
|
271
|
+
};
|
|
272
|
+
const rendition = await withPdfPersistStage("rendition_insert", () => financeService.createInvoiceRendition(db, invoiceId, {
|
|
273
|
+
format: "pdf",
|
|
274
|
+
status: "ready",
|
|
275
|
+
storageKey: uploaded.key,
|
|
276
|
+
fileSize: pdf.bytes.byteLength,
|
|
277
|
+
checksum,
|
|
278
|
+
language: language ?? null,
|
|
279
|
+
generatedAt: new Date().toISOString(),
|
|
280
|
+
metadata: commonMetadata,
|
|
281
|
+
}));
|
|
282
|
+
const attachment = await withPdfPersistStage("attachment_insert", () => financeService.createInvoiceAttachment(db, invoiceId, {
|
|
283
|
+
kind: SMARTBILL_ATTACHMENT_KIND,
|
|
284
|
+
name: smartbillAttachmentName(documentType, seriesName, number),
|
|
285
|
+
mimeType: contentType,
|
|
286
|
+
fileSize: pdf.bytes.byteLength,
|
|
287
|
+
storageKey: uploaded.key,
|
|
288
|
+
checksum,
|
|
289
|
+
metadata: {
|
|
290
|
+
...commonMetadata,
|
|
291
|
+
renditionId: rendition?.id ?? null,
|
|
292
|
+
},
|
|
293
|
+
}));
|
|
294
|
+
return { status: "persisted", rendition, attachment };
|
|
295
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface SmartbillApiErrorOptions {
|
|
2
|
+
operation: string;
|
|
3
|
+
status?: number;
|
|
4
|
+
body?: string;
|
|
5
|
+
response?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare class SmartbillApiError extends Error {
|
|
8
|
+
readonly operation: string;
|
|
9
|
+
readonly status?: number;
|
|
10
|
+
readonly body?: string;
|
|
11
|
+
readonly response?: unknown;
|
|
12
|
+
constructor(message: string, options: SmartbillApiErrorOptions);
|
|
13
|
+
}
|
|
14
|
+
export interface SmartbillRateLimitErrorOptions extends SmartbillApiErrorOptions {
|
|
15
|
+
retryAfterMs?: number;
|
|
16
|
+
retryAfterAt?: Date;
|
|
17
|
+
blockedAt?: Date;
|
|
18
|
+
}
|
|
19
|
+
export declare class SmartbillRateLimitError extends SmartbillApiError {
|
|
20
|
+
readonly retryAfterMs?: number;
|
|
21
|
+
readonly retryAfterAt?: Date;
|
|
22
|
+
readonly blockedAt?: Date;
|
|
23
|
+
constructor(message: string, options: SmartbillRateLimitErrorOptions);
|
|
24
|
+
}
|
|
25
|
+
export declare class SmartbillRateLimitCircuitOpenError extends SmartbillRateLimitError {
|
|
26
|
+
constructor(options: SmartbillRateLimitErrorOptions);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;gBAEf,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,wBAAwB;CAQ/D;AAED,MAAM,WAAW,8BAA+B,SAAQ,wBAAwB;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB;AAED,qBAAa,uBAAwB,SAAQ,iBAAiB;IAC5D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,IAAI,CAAA;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAA;gBAEb,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,8BAA8B;CAOrE;AAED,qBAAa,kCAAmC,SAAQ,uBAAuB;gBACjE,OAAO,EAAE,8BAA8B;CASpD"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export class SmartbillApiError extends Error {
|
|
2
|
+
operation;
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
response;
|
|
6
|
+
constructor(message, options) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SmartbillApiError";
|
|
9
|
+
this.operation = options.operation;
|
|
10
|
+
this.status = options.status;
|
|
11
|
+
this.body = options.body;
|
|
12
|
+
this.response = options.response;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class SmartbillRateLimitError extends SmartbillApiError {
|
|
16
|
+
retryAfterMs;
|
|
17
|
+
retryAfterAt;
|
|
18
|
+
blockedAt;
|
|
19
|
+
constructor(message, options) {
|
|
20
|
+
super(message, options);
|
|
21
|
+
this.name = "SmartbillRateLimitError";
|
|
22
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
23
|
+
this.retryAfterAt = options.retryAfterAt;
|
|
24
|
+
this.blockedAt = options.blockedAt;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class SmartbillRateLimitCircuitOpenError extends SmartbillRateLimitError {
|
|
28
|
+
constructor(options) {
|
|
29
|
+
super(`SmartBill rate-limit circuit is open${options.retryAfterMs !== undefined ? `; retry after ${options.retryAfterMs}ms` : ""}`, options);
|
|
30
|
+
this.name = "SmartbillRateLimitCircuitOpenError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["../../src/client/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAEjD,wBAAgB,0BAA0B,IAAI,cAAc,GAAG,SAAS,CAGvE;AAYD,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,cAAc,GAAG,OAAO,KAAK,CAcxE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function createGlobalSmartbillFetch() {
|
|
2
|
+
if (typeof globalThis.fetch !== "function")
|
|
3
|
+
return undefined;
|
|
4
|
+
return (input, init) => globalThis.fetch(input, init);
|
|
5
|
+
}
|
|
6
|
+
function headersForPluginFetch(headers) {
|
|
7
|
+
if (!headers)
|
|
8
|
+
return {};
|
|
9
|
+
if (headers instanceof Headers || Array.isArray(headers)) {
|
|
10
|
+
return Object.fromEntries(new Headers(headers).entries());
|
|
11
|
+
}
|
|
12
|
+
const out = {};
|
|
13
|
+
for (const [key, value] of Object.entries(headers))
|
|
14
|
+
out[key] = String(value);
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
export function asResilientFetch(fetchImpl) {
|
|
18
|
+
return async (input, init = {}) => {
|
|
19
|
+
const response = await fetchImpl(input instanceof Request ? input.url : String(input), {
|
|
20
|
+
method: init.method ?? "GET",
|
|
21
|
+
headers: headersForPluginFetch(init.headers),
|
|
22
|
+
...(typeof init.body === "string" ? { body: init.body } : {}),
|
|
23
|
+
});
|
|
24
|
+
if (response instanceof Response)
|
|
25
|
+
return response;
|
|
26
|
+
const contentType = response.headers?.get("content-type");
|
|
27
|
+
return new Response(await response.arrayBuffer(), {
|
|
28
|
+
status: response.status,
|
|
29
|
+
headers: contentType ? { "content-type": contentType } : undefined,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ParsedSmartbillRateLimit {
|
|
2
|
+
errorText: string;
|
|
3
|
+
retryAfterMs?: number;
|
|
4
|
+
retryAfterAt?: Date;
|
|
5
|
+
blockedAt?: Date;
|
|
6
|
+
}
|
|
7
|
+
export declare function parseSmartbillRateLimit(status: number, parsed: unknown, now: Date): ParsedSmartbillRateLimit | null;
|
|
8
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/client/rate-limit.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,IAAI,CAAA;CACjB;AAkDD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,EACf,GAAG,EAAE,IAAI,GACR,wBAAwB,GAAG,IAAI,CA+BjC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const RATE_LIMIT_TEXT_PATTERN = /limita\s+maxima\s+de\s+requesturi|vei\s+putea\s+executa\s+alte\s+requesturi/i;
|
|
2
|
+
const RATE_LIMIT_MINUTES_PATTERN = /dupa\s+(\d+)\s*min/i;
|
|
3
|
+
const RATE_LIMIT_DATE_PATTERN = /(\d{1,2})\/(\d{1,2})\/(\d{4})\s+(\d{1,2}):(\d{2}):(\d{2})|(\d{4})-(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{2}):(\d{2})/;
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === "object" && value !== null;
|
|
6
|
+
}
|
|
7
|
+
function parseSmartbillDate(value) {
|
|
8
|
+
const match = RATE_LIMIT_DATE_PATTERN.exec(value);
|
|
9
|
+
if (!match)
|
|
10
|
+
return undefined;
|
|
11
|
+
// Treat SmartBill's date string as UTC. The original code used `new
|
|
12
|
+
// Date(y, m, d, ...)` which interprets components as the JS host's
|
|
13
|
+
// local time, so the same response decoded to different instants on a
|
|
14
|
+
// CI runner (UTC) vs. a developer machine (Europe/Bucharest, EEST).
|
|
15
|
+
// The retry-window math downstream is duration-based (`retryAfterAt -
|
|
16
|
+
// now`), so anchoring to UTC keeps the math deterministic and matches
|
|
17
|
+
// the existing test fixtures that assert UTC timestamps.
|
|
18
|
+
if (match[1]) {
|
|
19
|
+
const [, day, month, year, hour, minute, second] = match;
|
|
20
|
+
return new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second)));
|
|
21
|
+
}
|
|
22
|
+
const [, , , , , , , year, month, day, hour, minute, second] = match;
|
|
23
|
+
return new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second)));
|
|
24
|
+
}
|
|
25
|
+
export function parseSmartbillRateLimit(status, parsed, now) {
|
|
26
|
+
if (!isRecord(parsed))
|
|
27
|
+
return null;
|
|
28
|
+
const errorText = typeof parsed.errorText === "string"
|
|
29
|
+
? parsed.errorText
|
|
30
|
+
: typeof parsed.message === "string"
|
|
31
|
+
? parsed.message
|
|
32
|
+
: "";
|
|
33
|
+
if (status !== 403 && !RATE_LIMIT_TEXT_PATTERN.test(errorText))
|
|
34
|
+
return null;
|
|
35
|
+
if (!RATE_LIMIT_TEXT_PATTERN.test(errorText))
|
|
36
|
+
return null;
|
|
37
|
+
const minutesMatch = RATE_LIMIT_MINUTES_PATTERN.exec(errorText);
|
|
38
|
+
const blockedAt = parseSmartbillDate(errorText);
|
|
39
|
+
const minutes = minutesMatch?.[1] ? Number(minutesMatch[1]) : undefined;
|
|
40
|
+
const retryAfterAt = blockedAt && minutes !== undefined
|
|
41
|
+
? new Date(blockedAt.getTime() + minutes * 60_000)
|
|
42
|
+
: typeof parsed.cooldown === "number" && parsed.cooldown > 0
|
|
43
|
+
? new Date(now.getTime() + parsed.cooldown * 1000)
|
|
44
|
+
: undefined;
|
|
45
|
+
const retryAfterMs = retryAfterAt
|
|
46
|
+
? Math.max(0, retryAfterAt.getTime() - now.getTime())
|
|
47
|
+
: undefined;
|
|
48
|
+
return {
|
|
49
|
+
errorText,
|
|
50
|
+
retryAfterMs,
|
|
51
|
+
retryAfterAt,
|
|
52
|
+
blockedAt,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type CircuitBreaker, type RetryOptions } from "@voyant-travel/utils/resilience";
|
|
2
|
+
/**
|
|
3
|
+
* Outbound-HTTP resilience knobs for the SmartBill client. Every call goes
|
|
4
|
+
* through `resilientFetch`, so a slow or down SmartBill fails fast instead
|
|
5
|
+
* of burning the Worker request's CPU/subrequest budget. Retries only apply
|
|
6
|
+
* to idempotent operations (GETs, PDF downloads, cancel/restore/delete);
|
|
7
|
+
* document-creating calls (invoice/proforma create, conversion, reversal)
|
|
8
|
+
* never retry because SmartBill has no idempotency keys.
|
|
9
|
+
*/
|
|
10
|
+
export interface SmartbillResilienceOptions {
|
|
11
|
+
/** Per-attempt timeout. Default 10s. */
|
|
12
|
+
timeoutMs?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Retry tuning (attempts/backoff) for retry-eligible operations, or
|
|
15
|
+
* `false` to disable retries entirely. Defaults to 3 attempts on network
|
|
16
|
+
* errors/timeouts/429/5xx.
|
|
17
|
+
*/
|
|
18
|
+
retry?: Pick<RetryOptions, "attempts" | "baseDelayMs" | "maxDelayMs"> | false;
|
|
19
|
+
/**
|
|
20
|
+
* Override/share the circuit breaker. Defaults to one breaker per client
|
|
21
|
+
* instance (clients are per-worker singletons, i.e. one per upstream).
|
|
22
|
+
* Distinct from the SmartBill-specific rate-limit circuit (`rateLimit`),
|
|
23
|
+
* which reacts to SmartBill's 403 quota responses.
|
|
24
|
+
*/
|
|
25
|
+
breaker?: CircuitBreaker;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Retry on network errors/timeouts/429/5xx, but surface the FINAL failing
|
|
29
|
+
* response to the caller instead of throwing, so `buildApiError` keeps the
|
|
30
|
+
* upstream status + body (including SmartBill's rate-limit envelope).
|
|
31
|
+
*/
|
|
32
|
+
export declare function surfacingRetry(maxAttempts: number, tuning?: {
|
|
33
|
+
baseDelayMs?: number;
|
|
34
|
+
maxDelayMs?: number;
|
|
35
|
+
}): RetryOptions;
|
|
36
|
+
//# sourceMappingURL=resilience.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilience.d.ts","sourceRoot":"","sources":["../../src/client/resilience.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,cAAc,EAEnB,KAAK,YAAY,EAClB,MAAM,iCAAiC,CAAA;AAExC;;;;;;;GAOG;AACH,MAAM,WAAW,0BAA0B;IACzC,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,KAAK,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,aAAa,GAAG,YAAY,CAAC,GAAG,KAAK,CAAA;IAC7E;;;;;OAKG;IACH,OAAO,CAAC,EAAE,cAAc,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACrD,YAAY,CAcd"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CircuitOpenError, } from "@voyant-travel/utils/resilience";
|
|
2
|
+
/**
|
|
3
|
+
* Retry on network errors/timeouts/429/5xx, but surface the FINAL failing
|
|
4
|
+
* response to the caller instead of throwing, so `buildApiError` keeps the
|
|
5
|
+
* upstream status + body (including SmartBill's rate-limit envelope).
|
|
6
|
+
*/
|
|
7
|
+
export function surfacingRetry(maxAttempts, tuning) {
|
|
8
|
+
let failedAttempts = 0;
|
|
9
|
+
return {
|
|
10
|
+
baseDelayMs: tuning?.baseDelayMs,
|
|
11
|
+
maxDelayMs: tuning?.maxDelayMs,
|
|
12
|
+
attempts: maxAttempts,
|
|
13
|
+
retryOn: ({ response, error }) => {
|
|
14
|
+
failedAttempts += 1;
|
|
15
|
+
if (failedAttempts >= maxAttempts)
|
|
16
|
+
return false;
|
|
17
|
+
if (error)
|
|
18
|
+
return !(error instanceof CircuitOpenError);
|
|
19
|
+
const status = response?.status ?? 0;
|
|
20
|
+
return status === 429 || status >= 500;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type SmartbillResilienceOptions } from "./client/resilience.js";
|
|
2
|
+
import type { SmartbillEnvelope, SmartbillEstimateInvoicesResponse, SmartbillFetch, SmartbillInvoiceBody, SmartbillInvoiceResponse, SmartbillPdfResponse, SmartbillSeriesResponse, SmartbillStatusResponse, SmartbillTaxesResponse } from "./types.js";
|
|
3
|
+
export type { SmartbillResilienceOptions } from "./client/resilience.js";
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link createSmartbillClient}.
|
|
6
|
+
*/
|
|
7
|
+
export interface SmartbillClientOptions {
|
|
8
|
+
/** SmartBill account username (email). */
|
|
9
|
+
username: string;
|
|
10
|
+
/** SmartBill API token. */
|
|
11
|
+
apiToken: string;
|
|
12
|
+
/**
|
|
13
|
+
* SmartBill API base URL. Defaults to `"https://ws.smartbill.ro/SBORO/api"`.
|
|
14
|
+
*/
|
|
15
|
+
apiUrl?: string;
|
|
16
|
+
/** Override `fetch` (e.g. in tests). Defaults to global `fetch`. */
|
|
17
|
+
fetch?: SmartbillFetch;
|
|
18
|
+
/** Optional process-local protection for SmartBill account rate limits. */
|
|
19
|
+
rateLimit?: {
|
|
20
|
+
/**
|
|
21
|
+
* When enabled, the client opens a process-local circuit after the first
|
|
22
|
+
* SmartBill rate-limit response and skips network calls until retry time.
|
|
23
|
+
*/
|
|
24
|
+
circuitBreaker?: boolean;
|
|
25
|
+
/** Test hook for deterministic time. */
|
|
26
|
+
now?: () => Date;
|
|
27
|
+
};
|
|
28
|
+
/** Timeout/retry/circuit-breaker tuning. See {@link SmartbillResilienceOptions}. */
|
|
29
|
+
resilience?: SmartbillResilienceOptions;
|
|
30
|
+
}
|
|
31
|
+
export interface SmartbillClientApi {
|
|
32
|
+
/** Create an invoice. Returns the live envelope: series + number + URL + status/message. */
|
|
33
|
+
createInvoice(body: SmartbillInvoiceBody): Promise<SmartbillInvoiceResponse>;
|
|
34
|
+
/** Create a proforma invoice. */
|
|
35
|
+
createProforma(body: SmartbillInvoiceBody): Promise<SmartbillInvoiceResponse>;
|
|
36
|
+
/** Create an invoice from an existing proforma estimate. */
|
|
37
|
+
convertEstimateToInvoice(companyVatCode: string, estimateSeriesName: string, estimateNumber: string, body: SmartbillInvoiceBody): Promise<SmartbillInvoiceResponse>;
|
|
38
|
+
/** Cancel an invoice by series + number. Returns the live envelope. */
|
|
39
|
+
cancelInvoice(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillEnvelope>;
|
|
40
|
+
/** Restore a previously cancelled invoice. */
|
|
41
|
+
restoreInvoice(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillEnvelope>;
|
|
42
|
+
/** Delete an invoice by series + number. */
|
|
43
|
+
deleteInvoice(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillEnvelope>;
|
|
44
|
+
/** Reverse an invoice — issues a credit-note style reversal invoice. */
|
|
45
|
+
reverseInvoice(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillInvoiceResponse>;
|
|
46
|
+
/** Download invoice PDF bytes. */
|
|
47
|
+
viewInvoicePdf(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillPdfResponse>;
|
|
48
|
+
/**
|
|
49
|
+
* Alias for {@link viewInvoicePdf}. Kept for backward compatibility with
|
|
50
|
+
* earlier client versions that only exposed an invoice PDF method.
|
|
51
|
+
*/
|
|
52
|
+
viewPdf(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillPdfResponse>;
|
|
53
|
+
/** Download proforma PDF bytes. */
|
|
54
|
+
viewEstimatePdf(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillPdfResponse>;
|
|
55
|
+
/** Get payment status for an invoice. */
|
|
56
|
+
getPaymentStatus(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillStatusResponse>;
|
|
57
|
+
/** List taxes configured on the SmartBill account. */
|
|
58
|
+
listTaxes(): Promise<SmartbillTaxesResponse>;
|
|
59
|
+
/** List document series configured on the SmartBill account. */
|
|
60
|
+
listSeries(): Promise<SmartbillSeriesResponse>;
|
|
61
|
+
/** List invoices created from a proforma (conversion lookup). */
|
|
62
|
+
listEstimateInvoices(companyVatCode: string, seriesName: string, number: string): Promise<SmartbillEstimateInvoicesResponse>;
|
|
63
|
+
}
|
|
64
|
+
export type { SmartbillApiErrorOptions } from "./client/errors.js";
|
|
65
|
+
export { SmartbillApiError, SmartbillRateLimitCircuitOpenError, SmartbillRateLimitError, } from "./client/errors.js";
|
|
66
|
+
export declare function createSmartbillClient(options: SmartbillClientOptions): SmartbillClientApi;
|
|
67
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|