@voyantjs/plugin-smartbill 0.40.0 → 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 +41 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/workflows.d.ts +119 -0
- package/dist/workflows.d.ts.map +1 -0
- package/dist/workflows.js +247 -0
- package/package.json +9 -4
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
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.
|
|
42
|
-
"@voyantjs/finance": "0.
|
|
43
|
-
"@voyantjs/storage": "0.
|
|
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",
|