@voyantjs/plugin-smartbill 0.77.12 → 0.77.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/sync.js ADDED
@@ -0,0 +1,299 @@
1
+ import { buildInvoiceIssuedEvent, financeService, } from "@voyantjs/finance";
2
+ import { isSmartbillPdfPersistMetadataUpdateError, persistSmartbillInvoiceArtifact, recordSmartbillInvoiceArtifactFailure, retrySmartbillInvoiceArtifact, } from "./artifacts.js";
3
+ import { createSmartbillSyncRuntime } from "./runtime.js";
4
+ import { parseSmartbillPluginOptions } from "./validation.js";
5
+ export async function syncSmartbillInvoice({ db, invoiceId, pluginOptions, client, issueRuntime, }) {
6
+ const invoice = await financeService.getInvoiceById(db, invoiceId);
7
+ if (!invoice)
8
+ return { status: "not_found", invoiceId };
9
+ const documentType = documentTypeForInvoice(invoice);
10
+ if (!documentType) {
11
+ return { status: "unsupported_document_type", invoiceId, invoiceType: invoice.invoiceType };
12
+ }
13
+ const validatedOptions = parseSmartbillPluginOptions(withDefaultArtifactDb(pluginOptions, db));
14
+ const runtime = createSmartbillSyncRuntime(validatedOptions, client ? { client } : undefined);
15
+ const issuedEvent = await buildInvoiceIssuedEvent(db, invoice, issueRuntime);
16
+ const event = { ...issuedEvent, id: issuedEvent.invoiceId };
17
+ return syncSmartbillInvoiceEvent({
18
+ event,
19
+ documentType,
20
+ runtime,
21
+ pluginOptions: validatedOptions,
22
+ operationLabel: documentType === "proforma" ? "createProforma" : "createInvoice",
23
+ });
24
+ }
25
+ export async function syncSmartbillInvoiceEvent({ event, documentType, runtime, pluginOptions, operationLabel = documentType === "proforma" ? "createProforma" : "createInvoice", }) {
26
+ try {
27
+ const body = await runtime.mapEvent(event);
28
+ const existingRef = await findExistingSmartbillRef(event, documentType, body, runtime);
29
+ if (existingRef) {
30
+ return handleExistingSmartbillRef(event, documentType, body, existingRef, runtime);
31
+ }
32
+ const result = documentType === "proforma"
33
+ ? await runtime.client.createProforma(body)
34
+ : await runtime.client.createInvoice(body);
35
+ runtime.logger.info?.(`[smartbill] ${documentType} created: ${result.series}-${result.number} for ${event.id}`, result);
36
+ const artifact = await persistArtifact(event, documentType, body, result, runtime);
37
+ await writeBackInvoiceNumberIfRequired(event, documentType, body, result, runtime);
38
+ await applyExternalAllocationIfRequired(event, documentType, body, result, runtime);
39
+ return { status: "created", invoiceId: event.id, documentType, result, artifact };
40
+ }
41
+ catch (err) {
42
+ runtime.logger.error(`[smartbill] ${operationLabel} failed for ${event.id}`, err);
43
+ await recordSyncError(event, documentType, err, runtime, pluginOptions);
44
+ throw err;
45
+ }
46
+ }
47
+ async function findExistingSmartbillRef(event, documentType, body, runtime) {
48
+ if (runtime.idempotency.skipExistingExternalRef === false)
49
+ return null;
50
+ const db = await resolveArtifactDb(event, documentType, body, undefined, runtime);
51
+ if (!db)
52
+ return null;
53
+ const refs = await financeService.listInvoiceExternalRefs(db, event.id);
54
+ return refs.find((ref) => isMatchingSmartbillRef(ref, documentType)) ?? null;
55
+ }
56
+ async function handleExistingSmartbillRef(event, documentType, body, externalRef, runtime) {
57
+ runtime.logger.info?.(`[smartbill] ${documentType} already has SmartBill ref for ${event.id}; skipping create`, externalRef);
58
+ await applyExternalAllocationFromRefIfRequired(event, documentType, body, externalRef, runtime);
59
+ await writeBackInvoiceNumberFromRefIfRequired(event, documentType, body, externalRef, runtime);
60
+ let artifact = null;
61
+ try {
62
+ artifact = await retrySmartbillInvoiceArtifact({
63
+ runtime: runtime.artifacts,
64
+ client: runtime.client,
65
+ externalRef,
66
+ documentType,
67
+ });
68
+ if (artifact.status === "persisted") {
69
+ runtime.logger.info?.(`[smartbill] ${documentType} PDF re-attached for ${event.id}`, artifact);
70
+ }
71
+ }
72
+ catch (err) {
73
+ const message = isSmartbillPdfPersistMetadataUpdateError(err)
74
+ ? `[smartbill] artifact re-attach metadata update failed for ${event.id}`
75
+ : `[smartbill] artifact re-attach failed for ${event.id}`;
76
+ runtime.logger.error(message, err);
77
+ }
78
+ return { status: "existing_ref", invoiceId: event.id, documentType, externalRef, artifact };
79
+ }
80
+ async function persistArtifact(event, documentType, body, result, runtime) {
81
+ try {
82
+ const persisted = await persistSmartbillInvoiceArtifact({
83
+ runtime: runtime.artifacts,
84
+ client: runtime.client,
85
+ event,
86
+ documentType,
87
+ body,
88
+ result,
89
+ });
90
+ if (persisted.status === "persisted") {
91
+ runtime.logger.info?.(`[smartbill] ${documentType} PDF persisted for ${event.id}`, persisted);
92
+ }
93
+ return persisted;
94
+ }
95
+ catch (err) {
96
+ if (isSmartbillPdfPersistMetadataUpdateError(err)) {
97
+ runtime.logger.error(`[smartbill] artifact persistence metadata update failed for ${event.id}`, err);
98
+ return null;
99
+ }
100
+ runtime.logger.error(`[smartbill] artifact persistence failed for ${event.id}`, err);
101
+ try {
102
+ await recordSmartbillInvoiceArtifactFailure({
103
+ runtime: runtime.artifacts,
104
+ event,
105
+ documentType,
106
+ body,
107
+ result,
108
+ error: err,
109
+ });
110
+ }
111
+ catch (recordError) {
112
+ runtime.logger.error(`[smartbill] artifact failure external-ref update failed for ${event.id}`, recordError);
113
+ }
114
+ return null;
115
+ }
116
+ }
117
+ async function applyExternalAllocationIfRequired(event, documentType, body, result, runtime) {
118
+ if (event.externalAllocationRequired !== true || !result.number)
119
+ return;
120
+ const db = await resolveArtifactDb(event, documentType, body, result, runtime);
121
+ if (!db) {
122
+ throw new Error("SmartBill external allocation requires artifact database access");
123
+ }
124
+ const allocation = await financeService.applyExternalInvoiceAllocation(db, event.id, {
125
+ invoiceNumber: formatExternalInvoiceNumber(result.series ?? body.seriesName, result.number),
126
+ });
127
+ if (allocation.status === "applied") {
128
+ runtime.logger.info?.(`[smartbill] external number applied for ${event.id}`, allocation.invoice);
129
+ }
130
+ }
131
+ async function applyExternalAllocationFromRefIfRequired(event, documentType, body, externalRef, runtime) {
132
+ if (event.externalAllocationRequired !== true)
133
+ return;
134
+ const metadata = coerceMetadata(externalRef.metadata);
135
+ const number = metadataString(metadata, "number") ??
136
+ externalRef.externalNumber ??
137
+ externalRef.externalId ??
138
+ null;
139
+ if (!number) {
140
+ throw new Error("SmartBill external allocation requires an existing external number");
141
+ }
142
+ await applyExternalAllocationIfRequired(event, documentType, body, {
143
+ number,
144
+ series: metadataString(metadata, "series") ??
145
+ metadataString(metadata, "seriesName") ??
146
+ body.seriesName,
147
+ url: externalRef.externalUrl ?? undefined,
148
+ }, runtime);
149
+ }
150
+ async function resolveWriteBackInvoiceNumber(event, body, result, writeBackInvoiceNumber) {
151
+ if (!writeBackInvoiceNumber)
152
+ return null;
153
+ if (typeof writeBackInvoiceNumber === "function") {
154
+ const invoiceNumber = await writeBackInvoiceNumber(event, result);
155
+ if (typeof invoiceNumber !== "string" || invoiceNumber.trim().length === 0) {
156
+ throw new Error("SmartBill invoice number write-back formatter returned an empty value");
157
+ }
158
+ return invoiceNumber;
159
+ }
160
+ if (!result.number)
161
+ return null;
162
+ return formatExternalInvoiceNumber(result.series ?? body.seriesName, result.number);
163
+ }
164
+ async function writeBackInvoiceNumberIfRequired(event, documentType, body, result, runtime) {
165
+ const invoiceNumber = await resolveWriteBackInvoiceNumber(event, body, result, runtime.writeBackInvoiceNumber);
166
+ if (!invoiceNumber)
167
+ return;
168
+ const db = await resolveArtifactDb(event, documentType, body, result, runtime);
169
+ if (!db) {
170
+ throw new Error("SmartBill invoice number write-back requires artifact database access");
171
+ }
172
+ const invoice = await financeService.updateInvoice(db, event.id, { invoiceNumber });
173
+ if (!invoice) {
174
+ throw new Error(`SmartBill invoice number write-back failed for missing invoice ${event.id}`);
175
+ }
176
+ runtime.logger.info?.(`[smartbill] invoice number write-back applied for ${event.id}`, invoice);
177
+ }
178
+ async function writeBackInvoiceNumberFromRefIfRequired(event, documentType, body, externalRef, runtime) {
179
+ if (!runtime.writeBackInvoiceNumber)
180
+ return;
181
+ const metadata = coerceMetadata(externalRef.metadata);
182
+ const number = metadataString(metadata, "number") ??
183
+ externalRef.externalNumber ??
184
+ externalRef.externalId ??
185
+ null;
186
+ if (!number)
187
+ return;
188
+ await writeBackInvoiceNumberIfRequired(event, documentType, body, {
189
+ number,
190
+ series: metadataString(metadata, "series") ??
191
+ metadataString(metadata, "seriesName") ??
192
+ body.seriesName,
193
+ url: externalRef.externalUrl ?? undefined,
194
+ }, runtime);
195
+ }
196
+ async function recordSyncError(event, documentType, err, runtime, pluginOptions) {
197
+ try {
198
+ await runtime.onError?.(event, err);
199
+ }
200
+ catch (handlerError) {
201
+ runtime.logger.error(`[smartbill] onError handler failed for ${event.id}`, handlerError);
202
+ }
203
+ try {
204
+ const db = await resolveArtifactDb(event, documentType, undefined, undefined, runtime, pluginOptions);
205
+ if (!db)
206
+ return;
207
+ const refs = await financeService.listInvoiceExternalRefs(db, event.id);
208
+ if (refs.some((ref) => isMatchingSmartbillRef(ref, documentType)))
209
+ return;
210
+ await financeService.registerInvoiceExternalRef(db, event.id, {
211
+ provider: "smartbill",
212
+ externalId: null,
213
+ externalNumber: null,
214
+ externalUrl: null,
215
+ status: "error",
216
+ syncedAt: new Date().toISOString(),
217
+ syncError: errorMessage(err),
218
+ metadata: { documentType },
219
+ });
220
+ }
221
+ catch (recordError) {
222
+ runtime.logger.error(`[smartbill] error external-ref recording failed for ${event.id}`, recordError);
223
+ }
224
+ }
225
+ async function resolveArtifactDb(event, documentType, body, result, runtime, pluginOptions) {
226
+ if (!runtime.artifacts.db)
227
+ return null;
228
+ const context = {
229
+ event,
230
+ documentType,
231
+ body: body ?? {
232
+ companyVatCode: pluginOptions?.companyVatCode ?? "",
233
+ client: { name: "Client" },
234
+ seriesName: await resolveSeriesName(pluginOptions?.seriesName, event),
235
+ currency: "RON",
236
+ products: [],
237
+ },
238
+ result: result ?? {},
239
+ };
240
+ return (await resolveMaybe(runtime.artifacts.db, context)) ?? null;
241
+ }
242
+ function withDefaultArtifactDb(pluginOptions, db) {
243
+ if (pluginOptions.artifacts?.db || pluginOptions.db)
244
+ return pluginOptions;
245
+ return {
246
+ ...pluginOptions,
247
+ artifacts: {
248
+ ...pluginOptions.artifacts,
249
+ db,
250
+ },
251
+ };
252
+ }
253
+ function documentTypeForInvoice(invoice) {
254
+ if (invoice.invoiceType === "proforma")
255
+ return "proforma";
256
+ if (invoice.invoiceType === "invoice")
257
+ return "invoice";
258
+ return null;
259
+ }
260
+ async function resolveSeriesName(seriesName, event) {
261
+ if (typeof seriesName === "function")
262
+ return seriesName(event);
263
+ return seriesName ?? "unknown";
264
+ }
265
+ async function resolveMaybe(value, context) {
266
+ return typeof value === "function"
267
+ ? await value(context)
268
+ : value;
269
+ }
270
+ function errorMessage(error) {
271
+ return error instanceof Error ? error.message : String(error);
272
+ }
273
+ function coerceMetadata(value) {
274
+ return value && typeof value === "object" && !Array.isArray(value)
275
+ ? value
276
+ : null;
277
+ }
278
+ function metadataString(metadata, key) {
279
+ const value = metadata?.[key];
280
+ return typeof value === "string" && value.length > 0 ? value : null;
281
+ }
282
+ function formatExternalInvoiceNumber(seriesName, number) {
283
+ const trimmedNumber = number.trim();
284
+ const trimmedSeries = seriesName?.trim();
285
+ if (!trimmedSeries || trimmedNumber.startsWith(`${trimmedSeries}-`))
286
+ return trimmedNumber;
287
+ return `${trimmedSeries}-${trimmedNumber}`;
288
+ }
289
+ function isUsableSmartbillRef(ref) {
290
+ return (ref.provider === "smartbill" &&
291
+ ref.status !== "error" &&
292
+ !ref.syncError &&
293
+ Boolean(ref.externalNumber || ref.externalId));
294
+ }
295
+ function isMatchingSmartbillRef(ref, documentType) {
296
+ if (!isUsableSmartbillRef(ref))
297
+ return false;
298
+ return metadataString(coerceMetadata(ref.metadata), "documentType") === documentType;
299
+ }
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import type { SmartbillArtifactPersistenceOptions, SmartbillDbResolver, SmartbillDocumentStorageResolver, SmartbillStorageKeyPrefixResolver } from "./artifacts.js";
3
- import type { SmartbillErrorHandler, SmartbillIdempotencyOptions, SmartbillInvoiceNumberWriteBackFormatter, SmartbillLogger, SmartbillMapFn, SmartbillSyncEventNames } from "./plugin.js";
3
+ import type { SmartbillErrorHandler, SmartbillIdempotencyOptions, SmartbillInvoiceNumberWriteBackFormatter, SmartbillLogger, SmartbillMapFn, SmartbillPluginOptions, SmartbillSyncEventNames } from "./plugin.js";
4
4
  import type { SmartbillFetch } from "./types.js";
