@voyantjs/plugin-smartbill 0.40.1 → 0.41.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
@@ -79,6 +79,46 @@ Use `retrySmartbillInvoiceArtifact({ runtime, client, externalRef, documentType
79
79
  })` to re-download and re-attach a SmartBill PDF from an existing external ref
80
80
  without issuing a new document.
81
81
 
82
+ ## Workflow Factories
83
+
84
+ The package also ships scheduler-agnostic workflow factories for recurring
85
+ SmartBill maintenance. They return async functions that can be called from
86
+ `@voyantjs/workflows`, a Cloudflare cron handler, Trigger.dev, Hatchet, or any
87
+ other job runner.
88
+
89
+ ```typescript
90
+ import {
91
+ createSmartbillDriftReconciler,
92
+ createSmartbillProformaConversionPoller,
93
+ } from "@voyantjs/plugin-smartbill"
94
+
95
+ const pollProformas = createSmartbillProformaConversionPoller({
96
+ db,
97
+ client: smartbillClient,
98
+ onConverted: async (proformaRef, conversion) => {
99
+ // Record a Voyant payment or emit a domain event in the host app.
100
+ // The plugin reports the SmartBill invoice series/number and source
101
+ // proforma ref, but leaves payment semantics to the consumer.
102
+ },
103
+ })
104
+
105
+ const reconcileSmartbill = createSmartbillDriftReconciler({
106
+ db,
107
+ client: smartbillClient,
108
+ listRemoteDocuments: async () => remoteSmartbillInventory,
109
+ onFinding: async (finding) => {
110
+ // Log, alert, or open an operator ticket.
111
+ },
112
+ })
113
+ ```
114
+
115
+ `createSmartbillProformaConversionPoller` scans SmartBill proforma external refs
116
+ and calls `client.listEstimateInvoices(...)` to detect SmartBill-created final
117
+ invoices. `createSmartbillDriftReconciler` verifies known SmartBill refs by
118
+ default and can compare a caller-provided remote inventory for `missing_local`
119
+ findings. The reconciler only reports drift; it does not delete, void, or create
120
+ finance records.
121
+
82
122
  ## Exports
83
123
 
84
124
  | Entry | Description |
@@ -87,6 +127,7 @@ without issuing a new document.
87
127
  | `./plugin` | `smartbillPlugin(options)` — packaged adapter/subscriber bundle |
88
128
  | `./client` | `createSmartbillClient` — `createInvoice`, `cancelInvoice`, `viewPdf`, `getPaymentStatus`, etc. |
89
129
  | `./mock` | `createSmartbillMockServer` — stateful local SmartBill-compatible mock for tests |
130
+ | `./workflows` | Proforma conversion polling and drift reconciliation factories |
90
131
  | `./types` | SmartBill adapter and bundle types |
91
132
 
92
133
  ## Local SmartBill Mock
package/dist/index.d.ts CHANGED
@@ -13,4 +13,6 @@ export { createSmartbillSyncRuntime } from "./runtime.js";
13
13
  export type { SmartbillInvoiceSettlementPoller, SmartbillInvoiceSettlementPollerOptions, SmartbillSettlementExternalRef, SmartbillSettlementInvoice, SmartbillSettlementPollerContext, SmartbillSettlementPollerResult, SmartbillSettlementSeriesContext, } from "./settlement.js";
14
14
  export { createSmartbillInvoiceSettlementPoller } from "./settlement.js";
15
15
  export type { SmartbillClient, SmartbillEnvelope, SmartbillEstimateInvoicesResponse, SmartbillFetch, SmartbillInvoiceBody, SmartbillInvoiceResponse, SmartbillPaymentEntry, SmartbillPdfResponse, SmartbillProduct, SmartbillSeriesResponse, SmartbillStatusResponse, SmartbillTaxesResponse, VoyantInvoiceEvent, } from "./types.js";
