@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,15 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260519210000_integration_ksef_direct extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`create table "ksef_direct_connections" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "status" text not null default 'unconfigured', "last_checked_at" timestamptz null, "error_message" text null, "error_code" text null, "created_at" timestamptz not null, "updated_at" timestamptz not null, primary key ("id"));`);
|
|
7
|
+
this.addSql(`create index "ksef_direct_connections_status_updated_at_index" on "ksef_direct_connections" ("status", "updated_at");`);
|
|
8
|
+
this.addSql(`alter table "ksef_direct_connections" add constraint "ksef_direct_connections_organization_id_tenant_id_unique" unique ("organization_id", "tenant_id");`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
override down(): void | Promise<void> {
|
|
12
|
+
this.addSql(`drop table if exists "ksef_direct_connections";`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260520120000_ksef_direct_documents extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`create table "ksef_direct_documents" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "source" text not null default 'manual', "status" text not null default 'draft', "ksef_reference_number" text null, "seller_nip" text not null, "buyer_nip" text not null, "buyer_name" text null, "invoice_number" text not null, "issue_date" timestamptz not null, "sale_date" timestamptz null, "net_amount" numeric(15,2) not null, "vat_amount" numeric(15,2) not null, "gross_amount" numeric(15,2) not null, "currency" text not null default 'PLN', "line_items" jsonb not null default '[]', "notes" text null, "error_message" text null, "created_at" timestamptz not null, "updated_at" timestamptz not null, primary key ("id"));`);
|
|
7
|
+
this.addSql(`create index "ksef_direct_documents_organization_id_tenant_id_index" on "ksef_direct_documents" ("organization_id", "tenant_id");`);
|
|
8
|
+
this.addSql(`create index "ksef_direct_documents_organization_id_tenant_id_status_index" on "ksef_direct_documents" ("organization_id", "tenant_id", "status");`);
|
|
9
|
+
this.addSql(`create index "ksef_direct_documents_organization_id_tenant_id_source_index" on "ksef_direct_documents" ("organization_id", "tenant_id", "source");`);
|
|
10
|
+
this.addSql(`create index "ksef_direct_documents_ksef_reference_number_index" on "ksef_direct_documents" ("ksef_reference_number");`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override down(): void | Promise<void> {
|
|
14
|
+
this.addSql(`drop table if exists "ksef_direct_documents";`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260520220000_ksef_direct_send_queue extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`alter table "ksef_direct_documents" add column "ksef_processing_reference_number" text null;`);
|
|
7
|
+
this.addSql(`alter table "ksef_direct_documents" add column "seller_name" text null;`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override down(): void | Promise<void> {
|
|
11
|
+
this.addSql(`alter table "ksef_direct_documents" drop column "ksef_processing_reference_number";`);
|
|
12
|
+
this.addSql(`alter table "ksef_direct_documents" drop column "seller_name";`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260520230000_ksef_direct_seller_per_document extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`alter table "ksef_direct_documents" add column "seller_address_l1" text null;`);
|
|
7
|
+
this.addSql(`alter table "ksef_direct_documents" add column "seller_city" text null;`);
|
|
8
|
+
this.addSql(`alter table "ksef_direct_documents" add column "seller_country" text null;`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
override down(): void | Promise<void> {
|
|
12
|
+
this.addSql(`alter table "ksef_direct_documents" drop column "seller_address_l1";`);
|
|
13
|
+
this.addSql(`alter table "ksef_direct_documents" drop column "seller_city";`);
|
|
14
|
+
this.addSql(`alter table "ksef_direct_documents" drop column "seller_country";`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260521120000_ksef_direct_received_documents extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`create table "ksef_direct_received_documents" ("id" uuid not null default gen_random_uuid(), "organization_id" uuid not null, "tenant_id" uuid not null, "ksef_reference_number" text not null, "raw_xml" text null, "invoice_number" text null, "seller_nip" text null, "seller_name" text null, "issue_date" date null, "currency" text null, "net_amount" numeric(15,2) null, "vat_amount" numeric(15,2) null, "gross_amount" numeric(15,2) null, "status" text not null default 'pending_download', "error_message" text null, "upo_download_url" text null, "invoice_download_url" text null, "synced_at" timestamptz null, "created_at" timestamptz not null, "updated_at" timestamptz not null, primary key ("id"));`);
|
|
7
|
+
this.addSql(`create index "ksef_direct_received_documents_organization_id_tenant_id_index" on "ksef_direct_received_documents" ("organization_id", "tenant_id");`);
|
|
8
|
+
this.addSql(`create index "ksef_direct_received_documents_organization_id_tenant_id_status_index" on "ksef_direct_received_documents" ("organization_id", "tenant_id", "status");`);
|
|
9
|
+
this.addSql(`alter table "ksef_direct_received_documents" add constraint "ksef_direct_received_documents_organization_id_ksef_reference_number_unique" unique ("organization_id", "ksef_reference_number");`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
override down(): void | Promise<void> {
|
|
13
|
+
this.addSql(`drop table if exists "ksef_direct_received_documents";`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20260521130000_ksef_received_download_urls extends Migration {
|
|
4
|
+
|
|
5
|
+
override up(): void | Promise<void> {
|
|
6
|
+
this.addSql(`alter table "ksef_direct_received_documents" add column if not exists "upo_download_url" text null;`);
|
|
7
|
+
this.addSql(`alter table "ksef_direct_received_documents" add column if not exists "invoice_download_url" text null;`);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
override down(): void | Promise<void> {
|
|
11
|
+
this.addSql(`alter table "ksef_direct_received_documents" drop column if exists "upo_download_url";`);
|
|
12
|
+
this.addSql(`alter table "ksef_direct_received_documents" drop column if exists "invoice_download_url";`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { enqueueKsefDirectDocument } from '../commands/enqueue-ksef-direct-document'
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
event: 'ksef_direct.document.created',
|
|
5
|
+
persistent: true,
|
|
6
|
+
id: 'ksef_direct.auto_enqueue_document',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default async function handler(
|
|
10
|
+
payload: { documentId: string; organizationId: string; tenantId: string },
|
|
11
|
+
ctx: { resolve: <T = unknown>(name: string) => T },
|
|
12
|
+
) {
|
|
13
|
+
const em = ctx.resolve<{ fork: () => unknown }>('em')?.fork() as any
|
|
14
|
+
if (!em) return
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
await enqueueKsefDirectDocument(em, payload.tenantId, payload.organizationId, payload.documentId)
|
|
18
|
+
} catch {
|
|
19
|
+
// If document is already queued or in a non-draft state, ignore — idempotent
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
2
|
+
import { createModuleQueue } from '@open-mercato/queue'
|
|
3
|
+
|
|
4
|
+
export const metadata: WorkerMeta = {
|
|
5
|
+
queue: 'ksef_direct_check',
|
|
6
|
+
id: 'ksef-direct-check',
|
|
7
|
+
concurrency: 5,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type HandlerContext = { resolve: <T = unknown>(name: string) => T }
|
|
11
|
+
|
|
12
|
+
type CheckJobPayload = {
|
|
13
|
+
documentId: string
|
|
14
|
+
organizationId: string
|
|
15
|
+
tenantId: string
|
|
16
|
+
attempt: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default async function handler(
|
|
20
|
+
job: QueuedJob<CheckJobPayload>,
|
|
21
|
+
ctx: HandlerContext,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const payload = job.payload
|
|
24
|
+
const em = (ctx.resolve('em') as { fork: () => unknown })?.fork() as any
|
|
25
|
+
if (!em) return
|
|
26
|
+
|
|
27
|
+
const { KsefDirectDocument } = await import('../data/entities')
|
|
28
|
+
const doc = await em.findOne(KsefDirectDocument, {
|
|
29
|
+
id: payload.documentId,
|
|
30
|
+
organizationId: payload.organizationId,
|
|
31
|
+
tenantId: payload.tenantId,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
if (!doc || doc.status !== 'sending') {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!doc.ksefProcessingReferenceNumber) {
|
|
39
|
+
doc.status = 'failed'
|
|
40
|
+
doc.errorMessage = 'Missing KSeF processing reference number'
|
|
41
|
+
doc.updatedAt = new Date()
|
|
42
|
+
await em.flush()
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const credentialsService = ctx.resolve('integrationCredentialsService') as any
|
|
47
|
+
const rawCreds = credentialsService
|
|
48
|
+
? await credentialsService.resolve('integration_ksef_direct', {
|
|
49
|
+
tenantId: payload.tenantId,
|
|
50
|
+
organizationId: payload.organizationId,
|
|
51
|
+
})
|
|
52
|
+
: null
|
|
53
|
+
|
|
54
|
+
const { KsefDirectCredentialsSchema } = await import('../data/validators')
|
|
55
|
+
const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds)
|
|
56
|
+
if (!credsParsed.success) {
|
|
57
|
+
doc.status = 'failed'
|
|
58
|
+
doc.errorMessage = 'KSeF Direct credentials not configured'
|
|
59
|
+
doc.updatedAt = new Date()
|
|
60
|
+
await em.flush()
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const credentials = {
|
|
65
|
+
ksefToken: credsParsed.data.ksef_token,
|
|
66
|
+
nip: credsParsed.data.nip,
|
|
67
|
+
environment: credsParsed.data.environment,
|
|
68
|
+
tenantId: payload.tenantId,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const { checkInvoiceStatus } = await import('../lib/ksefClient')
|
|
72
|
+
const { emitKsefDirectEvent } = await import('../events')
|
|
73
|
+
|
|
74
|
+
let result: { processingCode: number; ksefReferenceNumber?: string; errorDescription?: string }
|
|
75
|
+
try {
|
|
76
|
+
result = await checkInvoiceStatus(credentials, doc.ksefProcessingReferenceNumber)
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// Re-throw transient errors so BullMQ retries; do not permanently fail the document
|
|
79
|
+
throw err
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (result.processingCode === 200) {
|
|
83
|
+
doc.status = 'sent'
|
|
84
|
+
doc.updatedAt = new Date()
|
|
85
|
+
await em.flush()
|
|
86
|
+
|
|
87
|
+
await emitKsefDirectEvent('ksef_direct.document.sent', {
|
|
88
|
+
documentId: doc.id,
|
|
89
|
+
ksefReferenceNumber: doc.ksefReferenceNumber ?? undefined,
|
|
90
|
+
organizationId: payload.organizationId,
|
|
91
|
+
tenantId: payload.tenantId,
|
|
92
|
+
})
|
|
93
|
+
} else if (result.processingCode === 100) {
|
|
94
|
+
if (payload.attempt >= 10) {
|
|
95
|
+
doc.status = 'failed'
|
|
96
|
+
doc.errorMessage = 'Timeout: KSeF did not confirm the document within 10 minutes'
|
|
97
|
+
doc.updatedAt = new Date()
|
|
98
|
+
await em.flush()
|
|
99
|
+
|
|
100
|
+
await emitKsefDirectEvent('ksef_direct.document.failed', {
|
|
101
|
+
documentId: doc.id,
|
|
102
|
+
errorMessage: doc.errorMessage,
|
|
103
|
+
organizationId: payload.organizationId,
|
|
104
|
+
tenantId: payload.tenantId,
|
|
105
|
+
})
|
|
106
|
+
} else {
|
|
107
|
+
const checkQueue = createModuleQueue<CheckJobPayload>('ksef_direct_check')
|
|
108
|
+
await checkQueue.enqueue(
|
|
109
|
+
{ ...payload, attempt: payload.attempt + 1 },
|
|
110
|
+
{ delayMs: 60_000 },
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
const errorMessage = result.errorDescription
|
|
115
|
+
?? `KSeF processing error: code ${result.processingCode}`
|
|
116
|
+
doc.status = 'failed'
|
|
117
|
+
doc.errorMessage = errorMessage
|
|
118
|
+
doc.ksefReferenceNumber = null
|
|
119
|
+
doc.updatedAt = new Date()
|
|
120
|
+
await em.flush()
|
|
121
|
+
|
|
122
|
+
await emitKsefDirectEvent('ksef_direct.document.failed', {
|
|
123
|
+
documentId: doc.id,
|
|
124
|
+
errorMessage,
|
|
125
|
+
organizationId: payload.organizationId,
|
|
126
|
+
tenantId: payload.tenantId,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
2
|
+
import { createModuleQueue } from '@open-mercato/queue'
|
|
3
|
+
|
|
4
|
+
export const metadata: WorkerMeta = {
|
|
5
|
+
queue: 'ksef_direct_send',
|
|
6
|
+
id: 'ksef-direct-send',
|
|
7
|
+
concurrency: 5,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type HandlerContext = { resolve: <T = unknown>(name: string) => T }
|
|
11
|
+
|
|
12
|
+
type SendJobPayload = {
|
|
13
|
+
documentId: string
|
|
14
|
+
organizationId: string
|
|
15
|
+
tenantId: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type CheckJobPayload = {
|
|
19
|
+
documentId: string
|
|
20
|
+
organizationId: string
|
|
21
|
+
tenantId: string
|
|
22
|
+
attempt: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default async function handler(
|
|
26
|
+
job: QueuedJob<SendJobPayload>,
|
|
27
|
+
ctx: HandlerContext,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
const payload = job.payload
|
|
30
|
+
const em = (ctx.resolve('em') as { fork: () => unknown })?.fork() as any
|
|
31
|
+
if (!em) return
|
|
32
|
+
|
|
33
|
+
const { KsefDirectDocument } = await import('../data/entities')
|
|
34
|
+
const doc = await em.findOne(KsefDirectDocument, {
|
|
35
|
+
id: payload.documentId,
|
|
36
|
+
organizationId: payload.organizationId,
|
|
37
|
+
tenantId: payload.tenantId,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (!doc || doc.status !== 'queued') {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!doc.sellerName?.trim()) {
|
|
45
|
+
doc.status = 'failed'
|
|
46
|
+
doc.errorMessage = 'Seller name is required to send the document to KSeF'
|
|
47
|
+
doc.updatedAt = new Date()
|
|
48
|
+
await em.flush()
|
|
49
|
+
|
|
50
|
+
const { emitKsefDirectEvent } = await import('../events')
|
|
51
|
+
await emitKsefDirectEvent('ksef_direct.document.failed', {
|
|
52
|
+
documentId: doc.id,
|
|
53
|
+
errorMessage: doc.errorMessage,
|
|
54
|
+
organizationId: payload.organizationId,
|
|
55
|
+
tenantId: payload.tenantId,
|
|
56
|
+
})
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const credentialsService = ctx.resolve('integrationCredentialsService') as any
|
|
61
|
+
const rawCreds = credentialsService
|
|
62
|
+
? await credentialsService.resolve('integration_ksef_direct', {
|
|
63
|
+
tenantId: payload.tenantId,
|
|
64
|
+
organizationId: payload.organizationId,
|
|
65
|
+
})
|
|
66
|
+
: null
|
|
67
|
+
|
|
68
|
+
const { KsefDirectCredentialsSchema } = await import('../data/validators')
|
|
69
|
+
const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds)
|
|
70
|
+
|
|
71
|
+
if (!credsParsed.success) {
|
|
72
|
+
doc.status = 'failed'
|
|
73
|
+
doc.errorMessage = 'KSeF Direct credentials not configured'
|
|
74
|
+
doc.updatedAt = new Date()
|
|
75
|
+
await em.flush()
|
|
76
|
+
|
|
77
|
+
const { emitKsefDirectEvent } = await import('../events')
|
|
78
|
+
await emitKsefDirectEvent('ksef_direct.document.failed', {
|
|
79
|
+
documentId: doc.id,
|
|
80
|
+
errorMessage: doc.errorMessage,
|
|
81
|
+
organizationId: payload.organizationId,
|
|
82
|
+
tenantId: payload.tenantId,
|
|
83
|
+
})
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const creds = credsParsed.data
|
|
88
|
+
const credentials = {
|
|
89
|
+
ksefToken: creds.ksef_token,
|
|
90
|
+
nip: creds.nip,
|
|
91
|
+
environment: creds.environment,
|
|
92
|
+
tenantId: payload.tenantId,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const { fetchInvoicePublicKey, prepareInvoicePayload } = await import('../lib/ksefCrypto')
|
|
97
|
+
const { generateFa2Xml } = await import('../lib/ksefFa2Xml')
|
|
98
|
+
const { sendInvoice } = await import('../lib/ksefClient')
|
|
99
|
+
|
|
100
|
+
const xml = generateFa2Xml(doc, {
|
|
101
|
+
sellerName: doc.sellerName,
|
|
102
|
+
sellerAddressL1: doc.sellerAddressL1 ?? undefined,
|
|
103
|
+
sellerCity: doc.sellerCity ?? undefined,
|
|
104
|
+
sellerCountry: doc.sellerCountry ?? undefined,
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
const { publicKeyPem, publicKeyId } = await fetchInvoicePublicKey(credentials.environment)
|
|
108
|
+
const invoicePayload = prepareInvoicePayload(xml, publicKeyPem)
|
|
109
|
+
const { sessionReferenceNumber, invoiceReferenceNumber } = await sendInvoice(credentials, { ...invoicePayload, publicKeyId })
|
|
110
|
+
|
|
111
|
+
doc.status = 'sending'
|
|
112
|
+
doc.ksefProcessingReferenceNumber = sessionReferenceNumber
|
|
113
|
+
doc.ksefReferenceNumber = invoiceReferenceNumber
|
|
114
|
+
doc.updatedAt = new Date()
|
|
115
|
+
await em.flush()
|
|
116
|
+
|
|
117
|
+
const checkQueue = createModuleQueue<CheckJobPayload>('ksef_direct_check')
|
|
118
|
+
await checkQueue.enqueue(
|
|
119
|
+
{ documentId: doc.id, organizationId: payload.organizationId, tenantId: payload.tenantId, attempt: 1 },
|
|
120
|
+
{ delayMs: 30_000 },
|
|
121
|
+
)
|
|
122
|
+
} catch (err) {
|
|
123
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error during KSeF document send'
|
|
124
|
+
doc.status = 'failed'
|
|
125
|
+
doc.errorMessage = errorMessage
|
|
126
|
+
doc.updatedAt = new Date()
|
|
127
|
+
await em.flush()
|
|
128
|
+
|
|
129
|
+
const { emitKsefDirectEvent } = await import('../events')
|
|
130
|
+
await emitKsefDirectEvent('ksef_direct.document.failed', {
|
|
131
|
+
documentId: doc.id,
|
|
132
|
+
errorMessage,
|
|
133
|
+
organizationId: payload.organizationId,
|
|
134
|
+
tenantId: payload.tenantId,
|
|
135
|
+
})
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { QueuedJob, WorkerMeta } from '@open-mercato/queue'
|
|
2
|
+
|
|
3
|
+
export const metadata: WorkerMeta = {
|
|
4
|
+
queue: 'ksef_direct_receive',
|
|
5
|
+
id: 'ksef-direct-receive',
|
|
6
|
+
concurrency: 3,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type HandlerContext = { resolve: <T = unknown>(name: string) => T }
|
|
10
|
+
|
|
11
|
+
export type SyncReceivedDocumentsPayload = {
|
|
12
|
+
organizationId: string
|
|
13
|
+
tenantId: string
|
|
14
|
+
dateFrom: string
|
|
15
|
+
dateTo: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default async function handler(
|
|
19
|
+
job: QueuedJob<SyncReceivedDocumentsPayload>,
|
|
20
|
+
ctx: HandlerContext,
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const payload = job.payload
|
|
23
|
+
|
|
24
|
+
const em = (ctx.resolve('em') as { fork: () => unknown })?.fork() as any
|
|
25
|
+
if (!em) return
|
|
26
|
+
|
|
27
|
+
const credentialsService = ctx.resolve('integrationCredentialsService') as any
|
|
28
|
+
const rawCreds = credentialsService
|
|
29
|
+
? await credentialsService.resolve('integration_ksef_direct', {
|
|
30
|
+
tenantId: payload.tenantId,
|
|
31
|
+
organizationId: payload.organizationId,
|
|
32
|
+
})
|
|
33
|
+
: null
|
|
34
|
+
const { KsefDirectCredentialsSchema } = await import('../data/validators')
|
|
35
|
+
const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds)
|
|
36
|
+
if (!credsParsed.success) return
|
|
37
|
+
|
|
38
|
+
const credentials = {
|
|
39
|
+
ksefToken: credsParsed.data.ksef_token,
|
|
40
|
+
nip: credsParsed.data.nip,
|
|
41
|
+
environment: credsParsed.data.environment,
|
|
42
|
+
tenantId: payload.tenantId,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const { queryReceivedInvoices, downloadInvoiceFromUrl } = await import('../lib/ksefClient')
|
|
46
|
+
const { KsefDirectReceivedDocument } = await import('../data/entities')
|
|
47
|
+
const { parseReceivedInvoiceXml } = await import('../lib/ksefXmlParser')
|
|
48
|
+
const { emitKsefDirectEvent } = await import('../events')
|
|
49
|
+
|
|
50
|
+
// Phase 1: enumerate all received invoices for the date range and upsert summaries
|
|
51
|
+
const result = await queryReceivedInvoices(credentials, {
|
|
52
|
+
dateFrom: payload.dateFrom,
|
|
53
|
+
dateTo: payload.dateTo,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
const collectedRefs: string[] = []
|
|
57
|
+
|
|
58
|
+
for (const summary of result.items) {
|
|
59
|
+
collectedRefs.push(summary.ksefReferenceNumber)
|
|
60
|
+
|
|
61
|
+
const existing = await em.findOne(KsefDirectReceivedDocument, {
|
|
62
|
+
organizationId: payload.organizationId,
|
|
63
|
+
ksefReferenceNumber: summary.ksefReferenceNumber,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
if (!existing) {
|
|
67
|
+
const record = em.create(KsefDirectReceivedDocument, {
|
|
68
|
+
organizationId: payload.organizationId,
|
|
69
|
+
tenantId: payload.tenantId,
|
|
70
|
+
ksefReferenceNumber: summary.ksefReferenceNumber,
|
|
71
|
+
sellerNip: summary.sellerNip,
|
|
72
|
+
sellerName: summary.sellerName,
|
|
73
|
+
issueDate: summary.issueDate,
|
|
74
|
+
grossAmount: summary.grossAmount,
|
|
75
|
+
netAmount: summary.netAmount,
|
|
76
|
+
vatAmount: summary.vatAmount,
|
|
77
|
+
currency: summary.currency,
|
|
78
|
+
invoiceNumber: summary.invoiceNumber,
|
|
79
|
+
upoDownloadUrl: summary.upoDownloadUrl,
|
|
80
|
+
invoiceDownloadUrl: summary.invoiceDownloadUrl,
|
|
81
|
+
status: 'pending_download',
|
|
82
|
+
})
|
|
83
|
+
em.persist(record)
|
|
84
|
+
} else {
|
|
85
|
+
// Keep URL fields fresh in case they changed (pre-signed URLs rotate)
|
|
86
|
+
if (summary.upoDownloadUrl) existing.upoDownloadUrl = summary.upoDownloadUrl
|
|
87
|
+
if (summary.invoiceDownloadUrl) existing.invoiceDownloadUrl = summary.invoiceDownloadUrl
|
|
88
|
+
if (existing.status === 'failed') {
|
|
89
|
+
existing.status = 'pending_download'
|
|
90
|
+
existing.errorMessage = null
|
|
91
|
+
existing.updatedAt = new Date()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
await em.flush()
|
|
97
|
+
|
|
98
|
+
if (collectedRefs.length === 0) {
|
|
99
|
+
await emitKsefDirectEvent('ksef_direct.received_document.synced', {
|
|
100
|
+
organizationId: payload.organizationId,
|
|
101
|
+
tenantId: payload.tenantId,
|
|
102
|
+
syncedCount: 0,
|
|
103
|
+
failedCount: 0,
|
|
104
|
+
dateFrom: payload.dateFrom,
|
|
105
|
+
dateTo: payload.dateTo,
|
|
106
|
+
})
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Phase 2: download XML content for all pending records using stored URLs
|
|
111
|
+
const pendingRecords = await em.find(KsefDirectReceivedDocument, {
|
|
112
|
+
organizationId: payload.organizationId,
|
|
113
|
+
tenantId: payload.tenantId,
|
|
114
|
+
status: 'pending_download',
|
|
115
|
+
ksefReferenceNumber: { $in: collectedRefs },
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
let syncedCount = 0
|
|
119
|
+
let failedCount = 0
|
|
120
|
+
|
|
121
|
+
for (const record of pendingRecords) {
|
|
122
|
+
try {
|
|
123
|
+
const downloadUrl = record.invoiceDownloadUrl ?? record.upoDownloadUrl
|
|
124
|
+
if (!downloadUrl) {
|
|
125
|
+
throw new Error('No download URL available for this invoice')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const rawXml = await downloadInvoiceFromUrl(downloadUrl)
|
|
129
|
+
const parsed = parseReceivedInvoiceXml(rawXml)
|
|
130
|
+
|
|
131
|
+
record.rawXml = rawXml
|
|
132
|
+
// Only override metadata fields with parsed values when non-null (prefer KSeF API metadata)
|
|
133
|
+
if (parsed.invoiceNumber) record.invoiceNumber = parsed.invoiceNumber
|
|
134
|
+
if (parsed.sellerNip) record.sellerNip = parsed.sellerNip
|
|
135
|
+
if (parsed.sellerName) record.sellerName = parsed.sellerName
|
|
136
|
+
if (parsed.issueDate) record.issueDate = parsed.issueDate
|
|
137
|
+
if (parsed.currency) record.currency = parsed.currency
|
|
138
|
+
if (parsed.netAmount) record.netAmount = parsed.netAmount
|
|
139
|
+
if (parsed.vatAmount) record.vatAmount = parsed.vatAmount
|
|
140
|
+
if (parsed.grossAmount) record.grossAmount = parsed.grossAmount
|
|
141
|
+
record.status = 'downloaded'
|
|
142
|
+
record.syncedAt = new Date()
|
|
143
|
+
record.updatedAt = new Date()
|
|
144
|
+
await em.flush()
|
|
145
|
+
syncedCount++
|
|
146
|
+
} catch (err) {
|
|
147
|
+
const errorMessage = err instanceof Error ? err.message : 'Unknown error during XML download'
|
|
148
|
+
record.status = 'failed'
|
|
149
|
+
record.errorMessage = errorMessage
|
|
150
|
+
record.updatedAt = new Date()
|
|
151
|
+
await em.flush()
|
|
152
|
+
failedCount++
|
|
153
|
+
|
|
154
|
+
await emitKsefDirectEvent('ksef_direct.received_document.failed', {
|
|
155
|
+
organizationId: payload.organizationId,
|
|
156
|
+
tenantId: payload.tenantId,
|
|
157
|
+
ksefReferenceNumber: record.ksefReferenceNumber,
|
|
158
|
+
errorMessage,
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
await emitKsefDirectEvent('ksef_direct.received_document.synced', {
|
|
164
|
+
organizationId: payload.organizationId,
|
|
165
|
+
tenantId: payload.tenantId,
|
|
166
|
+
syncedCount,
|
|
167
|
+
failedCount,
|
|
168
|
+
dateFrom: payload.dateFrom,
|
|
169
|
+
dateTo: payload.dateTo,
|
|
170
|
+
})
|
|
171
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
declare module '@open-mercato/core/*'
|