5
5
  export declare const smartbillPluginOptionsSchema: z.ZodObject<{
6
6
  username: z.ZodString;
@@ -27,4 +27,5 @@ export declare const smartbillPluginOptionsSchema: z.ZodObject<{
27
27
  documentStorage: z.ZodOptional<z.ZodCustom<SmartbillDocumentStorageResolver | undefined, SmartbillDocumentStorageResolver | undefined>>;
28
28
  documentStorageKeyPrefix: z.ZodOptional<z.ZodCustom<SmartbillStorageKeyPrefixResolver | undefined, SmartbillStorageKeyPrefixResolver | undefined>>;
29
29
  }, z.core.$strip>;
30
+ export declare function parseSmartbillPluginOptions(options: SmartbillPluginOptions): SmartbillPluginOptions;
30
31
  //# sourceMappingURL=validation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,EACV,mCAAmC,EACnC,mBAAmB,EACnB,gCAAgC,EAChC,iCAAiC,EAClC,MAAM,gBAAgB,CAAA;AAEvB,OAAO,KAAK,EACV,qBAAqB,EACrB,2BAA2B,EAC3B,wCAAwC,EACxC,eAAe,EACf,cAAc,EAEd,uBAAuB,EACxB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAgHhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;iBAwBK,CAAA"}
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,CAAC,EAAE,MAAM,KAAK,CAAA;AACjC,OAAO,KAAK,EACV,mCAAmC,EACnC,mBAAmB,EACnB,gCAAgC,EAChC,iCAAiC,EAClC,MAAM,gBAAgB,CAAA;AAEvB,OAAO,KAAK,EACV,qBAAqB,EACrB,2BAA2B,EAC3B,wCAAwC,EACxC,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,uBAAuB,EACxB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAgHhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;iBAwBK,CAAA;AAE9C,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,sBAAsB,GAC9B,sBAAsB,CAexB"}
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import { ZodError, z } from "zod";
2
2
  const optionalString = z.string().trim().min(1).optional();
3
3
  const optionalUrl = z.string().trim().url().optional();
4
4
  const requiredEventString = z.custom((value) => (typeof value === "string" && value.trim().length > 0) || typeof value === "function", "Expected a non-empty string or event resolver function");
@@ -83,3 +83,20 @@ export const smartbillPluginOptionsSchema = z.object({
83
83
  documentStorage: optionalDocumentStorage.optional(),
84
84
  documentStorageKeyPrefix: optionalDocumentStorageKeyPrefix.optional(),
85
85
  });
86
+ export function parseSmartbillPluginOptions(options) {
87
+ try {
88
+ return smartbillPluginOptionsSchema.parse(options);
89
+ }
90
+ catch (error) {
91
+ if (error instanceof ZodError) {
92
+ const detail = error.issues
93
+ .map((issue) => {
94
+ const path = issue.path.join(".") || "options";
95
+ return `${path}: ${issue.message}`;
96
+ })
97
+ .join("; ");
98
+ throw new Error(`Invalid SmartBill plugin options: ${detail}`);
99
+ }
100
+ throw error;
101
+ }
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/plugin-smartbill",
3
- "version": "0.77.12",
3
+ "version": "0.77.13",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -24,6 +24,16 @@
24
24
  "import": "./dist/plugin.js",
25
25
  "default": "./dist/plugin.js"
26
26
  },
27
+ "./hono": {
28
+ "types": "./dist/hono.d.ts",
29
+ "import": "./dist/hono.js",
30
+ "default": "./dist/hono.js"
31
+ },
32
+ "./sync": {
33
+ "types": "./dist/sync.d.ts",
34
+ "import": "./dist/sync.js",
35
+ "default": "./dist/sync.js"
36
+ },
27
37
  "./invoice-ui": {
28
38
  "types": "./dist/invoice-ui.d.ts",
29
39
  "import": "./dist/invoice-ui.js",
@@ -47,18 +57,20 @@
47
57
  },
48
58
  "dependencies": {
49
59
  "drizzle-orm": "^0.45.2",
60
+ "hono": "^4.12.10",
50
61
  "zod": "^4.3.6",
51
- "@voyantjs/core": "0.77.12",
52
- "@voyantjs/finance": "0.77.12",
53
- "@voyantjs/storage": "0.77.12"
62
+ "@voyantjs/core": "0.77.13",
63
+ "@voyantjs/finance": "0.77.13",
64
+ "@voyantjs/hono": "0.77.13",
65
+ "@voyantjs/storage": "0.77.13"
54
66
  },
55
67
  "peerDependencies": {
56
68
  "@tanstack/react-query": "^5.0.0",
57
69
  "lucide-react": "^0.475.0",
58
70
  "react": "^19.0.0",
59
71
  "react-dom": "^19.0.0",
60
- "@voyantjs/finance-react": "0.77.12",
61
- "@voyantjs/ui": "0.77.12"
72
+ "@voyantjs/finance-react": "0.77.13",
73
+ "@voyantjs/ui": "0.77.13"
62
74
  },
63
75
  "peerDependenciesMeta": {
64
76
  "@tanstack/react-query": {
@@ -89,8 +101,8 @@
89
101
  "react-dom": "^19.2.4",
90
102
  "typescript": "^6.0.2",
91
103
  "vitest": "^4.1.2",
92
- "@voyantjs/finance-react": "0.77.12",
93
- "@voyantjs/ui": "0.77.12",
104
+ "@voyantjs/finance-react": "0.77.13",
105
+ "@voyantjs/ui": "0.77.13",
94
106
  "@voyantjs/voyant-typescript-config": "0.1.0"
95
107
  },
96
108
  "files": [