@voyantjs/plugin-smartbill 0.38.1 → 0.40.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 CHANGED
@@ -27,8 +27,11 @@ const smartbillSync = smartbillPlugin({
27
27
  username: env.SMARTBILL_USERNAME,
28
28
  apiToken: env.SMARTBILL_API_TOKEN,
29
29
  companyVatCode: "RO12345678",
30
- seriesName: "A",
31
- // optional: language, art311SpecialRegime, events, mapEvent, logger
30
+ seriesName: (event) => (event.channel === "online" ? "WEB" : "A"),
31
+ mentions: async (event) => `Booking ${event.bookingCode ?? event.id}`,
32
+ observations: "Generated by Voyant",
33
+ art311SpecialRegimeText: "Custom Art. 311 disclosure",
34
+ // optional: language, art311SpecialRegime, events, mapEvent, logger, onError
32
35
  artifacts: {
33
36
  db: appDb,
34
37
  documentStorage,
@@ -58,6 +61,24 @@ upload path defaults to `invoices/<invoiceId>/smartbill/...`; pass
58
61
  `smartbill_pdf` attachments are reused so repeat event delivery does not upload
59
62
  the same PDF again.
60
63
 
64
+ `seriesName`, `mentions`, and `observations` may be static strings or
65
+ event-specific callbacks. The packaged plugin awaits these callbacks in its
66
+ default mapper, so each invoice event can choose a SmartBill series or
67
+ compliance text based on document type, sales channel, or booking metadata.
68
+
69
+ When `artifacts.db` is configured, duplicate `invoice.issued` and
70
+ `invoice.proforma.issued` deliveries are idempotent by default: the plugin
71
+ checks for an existing non-error SmartBill external reference before creating a
72
+ new SmartBill document. Disable this with
73
+ `idempotency: { skipExistingExternalRef: false }` only when the caller owns
74
+ deduplication elsewhere. Create failures are recorded as SmartBill external refs
75
+ with `status: "error"` and `syncError`; `onError(event, error)` can be supplied
76
+ for application-specific reporting.
77
+
78
+ Use `retrySmartbillInvoiceArtifact({ runtime, client, externalRef, documentType
79
+ })` to re-download and re-attach a SmartBill PDF from an existing external ref
80
+ without issuing a new document.
81
+
61
82
  ## Exports
62
83
 
63
84
  | Entry | Description |
@@ -1,3 +1,4 @@
1
+ import { financeService, type InvoiceExternalRef } from "@voyantjs/finance";
1
2
  import type { StorageProvider } from "@voyantjs/storage";
2
3
  import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
4
  import type { SmartbillClientApi } from "./client.js";
@@ -30,72 +31,30 @@ export interface PersistSmartbillInvoiceArtifactInput {
30
31
  body: SmartbillInvoiceBody;
31
32
  result: SmartbillInvoiceResponse;
32
33
  }
33
- export declare function persistSmartbillInvoiceArtifact({ runtime, client, event, documentType, body, result, }: PersistSmartbillInvoiceArtifactInput): Promise<{
34
- status: "skipped";
35
- reason: "missing_db";
36
- attachment?: undefined;
37
- rendition?: undefined;
38
- } | {
34
+ export type SmartbillExternalRef = Pick<InvoiceExternalRef, "id" | "invoiceId" | "externalId" | "externalNumber" | "externalUrl" | "metadata">;
35
+ export interface RetrySmartbillInvoiceArtifactInput {
36
+ runtime: SmartbillArtifactPersistenceRuntime;
37
+ client: SmartbillClientApi;
38
+ externalRef: SmartbillExternalRef;
39
+ documentType: SmartbillDocumentType;
40
+ }
41
+ type InvoiceAttachmentRecord = Awaited<ReturnType<typeof financeService.createInvoiceAttachment>>;
42
+ type InvoiceRenditionRecord = Awaited<ReturnType<typeof financeService.createInvoiceRendition>>;
43
+ export type SmartbillArtifactPersistenceResult = {
39
44
  status: "skipped";
40
- reason: "missing_invoice";
41
- attachment?: undefined;
42
- rendition?: undefined;
43
- } | {
44
- status: "registered_ref";
45
- reason?: undefined;
46
- attachment?: undefined;
47
- rendition?: undefined;
45
+ reason: "missing_db" | "missing_invoice" | "missing_document_storage" | "missing_smartbill_reference";
48
46
  } | {
49
47
  status: "registered_ref";
50
- reason: "missing_number";
51
- attachment?: undefined;
52
- rendition?: undefined;
48
+ reason?: "missing_number";
53
49
  } | {
54
50
  status: "already_exists";
55
- attachment: {
56
- id: string;
57
- invoiceId: string;
58
- kind: string;
59
- name: string;
60
- mimeType: string | null;
61
- fileSize: number | null;
62
- storageKey: string | null;
63
- checksum: string | null;
64
- metadata: unknown;
65
- createdAt: Date;
66
- };
67
- reason?: undefined;
68
- rendition?: undefined;
51
+ attachment: InvoiceAttachmentRecord;
69
52
  } | {
70
53
  status: "persisted";
71
- rendition: {
72
- metadata: unknown;
73
- id: string;
74
- createdAt: Date;
75
- updatedAt: Date;
76
- status: "pending" | "failed" | "ready" | "stale";
77
- format: "json" | "pdf" | "html" | "xml";
78
- errorMessage: string | null;
79
- templateId: string | null;
80
- language: string | null;
81
- invoiceId: string;
82
- storageKey: string | null;
83
- fileSize: number | null;
84
- checksum: string | null;
85
- generatedAt: Date | null;
86
- } | null;
87
- attachment: {
88
- metadata: unknown;
89
- name: string;
90
- id: string;
91
- createdAt: Date;
92
- kind: string;
93
- invoiceId: string;
94
- storageKey: string | null;
95
- fileSize: number | null;
96
- checksum: string | null;
97
- mimeType: string | null;
98
- } | null;
99
- reason?: undefined;
100
- }>;
54
+ rendition: InvoiceRenditionRecord;
55
+ attachment: InvoiceAttachmentRecord;
56
+ };
57
+ export declare function persistSmartbillInvoiceArtifact({ runtime, client, event, documentType, body, result, }: PersistSmartbillInvoiceArtifactInput): Promise<SmartbillArtifactPersistenceResult>;
58
+ export declare function retrySmartbillInvoiceArtifact({ runtime, client, externalRef, documentType, }: RetrySmartbillInvoiceArtifactInput): Promise<SmartbillArtifactPersistenceResult>;
59
+ export {};
101
60
  //# sourceMappingURL=artifacts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpG,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,UAAU,CAAA;AAE1D,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,kBAAkB,CAAA;IACzB,YAAY,EAAE,qBAAqB,CAAA;IACnC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAED,MAAM,MAAM,mBAAmB,GAC3B,kBAAkB,GAClB,CAAC,CACC,OAAO,EAAE,+BAA+B,KACrC,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC,CAAA;AAExE,MAAM,MAAM,gCAAgC,GACxC,eAAe,GACf,IAAI,GACJ,CAAC,CACC,OAAO,EAAE,+BAA+B,KACrC,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAA;AAElE,MAAM,MAAM,iCAAiC,GACzC,MAAM,GACN,CAAC,CAAC,OAAO,EAAE,+BAA+B,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;AAE5E,MAAM,WAAW,mCAAmC;IAClD,EAAE,CAAC,EAAE,mBAAmB,CAAA;IACxB,eAAe,CAAC,EAAE,gCAAgC,CAAA;IAClD,wBAAwB,CAAC,EAAE,iCAAiC,CAAA;CAC7D;AAED,MAAM,WAAW,mCAAmC;IAClD,EAAE,CAAC,EAAE,mBAAmB,CAAA;IACxB,eAAe,CAAC,EAAE,gCAAgC,CAAA;IAClD,wBAAwB,CAAC,EAAE,iCAAiC,CAAA;CAC7D;AAED,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE,mCAAmC,CAAA;IAC5C,MAAM,EAAE,kBAAkB,CAAA;IAC1B,KAAK,EAAE,kBAAkB,CAAA;IACzB,YAAY,EAAE,qBAAqB,CAAA;IACnC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAwCD,wBAAsB,+BAA+B,CAAC,EACpD,OAAO,EACP,MAAM,EACN,KAAK,EACL,YAAY,EACZ,IAAI,EACJ,MAAM,GACP,EAAE,oCAAoC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiGtC"}
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAEjE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AACrD,OAAO,KAAK,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpG,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,UAAU,CAAA;AAE1D,MAAM,WAAW,+BAA+B;IAC9C,KAAK,EAAE,kBAAkB,CAAA;IACzB,YAAY,EAAE,qBAAqB,CAAA;IACnC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAED,MAAM,MAAM,mBAAmB,GAC3B,kBAAkB,GAClB,CAAC,CACC,OAAO,EAAE,+BAA+B,KACrC,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC,CAAA;AAExE,MAAM,MAAM,gCAAgC,GACxC,eAAe,GACf,IAAI,GACJ,CAAC,CACC,OAAO,EAAE,+BAA+B,KACrC,eAAe,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC,CAAA;AAElE,MAAM,MAAM,iCAAiC,GACzC,MAAM,GACN,CAAC,CAAC,OAAO,EAAE,+BAA+B,KAAK,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;AAE5E,MAAM,WAAW,mCAAmC;IAClD,EAAE,CAAC,EAAE,mBAAmB,CAAA;IACxB,eAAe,CAAC,EAAE,gCAAgC,CAAA;IAClD,wBAAwB,CAAC,EAAE,iCAAiC,CAAA;CAC7D;AAED,MAAM,WAAW,mCAAmC;IAClD,EAAE,CAAC,EAAE,mBAAmB,CAAA;IACxB,eAAe,CAAC,EAAE,gCAAgC,CAAA;IAClD,wBAAwB,CAAC,EAAE,iCAAiC,CAAA;CAC7D;AAED,MAAM,WAAW,oCAAoC;IACnD,OAAO,EAAE,mCAAmC,CAAA;IAC5C,MAAM,EAAE,kBAAkB,CAAA;IAC1B,KAAK,EAAE,kBAAkB,CAAA;IACzB,YAAY,EAAE,qBAAqB,CAAA;IACnC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,MAAM,EAAE,wBAAwB,CAAA;CACjC;AAED,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,kBAAkB,EAClB,IAAI,GAAG,WAAW,GAAG,YAAY,GAAG,gBAAgB,GAAG,aAAa,GAAG,UAAU,CAClF,CAAA;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,mCAAmC,CAAA;IAC5C,MAAM,EAAE,kBAAkB,CAAA;IAC1B,WAAW,EAAE,oBAAoB,CAAA;IACjC,YAAY,EAAE,qBAAqB,CAAA;CACpC;AAED,KAAK,uBAAuB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,uBAAuB,CAAC,CAAC,CAAA;AACjG,KAAK,sBAAsB,GAAG,OAAO,CAAC,UAAU,CAAC,OAAO,cAAc,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAE/F,MAAM,MAAM,kCAAkC,GAC1C;IACE,MAAM,EAAE,SAAS,CAAA;IACjB,MAAM,EACF,YAAY,GACZ,iBAAiB,GACjB,0BAA0B,GAC1B,6BAA6B,CAAA;CAClC,GACD;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,MAAM,CAAC,EAAE,gBAAgB,CAAA;CAAE,GACvD;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,UAAU,EAAE,uBAAuB,CAAA;CAAE,GACjE;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,sBAAsB,CAAC;IAAC,UAAU,EAAE,uBAAuB,CAAA;CAAE,CAAA;AAmDnG,wBAAsB,+BAA+B,CAAC,EACpD,OAAO,EACP,MAAM,EACN,KAAK,EACL,YAAY,EACZ,IAAI,EACJ,MAAM,GACP,EAAE,oCAAoC,GAAG,OAAO,CAAC,kCAAkC,CAAC,CA6CpF;AAED,wBAAsB,6BAA6B,CAAC,EAClD,OAAO,EACP,MAAM,EACN,WAAW,EACX,YAAY,GACb,EAAE,kCAAkC,GAAG,OAAO,CAAC,kCAAkC,CAAC,CAqDlF"}
package/dist/artifacts.js CHANGED
@@ -23,6 +23,15 @@ async function sha256(bytes) {
23
23
  function smartbillAttachmentName(documentType, seriesName, number) {
24
24
  return `SmartBill ${documentType} ${seriesName}-${number}.pdf`;
25
25
  }
26
+ function coerceMetadata(value) {
27
+ return value && typeof value === "object" && !Array.isArray(value)
28
+ ? value
29
+ : null;
30
+ }
31
+ function metadataString(metadata, key) {
32
+ const value = metadata?.[key];
33
+ return typeof value === "string" && value.length > 0 ? value : null;
34
+ }
26
35
  export async function persistSmartbillInvoiceArtifact({ runtime, client, event, documentType, body, result, }) {
27
36
  if (!runtime.db)
28
37
  return { status: "skipped", reason: "missing_db" };
@@ -55,17 +64,82 @@ export async function persistSmartbillInvoiceArtifact({ runtime, client, event,
55
64
  return { status: "registered_ref" };
56
65
  if (!number)
57
66
  return { status: "registered_ref", reason: "missing_number" };
58
- const existingAttachments = await financeService.listInvoiceAttachments(db, event.id);
67
+ const keyPrefix = await resolveMaybe(runtime.documentStorageKeyPrefix, context);
68
+ return persistSmartbillPdfArtifact({
69
+ db,
70
+ storage,
71
+ client,
72
+ invoiceId: event.id,
73
+ documentType,
74
+ companyVatCode: body.companyVatCode,
75
+ seriesName,
76
+ number,
77
+ language: body.language ?? null,
78
+ keyPrefix,
79
+ });
80
+ }
81
+ export async function retrySmartbillInvoiceArtifact({ runtime, client, externalRef, documentType, }) {
82
+ if (!runtime.db)
83
+ return { status: "skipped", reason: "missing_db" };
84
+ const metadata = coerceMetadata(externalRef.metadata);
85
+ const companyVatCode = metadataString(metadata, "companyVatCode") ?? metadataString(metadata, "vatCode");
86
+ const seriesName = metadataString(metadata, "series") ?? metadataString(metadata, "seriesName");
87
+ const number = metadataString(metadata, "number") ??
88
+ externalRef.externalNumber ??
89
+ externalRef.externalId ??
90
+ null;
91
+ if (!companyVatCode || !seriesName || !number) {
92
+ return { status: "skipped", reason: "missing_smartbill_reference" };
93
+ }
94
+ const context = {
95
+ event: { id: externalRef.invoiceId },
96
+ documentType,
97
+ body: {
98
+ companyVatCode,
99
+ client: { name: "Client" },
100
+ seriesName,
101
+ currency: "RON",
102
+ language: metadataString(metadata, "language") ?? undefined,
103
+ products: [],
104
+ },
105
+ result: {
106
+ number,
107
+ series: seriesName,
108
+ url: externalRef.externalUrl ?? undefined,
109
+ },
110
+ };
111
+ const db = await resolveMaybe(runtime.db, context);
112
+ if (!db)
113
+ return { status: "skipped", reason: "missing_db" };
114
+ const storage = await resolveMaybe(runtime.documentStorage, context);
115
+ if (!storage)
116
+ return { status: "skipped", reason: "missing_document_storage" };
117
+ const keyPrefix = await resolveMaybe(runtime.documentStorageKeyPrefix, context);
118
+ return persistSmartbillPdfArtifact({
119
+ db,
120
+ storage,
121
+ client,
122
+ invoiceId: externalRef.invoiceId,
123
+ documentType,
124
+ companyVatCode,
125
+ seriesName,
126
+ number,
127
+ language: metadataString(metadata, "language"),
128
+ keyPrefix,
129
+ });
130
+ }
131
+ async function persistSmartbillPdfArtifact({ db, storage, client, invoiceId, documentType, companyVatCode, seriesName, number, language, keyPrefix, }) {
132
+ const existingAttachments = await financeService.listInvoiceAttachments(db, invoiceId);
59
133
  const existingSmartbillAttachment = existingAttachments.find((attachment) => attachment.kind === SMARTBILL_ATTACHMENT_KIND);
60
134
  if (existingSmartbillAttachment) {
61
135
  return { status: "already_exists", attachment: existingSmartbillAttachment };
62
136
  }
63
137
  const pdf = documentType === "proforma"
64
- ? await client.viewEstimatePdf(body.companyVatCode, seriesName, number)
65
- : await client.viewInvoicePdf(body.companyVatCode, seriesName, number);
66
- const defaultPrefix = `invoices/${event.id}/smartbill`;
67
- const keyPrefix = (await resolveMaybe(runtime.documentStorageKeyPrefix, context)) ?? defaultPrefix;
68
- const key = `${keyPrefix.replace(/\/$/, "")}/${documentType}-${sanitizeKeyPart(seriesName)}-${sanitizeKeyPart(number)}.pdf`;
138
+ ? await client.viewEstimatePdf(companyVatCode, seriesName, number)
139
+ : await client.viewInvoicePdf(companyVatCode, seriesName, number);
140
+ const defaultPrefix = `invoices/${invoiceId}/smartbill`;
141
+ const resolvedKeyPrefix = keyPrefix ?? defaultPrefix;
142
+ const key = `${resolvedKeyPrefix.replace(/\/$/, "")}/${documentType}-${sanitizeKeyPart(seriesName)}-${sanitizeKeyPart(number)}.pdf`;
69
143
  const contentType = pdf.contentType || "application/pdf";
70
144
  const checksum = await sha256(pdf.bytes);
71
145
  const uploaded = await storage.upload(pdf.bytes, {
@@ -74,7 +148,7 @@ export async function persistSmartbillInvoiceArtifact({ runtime, client, event,
74
148
  metadata: {
75
149
  provider: "smartbill",
76
150
  documentType,
77
- invoiceId: event.id,
151
+ invoiceId,
78
152
  seriesName,
79
153
  number,
80
154
  },
@@ -82,23 +156,23 @@ export async function persistSmartbillInvoiceArtifact({ runtime, client, event,
82
156
  const commonMetadata = {
83
157
  provider: "smartbill",
84
158
  documentType,
85
- companyVatCode: body.companyVatCode,
159
+ companyVatCode,
86
160
  seriesName,
87
161
  number,
88
162
  storageProvider: storage.name,
89
163
  ...(uploaded.url ? { url: uploaded.url } : {}),
90
164
  };
91
- const rendition = await financeService.createInvoiceRendition(db, event.id, {
165
+ const rendition = await financeService.createInvoiceRendition(db, invoiceId, {
92
166
  format: "pdf",
93
167
  status: "ready",
94
168
  storageKey: uploaded.key,
95
169
  fileSize: pdf.bytes.byteLength,
96
170
  checksum,
97
- language: body.language ?? null,
171
+ language: language ?? null,
98
172
  generatedAt: new Date().toISOString(),
99
173
  metadata: commonMetadata,
100
174
  });
101
- const attachment = await financeService.createInvoiceAttachment(db, event.id, {
175
+ const attachment = await financeService.createInvoiceAttachment(db, invoiceId, {
102
176
  kind: SMARTBILL_ATTACHMENT_KIND,
103
177
  name: smartbillAttachmentName(documentType, seriesName, number),
104
178
  mimeType: contentType,
package/dist/index.d.ts CHANGED
@@ -1,15 +1,16 @@
1
- export type { SmartbillArtifactPersistenceOptions, SmartbillArtifactStorageContext, SmartbillDbResolver, SmartbillDocumentStorageResolver, SmartbillDocumentType, SmartbillStorageKeyPrefixResolver, } from "./artifacts.js";
1
+ export type { RetrySmartbillInvoiceArtifactInput, SmartbillArtifactPersistenceOptions, SmartbillArtifactPersistenceResult, SmartbillArtifactPersistenceRuntime, SmartbillArtifactStorageContext, SmartbillDbResolver, SmartbillDocumentStorageResolver, SmartbillDocumentType, SmartbillExternalRef, SmartbillStorageKeyPrefixResolver, } from "./artifacts.js";
2
+ export { retrySmartbillInvoiceArtifact } from "./artifacts.js";
2
3
  export type { SmartbillClientApi, SmartbillClientOptions } from "./client.js";
3
4
  export { createSmartbillClient } from "./client.js";
4
- export type { SmartbillMappingOptions } from "./mapping.js";
5
- export { mapClient, mapLineItems, mapVoyantInvoiceToSmartbill } from "./mapping.js";
5
+ export type { SmartbillEventValue, SmartbillMappingOptions, SmartbillMaybePromise, } from "./mapping.js";
6
+ export { mapClient, mapLineItems, mapVoyantInvoiceToSmartbill, mapVoyantInvoiceToSmartbillAsync, } from "./mapping.js";
6
7
  export type { SmartbillMockDocument, SmartbillMockDocumentKind, SmartbillMockDocumentStatus, SmartbillMockListenOptions, SmartbillMockRequest, SmartbillMockResponse, SmartbillMockSeries, SmartbillMockServer, SmartbillMockServerHandle, SmartbillMockServerOptions, SmartbillMockTax, } from "./mock.js";
7
8
  export { createSmartbillMockServer } from "./mock.js";
8
- export type { SmartbillLogger, SmartbillMapFn, SmartbillPluginOptions, SmartbillSyncEventNames, } from "./plugin.js";
9
+ export type { SmartbillErrorHandler, SmartbillIdempotencyOptions, SmartbillLogger, SmartbillMapFn, SmartbillPluginOptions, SmartbillSyncEventNames, } from "./plugin.js";
9
10
  export { smartbillPlugin } from "./plugin.js";
10
11
  export type { ResolvedSmartbillSyncEventNames, SmartbillSyncRuntime } from "./runtime.js";
11
12
  export { createSmartbillSyncRuntime } from "./runtime.js";
12
- export type { SmartbillInvoiceSettlementPoller, SmartbillInvoiceSettlementPollerOptions, SmartbillSettlementExternalRef, SmartbillSettlementInvoice, SmartbillSettlementPollerContext, SmartbillSettlementPollerResult, } from "./settlement.js";
13
+ export type { SmartbillInvoiceSettlementPoller, SmartbillInvoiceSettlementPollerOptions, SmartbillSettlementExternalRef, SmartbillSettlementInvoice, SmartbillSettlementPollerContext, SmartbillSettlementPollerResult, SmartbillSettlementSeriesContext, } from "./settlement.js";
13
14
  export { createSmartbillInvoiceSettlementPoller } from "./settlement.js";
14
15
  export type { SmartbillClient, SmartbillEnvelope, SmartbillEstimateInvoicesResponse, SmartbillFetch, SmartbillInvoiceBody, SmartbillInvoiceResponse, SmartbillPaymentEntry, SmartbillPdfResponse, SmartbillProduct, SmartbillSeriesResponse, SmartbillStatusResponse, SmartbillTaxesResponse, VoyantInvoiceEvent, } from "./types.js";
15
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mCAAmC,EACnC,+BAA+B,EAC/B,mBAAmB,EACnB,gCAAgC,EAChC,qBAAqB,EACrB,iCAAiC,GAClC,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnD,YAAY,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAC3D,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA;AACnF,YAAY,EACV,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,0BAA0B,EAC1B,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,gBAAgB,GACjB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AACrD,YAAY,EACV,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,YAAY,EAAE,+BAA+B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACzF,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EACV,gCAAgC,EAChC,uCAAuC,EACvC,8BAA8B,EAC9B,0BAA0B,EAC1B,gCAAgC,EAChC,+BAA+B,GAChC,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,sCAAsC,EAAE,MAAM,iBAAiB,CAAA;AACxE,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,iCAAiC,EACjC,cAAc,EACd,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,kCAAkC,EAClC,mCAAmC,EACnC,kCAAkC,EAClC,mCAAmC,EACnC,+BAA+B,EAC/B,mBAAmB,EACnB,gCAAgC,EAChC,qBAAqB,EACrB,oBAAoB,EACpB,iCAAiC,GAClC,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,6BAA6B,EAAE,MAAM,gBAAgB,CAAA;AAC9D,YAAY,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACnD,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,SAAS,EACT,YAAY,EACZ,2BAA2B,EAC3B,gCAAgC,GACjC,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,0BAA0B,EAC1B,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EACzB,0BAA0B,EAC1B,gBAAgB,GACjB,MAAM,WAAW,CAAA;AAClB,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAA;AACrD,YAAY,EACV,qBAAqB,EACrB,2BAA2B,EAC3B,eAAe,EACf,cAAc,EACd,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,YAAY,EAAE,+BAA+B,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AACzF,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EACV,gCAAgC,EAChC,uCAAuC,EACvC,8BAA8B,EAC9B,0BAA0B,EAC1B,gCAAgC,EAChC,+BAA+B,EAC/B,gCAAgC,GACjC,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,sCAAsC,EAAE,MAAM,iBAAiB,CAAA;AACxE,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,iCAAiC,EACjC,cAAc,EACd,oBAAoB,EACpB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
+ export { retrySmartbillInvoiceArtifact } from "./artifacts.js";
1
2
  export { createSmartbillClient } from "./client.js";
2
- export { mapClient, mapLineItems, mapVoyantInvoiceToSmartbill } from "./mapping.js";
3
+ export { mapClient, mapLineItems, mapVoyantInvoiceToSmartbill, mapVoyantInvoiceToSmartbillAsync, } from "./mapping.js";
3
4
  export { createSmartbillMockServer } from "./mock.js";
4
5
  export { smartbillPlugin } from "./plugin.js";
5
6
  export { createSmartbillSyncRuntime } from "./runtime.js";
package/dist/mapping.d.ts CHANGED
@@ -1,18 +1,26 @@
1
1
  import type { SmartbillClient, SmartbillInvoiceBody, SmartbillProduct, VoyantInvoiceEvent } from "./types.js";
2
+ export type SmartbillMaybePromise<T> = T | Promise<T>;
3
+ export type SmartbillEventValue<T> = T | ((event: VoyantInvoiceEvent) => SmartbillMaybePromise<T>);
2
4
  /**
3
5
  * Options for the default invoice mapper.
4
6
  */
5
7
  export interface SmartbillMappingOptions {
6
8
  /** Romanian company VAT code (e.g. `"RO12345678"`). */
7
9
  companyVatCode: string;
8
- /** SmartBill invoice series name (e.g. `"A"`). */
9
- seriesName: string;
10
+ /** SmartBill invoice series name (e.g. `"A"`), or an event-specific resolver. */
11
+ seriesName: SmartbillEventValue<string>;
10
12
  /** Invoice language. Defaults to `"RO"`. */
11
13
  language?: string;
12
14
  /** Whether VAT is included in line item prices. Defaults to `true`. */
13
15
  isTaxIncluded?: boolean;
14
16
  /** Whether to use Art. 311 special regime (margin scheme for travel). */
15
17
  art311SpecialRegime?: boolean;
18
+ /** Text appended to mentions when Art. 311 special regime is enabled. */
19
+ art311SpecialRegimeText?: string;
20
+ /** SmartBill mentions override, or an event-specific resolver. Defaults to event.mentions. */
21
+ mentions?: SmartbillEventValue<string | null | undefined>;
22
+ /** SmartBill observations override, or an event-specific resolver. Defaults to event.observations. */
23
+ observations?: SmartbillEventValue<string | null | undefined>;
16
24
  }
17
25
  /**
18
26
  * Extract the SmartBill client block from a Voyant invoice event.
@@ -24,9 +32,15 @@ export declare function mapClient(event: VoyantInvoiceEvent): SmartbillClient;
24
32
  * Expects `event.lineItems` to be an array of objects with at minimum
25
33
  * `description`/`name`, `quantity`, `unitPrice`, `currency`.
26
34
  */
27
- export declare function mapLineItems(event: VoyantInvoiceEvent, options: SmartbillMappingOptions): SmartbillProduct[];
35
+ export declare function mapLineItems(event: VoyantInvoiceEvent, options: Pick<SmartbillMappingOptions, "isTaxIncluded">): SmartbillProduct[];
28
36
  /**
29
37
  * Map a full Voyant invoice event to a SmartBill invoice body.
30
38
  */
31
39
  export declare function mapVoyantInvoiceToSmartbill(event: VoyantInvoiceEvent, options: SmartbillMappingOptions): SmartbillInvoiceBody;
40
+ /**
41
+ * Async variant of the default mapper. Use this when mapping options include
42
+ * promise-returning callbacks such as `seriesName`, `mentions`, or
43
+ * `observations`.
44
+ */
45
+ export declare function mapVoyantInvoiceToSmartbillAsync(event: VoyantInvoiceEvent, options: SmartbillMappingOptions): Promise<SmartbillInvoiceBody>;
32
46
  //# sourceMappingURL=mapping.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAEnB;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAA;IACtB,kDAAkD;IAClD,UAAU,EAAE,MAAM,CAAA;IAClB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uEAAuE;IACvE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAC9B;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,eAAe,CAapE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,uBAAuB,GAC/B,gBAAgB,EAAE,CAiBpB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,uBAAuB,GAC/B,oBAAoB,CA2BtB"}
1
+ {"version":3,"file":"mapping.d.ts","sourceRoot":"","sources":["../src/mapping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAEnB,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAErD,MAAM,MAAM,mBAAmB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,kBAAkB,KAAK,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAA;AAKlG;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,uDAAuD;IACvD,cAAc,EAAE,MAAM,CAAA;IACtB,iFAAiF;IACjF,UAAU,EAAE,mBAAmB,CAAC,MAAM,CAAC,CAAA;IACvC,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uEAAuE;IACvE,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,yEAAyE;IACzE,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,yEAAyE;IACzE,uBAAuB,CAAC,EAAE,MAAM,CAAA;IAChC,8FAA8F;IAC9F,QAAQ,CAAC,EAAE,mBAAmB,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IACzD,sGAAsG;IACtG,YAAY,CAAC,EAAE,mBAAmB,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;CAC9D;AAeD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,kBAAkB,GAAG,eAAe,CAapE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,IAAI,CAAC,uBAAuB,EAAE,eAAe,CAAC,GACtD,gBAAgB,EAAE,CAiBpB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,uBAAuB,GAC/B,oBAAoB,CAEtB;AAED;;;;GAIG;AACH,wBAAsB,gCAAgC,CACpD,KAAK,EAAE,kBAAkB,EACzB,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAE/B"}
package/dist/mapping.js CHANGED
@@ -1,3 +1,4 @@
1
+ const DEFAULT_ART311_SPECIAL_REGIME_TEXT = "Regimul special de taxare - agentie de turism (Art. 311 Cod Fiscal)";
1
2
  /**
2
3
  * Extract the SmartBill client block from a Voyant invoice event.
3
4
  * Falls back to empty strings for missing fields.
@@ -43,6 +44,17 @@ export function mapLineItems(event, options) {
43
44
  * Map a full Voyant invoice event to a SmartBill invoice body.
44
45
  */
45
46
  export function mapVoyantInvoiceToSmartbill(event, options) {
47
+ return buildSmartbillInvoiceBody(event, resolveMappingOptionsSync(event, options));
48
+ }
49
+ /**
50
+ * Async variant of the default mapper. Use this when mapping options include
51
+ * promise-returning callbacks such as `seriesName`, `mentions`, or
52
+ * `observations`.
53
+ */
54
+ export async function mapVoyantInvoiceToSmartbillAsync(event, options) {
55
+ return buildSmartbillInvoiceBody(event, await resolveMappingOptions(event, options));
56
+ }
57
+ function buildSmartbillInvoiceBody(event, options) {
46
58
  const body = {
47
59
  companyVatCode: options.companyVatCode,
48
60
  client: mapClient(event),
@@ -59,14 +71,20 @@ export function mapVoyantInvoiceToSmartbill(event, options) {
59
71
  body.issueDate = event.issueDate;
60
72
  if (typeof event.deliveryDate === "string")
61
73
  body.deliveryDate = event.deliveryDate;
62
- if (typeof event.mentions === "string")
63
- body.mentions = event.mentions;
64
- if (typeof event.observations === "string")
65
- body.observations = event.observations;
74
+ const mentions = options.hasMentionsOverride
75
+ ? options.mentions
76
+ : asStringOrUndefined(event.mentions);
77
+ if (mentions)
78
+ body.mentions = mentions;
79
+ const observations = options.hasObservationsOverride
80
+ ? options.observations
81
+ : asStringOrUndefined(event.observations);
82
+ if (observations)
83
+ body.observations = observations;
66
84
  if (options.art311SpecialRegime) {
67
85
  body.mentions = [
68
86
  body.mentions,
69
- "Regimul special de taxare - agentie de turism (Art. 311 Cod Fiscal)",
87
+ options.art311SpecialRegimeText ?? DEFAULT_ART311_SPECIAL_REGIME_TEXT,
70
88
  ]
71
89
  .filter(Boolean)
72
90
  .join("\n");
@@ -74,6 +92,70 @@ export function mapVoyantInvoiceToSmartbill(event, options) {
74
92
  return body;
75
93
  }
76
94
  // --- helpers ---
95
+ function resolveMappingOptionsSync(event, options) {
96
+ return {
97
+ companyVatCode: options.companyVatCode,
98
+ seriesName: resolveRequiredEventValueSync(event, options.seriesName, "seriesName"),
99
+ language: options.language,
100
+ isTaxIncluded: options.isTaxIncluded,
101
+ art311SpecialRegime: options.art311SpecialRegime,
102
+ art311SpecialRegimeText: options.art311SpecialRegimeText,
103
+ mentions: normalizeOptionalText(resolveEventValueSync(event, options.mentions, "mentions")),
104
+ observations: normalizeOptionalText(resolveEventValueSync(event, options.observations, "observations")),
105
+ hasMentionsOverride: options.mentions !== undefined,
106
+ hasObservationsOverride: options.observations !== undefined,
107
+ };
108
+ }
109
+ async function resolveMappingOptions(event, options) {
110
+ return {
111
+ companyVatCode: options.companyVatCode,
112
+ seriesName: await resolveRequiredEventValue(event, options.seriesName, "seriesName"),
113
+ language: options.language,
114
+ isTaxIncluded: options.isTaxIncluded,
115
+ art311SpecialRegime: options.art311SpecialRegime,
116
+ art311SpecialRegimeText: options.art311SpecialRegimeText,
117
+ mentions: normalizeOptionalText(await resolveEventValue(event, options.mentions)),
118
+ observations: normalizeOptionalText(await resolveEventValue(event, options.observations)),
119
+ hasMentionsOverride: options.mentions !== undefined,
120
+ hasObservationsOverride: options.observations !== undefined,
121
+ };
122
+ }
123
+ function resolveEventValueSync(event, value, field) {
124
+ const resolved = typeof value === "function"
125
+ ? value(event)
126
+ : value;
127
+ if (isPromiseLike(resolved)) {
128
+ throw new Error(`SmartBill mapping option "${field}" returned a Promise; use mapVoyantInvoiceToSmartbillAsync`);
129
+ }
130
+ return resolved;
131
+ }
132
+ function resolveRequiredEventValueSync(event, value, field) {
133
+ const resolved = resolveEventValueSync(event, value, field);
134
+ if (resolved === undefined || resolved === null) {
135
+ throw new Error(`SmartBill mapping option "${field}" is required`);
136
+ }
137
+ return resolved;
138
+ }
139
+ async function resolveEventValue(event, value) {
140
+ return typeof value === "function"
141
+ ? await value(event)
142
+ : value;
143
+ }
144
+ async function resolveRequiredEventValue(event, value, field) {
145
+ const resolved = await resolveEventValue(event, value);
146
+ if (resolved === undefined || resolved === null) {
147
+ throw new Error(`SmartBill mapping option "${field}" is required`);
148
+ }
149
+ return resolved;
150
+ }
151
+ function isPromiseLike(value) {
152
+ return (value != null &&
153
+ typeof value === "object" &&
154
+ typeof value.then === "function");
155
+ }
156
+ function normalizeOptionalText(value) {
157
+ return typeof value === "string" && value.length > 0 ? value : undefined;
158
+ }
77
159
  function asString(value, fallback) {
78
160
  if (typeof value === "string" && value.length > 0)
79
161
  return value;
package/dist/plugin.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Plugin } from "@voyantjs/core";
2
2
  import { type SmartbillArtifactPersistenceOptions } from "./artifacts.js";
3
3
  import type { SmartbillClientOptions } from "./client.js";
4
- import type { mapVoyantInvoiceToSmartbill, SmartbillMappingOptions } from "./mapping.js";
5
- import type { VoyantInvoiceEvent } from "./types.js";
4
+ import type { SmartbillMappingOptions } from "./mapping.js";
5
+ import type { SmartbillInvoiceBody, VoyantInvoiceEvent } from "./types.js";
6
6
  export interface SmartbillSyncEventNames {
7
7
  issued?: string;
8
8
  proformaIssued?: string;
@@ -13,11 +13,22 @@ export interface SmartbillLogger {
13
13
  error: (message: string, meta?: unknown) => void;
14
14
  info?: (message: string, meta?: unknown) => void;
15
15
  }
16
- export type SmartbillMapFn = (event: VoyantInvoiceEvent) => ReturnType<typeof mapVoyantInvoiceToSmartbill>;
16
+ export type SmartbillMapFn = (event: VoyantInvoiceEvent) => SmartbillInvoiceBody | Promise<SmartbillInvoiceBody>;
17
+ export interface SmartbillIdempotencyOptions {
18
+ /**
19
+ * When artifact DB access is configured, skip SmartBill create calls for
20
+ * duplicate invoice events that already have a non-error SmartBill ref.
21
+ * Defaults to true.
22
+ */
23
+ skipExistingExternalRef?: boolean;
24
+ }
25
+ export type SmartbillErrorHandler = (event: VoyantInvoiceEvent, error: unknown) => void | Promise<void>;
17
26
  export interface SmartbillPluginOptions extends SmartbillClientOptions, SmartbillMappingOptions {
18
27
  events?: SmartbillSyncEventNames;
19
28
  mapEvent?: SmartbillMapFn;
20
29
  logger?: SmartbillLogger;
30
+ idempotency?: SmartbillIdempotencyOptions;
31
+ onError?: SmartbillErrorHandler;
21
32
  /**
22
33
  * Optional finance artifact persistence. When `db` is supplied, the plugin
23
34
  * registers the SmartBill external ref after creation. When
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,gBAAgB,CAAA;AAGxD,OAAO,EAEL,KAAK,mCAAmC,EAEzC,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,KAAK,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAExF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAGpD,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACjD;AAED,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE,kBAAkB,KACtB,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAEnD,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB,EAAE,uBAAuB;IAC7F,MAAM,CAAC,EAAE,uBAAuB,CAAA;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,mCAAmC,CAAA;IAC/C,sCAAsC;IACtC,EAAE,CAAC,EAAE,mCAAmC,CAAC,IAAI,CAAC,CAAA;IAC9C,mDAAmD;IACnD,eAAe,CAAC,EAAE,mCAAmC,CAAC,iBAAiB,CAAC,CAAA;IACxE,4DAA4D;IAC5D,wBAAwB,CAAC,EAAE,mCAAmC,CAAC,0BAA0B,CAAC,CAAA;CAC3F;AAYD,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAwJvE"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,gBAAgB,CAAA;AAIxD,OAAO,EAGL,KAAK,mCAAmC,EAIzC,MAAM,gBAAgB,CAAA;AACvB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AAE3D,OAAO,KAAK,EAAE,oBAAoB,EAA4B,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAGpG,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACjD;AAED,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE,kBAAkB,KACtB,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;AAEzD,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;CAClC;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,KAAK,EAAE,kBAAkB,EACzB,KAAK,EAAE,OAAO,KACX,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAEzB,MAAM,WAAW,sBAAuB,SAAQ,sBAAsB,EAAE,uBAAuB;IAC7F,MAAM,CAAC,EAAE,uBAAuB,CAAA;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,WAAW,CAAC,EAAE,2BAA2B,CAAA;IACzC,OAAO,CAAC,EAAE,qBAAqB,CAAA;IAC/B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,mCAAmC,CAAA;IAC/C,sCAAsC;IACtC,EAAE,CAAC,EAAE,mCAAmC,CAAC,IAAI,CAAC,CAAA;IAC9C,mDAAmD;IACnD,eAAe,CAAC,EAAE,mCAAmC,CAAC,iBAAiB,CAAC,CAAA;IACxE,4DAA4D;IAC5D,wBAAwB,CAAC,EAAE,mCAAmC,CAAC,0BAA0B,CAAC,CAAA;CAC3F;AAYD,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CA6QvE"}
package/dist/plugin.js CHANGED
@@ -1,5 +1,6 @@
1
+ import { financeService } from "@voyantjs/finance";
1
2
  import { ZodError } from "zod";
2
- import { persistSmartbillInvoiceArtifact, } from "./artifacts.js";
3
+ import { persistSmartbillInvoiceArtifact, retrySmartbillInvoiceArtifact, } from "./artifacts.js";
3
4
  import { createSmartbillSyncRuntime } from "./runtime.js";
4
5
  import { smartbillPluginOptionsSchema } from "./validation.js";
5
6
  function coerceEvent(data) {
@@ -15,7 +16,55 @@ function coerceEvent(data) {
15
16
  }
16
17
  export function smartbillPlugin(options) {
17
18
  const validatedOptions = parseSmartbillPluginOptions(options);
18
- const { client, logger, mapEvent, eventNames, artifacts } = createSmartbillSyncRuntime(validatedOptions);
19
+ const { client, logger, mapEvent, eventNames, artifacts, idempotency, onError } = createSmartbillSyncRuntime(validatedOptions);
20
+ async function resolveMaybe(value, context) {
21
+ return typeof value === "function"
22
+ ? await value(context)
23
+ : value;
24
+ }
25
+ async function resolveArtifactDb(event, documentType, body, result) {
26
+ if (!artifacts.db)
27
+ return null;
28
+ const context = {
29
+ event,
30
+ documentType,
31
+ body: body ?? {
32
+ companyVatCode: validatedOptions.companyVatCode,
33
+ client: { name: "Client" },
34
+ seriesName: "unknown",
35
+ currency: "RON",
36
+ products: [],
37
+ },
38
+ result: result ?? {},
39
+ };
40
+ return (await resolveMaybe(artifacts.db, context)) ?? null;
41
+ }
42
+ async function findExistingSmartbillRef(event, documentType, body) {
43
+ if (idempotency.skipExistingExternalRef === false)
44
+ return null;
45
+ const db = await resolveArtifactDb(event, documentType, body);
46
+ if (!db)
47
+ return null;
48
+ const refs = await financeService.listInvoiceExternalRefs(db, event.id);
49
+ return refs.find((ref) => isUsableSmartbillRef(ref)) ?? null;
50
+ }
51
+ async function handleExistingSmartbillRef(event, documentType, externalRef) {
52
+ logger.info?.(`[smartbill] ${documentType} already has SmartBill ref for ${event.id}; skipping create`, externalRef);
53
+ try {
54
+ const persisted = await retrySmartbillInvoiceArtifact({
55
+ runtime: artifacts,
56
+ client,
57
+ externalRef,
58
+ documentType,
59
+ });
60
+ if (persisted.status === "persisted") {
61
+ logger.info?.(`[smartbill] ${documentType} PDF re-attached for ${event.id}`, persisted);
62
+ }
63
+ }
64
+ catch (err) {
65
+ logger.error(`[smartbill] artifact re-attach failed for ${event.id}`, err);
66
+ }
67
+ }
19
68
  async function persistArtifact(event, documentType, body, result) {
20
69
  try {
21
70
  const persisted = await persistSmartbillInvoiceArtifact({
@@ -34,6 +83,44 @@ export function smartbillPlugin(options) {
34
83
  logger.error(`[smartbill] artifact persistence failed for ${event.id}`, err);
35
84
  }
36
85
  }
86
+ async function recordSyncError(event, documentType, err) {
87
+ try {
88
+ await onError?.(event, err);
89
+ }
90
+ catch (handlerError) {
91
+ logger.error(`[smartbill] onError handler failed for ${event.id}`, handlerError);
92
+ }
93
+ try {
94
+ const db = await resolveArtifactDb(event, documentType);
95
+ if (!db)
96
+ return;
97
+ const refs = await financeService.listInvoiceExternalRefs(db, event.id);
98
+ if (refs.some((ref) => isUsableSmartbillRef(ref)))
99
+ return;
100
+ await financeService.registerInvoiceExternalRef(db, event.id, {
101
+ provider: "smartbill",
102
+ externalId: null,
103
+ externalNumber: null,
104
+ externalUrl: null,
105
+ status: "error",
106
+ syncedAt: new Date().toISOString(),
107
+ syncError: errorMessage(err),
108
+ metadata: { documentType },
109
+ });
110
+ }
111
+ catch (recordError) {
112
+ logger.error(`[smartbill] error external-ref recording failed for ${event.id}`, recordError);
113
+ }
114
+ }
115
+ async function resolveConfiguredSeriesName(event) {
116
+ const value = validatedOptions.seriesName;
117
+ return typeof value === "function" ? await value(event) : value;
118
+ }
119
+ async function resolveExternalSeriesName(event) {
120
+ return typeof event.externalSeriesName === "string"
121
+ ? event.externalSeriesName
122
+ : await resolveConfiguredSeriesName(event);
123
+ }
37
124
  const subscribers = [
38
125
  {
39
126
  event: eventNames.issued,
@@ -42,13 +129,19 @@ export function smartbillPlugin(options) {
42
129
  if (!event)
43
130
  return;
44
131
  try {
45
- const body = mapEvent(event);
132
+ const body = await mapEvent(event);
133
+ const existingRef = await findExistingSmartbillRef(event, "invoice", body);
134
+ if (existingRef) {
135
+ await handleExistingSmartbillRef(event, "invoice", existingRef);
136
+ return;
137
+ }
46
138
  const result = await client.createInvoice(body);
47
139
  logger.info?.(`[smartbill] invoice created: ${result.series}-${result.number} for ${event.id}`, result);
48
140
  await persistArtifact(event, "invoice", body, result);
49
141
  }
50
142
  catch (err) {
51
143
  logger.error(`[smartbill] createInvoice on "${eventNames.issued}" failed for ${event.id}`, err);
144
+ await recordSyncError(event, "invoice", err);
52
145
  }
53
146
  },
54
147
  },
@@ -61,13 +154,19 @@ export function smartbillPlugin(options) {
61
154
  try {
62
155
  // Same shape as createInvoice — SmartBill's `/proforma`
63
156
  // endpoint accepts the same body as `/invoice`.
64
- const body = mapEvent(event);
157
+ const body = await mapEvent(event);
158
+ const existingRef = await findExistingSmartbillRef(event, "proforma", body);
159
+ if (existingRef) {
160
+ await handleExistingSmartbillRef(event, "proforma", existingRef);
161
+ return;
162
+ }
65
163
  const result = await client.createProforma(body);
66
164
  logger.info?.(`[smartbill] proforma created: ${result.series}-${result.number} for ${event.id}`, result);
67
165
  await persistArtifact(event, "proforma", body, result);
68
166
  }
69
167
  catch (err) {
70
168
  logger.error(`[smartbill] createProforma on "${eventNames.proformaIssued}" failed for ${event.id}`, err);
169
+ await recordSyncError(event, "proforma", err);
71
170
  }
72
171
  },
73
172
  },
@@ -78,9 +177,7 @@ export function smartbillPlugin(options) {
78
177
  if (!event)
79
178
  return;
80
179
  try {
81
- const seriesName = typeof event.externalSeriesName === "string"
82
- ? event.externalSeriesName
83
- : validatedOptions.seriesName;
180
+ const seriesName = await resolveExternalSeriesName(event);
84
181
  const number = typeof event.externalNumber === "string"
85
182
  ? event.externalNumber
86
183
  : typeof event.invoiceNumber === "string"
@@ -105,9 +202,7 @@ export function smartbillPlugin(options) {
105
202
  if (!event)
106
203
  return;
107
204
  try {
108
- const seriesName = typeof event.externalSeriesName === "string"
109
- ? event.externalSeriesName
110
- : validatedOptions.seriesName;
205
+ const seriesName = await resolveExternalSeriesName(event);
111
206
  const number = typeof event.externalNumber === "string"
112
207
  ? event.externalNumber
113
208
  : typeof event.invoiceNumber === "string"
@@ -137,6 +232,15 @@ export function smartbillPlugin(options) {
137
232
  subscribers,
138
233
  };
139
234
  }
235
+ function errorMessage(error) {
236
+ return error instanceof Error ? error.message : String(error);
237
+ }
238
+ function isUsableSmartbillRef(ref) {
239
+ return (ref.provider === "smartbill" &&
240
+ ref.status !== "error" &&
241
+ !ref.syncError &&
242
+ Boolean(ref.externalNumber || ref.externalId));
243
+ }
140
244
  function parseSmartbillPluginOptions(options) {
141
245
  try {
142
246
  return smartbillPluginOptionsSchema.parse(options);
package/dist/runtime.d.ts CHANGED
@@ -13,6 +13,8 @@ export interface SmartbillSyncRuntime {
13
13
  mapEvent: SmartbillMapFn;
14
14
  eventNames: ResolvedSmartbillSyncEventNames;
15
15
  artifacts: SmartbillArtifactPersistenceOptions;
16
+ idempotency: NonNullable<SmartbillPluginOptions["idempotency"]>;
17
+ onError: SmartbillPluginOptions["onError"];
16
18
  }
17
19
  export declare function createSmartbillSyncRuntime(options: SmartbillPluginOptions): SmartbillSyncRuntime;
18
20
  //# sourceMappingURL=runtime.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAG1F,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAA;IAChD,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,cAAc,CAAA;IACxB,UAAU,EAAE,+BAA+B,CAAA;IAC3C,SAAS,EAAE,mCAAmC,CAAA;CAC/C;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,sBAAsB,GAAG,oBAAoB,CAgChG"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mCAAmC,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEnD,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAG1F,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAA;IAChD,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,EAAE,cAAc,CAAA;IACxB,UAAU,EAAE,+BAA+B,CAAA;IAC3C,SAAS,EAAE,mCAAmC,CAAA;IAC9C,WAAW,EAAE,WAAW,CAAC,sBAAsB,CAAC,aAAa,CAAC,CAAC,CAAA;IAC/D,OAAO,EAAE,sBAAsB,CAAC,SAAS,CAAC,CAAA;CAC3C;AAED,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,sBAAsB,GAAG,oBAAoB,CAuChG"}
package/dist/runtime.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createSmartbillClient } from "./client.js";
2
- import { mapVoyantInvoiceToSmartbill } from "./mapping.js";
2
+ import { mapVoyantInvoiceToSmartbillAsync } from "./mapping.js";
3
3
  export function createSmartbillSyncRuntime(options) {
4
4
  const client = createSmartbillClient(options);
5
5
  const logger = options.logger ?? console;
@@ -9,9 +9,12 @@ export function createSmartbillSyncRuntime(options) {
9
9
  language: options.language,
10
10
  isTaxIncluded: options.isTaxIncluded,
11
11
  art311SpecialRegime: options.art311SpecialRegime,
12
+ art311SpecialRegimeText: options.art311SpecialRegimeText,
13
+ mentions: options.mentions,
14
+ observations: options.observations,
12
15
  };
13
16
  const mapEvent = options.mapEvent ??
14
- ((event) => mapVoyantInvoiceToSmartbill(event, mappingOptions));
17
+ ((event) => mapVoyantInvoiceToSmartbillAsync(event, mappingOptions));
15
18
  const eventNames = {
16
19
  issued: options.events?.issued ?? "invoice.issued",
17
20
  proformaIssued: options.events?.proformaIssued ?? "invoice.proforma.issued",
@@ -28,5 +31,9 @@ export function createSmartbillSyncRuntime(options) {
28
31
  documentStorage: options.artifacts?.documentStorage ?? options.documentStorage,
29
32
  documentStorageKeyPrefix: options.artifacts?.documentStorageKeyPrefix ?? options.documentStorageKeyPrefix,
30
33
  },
34
+ idempotency: {
35
+ skipExistingExternalRef: options.idempotency?.skipExistingExternalRef ?? true,
36
+ },
37
+ onError: options.onError,
31
38
  };
32
39
  }
@@ -1,7 +1,7 @@
1
1
  import type { SmartbillClientOptions } from "./client.js";
2
2
  export interface SmartbillInvoiceSettlementPollerOptions extends SmartbillClientOptions {
3
3
  companyVatCode?: string;
4
- seriesName?: string;
4
+ seriesName?: string | ((context: SmartbillSettlementSeriesContext) => string | null | undefined | Promise<string | null | undefined>);
5
5
  }
6
6
  export interface SmartbillSettlementInvoice {
7
7
  invoiceNumber: string;
@@ -15,6 +15,9 @@ export interface SmartbillSettlementPollerContext {
15
15
  invoice: SmartbillSettlementInvoice;
16
16
  externalRef: SmartbillSettlementExternalRef;
17
17
  }
18
+ export interface SmartbillSettlementSeriesContext extends SmartbillSettlementPollerContext {
19
+ metadata: Record<string, unknown> | null;
20
+ }
18
21
  export interface SmartbillSettlementPollerResult {
19
22
  externalId?: string | null;
20
23
  externalNumber?: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"settlement.d.ts","sourceRoot":"","sources":["../src/settlement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAGzD,MAAM,WAAW,uCAAwC,SAAQ,sBAAsB;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,0BAA0B,CAAA;IACnC,WAAW,EAAE,8BAA8B,CAAA;CAC5C;AAED,MAAM,WAAW,+BAA+B;IAC9C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAC1C;AAED,MAAM,MAAM,gCAAgC,GAAG,CAC7C,OAAO,EAAE,gCAAgC,KACtC,OAAO,CAAC,+BAA+B,CAAC,CAAA;AAuD7C,wBAAgB,sCAAsC,CACpD,OAAO,EAAE,uCAAuC,GAC/C,gCAAgC,CA2ClC"}
1
+ {"version":3,"file":"settlement.d.ts","sourceRoot":"","sources":["../src/settlement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AAGzD,MAAM,WAAW,uCAAwC,SAAQ,sBAAsB;IACrF,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EACP,MAAM,GACN,CAAC,CACC,OAAO,EAAE,gCAAgC,KACtC,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC,CAAA;CACzE;AAED,MAAM,WAAW,0BAA0B;IACzC,aAAa,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,8BAA8B;IAC7C,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,0BAA0B,CAAA;IACnC,WAAW,EAAE,8BAA8B,CAAA;CAC5C;AAED,MAAM,WAAW,gCAAiC,SAAQ,gCAAgC;IACxF,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CACzC;AAED,MAAM,WAAW,+BAA+B;IAC9C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAA;CAC1C;AAED,MAAM,MAAM,gCAAgC,GAAG,CAC7C,OAAO,EAAE,gCAAgC,KACtC,OAAO,CAAC,+BAA+B,CAAC,CAAA;AAyD7C,wBAAgB,sCAAsC,CACpD,OAAO,EAAE,uCAAuC,GAC/C,gCAAgC,CA4ClC"}
@@ -13,11 +13,11 @@ function resolveCompanyVatCode(metadata, options) {
13
13
  options.companyVatCode ??
14
14
  null);
15
15
  }
16
- function resolveSeriesName(metadata, options) {
17
- return (coerceString(metadata?.seriesName) ??
18
- coerceString(metadata?.series) ??
19
- options.seriesName ??
20
- null);
16
+ async function resolveSeriesName(metadata, options, context) {
17
+ const optionSeriesName = typeof options.seriesName === "function"
18
+ ? await options.seriesName({ ...context, metadata })
19
+ : options.seriesName;
20
+ return (coerceString(metadata?.seriesName) ?? coerceString(metadata?.series) ?? optionSeriesName ?? null);
21
21
  }
22
22
  function resolveInvoiceNumber(metadata, externalNumber, externalId, invoiceNumber) {
23
23
  return (coerceString(metadata?.number) ??
@@ -32,9 +32,10 @@ function toCents(value) {
32
32
  export function createSmartbillInvoiceSettlementPoller(options) {
33
33
  const client = createSmartbillClient(options);
34
34
  return async ({ invoice, externalRef }) => {
35
+ const context = { invoice, externalRef };
35
36
  const metadata = coerceMetadata(externalRef.metadata);
36
37
  const companyVatCode = resolveCompanyVatCode(metadata, options);
37
- const seriesName = resolveSeriesName(metadata, options);
38
+ const seriesName = await resolveSeriesName(metadata, options, context);
38
39
  const number = resolveInvoiceNumber(metadata, externalRef.externalNumber, externalRef.externalId, invoice.invoiceNumber);
39
40
  if (!companyVatCode) {
40
41
  return { syncError: "SmartBill settlement polling requires companyVatCode" };
@@ -1,20 +1,25 @@
1
1
  import { z } from "zod";
2
2
  import type { SmartbillArtifactPersistenceOptions, SmartbillDbResolver, SmartbillDocumentStorageResolver, SmartbillStorageKeyPrefixResolver } from "./artifacts.js";
3
- import type { SmartbillLogger, SmartbillMapFn, SmartbillSyncEventNames } from "./plugin.js";
3
+ import type { SmartbillErrorHandler, SmartbillIdempotencyOptions, SmartbillLogger, SmartbillMapFn, 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;
7
7
  apiToken: z.ZodString;
8
8
  companyVatCode: z.ZodString;
9
- seriesName: z.ZodString;
9
+ seriesName: z.ZodCustom<import("./mapping.js").SmartbillEventValue<string>, import("./mapping.js").SmartbillEventValue<string>>;
10
10
  apiUrl: z.ZodOptional<z.ZodString>;
11
11
  fetch: z.ZodOptional<z.ZodCustom<SmartbillFetch | undefined, SmartbillFetch | undefined>>;
12
12
  language: z.ZodOptional<z.ZodString>;
13
13
  isTaxIncluded: z.ZodOptional<z.ZodBoolean>;
14
14
  art311SpecialRegime: z.ZodOptional<z.ZodBoolean>;
15
+ art311SpecialRegimeText: z.ZodOptional<z.ZodString>;
16
+ mentions: z.ZodOptional<z.ZodCustom<import("./mapping.js").SmartbillEventValue<string | null | undefined>, import("./mapping.js").SmartbillEventValue<string | null | undefined>>>;
17
+ observations: z.ZodOptional<z.ZodCustom<import("./mapping.js").SmartbillEventValue<string | null | undefined>, import("./mapping.js").SmartbillEventValue<string | null | undefined>>>;
15
18
  events: z.ZodOptional<z.ZodCustom<SmartbillSyncEventNames | undefined, SmartbillSyncEventNames | undefined>>;
16
19
  mapEvent: z.ZodOptional<z.ZodCustom<SmartbillMapFn | undefined, SmartbillMapFn | undefined>>;
17
20
  logger: z.ZodOptional<z.ZodCustom<SmartbillLogger | undefined, SmartbillLogger | undefined>>;
21
+ idempotency: z.ZodOptional<z.ZodCustom<SmartbillIdempotencyOptions | undefined, SmartbillIdempotencyOptions | undefined>>;
22
+ onError: z.ZodOptional<z.ZodCustom<SmartbillErrorHandler | undefined, SmartbillErrorHandler | undefined>>;
18
23
  artifacts: z.ZodOptional<z.ZodCustom<SmartbillArtifactPersistenceOptions | undefined, SmartbillArtifactPersistenceOptions | undefined>>;
19
24
  db: z.ZodOptional<z.ZodCustom<SmartbillDbResolver | undefined, SmartbillDbResolver | undefined>>;
20
25
  documentStorage: z.ZodOptional<z.ZodCustom<SmartbillDocumentStorageResolver | undefined, SmartbillDocumentStorageResolver | undefined>>;
@@ -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;AACvB,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EAEd,uBAAuB,EACxB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA+EhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;iBAiBK,CAAA"}
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,eAAe,EACf,cAAc,EAEd,uBAAuB,EACxB,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAyGhD,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;;;;iBAsBK,CAAA"}
@@ -1,6 +1,10 @@
1
1
  import { z } from "zod";
2
2
  const optionalString = z.string().trim().min(1).optional();
3
3
  const optionalUrl = z.string().trim().url().optional();
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");
5
+ const optionalEventText = z.custom((value) => value === undefined ||
6
+ (typeof value === "string" && value.trim().length > 0) ||
7
+ typeof value === "function", "Expected a non-empty string or event resolver function");
4
8
  const optionalFetch = z.custom((value) => value === undefined || typeof value === "function", "Expected a fetch implementation function");
5
9
  const optionalLogger = z.custom((value) => value === undefined ||
6
10
  (typeof value === "object" &&
@@ -9,6 +13,7 @@ const optionalLogger = z.custom((value) => value === undefined ||
9
13
  ((value.info ?? undefined) === undefined ||
10
14
  typeof value.info === "function")), "Expected a logger with an error function");
11
15
  const optionalMapEvent = z.custom((value) => value === undefined || typeof value === "function", "Expected a mapEvent function");
16
+ const optionalOnError = z.custom((value) => value === undefined || typeof value === "function", "Expected an onError function");
12
17
  const optionalDb = z.custom((value) => value === undefined ||
13
18
  typeof value === "function" ||
14
19
  (typeof value === "object" && value !== null), "Expected a database handle or resolver function");
@@ -43,19 +48,33 @@ const optionalEvents = z.custom((value) => {
43
48
  const events = value;
44
49
  return [events.issued, events.proformaIssued, events.voided, events.syncRequested].every((entry) => entry === undefined || (typeof entry === "string" && entry.trim().length > 0));
45
50
  }, "Expected event names to be non-empty strings");
51
+ const optionalIdempotency = z.custom((value) => {
52
+ if (value === undefined)
53
+ return true;
54
+ if (typeof value !== "object" || value === null)
55
+ return false;
56
+ const options = value;
57
+ return (options.skipExistingExternalRef === undefined ||
58
+ typeof options.skipExistingExternalRef === "boolean");
59
+ }, "Expected valid SmartBill idempotency options");
46
60
  export const smartbillPluginOptionsSchema = z.object({
47
61
  username: z.string().trim().min(1),
48
62
  apiToken: z.string().trim().min(1),
49
63
  companyVatCode: z.string().trim().min(1),
50
- seriesName: z.string().trim().min(1),
64
+ seriesName: requiredEventString,
51
65
  apiUrl: optionalUrl,
52
66
  fetch: optionalFetch.optional(),
53
67
  language: optionalString,
54
68
  isTaxIncluded: z.boolean().optional(),
55
69
  art311SpecialRegime: z.boolean().optional(),
70
+ art311SpecialRegimeText: optionalString,
71
+ mentions: optionalEventText.optional(),
72
+ observations: optionalEventText.optional(),
56
73
  events: optionalEvents.optional(),
57
74
  mapEvent: optionalMapEvent.optional(),
58
75
  logger: optionalLogger.optional(),
76
+ idempotency: optionalIdempotency.optional(),
77
+ onError: optionalOnError.optional(),
59
78
  artifacts: optionalArtifacts.optional(),
60
79
  db: optionalDb.optional(),
61
80
  documentStorage: optionalDocumentStorage.optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/plugin-smartbill",
3
- "version": "0.38.1",
3
+ "version": "0.40.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -38,9 +38,9 @@
38
38
  "dependencies": {
39
39
  "drizzle-orm": "^0.45.2",
40
40
  "zod": "^4.3.6",
41
- "@voyantjs/core": "0.38.1",
42
- "@voyantjs/finance": "0.38.1",
43
- "@voyantjs/storage": "0.38.1"
41
+ "@voyantjs/core": "0.40.0",
42
+ "@voyantjs/finance": "0.40.0",
43
+ "@voyantjs/storage": "0.40.0"
44
44
  },
45
45
  "devDependencies": {
46
46
  "typescript": "^6.0.2",