@fast-white-cat/integration-ksef-direct 0.1.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 +36 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/integration_ksef_direct/acl.js +13 -0
- package/dist/modules/integration_ksef_direct/acl.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].js +92 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.js +105 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.js +158 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].js +86 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.js +112 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.js +54 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.js +64 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.js +104 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.js +41 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.js +172 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.js +80 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.js +441 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.js +8 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.js +193 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.js +314 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/page.js +154 -0
- package/dist/modules/integration_ksef_direct/backend/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/commands/create-ksef-direct-document.js +80 -0
- package/dist/modules/integration_ksef_direct/commands/create-ksef-direct-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.js +43 -0
- package/dist/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/data/entities.js +224 -0
- package/dist/modules/integration_ksef_direct/data/entities.js.map +7 -0
- package/dist/modules/integration_ksef_direct/data/validators.js +103 -0
- package/dist/modules/integration_ksef_direct/data/validators.js.map +7 -0
- package/dist/modules/integration_ksef_direct/di.js +11 -0
- package/dist/modules/integration_ksef_direct/di.js.map +7 -0
- package/dist/modules/integration_ksef_direct/events.js +21 -0
- package/dist/modules/integration_ksef_direct/events.js.map +7 -0
- package/dist/modules/integration_ksef_direct/index.js +10 -0
- package/dist/modules/integration_ksef_direct/index.js.map +7 -0
- package/dist/modules/integration_ksef_direct/integration.js +56 -0
- package/dist/modules/integration_ksef_direct/integration.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/health.js +32 -0
- package/dist/modules/integration_ksef_direct/lib/health.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/invoiceNumberFormat.js +23 -0
- package/dist/modules/integration_ksef_direct/lib/invoiceNumberFormat.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefClient.js +523 -0
- package/dist/modules/integration_ksef_direct/lib/ksefClient.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefCrypto.js +103 -0
- package/dist/modules/integration_ksef_direct/lib/ksefCrypto.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefFa2Xml.js +123 -0
- package/dist/modules/integration_ksef_direct/lib/ksefFa2Xml.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefXmlParser.js +76 -0
- package/dist/modules/integration_ksef_direct/lib/ksefXmlParser.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.js +15 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.js +17 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.js +15 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.js +17 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.js +16 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.js +15 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.js.map +7 -0
- package/dist/modules/integration_ksef_direct/setup.js +11 -0
- package/dist/modules/integration_ksef_direct/setup.js.map +7 -0
- package/dist/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.js +19 -0
- package/dist/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/workers/check-ksef-document-status.js +103 -0
- package/dist/modules/integration_ksef_direct/workers/check-ksef-document-status.js.map +7 -0
- package/dist/modules/integration_ksef_direct/workers/send-ksef-document.js +104 -0
- package/dist/modules/integration_ksef_direct/workers/send-ksef-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/workers/sync-received-documents.js +137 -0
- package/dist/modules/integration_ksef_direct/workers/sync-received-documents.js.map +7 -0
- package/dist/types/declarations.d.js +1 -0
- package/dist/types/declarations.d.js.map +7 -0
- package/package.json +98 -0
- package/src/index.ts +1 -0
- package/src/modules/integration_ksef_direct/__tests__/invoiceNumberFormat.test.ts +42 -0
- package/src/modules/integration_ksef_direct/__tests__/ksefFa2Xml.test.ts +407 -0
- package/src/modules/integration_ksef_direct/__tests__/ksefXmlParser.test.ts +230 -0
- package/src/modules/integration_ksef_direct/acl.ts +9 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].ts +94 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.ts +111 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.ts +194 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].ts +88 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.ts +119 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.ts +62 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.ts +64 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.ts +109 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.ts +40 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.ts +185 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.ts +86 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.ts +4 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.tsx +470 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.tsx +233 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.tsx +415 -0
- package/src/modules/integration_ksef_direct/backend/page.tsx +183 -0
- package/src/modules/integration_ksef_direct/commands/create-ksef-direct-document.ts +93 -0
- package/src/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.ts +57 -0
- package/src/modules/integration_ksef_direct/data/entities.ts +195 -0
- package/src/modules/integration_ksef_direct/data/validators.ts +115 -0
- package/src/modules/integration_ksef_direct/di.ts +9 -0
- package/src/modules/integration_ksef_direct/events.ts +18 -0
- package/src/modules/integration_ksef_direct/i18n/en.json +115 -0
- package/src/modules/integration_ksef_direct/i18n/pl.json +115 -0
- package/src/modules/integration_ksef_direct/index.ts +6 -0
- package/src/modules/integration_ksef_direct/integration.ts +54 -0
- package/src/modules/integration_ksef_direct/lib/health.ts +43 -0
- package/src/modules/integration_ksef_direct/lib/invoiceNumberFormat.ts +23 -0
- package/src/modules/integration_ksef_direct/lib/ksefClient.ts +668 -0
- package/src/modules/integration_ksef_direct/lib/ksefCrypto.ts +138 -0
- package/src/modules/integration_ksef_direct/lib/ksefFa2Xml.ts +147 -0
- package/src/modules/integration_ksef_direct/lib/ksefXmlParser.ts +97 -0
- package/src/modules/integration_ksef_direct/migrations/.snapshot-open-mercato.json +1028 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.ts +15 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.ts +17 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.ts +15 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.ts +17 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.ts +16 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.ts +15 -0
- package/src/modules/integration_ksef_direct/setup.ts +9 -0
- package/src/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.ts +21 -0
- package/src/modules/integration_ksef_direct/workers/check-ksef-document-status.ts +129 -0
- package/src/modules/integration_ksef_direct/workers/send-ksef-document.ts +137 -0
- package/src/modules/integration_ksef_direct/workers/sync-received-documents.ts +171 -0
- package/src/types/declarations.d.ts +1 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { createModuleQueue } from "@open-mercato/queue";
|
|
2
|
+
const metadata = {
|
|
3
|
+
queue: "ksef_direct_check",
|
|
4
|
+
id: "ksef-direct-check",
|
|
5
|
+
concurrency: 5
|
|
6
|
+
};
|
|
7
|
+
async function handler(job, ctx) {
|
|
8
|
+
const payload = job.payload;
|
|
9
|
+
const em = ctx.resolve("em")?.fork();
|
|
10
|
+
if (!em) return;
|
|
11
|
+
const { KsefDirectDocument } = await import("../data/entities.js");
|
|
12
|
+
const doc = await em.findOne(KsefDirectDocument, {
|
|
13
|
+
id: payload.documentId,
|
|
14
|
+
organizationId: payload.organizationId,
|
|
15
|
+
tenantId: payload.tenantId
|
|
16
|
+
});
|
|
17
|
+
if (!doc || doc.status !== "sending") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!doc.ksefProcessingReferenceNumber) {
|
|
21
|
+
doc.status = "failed";
|
|
22
|
+
doc.errorMessage = "Missing KSeF processing reference number";
|
|
23
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
24
|
+
await em.flush();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const credentialsService = ctx.resolve("integrationCredentialsService");
|
|
28
|
+
const rawCreds = credentialsService ? await credentialsService.resolve("integration_ksef_direct", {
|
|
29
|
+
tenantId: payload.tenantId,
|
|
30
|
+
organizationId: payload.organizationId
|
|
31
|
+
}) : null;
|
|
32
|
+
const { KsefDirectCredentialsSchema } = await import("../data/validators.js");
|
|
33
|
+
const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds);
|
|
34
|
+
if (!credsParsed.success) {
|
|
35
|
+
doc.status = "failed";
|
|
36
|
+
doc.errorMessage = "KSeF Direct credentials not configured";
|
|
37
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
38
|
+
await em.flush();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const credentials = {
|
|
42
|
+
ksefToken: credsParsed.data.ksef_token,
|
|
43
|
+
nip: credsParsed.data.nip,
|
|
44
|
+
environment: credsParsed.data.environment,
|
|
45
|
+
tenantId: payload.tenantId
|
|
46
|
+
};
|
|
47
|
+
const { checkInvoiceStatus } = await import("../lib/ksefClient.js");
|
|
48
|
+
const { emitKsefDirectEvent } = await import("../events.js");
|
|
49
|
+
let result;
|
|
50
|
+
try {
|
|
51
|
+
result = await checkInvoiceStatus(credentials, doc.ksefProcessingReferenceNumber);
|
|
52
|
+
} catch (err) {
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
if (result.processingCode === 200) {
|
|
56
|
+
doc.status = "sent";
|
|
57
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
58
|
+
await em.flush();
|
|
59
|
+
await emitKsefDirectEvent("ksef_direct.document.sent", {
|
|
60
|
+
documentId: doc.id,
|
|
61
|
+
ksefReferenceNumber: doc.ksefReferenceNumber ?? void 0,
|
|
62
|
+
organizationId: payload.organizationId,
|
|
63
|
+
tenantId: payload.tenantId
|
|
64
|
+
});
|
|
65
|
+
} else if (result.processingCode === 100) {
|
|
66
|
+
if (payload.attempt >= 10) {
|
|
67
|
+
doc.status = "failed";
|
|
68
|
+
doc.errorMessage = "Timeout: KSeF did not confirm the document within 10 minutes";
|
|
69
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
70
|
+
await em.flush();
|
|
71
|
+
await emitKsefDirectEvent("ksef_direct.document.failed", {
|
|
72
|
+
documentId: doc.id,
|
|
73
|
+
errorMessage: doc.errorMessage,
|
|
74
|
+
organizationId: payload.organizationId,
|
|
75
|
+
tenantId: payload.tenantId
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
const checkQueue = createModuleQueue("ksef_direct_check");
|
|
79
|
+
await checkQueue.enqueue(
|
|
80
|
+
{ ...payload, attempt: payload.attempt + 1 },
|
|
81
|
+
{ delayMs: 6e4 }
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
const errorMessage = result.errorDescription ?? `KSeF processing error: code ${result.processingCode}`;
|
|
86
|
+
doc.status = "failed";
|
|
87
|
+
doc.errorMessage = errorMessage;
|
|
88
|
+
doc.ksefReferenceNumber = null;
|
|
89
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
90
|
+
await em.flush();
|
|
91
|
+
await emitKsefDirectEvent("ksef_direct.document.failed", {
|
|
92
|
+
documentId: doc.id,
|
|
93
|
+
errorMessage,
|
|
94
|
+
organizationId: payload.organizationId,
|
|
95
|
+
tenantId: payload.tenantId
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
handler as default,
|
|
101
|
+
metadata
|
|
102
|
+
};
|
|
103
|
+
//# sourceMappingURL=check-ksef-document-status.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/integration_ksef_direct/workers/check-ksef-document-status.ts"],
|
|
4
|
+
"sourcesContent": ["import type { QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport { createModuleQueue } from '@open-mercato/queue'\n\nexport const metadata: WorkerMeta = {\n queue: 'ksef_direct_check',\n id: 'ksef-direct-check',\n concurrency: 5,\n}\n\ntype HandlerContext = { resolve: <T = unknown>(name: string) => T }\n\ntype CheckJobPayload = {\n documentId: string\n organizationId: string\n tenantId: string\n attempt: number\n}\n\nexport default async function handler(\n job: QueuedJob<CheckJobPayload>,\n ctx: HandlerContext,\n): Promise<void> {\n const payload = job.payload\n const em = (ctx.resolve('em') as { fork: () => unknown })?.fork() as any\n if (!em) return\n\n const { KsefDirectDocument } = await import('../data/entities')\n const doc = await em.findOne(KsefDirectDocument, {\n id: payload.documentId,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n\n if (!doc || doc.status !== 'sending') {\n return\n }\n\n if (!doc.ksefProcessingReferenceNumber) {\n doc.status = 'failed'\n doc.errorMessage = 'Missing KSeF processing reference number'\n doc.updatedAt = new Date()\n await em.flush()\n return\n }\n\n const credentialsService = ctx.resolve('integrationCredentialsService') as any\n const rawCreds = credentialsService\n ? await credentialsService.resolve('integration_ksef_direct', {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n })\n : null\n\n const { KsefDirectCredentialsSchema } = await import('../data/validators')\n const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds)\n if (!credsParsed.success) {\n doc.status = 'failed'\n doc.errorMessage = 'KSeF Direct credentials not configured'\n doc.updatedAt = new Date()\n await em.flush()\n return\n }\n\n const credentials = {\n ksefToken: credsParsed.data.ksef_token,\n nip: credsParsed.data.nip,\n environment: credsParsed.data.environment,\n tenantId: payload.tenantId,\n }\n\n const { checkInvoiceStatus } = await import('../lib/ksefClient')\n const { emitKsefDirectEvent } = await import('../events')\n\n let result: { processingCode: number; ksefReferenceNumber?: string; errorDescription?: string }\n try {\n result = await checkInvoiceStatus(credentials, doc.ksefProcessingReferenceNumber)\n } catch (err) {\n // Re-throw transient errors so BullMQ retries; do not permanently fail the document\n throw err\n }\n\n if (result.processingCode === 200) {\n doc.status = 'sent'\n doc.updatedAt = new Date()\n await em.flush()\n\n await emitKsefDirectEvent('ksef_direct.document.sent', {\n documentId: doc.id,\n ksefReferenceNumber: doc.ksefReferenceNumber ?? undefined,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n } else if (result.processingCode === 100) {\n if (payload.attempt >= 10) {\n doc.status = 'failed'\n doc.errorMessage = 'Timeout: KSeF did not confirm the document within 10 minutes'\n doc.updatedAt = new Date()\n await em.flush()\n\n await emitKsefDirectEvent('ksef_direct.document.failed', {\n documentId: doc.id,\n errorMessage: doc.errorMessage,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n } else {\n const checkQueue = createModuleQueue<CheckJobPayload>('ksef_direct_check')\n await checkQueue.enqueue(\n { ...payload, attempt: payload.attempt + 1 },\n { delayMs: 60_000 },\n )\n }\n } else {\n const errorMessage = result.errorDescription\n ?? `KSeF processing error: code ${result.processingCode}`\n doc.status = 'failed'\n doc.errorMessage = errorMessage\n doc.ksefReferenceNumber = null\n doc.updatedAt = new Date()\n await em.flush()\n\n await emitKsefDirectEvent('ksef_direct.document.failed', {\n documentId: doc.id,\n errorMessage,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createModuleQueue } from "@open-mercato/queue";
|
|
2
|
+
const metadata = {
|
|
3
|
+
queue: "ksef_direct_send",
|
|
4
|
+
id: "ksef-direct-send",
|
|
5
|
+
concurrency: 5
|
|
6
|
+
};
|
|
7
|
+
async function handler(job, ctx) {
|
|
8
|
+
const payload = job.payload;
|
|
9
|
+
const em = ctx.resolve("em")?.fork();
|
|
10
|
+
if (!em) return;
|
|
11
|
+
const { KsefDirectDocument } = await import("../data/entities.js");
|
|
12
|
+
const doc = await em.findOne(KsefDirectDocument, {
|
|
13
|
+
id: payload.documentId,
|
|
14
|
+
organizationId: payload.organizationId,
|
|
15
|
+
tenantId: payload.tenantId
|
|
16
|
+
});
|
|
17
|
+
if (!doc || doc.status !== "queued") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (!doc.sellerName?.trim()) {
|
|
21
|
+
doc.status = "failed";
|
|
22
|
+
doc.errorMessage = "Seller name is required to send the document to KSeF";
|
|
23
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
24
|
+
await em.flush();
|
|
25
|
+
const { emitKsefDirectEvent } = await import("../events.js");
|
|
26
|
+
await emitKsefDirectEvent("ksef_direct.document.failed", {
|
|
27
|
+
documentId: doc.id,
|
|
28
|
+
errorMessage: doc.errorMessage,
|
|
29
|
+
organizationId: payload.organizationId,
|
|
30
|
+
tenantId: payload.tenantId
|
|
31
|
+
});
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const credentialsService = ctx.resolve("integrationCredentialsService");
|
|
35
|
+
const rawCreds = credentialsService ? await credentialsService.resolve("integration_ksef_direct", {
|
|
36
|
+
tenantId: payload.tenantId,
|
|
37
|
+
organizationId: payload.organizationId
|
|
38
|
+
}) : null;
|
|
39
|
+
const { KsefDirectCredentialsSchema } = await import("../data/validators.js");
|
|
40
|
+
const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds);
|
|
41
|
+
if (!credsParsed.success) {
|
|
42
|
+
doc.status = "failed";
|
|
43
|
+
doc.errorMessage = "KSeF Direct credentials not configured";
|
|
44
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
45
|
+
await em.flush();
|
|
46
|
+
const { emitKsefDirectEvent } = await import("../events.js");
|
|
47
|
+
await emitKsefDirectEvent("ksef_direct.document.failed", {
|
|
48
|
+
documentId: doc.id,
|
|
49
|
+
errorMessage: doc.errorMessage,
|
|
50
|
+
organizationId: payload.organizationId,
|
|
51
|
+
tenantId: payload.tenantId
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const creds = credsParsed.data;
|
|
56
|
+
const credentials = {
|
|
57
|
+
ksefToken: creds.ksef_token,
|
|
58
|
+
nip: creds.nip,
|
|
59
|
+
environment: creds.environment,
|
|
60
|
+
tenantId: payload.tenantId
|
|
61
|
+
};
|
|
62
|
+
try {
|
|
63
|
+
const { fetchInvoicePublicKey, prepareInvoicePayload } = await import("../lib/ksefCrypto.js");
|
|
64
|
+
const { generateFa2Xml } = await import("../lib/ksefFa2Xml.js");
|
|
65
|
+
const { sendInvoice } = await import("../lib/ksefClient.js");
|
|
66
|
+
const xml = generateFa2Xml(doc, {
|
|
67
|
+
sellerName: doc.sellerName,
|
|
68
|
+
sellerAddressL1: doc.sellerAddressL1 ?? void 0,
|
|
69
|
+
sellerCity: doc.sellerCity ?? void 0,
|
|
70
|
+
sellerCountry: doc.sellerCountry ?? void 0
|
|
71
|
+
});
|
|
72
|
+
const { publicKeyPem, publicKeyId } = await fetchInvoicePublicKey(credentials.environment);
|
|
73
|
+
const invoicePayload = prepareInvoicePayload(xml, publicKeyPem);
|
|
74
|
+
const { sessionReferenceNumber, invoiceReferenceNumber } = await sendInvoice(credentials, { ...invoicePayload, publicKeyId });
|
|
75
|
+
doc.status = "sending";
|
|
76
|
+
doc.ksefProcessingReferenceNumber = sessionReferenceNumber;
|
|
77
|
+
doc.ksefReferenceNumber = invoiceReferenceNumber;
|
|
78
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
79
|
+
await em.flush();
|
|
80
|
+
const checkQueue = createModuleQueue("ksef_direct_check");
|
|
81
|
+
await checkQueue.enqueue(
|
|
82
|
+
{ documentId: doc.id, organizationId: payload.organizationId, tenantId: payload.tenantId, attempt: 1 },
|
|
83
|
+
{ delayMs: 3e4 }
|
|
84
|
+
);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error during KSeF document send";
|
|
87
|
+
doc.status = "failed";
|
|
88
|
+
doc.errorMessage = errorMessage;
|
|
89
|
+
doc.updatedAt = /* @__PURE__ */ new Date();
|
|
90
|
+
await em.flush();
|
|
91
|
+
const { emitKsefDirectEvent } = await import("../events.js");
|
|
92
|
+
await emitKsefDirectEvent("ksef_direct.document.failed", {
|
|
93
|
+
documentId: doc.id,
|
|
94
|
+
errorMessage,
|
|
95
|
+
organizationId: payload.organizationId,
|
|
96
|
+
tenantId: payload.tenantId
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
handler as default,
|
|
102
|
+
metadata
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=send-ksef-document.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/integration_ksef_direct/workers/send-ksef-document.ts"],
|
|
4
|
+
"sourcesContent": ["import type { QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport { createModuleQueue } from '@open-mercato/queue'\n\nexport const metadata: WorkerMeta = {\n queue: 'ksef_direct_send',\n id: 'ksef-direct-send',\n concurrency: 5,\n}\n\ntype HandlerContext = { resolve: <T = unknown>(name: string) => T }\n\ntype SendJobPayload = {\n documentId: string\n organizationId: string\n tenantId: string\n}\n\ntype CheckJobPayload = {\n documentId: string\n organizationId: string\n tenantId: string\n attempt: number\n}\n\nexport default async function handler(\n job: QueuedJob<SendJobPayload>,\n ctx: HandlerContext,\n): Promise<void> {\n const payload = job.payload\n const em = (ctx.resolve('em') as { fork: () => unknown })?.fork() as any\n if (!em) return\n\n const { KsefDirectDocument } = await import('../data/entities')\n const doc = await em.findOne(KsefDirectDocument, {\n id: payload.documentId,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n\n if (!doc || doc.status !== 'queued') {\n return\n }\n\n if (!doc.sellerName?.trim()) {\n doc.status = 'failed'\n doc.errorMessage = 'Seller name is required to send the document to KSeF'\n doc.updatedAt = new Date()\n await em.flush()\n\n const { emitKsefDirectEvent } = await import('../events')\n await emitKsefDirectEvent('ksef_direct.document.failed', {\n documentId: doc.id,\n errorMessage: doc.errorMessage,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n return\n }\n\n const credentialsService = ctx.resolve('integrationCredentialsService') as any\n const rawCreds = credentialsService\n ? await credentialsService.resolve('integration_ksef_direct', {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n })\n : null\n\n const { KsefDirectCredentialsSchema } = await import('../data/validators')\n const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds)\n\n if (!credsParsed.success) {\n doc.status = 'failed'\n doc.errorMessage = 'KSeF Direct credentials not configured'\n doc.updatedAt = new Date()\n await em.flush()\n\n const { emitKsefDirectEvent } = await import('../events')\n await emitKsefDirectEvent('ksef_direct.document.failed', {\n documentId: doc.id,\n errorMessage: doc.errorMessage,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n return\n }\n\n const creds = credsParsed.data\n const credentials = {\n ksefToken: creds.ksef_token,\n nip: creds.nip,\n environment: creds.environment,\n tenantId: payload.tenantId,\n }\n\n try {\n const { fetchInvoicePublicKey, prepareInvoicePayload } = await import('../lib/ksefCrypto')\n const { generateFa2Xml } = await import('../lib/ksefFa2Xml')\n const { sendInvoice } = await import('../lib/ksefClient')\n\n const xml = generateFa2Xml(doc, {\n sellerName: doc.sellerName,\n sellerAddressL1: doc.sellerAddressL1 ?? undefined,\n sellerCity: doc.sellerCity ?? undefined,\n sellerCountry: doc.sellerCountry ?? undefined,\n })\n\n const { publicKeyPem, publicKeyId } = await fetchInvoicePublicKey(credentials.environment)\n const invoicePayload = prepareInvoicePayload(xml, publicKeyPem)\n const { sessionReferenceNumber, invoiceReferenceNumber } = await sendInvoice(credentials, { ...invoicePayload, publicKeyId })\n\n doc.status = 'sending'\n doc.ksefProcessingReferenceNumber = sessionReferenceNumber\n doc.ksefReferenceNumber = invoiceReferenceNumber\n doc.updatedAt = new Date()\n await em.flush()\n\n const checkQueue = createModuleQueue<CheckJobPayload>('ksef_direct_check')\n await checkQueue.enqueue(\n { documentId: doc.id, organizationId: payload.organizationId, tenantId: payload.tenantId, attempt: 1 },\n { delayMs: 30_000 },\n )\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Unknown error during KSeF document send'\n doc.status = 'failed'\n doc.errorMessage = errorMessage\n doc.updatedAt = new Date()\n await em.flush()\n\n const { emitKsefDirectEvent } = await import('../events')\n await emitKsefDirectEvent('ksef_direct.document.failed', {\n documentId: doc.id,\n errorMessage,\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n })\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const metadata = {
|
|
2
|
+
queue: "ksef_direct_receive",
|
|
3
|
+
id: "ksef-direct-receive",
|
|
4
|
+
concurrency: 3
|
|
5
|
+
};
|
|
6
|
+
async function handler(job, ctx) {
|
|
7
|
+
const payload = job.payload;
|
|
8
|
+
const em = ctx.resolve("em")?.fork();
|
|
9
|
+
if (!em) return;
|
|
10
|
+
const credentialsService = ctx.resolve("integrationCredentialsService");
|
|
11
|
+
const rawCreds = credentialsService ? await credentialsService.resolve("integration_ksef_direct", {
|
|
12
|
+
tenantId: payload.tenantId,
|
|
13
|
+
organizationId: payload.organizationId
|
|
14
|
+
}) : null;
|
|
15
|
+
const { KsefDirectCredentialsSchema } = await import("../data/validators.js");
|
|
16
|
+
const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds);
|
|
17
|
+
if (!credsParsed.success) return;
|
|
18
|
+
const credentials = {
|
|
19
|
+
ksefToken: credsParsed.data.ksef_token,
|
|
20
|
+
nip: credsParsed.data.nip,
|
|
21
|
+
environment: credsParsed.data.environment,
|
|
22
|
+
tenantId: payload.tenantId
|
|
23
|
+
};
|
|
24
|
+
const { queryReceivedInvoices, downloadInvoiceFromUrl } = await import("../lib/ksefClient.js");
|
|
25
|
+
const { KsefDirectReceivedDocument } = await import("../data/entities.js");
|
|
26
|
+
const { parseReceivedInvoiceXml } = await import("../lib/ksefXmlParser.js");
|
|
27
|
+
const { emitKsefDirectEvent } = await import("../events.js");
|
|
28
|
+
const result = await queryReceivedInvoices(credentials, {
|
|
29
|
+
dateFrom: payload.dateFrom,
|
|
30
|
+
dateTo: payload.dateTo
|
|
31
|
+
});
|
|
32
|
+
const collectedRefs = [];
|
|
33
|
+
for (const summary of result.items) {
|
|
34
|
+
collectedRefs.push(summary.ksefReferenceNumber);
|
|
35
|
+
const existing = await em.findOne(KsefDirectReceivedDocument, {
|
|
36
|
+
organizationId: payload.organizationId,
|
|
37
|
+
ksefReferenceNumber: summary.ksefReferenceNumber
|
|
38
|
+
});
|
|
39
|
+
if (!existing) {
|
|
40
|
+
const record = em.create(KsefDirectReceivedDocument, {
|
|
41
|
+
organizationId: payload.organizationId,
|
|
42
|
+
tenantId: payload.tenantId,
|
|
43
|
+
ksefReferenceNumber: summary.ksefReferenceNumber,
|
|
44
|
+
sellerNip: summary.sellerNip,
|
|
45
|
+
sellerName: summary.sellerName,
|
|
46
|
+
issueDate: summary.issueDate,
|
|
47
|
+
grossAmount: summary.grossAmount,
|
|
48
|
+
netAmount: summary.netAmount,
|
|
49
|
+
vatAmount: summary.vatAmount,
|
|
50
|
+
currency: summary.currency,
|
|
51
|
+
invoiceNumber: summary.invoiceNumber,
|
|
52
|
+
upoDownloadUrl: summary.upoDownloadUrl,
|
|
53
|
+
invoiceDownloadUrl: summary.invoiceDownloadUrl,
|
|
54
|
+
status: "pending_download"
|
|
55
|
+
});
|
|
56
|
+
em.persist(record);
|
|
57
|
+
} else {
|
|
58
|
+
if (summary.upoDownloadUrl) existing.upoDownloadUrl = summary.upoDownloadUrl;
|
|
59
|
+
if (summary.invoiceDownloadUrl) existing.invoiceDownloadUrl = summary.invoiceDownloadUrl;
|
|
60
|
+
if (existing.status === "failed") {
|
|
61
|
+
existing.status = "pending_download";
|
|
62
|
+
existing.errorMessage = null;
|
|
63
|
+
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
await em.flush();
|
|
68
|
+
if (collectedRefs.length === 0) {
|
|
69
|
+
await emitKsefDirectEvent("ksef_direct.received_document.synced", {
|
|
70
|
+
organizationId: payload.organizationId,
|
|
71
|
+
tenantId: payload.tenantId,
|
|
72
|
+
syncedCount: 0,
|
|
73
|
+
failedCount: 0,
|
|
74
|
+
dateFrom: payload.dateFrom,
|
|
75
|
+
dateTo: payload.dateTo
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const pendingRecords = await em.find(KsefDirectReceivedDocument, {
|
|
80
|
+
organizationId: payload.organizationId,
|
|
81
|
+
tenantId: payload.tenantId,
|
|
82
|
+
status: "pending_download",
|
|
83
|
+
ksefReferenceNumber: { $in: collectedRefs }
|
|
84
|
+
});
|
|
85
|
+
let syncedCount = 0;
|
|
86
|
+
let failedCount = 0;
|
|
87
|
+
for (const record of pendingRecords) {
|
|
88
|
+
try {
|
|
89
|
+
const downloadUrl = record.invoiceDownloadUrl ?? record.upoDownloadUrl;
|
|
90
|
+
if (!downloadUrl) {
|
|
91
|
+
throw new Error("No download URL available for this invoice");
|
|
92
|
+
}
|
|
93
|
+
const rawXml = await downloadInvoiceFromUrl(downloadUrl);
|
|
94
|
+
const parsed = parseReceivedInvoiceXml(rawXml);
|
|
95
|
+
record.rawXml = rawXml;
|
|
96
|
+
if (parsed.invoiceNumber) record.invoiceNumber = parsed.invoiceNumber;
|
|
97
|
+
if (parsed.sellerNip) record.sellerNip = parsed.sellerNip;
|
|
98
|
+
if (parsed.sellerName) record.sellerName = parsed.sellerName;
|
|
99
|
+
if (parsed.issueDate) record.issueDate = parsed.issueDate;
|
|
100
|
+
if (parsed.currency) record.currency = parsed.currency;
|
|
101
|
+
if (parsed.netAmount) record.netAmount = parsed.netAmount;
|
|
102
|
+
if (parsed.vatAmount) record.vatAmount = parsed.vatAmount;
|
|
103
|
+
if (parsed.grossAmount) record.grossAmount = parsed.grossAmount;
|
|
104
|
+
record.status = "downloaded";
|
|
105
|
+
record.syncedAt = /* @__PURE__ */ new Date();
|
|
106
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
107
|
+
await em.flush();
|
|
108
|
+
syncedCount++;
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const errorMessage = err instanceof Error ? err.message : "Unknown error during XML download";
|
|
111
|
+
record.status = "failed";
|
|
112
|
+
record.errorMessage = errorMessage;
|
|
113
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
114
|
+
await em.flush();
|
|
115
|
+
failedCount++;
|
|
116
|
+
await emitKsefDirectEvent("ksef_direct.received_document.failed", {
|
|
117
|
+
organizationId: payload.organizationId,
|
|
118
|
+
tenantId: payload.tenantId,
|
|
119
|
+
ksefReferenceNumber: record.ksefReferenceNumber,
|
|
120
|
+
errorMessage
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
await emitKsefDirectEvent("ksef_direct.received_document.synced", {
|
|
125
|
+
organizationId: payload.organizationId,
|
|
126
|
+
tenantId: payload.tenantId,
|
|
127
|
+
syncedCount,
|
|
128
|
+
failedCount,
|
|
129
|
+
dateFrom: payload.dateFrom,
|
|
130
|
+
dateTo: payload.dateTo
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
export {
|
|
134
|
+
handler as default,
|
|
135
|
+
metadata
|
|
136
|
+
};
|
|
137
|
+
//# sourceMappingURL=sync-received-documents.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/integration_ksef_direct/workers/sync-received-documents.ts"],
|
|
4
|
+
"sourcesContent": ["import type { QueuedJob, WorkerMeta } from '@open-mercato/queue'\n\nexport const metadata: WorkerMeta = {\n queue: 'ksef_direct_receive',\n id: 'ksef-direct-receive',\n concurrency: 3,\n}\n\ntype HandlerContext = { resolve: <T = unknown>(name: string) => T }\n\nexport type SyncReceivedDocumentsPayload = {\n organizationId: string\n tenantId: string\n dateFrom: string\n dateTo: string\n}\n\nexport default async function handler(\n job: QueuedJob<SyncReceivedDocumentsPayload>,\n ctx: HandlerContext,\n): Promise<void> {\n const payload = job.payload\n\n const em = (ctx.resolve('em') as { fork: () => unknown })?.fork() as any\n if (!em) return\n\n const credentialsService = ctx.resolve('integrationCredentialsService') as any\n const rawCreds = credentialsService\n ? await credentialsService.resolve('integration_ksef_direct', {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId,\n })\n : null\n const { KsefDirectCredentialsSchema } = await import('../data/validators')\n const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds)\n if (!credsParsed.success) return\n\n const credentials = {\n ksefToken: credsParsed.data.ksef_token,\n nip: credsParsed.data.nip,\n environment: credsParsed.data.environment,\n tenantId: payload.tenantId,\n }\n\n const { queryReceivedInvoices, downloadInvoiceFromUrl } = await import('../lib/ksefClient')\n const { KsefDirectReceivedDocument } = await import('../data/entities')\n const { parseReceivedInvoiceXml } = await import('../lib/ksefXmlParser')\n const { emitKsefDirectEvent } = await import('../events')\n\n // Phase 1: enumerate all received invoices for the date range and upsert summaries\n const result = await queryReceivedInvoices(credentials, {\n dateFrom: payload.dateFrom,\n dateTo: payload.dateTo,\n })\n\n const collectedRefs: string[] = []\n\n for (const summary of result.items) {\n collectedRefs.push(summary.ksefReferenceNumber)\n\n const existing = await em.findOne(KsefDirectReceivedDocument, {\n organizationId: payload.organizationId,\n ksefReferenceNumber: summary.ksefReferenceNumber,\n })\n\n if (!existing) {\n const record = em.create(KsefDirectReceivedDocument, {\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n ksefReferenceNumber: summary.ksefReferenceNumber,\n sellerNip: summary.sellerNip,\n sellerName: summary.sellerName,\n issueDate: summary.issueDate,\n grossAmount: summary.grossAmount,\n netAmount: summary.netAmount,\n vatAmount: summary.vatAmount,\n currency: summary.currency,\n invoiceNumber: summary.invoiceNumber,\n upoDownloadUrl: summary.upoDownloadUrl,\n invoiceDownloadUrl: summary.invoiceDownloadUrl,\n status: 'pending_download',\n })\n em.persist(record)\n } else {\n // Keep URL fields fresh in case they changed (pre-signed URLs rotate)\n if (summary.upoDownloadUrl) existing.upoDownloadUrl = summary.upoDownloadUrl\n if (summary.invoiceDownloadUrl) existing.invoiceDownloadUrl = summary.invoiceDownloadUrl\n if (existing.status === 'failed') {\n existing.status = 'pending_download'\n existing.errorMessage = null\n existing.updatedAt = new Date()\n }\n }\n }\n\n await em.flush()\n\n if (collectedRefs.length === 0) {\n await emitKsefDirectEvent('ksef_direct.received_document.synced', {\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n syncedCount: 0,\n failedCount: 0,\n dateFrom: payload.dateFrom,\n dateTo: payload.dateTo,\n })\n return\n }\n\n // Phase 2: download XML content for all pending records using stored URLs\n const pendingRecords = await em.find(KsefDirectReceivedDocument, {\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n status: 'pending_download',\n ksefReferenceNumber: { $in: collectedRefs },\n })\n\n let syncedCount = 0\n let failedCount = 0\n\n for (const record of pendingRecords) {\n try {\n const downloadUrl = record.invoiceDownloadUrl ?? record.upoDownloadUrl\n if (!downloadUrl) {\n throw new Error('No download URL available for this invoice')\n }\n\n const rawXml = await downloadInvoiceFromUrl(downloadUrl)\n const parsed = parseReceivedInvoiceXml(rawXml)\n\n record.rawXml = rawXml\n // Only override metadata fields with parsed values when non-null (prefer KSeF API metadata)\n if (parsed.invoiceNumber) record.invoiceNumber = parsed.invoiceNumber\n if (parsed.sellerNip) record.sellerNip = parsed.sellerNip\n if (parsed.sellerName) record.sellerName = parsed.sellerName\n if (parsed.issueDate) record.issueDate = parsed.issueDate\n if (parsed.currency) record.currency = parsed.currency\n if (parsed.netAmount) record.netAmount = parsed.netAmount\n if (parsed.vatAmount) record.vatAmount = parsed.vatAmount\n if (parsed.grossAmount) record.grossAmount = parsed.grossAmount\n record.status = 'downloaded'\n record.syncedAt = new Date()\n record.updatedAt = new Date()\n await em.flush()\n syncedCount++\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Unknown error during XML download'\n record.status = 'failed'\n record.errorMessage = errorMessage\n record.updatedAt = new Date()\n await em.flush()\n failedCount++\n\n await emitKsefDirectEvent('ksef_direct.received_document.failed', {\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n ksefReferenceNumber: record.ksefReferenceNumber,\n errorMessage,\n })\n }\n }\n\n await emitKsefDirectEvent('ksef_direct.received_document.synced', {\n organizationId: payload.organizationId,\n tenantId: payload.tenantId,\n syncedCount,\n failedCount,\n dateFrom: payload.dateFrom,\n dateTo: payload.dateTo,\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=declarations.d.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fast-white-cat/integration-ksef-direct",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "node build.mjs",
|
|
8
|
+
"watch": "node watch.mjs",
|
|
9
|
+
"test": "jest --config jest.config.cjs",
|
|
10
|
+
"typecheck": "node scripts/typecheck.mjs"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": "./dist/index.js",
|
|
14
|
+
"./*.ts": {
|
|
15
|
+
"types": "./src/*.ts",
|
|
16
|
+
"default": "./dist/*.js"
|
|
17
|
+
},
|
|
18
|
+
"./*.tsx": {
|
|
19
|
+
"types": "./src/*.tsx",
|
|
20
|
+
"default": "./dist/*.js"
|
|
21
|
+
},
|
|
22
|
+
"./*.json": "./src/*.json",
|
|
23
|
+
"./*": {
|
|
24
|
+
"types": [
|
|
25
|
+
"./src/*.ts",
|
|
26
|
+
"./src/*.tsx"
|
|
27
|
+
],
|
|
28
|
+
"default": "./dist/*.js"
|
|
29
|
+
},
|
|
30
|
+
"./*/*.json": "./src/*/*.json",
|
|
31
|
+
"./*/*": {
|
|
32
|
+
"types": [
|
|
33
|
+
"./src/*/*.ts",
|
|
34
|
+
"./src/*/*.tsx"
|
|
35
|
+
],
|
|
36
|
+
"default": "./dist/*/*.js"
|
|
37
|
+
},
|
|
38
|
+
"./*/*/*.json": "./src/*/*/*.json",
|
|
39
|
+
"./*/*/*": {
|
|
40
|
+
"types": [
|
|
41
|
+
"./src/*/*/*.ts",
|
|
42
|
+
"./src/*/*/*.tsx"
|
|
43
|
+
],
|
|
44
|
+
"default": "./dist/*/*/*. js"
|
|
45
|
+
},
|
|
46
|
+
"./*/*/*/*.json": "./src/*/*/*/*.json",
|
|
47
|
+
"./*/*/*/*": {
|
|
48
|
+
"types": [
|
|
49
|
+
"./src/*/*/*/*.ts",
|
|
50
|
+
"./src/*/*/*/*.tsx"
|
|
51
|
+
],
|
|
52
|
+
"default": "./dist/*/*/*.js"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@mikro-orm/migrations": "^6.5.9",
|
|
57
|
+
"@open-mercato/queue": ">=0.6.2 <0.7.0",
|
|
58
|
+
"@open-mercato/shared": ">=0.6.2 <0.7.0",
|
|
59
|
+
"@open-mercato/ui": ">=0.6.2 <0.7.0",
|
|
60
|
+
"@tanstack/react-table": "^8.0.0",
|
|
61
|
+
"awilix": "^10.0.0",
|
|
62
|
+
"react": "^19.0.0",
|
|
63
|
+
"react-dom": "^19.0.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@mikro-orm/decorators": "^7.1.1",
|
|
67
|
+
"@mikro-orm/migrations": "^7.1.1",
|
|
68
|
+
"@open-mercato/queue": "0.6.2",
|
|
69
|
+
"@open-mercato/shared": "0.6.2",
|
|
70
|
+
"@open-mercato/ui": "0.6.2",
|
|
71
|
+
"@tanstack/react-table": "^8.21.3",
|
|
72
|
+
"@types/jest": "^30.0.0",
|
|
73
|
+
"awilix": "^13.0.3",
|
|
74
|
+
"esbuild": "^0.25.0",
|
|
75
|
+
"glob": "^13.0.6",
|
|
76
|
+
"jest": "^30.2.0",
|
|
77
|
+
"lucide-react": "^1.16.0",
|
|
78
|
+
"next": "^16.2.6",
|
|
79
|
+
"react": "19.2.1",
|
|
80
|
+
"react-dom": "19.2.1",
|
|
81
|
+
"ts-jest": "^29.4.6",
|
|
82
|
+
"typescript": "^5.9.3",
|
|
83
|
+
"zod": "^3.25.76"
|
|
84
|
+
},
|
|
85
|
+
"open-mercato": {
|
|
86
|
+
"displayName": "KSeF Direct Integration",
|
|
87
|
+
"ejectable": true,
|
|
88
|
+
"testedCoreRange": ">=0.6.2 <0.7.0",
|
|
89
|
+
"upgradeNotesUrl": "https://github.com/FastWhiteCat/openmercato-module-ksef/releases"
|
|
90
|
+
},
|
|
91
|
+
"files": [
|
|
92
|
+
"dist",
|
|
93
|
+
"src"
|
|
94
|
+
],
|
|
95
|
+
"publishConfig": {
|
|
96
|
+
"access": "public"
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './modules/integration_ksef_direct/index'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { generateKsefInvoiceNumber } from '../lib/invoiceNumberFormat'
|
|
2
|
+
|
|
3
|
+
describe('generateKsefInvoiceNumber', () => {
|
|
4
|
+
it('returns a string in the format FV/{yyyy}/{mm}/{8 chars}', () => {
|
|
5
|
+
const result = generateKsefInvoiceNumber()
|
|
6
|
+
expect(typeof result).toBe('string')
|
|
7
|
+
expect(result).toMatch(/^FV\/\d{4}\/\d{2}\/[0-9A-Z]{8}$/)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('uses the current year in the generated number', () => {
|
|
11
|
+
const result = generateKsefInvoiceNumber()
|
|
12
|
+
const year = new Date().getFullYear().toString()
|
|
13
|
+
expect(result.startsWith(`FV/${year}/`)).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('uses the current month (zero-padded) in the generated number', () => {
|
|
17
|
+
const result = generateKsefInvoiceNumber()
|
|
18
|
+
const month = String(new Date().getMonth() + 1).padStart(2, '0')
|
|
19
|
+
const parts = result.split('/')
|
|
20
|
+
expect(parts[2]).toBe(month)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('generates an 8-character alphanumeric suffix using only uppercase letters and digits', () => {
|
|
24
|
+
const result = generateKsefInvoiceNumber()
|
|
25
|
+
const suffix = result.split('/')[3]!
|
|
26
|
+
expect(suffix).toHaveLength(8)
|
|
27
|
+
expect(suffix).toMatch(/^[0-9A-Z]+$/)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('generates unique values on successive calls', () => {
|
|
31
|
+
const results = new Set(Array.from({ length: 20 }, () => generateKsefInvoiceNumber()))
|
|
32
|
+
// With 36^8 ≈ 2.8 trillion combinations, collision in 20 draws is astronomically unlikely
|
|
33
|
+
expect(results.size).toBe(20)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('always produces exactly 4 parts when split by "/"', () => {
|
|
37
|
+
const result = generateKsefInvoiceNumber()
|
|
38
|
+
const parts = result.split('/')
|
|
39
|
+
expect(parts).toHaveLength(4)
|
|
40
|
+
expect(parts[0]).toBe('FV')
|
|
41
|
+
})
|
|
42
|
+
})
|