16
+ export type { SmartbillDriftFinding, SmartbillDriftFindingType, SmartbillDriftReconciler, SmartbillDriftReconcilerOptions, SmartbillDriftReconcilerResult, SmartbillProformaConversion, SmartbillProformaConversionPoller, SmartbillProformaConversionPollerOptions, SmartbillProformaConversionPollerResult, SmartbillReferenceParts, SmartbillRemoteDocument, SmartbillRemoteDocumentStatus, SmartbillWorkflowDocumentType, SmartbillWorkflowError, SmartbillWorkflowExternalRef, SmartbillWorkflowInvoice, SmartbillWorkflowLogger, } from "./workflows.js";
17
+ export { createSmartbillDriftReconciler, createSmartbillProformaConversionPoller, } from "./workflows.js";
16
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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"}
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;AACnB,YAAY,EACV,qBAAqB,EACrB,yBAAyB,EACzB,wBAAwB,EACxB,+BAA+B,EAC/B,8BAA8B,EAC9B,2BAA2B,EAC3B,iCAAiC,EACjC,wCAAwC,EACxC,uCAAuC,EACvC,uBAAuB,EACvB,uBAAuB,EACvB,6BAA6B,EAC7B,6BAA6B,EAC7B,sBAAsB,EACtB,4BAA4B,EAC5B,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,8BAA8B,EAC9B,uCAAuC,GACxC,MAAM,gBAAgB,CAAA"}
package/dist/index.js CHANGED
@@ -5,3 +5,4 @@ export { createSmartbillMockServer } from "./mock.js";
5
5
  export { smartbillPlugin } from "./plugin.js";
6
6
  export { createSmartbillSyncRuntime } from "./runtime.js";
7
7
  export { createSmartbillInvoiceSettlementPoller } from "./settlement.js";
8
+ export { createSmartbillDriftReconciler, createSmartbillProformaConversionPoller, } from "./workflows.js";
@@ -0,0 +1,119 @@
1
+ import { type Invoice } from "@voyantjs/finance";
2
+ import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
3
+ import type { SmartbillClientApi } from "./client.js";
4
+ import type { SmartbillEstimateInvoicesResponse, SmartbillInvoiceResponse } from "./types.js";
5
+ export type SmartbillWorkflowDocumentType = "invoice" | "proforma";
6
+ export interface SmartbillWorkflowLogger {
7
+ info?: (message: string, meta?: unknown) => void;
8
+ error?: (message: string, meta?: unknown) => void;
9
+ }
10
+ export interface SmartbillWorkflowInvoice {
11
+ id: string;
12
+ invoiceNumber: string;
13
+ invoiceType: Invoice["invoiceType"];
14
+ status: Invoice["status"];
15
+ currency: string;
16
+ totalCents: number;
17
+ paidCents: number;
18
+ balanceDueCents: number;
19
+ }
20
+ export interface SmartbillWorkflowExternalRef {
21
+ id: string;
22
+ invoiceId: string;
23
+ provider: string;
24
+ externalId: string | null;
25
+ externalNumber: string | null;
26
+ externalUrl: string | null;
27
+ status: string | null;
28
+ metadata: unknown;
29
+ syncError: string | null;
30
+ createdAt?: Date | null;
31
+ updatedAt?: Date | null;
32
+ invoice?: SmartbillWorkflowInvoice | null;
33
+ }
34
+ export interface SmartbillReferenceParts {
35
+ companyVatCode: string;
36
+ seriesName: string;
37
+ number: string;
38
+ documentType: SmartbillWorkflowDocumentType;
39
+ }
40
+ export interface SmartbillProformaConversion {
41
+ proformaRef: SmartbillWorkflowExternalRef;
42
+ invoice: SmartbillWorkflowInvoice | null;
43
+ companyVatCode: string;
44
+ proformaSeriesName: string;
45
+ proformaNumber: string;
46
+ invoiceSeriesName: string;
47
+ invoiceNumber: string;
48
+ invoiceUrl: string | null;
49
+ response: SmartbillEstimateInvoicesResponse;
50
+ smartbillInvoice: SmartbillInvoiceResponse;
51
+ }
52
+ export interface SmartbillProformaConversionPollerOptions {
53
+ db?: PostgresJsDatabase;
54
+ client: SmartbillClientApi;
55
+ limit?: number;
56
+ companyVatCode?: string;
57
+ logger?: SmartbillWorkflowLogger;
58
+ listExternalRefs?: () => Promise<SmartbillWorkflowExternalRef[]>;
59
+ onConverted: (proformaRef: SmartbillWorkflowExternalRef, conversion: SmartbillProformaConversion) => void | Promise<void>;
60
+ onError?: (error: SmartbillWorkflowError) => void | Promise<void>;
61
+ }
62
+ export interface SmartbillWorkflowError {
63
+ ref?: SmartbillWorkflowExternalRef;
64
+ error: unknown;
65
+ }
66
+ export interface SmartbillProformaConversionPollerResult {
67
+ checked: number;
68
+ converted: SmartbillProformaConversion[];
69
+ skipped: Array<{
70
+ ref: SmartbillWorkflowExternalRef;
71
+ reason: string;
72
+ }>;
73
+ errors: SmartbillWorkflowError[];
74
+ }
75
+ export type SmartbillRemoteDocumentStatus = "present" | "issued" | "paid" | "unpaid" | "partially_paid" | "voided" | "cancelled" | "reversed" | "deleted" | "missing";
76
+ export interface SmartbillRemoteDocument extends SmartbillReferenceParts {
77
+ status?: SmartbillRemoteDocumentStatus;
78
+ metadata?: Record<string, unknown>;
79
+ }
80
+ export type SmartbillDriftFindingType = "missing_local" | "missing_remote" | "voided_remote";
81
+ export interface SmartbillDriftFinding {
82
+ type: SmartbillDriftFindingType;
83
+ document: SmartbillReferenceParts;
84
+ ref?: SmartbillWorkflowExternalRef;
85
+ invoice?: SmartbillWorkflowInvoice | null;
86
+ remote?: SmartbillRemoteDocument | null;
87
+ error?: unknown;
88
+ }
89
+ export interface SmartbillDriftReconcilerOptions {
90
+ db?: PostgresJsDatabase;
91
+ client: SmartbillClientApi;
92
+ limit?: number;
93
+ companyVatCode?: string;
94
+ logger?: SmartbillWorkflowLogger;
95
+ listExternalRefs?: () => Promise<SmartbillWorkflowExternalRef[]>;
96
+ listRemoteDocuments?: (context: {
97
+ refs: SmartbillWorkflowExternalRef[];
98
+ }) => Promise<SmartbillRemoteDocument[]>;
99
+ verifyRemoteDocument?: (context: {
100
+ ref: SmartbillWorkflowExternalRef;
101
+ document: SmartbillReferenceParts;
102
+ }) => Promise<SmartbillRemoteDocumentStatus | SmartbillRemoteDocument>;
103
+ onFinding?: (finding: SmartbillDriftFinding) => void | Promise<void>;
104
+ onError?: (error: SmartbillWorkflowError) => void | Promise<void>;
105
+ }
106
+ export interface SmartbillDriftReconcilerResult {
107
+ checked: number;
108
+ findings: SmartbillDriftFinding[];
109
+ skipped: Array<{
110
+ ref: SmartbillWorkflowExternalRef;
111
+ reason: string;
112
+ }>;
113
+ errors: SmartbillWorkflowError[];
114
+ }
115
+ export type SmartbillProformaConversionPoller = () => Promise<SmartbillProformaConversionPollerResult>;
116
+ export type SmartbillDriftReconciler = () => Promise<SmartbillDriftReconcilerResult>;
117
+ export declare function createSmartbillProformaConversionPoller(options: SmartbillProformaConversionPollerOptions): SmartbillProformaConversionPoller;
118
+ export declare function createSmartbillDriftReconciler(options: SmartbillDriftReconcilerOptions): SmartbillDriftReconciler;
119
+ //# sourceMappingURL=workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflows.d.ts","sourceRoot":"","sources":["../src/workflows.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,247 @@
1
+ import { invoiceExternalRefs, invoices, } from "@voyantjs/finance";
2
+ import { desc, eq } from "drizzle-orm";
3
+ export function createSmartbillProformaConversionPoller(options) {
4
+ return async () => {
5
+ const result = {
6
+ checked: 0,
7
+ converted: [],
8
+ skipped: [],
9
+ errors: [],
10
+ };
11
+ const refs = await loadSmartbillExternalRefs(options);
12
+ for (const ref of refs) {
13
+ const document = resolveReferenceParts(ref, options.companyVatCode);
14
+ if (document?.documentType !== "proforma")
15
+ continue;
16
+ result.checked += 1;
17
+ try {
18
+ const response = await options.client.listEstimateInvoices(document.companyVatCode, document.seriesName, document.number);
19
+ const convertedInvoices = convertedInvoicesFromResponse(response);
20
+ if (convertedInvoices.length === 0) {
21
+ result.skipped.push({ ref, reason: "not_converted" });
22
+ continue;
23
+ }
24
+ for (const smartbillInvoice of convertedInvoices) {
25
+ if (!smartbillInvoice.series || !smartbillInvoice.number) {
26
+ result.skipped.push({ ref, reason: "missing_converted_invoice_number" });
27
+ continue;
28
+ }
29
+ const conversion = {
30
+ proformaRef: ref,
31
+ invoice: ref.invoice ?? null,
32
+ companyVatCode: document.companyVatCode,
33
+ proformaSeriesName: document.seriesName,
34
+ proformaNumber: document.number,
35
+ invoiceSeriesName: smartbillInvoice.series,
36
+ invoiceNumber: smartbillInvoice.number,
37
+ invoiceUrl: smartbillInvoice.url ?? null,
38
+ response,
39
+ smartbillInvoice,
40
+ };
41
+ await options.onConverted(ref, conversion);
42
+ options.logger?.info?.("[smartbill] proforma conversion detected", conversion);
43
+ result.converted.push(conversion);
44
+ }
45
+ }
46
+ catch (error) {
47
+ await recordWorkflowError(options, result.errors, { ref, error });
48
+ }
49
+ }
50
+ return result;
51
+ };
52
+ }
53
+ export function createSmartbillDriftReconciler(options) {
54
+ return async () => {
55
+ const result = {
56
+ checked: 0,
57
+ findings: [],
58
+ skipped: [],
59
+ errors: [],
60
+ };
61
+ const refs = await loadSmartbillExternalRefs(options);
62
+ const remoteDocuments = await options.listRemoteDocuments?.({ refs });
63
+ const remoteDocumentMap = new Map((remoteDocuments ?? []).map((remote) => [documentKey(remote), remote]));
64
+ const hasRemoteInventory = remoteDocuments !== undefined;
65
+ const localDocuments = new Map();
66
+ for (const ref of refs) {
67
+ const document = resolveReferenceParts(ref, options.companyVatCode);
68
+ if (!document) {
69
+ result.skipped.push({ ref, reason: "missing_smartbill_reference" });
70
+ continue;
71
+ }
72
+ const key = documentKey(document);
73
+ localDocuments.set(key, document);
74
+ result.checked += 1;
75
+ try {
76
+ const remote = hasRemoteInventory
77
+ ? (remoteDocumentMap.get(key) ?? { ...document, status: "missing" })
78
+ : await verifyRemoteDocument(options, ref, document);
79
+ if (remote.status === "missing" || remote.status === "deleted") {
80
+ await recordFinding(options, result.findings, {
81
+ type: "missing_remote",
82
+ document,
83
+ ref,
84
+ invoice: ref.invoice ?? null,
85
+ remote,
86
+ });
87
+ }
88
+ else if (isVoidedRemote(remote.status) && ref.invoice?.status !== "void") {
89
+ await recordFinding(options, result.findings, {
90
+ type: "voided_remote",
91
+ document,
92
+ ref,
93
+ invoice: ref.invoice ?? null,
94
+ remote,
95
+ });
96
+ }
97
+ }
98
+ catch (error) {
99
+ await recordWorkflowError(options, result.errors, { ref, error });
100
+ await recordFinding(options, result.findings, {
101
+ type: "missing_remote",
102
+ document,
103
+ ref,
104
+ invoice: ref.invoice ?? null,
105
+ remote: null,
106
+ error,
107
+ });
108
+ }
109
+ }
110
+ for (const remote of remoteDocuments ?? []) {
111
+ if (!localDocuments.has(documentKey(remote))) {
112
+ await recordFinding(options, result.findings, {
113
+ type: "missing_local",
114
+ document: remote,
115
+ remote,
116
+ });
117
+ }
118
+ }
119
+ return result;
120
+ };
121
+ }
122
+ async function loadSmartbillExternalRefs(options) {
123
+ if (options.listExternalRefs)
124
+ return options.listExternalRefs();
125
+ if (!options.db)
126
+ throw new Error("SmartBill workflow requires db or listExternalRefs");
127
+ const rows = await options.db
128
+ .select({
129
+ ref: invoiceExternalRefs,
130
+ invoice: {
131
+ id: invoices.id,
132
+ invoiceNumber: invoices.invoiceNumber,
133
+ invoiceType: invoices.invoiceType,
134
+ status: invoices.status,
135
+ currency: invoices.currency,
136
+ totalCents: invoices.totalCents,
137
+ paidCents: invoices.paidCents,
138
+ balanceDueCents: invoices.balanceDueCents,
139
+ },
140
+ })
141
+ .from(invoiceExternalRefs)
142
+ .leftJoin(invoices, eq(invoiceExternalRefs.invoiceId, invoices.id))
143
+ .where(eq(invoiceExternalRefs.provider, "smartbill"))
144
+ .orderBy(desc(invoiceExternalRefs.createdAt))
145
+ .limit(options.limit ?? 500);
146
+ return rows.map((row) => toWorkflowExternalRef(row.ref, row.invoice));
147
+ }
148
+ function toWorkflowExternalRef(ref, invoice) {
149
+ return {
150
+ id: ref.id,
151
+ invoiceId: ref.invoiceId,
152
+ provider: ref.provider,
153
+ externalId: ref.externalId,
154
+ externalNumber: ref.externalNumber,
155
+ externalUrl: ref.externalUrl,
156
+ status: ref.status,
157
+ metadata: ref.metadata,
158
+ syncError: ref.syncError,
159
+ createdAt: ref.createdAt,
160
+ updatedAt: ref.updatedAt,
161
+ invoice,
162
+ };
163
+ }
164
+ function convertedInvoicesFromResponse(response) {
165
+ if (Array.isArray(response.invoices) && response.invoices.length > 0) {
166
+ return response.invoices.filter((invoice) => invoice.series && invoice.number);
167
+ }
168
+ if (response.areInvoicesCreated && response.series && response.number) {
169
+ return [{ series: response.series, number: response.number }];
170
+ }
171
+ return [];
172
+ }
173
+ async function verifyRemoteDocument(options, ref, document) {
174
+ const verified = await (options.verifyRemoteDocument
175
+ ? options.verifyRemoteDocument({ ref, document })
176
+ : defaultVerifyRemoteDocument(options.client, document));
177
+ return typeof verified === "string" ? { ...document, status: verified } : verified;
178
+ }
179
+ async function defaultVerifyRemoteDocument(client, document) {
180
+ if (document.documentType === "proforma") {
181
+ await client.listEstimateInvoices(document.companyVatCode, document.seriesName, document.number);
182
+ return "present";
183
+ }
184
+ const status = await client.getPaymentStatus(document.companyVatCode, document.seriesName, document.number);
185
+ return paymentStatusToRemoteStatus(status);
186
+ }
187
+ function paymentStatusToRemoteStatus(status) {
188
+ const providerStatus = `${status.message ?? ""} ${status.errorText ?? ""}`.toLowerCase();
189
+ if (providerStatus.includes("cancel"))
190
+ return "cancelled";
191
+ if (providerStatus.includes("reverse"))
192
+ return "reversed";
193
+ if (providerStatus.includes("void"))
194
+ return "voided";
195
+ if (providerStatus.includes("delete"))
196
+ return "deleted";
197
+ if (status.paid)
198
+ return "paid";
199
+ if ((status.paidAmount ?? 0) > 0)
200
+ return "partially_paid";
201
+ return "unpaid";
202
+ }
203
+ function resolveReferenceParts(ref, fallbackCompanyVatCode) {
204
+ const metadata = coerceMetadata(ref.metadata);
205
+ const documentType = coerceDocumentType(metadata?.documentType);
206
+ const companyVatCode = metadataString(metadata, "companyVatCode") ??
207
+ metadataString(metadata, "vatCode") ??
208
+ fallbackCompanyVatCode;
209
+ const seriesName = metadataString(metadata, "series") ?? metadataString(metadata, "seriesName");
210
+ const number = metadataString(metadata, "number") ?? ref.externalNumber ?? ref.externalId;
211
+ if (!documentType || !companyVatCode || !seriesName || !number)
212
+ return null;
213
+ return { companyVatCode, seriesName, number, documentType };
214
+ }
215
+ function coerceMetadata(value) {
216
+ return value && typeof value === "object" && !Array.isArray(value)
217
+ ? value
218
+ : null;
219
+ }
220
+ function metadataString(metadata, key) {
221
+ const value = metadata?.[key];
222
+ return typeof value === "string" && value.length > 0 ? value : null;
223
+ }
224
+ function coerceDocumentType(value) {
225
+ return value === "invoice" || value === "proforma" ? value : null;
226
+ }
227
+ function documentKey(document) {
228
+ return [
229
+ document.documentType,
230
+ document.companyVatCode,
231
+ document.seriesName,
232
+ document.number,
233
+ ].join(":");
234
+ }
235
+ function isVoidedRemote(status) {
236
+ return status === "voided" || status === "cancelled" || status === "reversed";
237
+ }
238
+ async function recordFinding(options, findings, finding) {
239
+ findings.push(finding);
240
+ options.logger?.info?.("[smartbill] drift finding", finding);
241
+ await options.onFinding?.(finding);
242
+ }
243
+ async function recordWorkflowError(options, errors, error) {
244
+ errors.push(error);
245
+ options.logger?.error?.("[smartbill] workflow error", error);
246
+ await options.onError?.(error);
247
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/plugin-smartbill",
3
- "version": "0.40.1",
3
+ "version": "0.41.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
@@ -29,6 +29,11 @@
29
29
  "import": "./dist/settlement.js",
30
30
  "default": "./dist/settlement.js"
31
31
  },
32
+ "./workflows": {
33
+ "types": "./dist/workflows.d.ts",
34
+ "import": "./dist/workflows.js",
35
+ "default": "./dist/workflows.js"
36
+ },
32
37
  "./types": {
33
38
  "types": "./dist/types.d.ts",
34
39
  "import": "./dist/types.js",
@@ -38,9 +43,9 @@
38
43
  "dependencies": {
39
44
  "drizzle-orm": "^0.45.2",
40
45
  "zod": "^4.3.6",
41
- "@voyantjs/core": "0.40.1",
42
- "@voyantjs/finance": "0.40.1",
43
- "@voyantjs/storage": "0.40.1"
46
+ "@voyantjs/core": "0.41.0",
47
+ "@voyantjs/finance": "0.41.0",
48
+ "@voyantjs/storage": "0.41.0"
44
49
  },
45
50
  "devDependencies": {
46
51
  "typescript": "^6.0.2",