@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
package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import {
|
|
7
|
+
enqueueKsefDirectDocument,
|
|
8
|
+
KsefDocumentNotFoundError,
|
|
9
|
+
KsefDocumentNotQueueableError,
|
|
10
|
+
} from '../../../../../commands/enqueue-ksef-direct-document'
|
|
11
|
+
|
|
12
|
+
export const metadata = {
|
|
13
|
+
path: '/integration-ksef-direct/documents/[id]/send',
|
|
14
|
+
POST: { requireAuth: true, requireFeatures: ['integration_ksef_direct.documents.send'] },
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function POST(req: Request, { params }: { params: { id: string } }) {
|
|
18
|
+
const auth = await getAuthFromRequest(req)
|
|
19
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
20
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const container = await createRequestContainer()
|
|
24
|
+
const em = container.resolve('em') as any
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const result = await enqueueKsefDirectDocument(em, auth.tenantId, auth.orgId, params.id)
|
|
28
|
+
return NextResponse.json(result, { status: 200 })
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err instanceof KsefDocumentNotFoundError) {
|
|
31
|
+
return NextResponse.json({ error: 'Document not found' }, { status: 404 })
|
|
32
|
+
}
|
|
33
|
+
if (err instanceof KsefDocumentNotQueueableError) {
|
|
34
|
+
return NextResponse.json({ error: err.message }, { status: 409 })
|
|
35
|
+
}
|
|
36
|
+
throw err
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const openApi: OpenApiRouteDoc = {
|
|
41
|
+
tag: 'KSeF Direct',
|
|
42
|
+
summary: 'Send KSeF Direct document',
|
|
43
|
+
methods: {
|
|
44
|
+
POST: {
|
|
45
|
+
summary: 'Queue a KSeF Direct document for sending',
|
|
46
|
+
description: 'Transitions a document from draft or failed status to queued and enqueues it for KSeF submission.',
|
|
47
|
+
responses: [
|
|
48
|
+
{
|
|
49
|
+
status: 200,
|
|
50
|
+
description: 'Document queued',
|
|
51
|
+
schema: z.object({
|
|
52
|
+
id: z.string().uuid(),
|
|
53
|
+
status: z.literal('queued'),
|
|
54
|
+
invoiceNumber: z.string(),
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
errors: [
|
|
59
|
+
{ status: 404, description: 'Document not found', schema: z.object({ error: z.string() }) },
|
|
60
|
+
{ status: 409, description: 'Document cannot be queued in current status', schema: z.object({ error: z.string() }) },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
7
|
+
import { validateCrudMutationGuard, runCrudMutationGuardAfterSuccess } from '@open-mercato/shared/lib/crud/mutation-guard'
|
|
8
|
+
import { CreateKsefDirectDocumentSchema } from '../../../data/validators'
|
|
9
|
+
import { createKsefDirectDocument, KsefDirectNotConfiguredError } from '../../../commands/create-ksef-direct-document'
|
|
10
|
+
|
|
11
|
+
export const metadata = {
|
|
12
|
+
path: '/integration-ksef-direct/documents',
|
|
13
|
+
POST: { requireAuth: true, requireFeatures: ['integration_ksef_direct.documents.create'] },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function POST(req: Request) {
|
|
17
|
+
const auth = await getAuthFromRequest(req)
|
|
18
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
19
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const body = await readJsonSafe(req, {})
|
|
23
|
+
const parsed = CreateKsefDirectDocumentSchema.safeParse(body)
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'Validation failed', fieldErrors: parsed.error.flatten().fieldErrors },
|
|
27
|
+
{ status: 400 },
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const container = await createRequestContainer()
|
|
32
|
+
|
|
33
|
+
const guardResult = await validateCrudMutationGuard(container, {
|
|
34
|
+
tenantId: auth.tenantId,
|
|
35
|
+
organizationId: auth.orgId,
|
|
36
|
+
userId: auth.sub ?? '',
|
|
37
|
+
resourceKind: 'integration_ksef_direct.document',
|
|
38
|
+
resourceId: 'new',
|
|
39
|
+
operation: 'create',
|
|
40
|
+
requestMethod: 'POST',
|
|
41
|
+
requestHeaders: req.headers,
|
|
42
|
+
})
|
|
43
|
+
if (guardResult && !guardResult.ok) {
|
|
44
|
+
return NextResponse.json(guardResult.body, { status: guardResult.status })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const em = container.resolve('em') as any
|
|
48
|
+
const credentialsService = container.resolve('integrationCredentialsService') as any
|
|
49
|
+
|
|
50
|
+
let result: { id: string; status: string; invoiceNumber: string; sellerNip: string }
|
|
51
|
+
try {
|
|
52
|
+
result = await createKsefDirectDocument(em, auth.tenantId, auth.orgId, parsed.data, credentialsService)
|
|
53
|
+
} catch (err) {
|
|
54
|
+
if (err instanceof KsefDirectNotConfiguredError) {
|
|
55
|
+
return NextResponse.json(
|
|
56
|
+
{ error: 'KSeF Direct integration is not configured. Set up NIP in integration credentials first.' },
|
|
57
|
+
{ status: 422 },
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
throw err
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (guardResult?.shouldRunAfterSuccess) {
|
|
64
|
+
await runCrudMutationGuardAfterSuccess(container, {
|
|
65
|
+
tenantId: auth.tenantId,
|
|
66
|
+
organizationId: auth.orgId,
|
|
67
|
+
userId: auth.sub ?? '',
|
|
68
|
+
resourceKind: 'integration_ksef_direct.document',
|
|
69
|
+
resourceId: result.id,
|
|
70
|
+
operation: 'create',
|
|
71
|
+
requestMethod: 'POST',
|
|
72
|
+
requestHeaders: req.headers,
|
|
73
|
+
metadata: guardResult.metadata,
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return NextResponse.json(result, { status: 201 })
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const openApi: OpenApiRouteDoc = {
|
|
81
|
+
tag: 'KSeF Direct',
|
|
82
|
+
summary: 'Create KSeF Direct document',
|
|
83
|
+
methods: {
|
|
84
|
+
POST: {
|
|
85
|
+
summary: 'Create a manual KSeF Direct document',
|
|
86
|
+
description: 'Creates a document record in draft status from manually entered invoice data.',
|
|
87
|
+
requestBody: {
|
|
88
|
+
contentType: 'application/json',
|
|
89
|
+
schema: CreateKsefDirectDocumentSchema,
|
|
90
|
+
},
|
|
91
|
+
responses: [
|
|
92
|
+
{
|
|
93
|
+
status: 201,
|
|
94
|
+
description: 'Document created',
|
|
95
|
+
schema: z.object({
|
|
96
|
+
id: z.string().uuid(),
|
|
97
|
+
status: z.literal('draft'),
|
|
98
|
+
invoiceNumber: z.string(),
|
|
99
|
+
sellerNip: z.string(),
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
errors: [
|
|
104
|
+
{ status: 400, description: 'Validation error', schema: z.object({ error: z.string(), fieldErrors: z.record(z.string(), z.array(z.string())).optional() }) },
|
|
105
|
+
{ status: 422, description: 'Integration not configured', schema: z.object({ error: z.string() }) },
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
}
|
package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { generateKsefInvoiceNumber } from '../../../lib/invoiceNumberFormat'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
path: '/integration-ksef-direct/invoice-numbers',
|
|
9
|
+
POST: { requireAuth: true, requireFeatures: ['integration_ksef_direct.documents.create'] },
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function POST(req: Request) {
|
|
13
|
+
const auth = await getAuthFromRequest(req)
|
|
14
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
15
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const number = generateKsefInvoiceNumber()
|
|
19
|
+
return NextResponse.json({ number }, { status: 201 })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const openApi: OpenApiRouteDoc = {
|
|
23
|
+
tag: 'KSeF Direct',
|
|
24
|
+
summary: 'Generate KSeF invoice number',
|
|
25
|
+
methods: {
|
|
26
|
+
POST: {
|
|
27
|
+
summary: 'Generate a unique KSeF invoice number',
|
|
28
|
+
description: 'Returns a unique invoice number in FV/{yyyy}/{mm}/{id} format using a cryptographically random identifier.',
|
|
29
|
+
responses: [
|
|
30
|
+
{
|
|
31
|
+
status: 201,
|
|
32
|
+
description: 'Generated invoice number',
|
|
33
|
+
schema: z.object({
|
|
34
|
+
number: z.string(),
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
7
|
+
import { ReceivedDocumentFetchSchema } from '../../../../data/validators'
|
|
8
|
+
import { KsefNetworkError } from '../../../../lib/ksefClient'
|
|
9
|
+
|
|
10
|
+
export const metadata = {
|
|
11
|
+
path: '/integration-ksef-direct/received-documents/fetch',
|
|
12
|
+
POST: { requireAuth: true, requireFeatures: ['integration_ksef_direct.received_documents.sync'] },
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const receivedDocumentResponseSchema = z.object({
|
|
16
|
+
id: z.string().uuid(),
|
|
17
|
+
ksefReferenceNumber: z.string(),
|
|
18
|
+
invoiceNumber: z.string().nullable(),
|
|
19
|
+
sellerNip: z.string().nullable(),
|
|
20
|
+
sellerName: z.string().nullable(),
|
|
21
|
+
issueDate: z.string().nullable(),
|
|
22
|
+
currency: z.string().nullable(),
|
|
23
|
+
netAmount: z.string().nullable(),
|
|
24
|
+
vatAmount: z.string().nullable(),
|
|
25
|
+
grossAmount: z.string().nullable(),
|
|
26
|
+
rawXml: z.string(),
|
|
27
|
+
status: z.literal('downloaded'),
|
|
28
|
+
errorMessage: z.null(),
|
|
29
|
+
syncedAt: z.string(),
|
|
30
|
+
createdAt: z.string(),
|
|
31
|
+
updatedAt: z.string(),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
export async function POST(req: Request) {
|
|
35
|
+
const auth = await getAuthFromRequest(req)
|
|
36
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
37
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const body = await readJsonSafe(req, {})
|
|
41
|
+
const parsed = ReceivedDocumentFetchSchema.safeParse(body)
|
|
42
|
+
if (!parsed.success) {
|
|
43
|
+
return NextResponse.json({ error: 'Invalid request' }, { status: 400 })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const container = await createRequestContainer()
|
|
47
|
+
const credentialsService = container.resolve('integrationCredentialsService') as any
|
|
48
|
+
const rawCreds = credentialsService
|
|
49
|
+
? await credentialsService.resolve('integration_ksef_direct', {
|
|
50
|
+
tenantId: auth.tenantId,
|
|
51
|
+
organizationId: auth.orgId,
|
|
52
|
+
})
|
|
53
|
+
: null
|
|
54
|
+
|
|
55
|
+
const { KsefDirectCredentialsSchema } = await import('../../../../data/validators')
|
|
56
|
+
const credsParsed = KsefDirectCredentialsSchema.safeParse(rawCreds)
|
|
57
|
+
if (!credsParsed.success) {
|
|
58
|
+
return NextResponse.json({ error: 'KSeF credentials not configured' }, { status: 409 })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const credentials = {
|
|
62
|
+
ksefToken: credsParsed.data.ksef_token,
|
|
63
|
+
nip: credsParsed.data.nip,
|
|
64
|
+
environment: credsParsed.data.environment,
|
|
65
|
+
tenantId: auth.tenantId,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { downloadInvoice } = await import('../../../../lib/ksefClient')
|
|
69
|
+
const { parseReceivedInvoiceXml } = await import('../../../../lib/ksefXmlParser')
|
|
70
|
+
const { KsefDirectReceivedDocument } = await import('../../../../data/entities')
|
|
71
|
+
|
|
72
|
+
let rawContent: string
|
|
73
|
+
let upoDownloadUrl: string | null = null
|
|
74
|
+
let invoiceDownloadUrl: string | null = null
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const result = await downloadInvoice(credentials, parsed.data.ksefReferenceNumber)
|
|
78
|
+
rawContent = result.rawContent
|
|
79
|
+
upoDownloadUrl = result.upoDownloadUrl
|
|
80
|
+
invoiceDownloadUrl = result.invoiceDownloadUrl
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (err instanceof KsefNetworkError) {
|
|
83
|
+
return NextResponse.json({ error: `KSeF API error: ${err.message}` }, { status: 502 })
|
|
84
|
+
}
|
|
85
|
+
if (err instanceof Error && (err.name === 'TimeoutError' || err.name === 'AbortError')) {
|
|
86
|
+
return NextResponse.json({ error: 'KSeF API request timed out' }, { status: 504 })
|
|
87
|
+
}
|
|
88
|
+
throw err
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parsedXml = parseReceivedInvoiceXml(rawContent)
|
|
92
|
+
const em = container.resolve('em') as any
|
|
93
|
+
const now = new Date()
|
|
94
|
+
|
|
95
|
+
let record = await em.findOne(KsefDirectReceivedDocument, {
|
|
96
|
+
organizationId: auth.orgId,
|
|
97
|
+
ksefReferenceNumber: parsed.data.ksefReferenceNumber,
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
if (record) {
|
|
101
|
+
record.rawXml = rawContent
|
|
102
|
+
if (parsedXml.invoiceNumber) record.invoiceNumber = parsedXml.invoiceNumber
|
|
103
|
+
if (parsedXml.sellerNip) record.sellerNip = parsedXml.sellerNip
|
|
104
|
+
if (parsedXml.sellerName) record.sellerName = parsedXml.sellerName
|
|
105
|
+
if (parsedXml.issueDate) record.issueDate = parsedXml.issueDate
|
|
106
|
+
if (parsedXml.currency) record.currency = parsedXml.currency
|
|
107
|
+
if (parsedXml.netAmount) record.netAmount = parsedXml.netAmount
|
|
108
|
+
if (parsedXml.vatAmount) record.vatAmount = parsedXml.vatAmount
|
|
109
|
+
if (parsedXml.grossAmount) record.grossAmount = parsedXml.grossAmount
|
|
110
|
+
if (upoDownloadUrl) record.upoDownloadUrl = upoDownloadUrl
|
|
111
|
+
if (invoiceDownloadUrl) record.invoiceDownloadUrl = invoiceDownloadUrl
|
|
112
|
+
record.status = 'downloaded'
|
|
113
|
+
record.errorMessage = null
|
|
114
|
+
record.syncedAt = now
|
|
115
|
+
record.updatedAt = now
|
|
116
|
+
} else {
|
|
117
|
+
record = em.create(KsefDirectReceivedDocument, {
|
|
118
|
+
organizationId: auth.orgId,
|
|
119
|
+
tenantId: auth.tenantId,
|
|
120
|
+
ksefReferenceNumber: parsed.data.ksefReferenceNumber,
|
|
121
|
+
rawXml: rawContent,
|
|
122
|
+
invoiceNumber: parsedXml.invoiceNumber ?? null,
|
|
123
|
+
sellerNip: parsedXml.sellerNip ?? null,
|
|
124
|
+
sellerName: parsedXml.sellerName ?? null,
|
|
125
|
+
issueDate: parsedXml.issueDate ?? null,
|
|
126
|
+
currency: parsedXml.currency ?? null,
|
|
127
|
+
netAmount: parsedXml.netAmount ?? null,
|
|
128
|
+
vatAmount: parsedXml.vatAmount ?? null,
|
|
129
|
+
grossAmount: parsedXml.grossAmount ?? null,
|
|
130
|
+
upoDownloadUrl,
|
|
131
|
+
invoiceDownloadUrl,
|
|
132
|
+
status: 'downloaded',
|
|
133
|
+
syncedAt: now,
|
|
134
|
+
})
|
|
135
|
+
em.persist(record)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await em.flush()
|
|
139
|
+
|
|
140
|
+
return NextResponse.json({
|
|
141
|
+
id: record.id,
|
|
142
|
+
ksefReferenceNumber: record.ksefReferenceNumber,
|
|
143
|
+
invoiceNumber: record.invoiceNumber ?? null,
|
|
144
|
+
sellerNip: record.sellerNip ?? null,
|
|
145
|
+
sellerName: record.sellerName ?? null,
|
|
146
|
+
issueDate: record.issueDate ?? null,
|
|
147
|
+
currency: record.currency ?? null,
|
|
148
|
+
netAmount: record.netAmount ?? null,
|
|
149
|
+
vatAmount: record.vatAmount ?? null,
|
|
150
|
+
grossAmount: record.grossAmount ?? null,
|
|
151
|
+
rawXml: record.rawXml,
|
|
152
|
+
status: 'downloaded',
|
|
153
|
+
errorMessage: null,
|
|
154
|
+
syncedAt: record.syncedAt?.toISOString() ?? now.toISOString(),
|
|
155
|
+
createdAt: record.createdAt.toISOString(),
|
|
156
|
+
updatedAt: record.updatedAt.toISOString(),
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const openApi: OpenApiRouteDoc = {
|
|
161
|
+
tag: 'KSeF Direct',
|
|
162
|
+
summary: 'Fetch received document by KSeF reference',
|
|
163
|
+
methods: {
|
|
164
|
+
POST: {
|
|
165
|
+
summary: 'Fetch and store a single received KSeF document',
|
|
166
|
+
description: 'Downloads the FA(2) XML for a given KSeF reference number and stores it locally. Synchronous — waits for the download to complete.',
|
|
167
|
+
requestBody: {
|
|
168
|
+
contentType: 'application/json',
|
|
169
|
+
schema: ReceivedDocumentFetchSchema,
|
|
170
|
+
},
|
|
171
|
+
responses: [
|
|
172
|
+
{
|
|
173
|
+
status: 200,
|
|
174
|
+
description: 'Document fetched and stored',
|
|
175
|
+
schema: receivedDocumentResponseSchema,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
errors: [
|
|
179
|
+
{ status: 400, description: 'Invalid request', schema: z.object({ error: z.string() }) },
|
|
180
|
+
{ status: 409, description: 'KSeF credentials not configured', schema: z.object({ error: z.string() }) },
|
|
181
|
+
{ status: 502, description: 'KSeF API error', schema: z.object({ error: z.string() }) },
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
4
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
|
|
7
|
+
import { createModuleQueue } from '@open-mercato/queue'
|
|
8
|
+
import { ReceivedDocumentSyncSchema } from '../../../../data/validators'
|
|
9
|
+
import type { SyncReceivedDocumentsPayload } from '../../../../workers/sync-received-documents'
|
|
10
|
+
|
|
11
|
+
export const metadata = {
|
|
12
|
+
path: '/integration-ksef-direct/received-documents/sync',
|
|
13
|
+
POST: { requireAuth: true, requireFeatures: ['integration_ksef_direct.received_documents.sync'] },
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function POST(req: Request) {
|
|
17
|
+
const auth = await getAuthFromRequest(req)
|
|
18
|
+
if (!auth?.tenantId || !auth.orgId) {
|
|
19
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const body = await readJsonSafe(req, {})
|
|
23
|
+
const parsed = ReceivedDocumentSyncSchema.safeParse(body)
|
|
24
|
+
if (!parsed.success) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'Invalid date range', fieldErrors: parsed.error.flatten().fieldErrors },
|
|
27
|
+
{ status: 400 },
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const container = await createRequestContainer()
|
|
32
|
+
const credentialsService = container.resolve('integrationCredentialsService') as any
|
|
33
|
+
const rawCreds = credentialsService
|
|
34
|
+
? await credentialsService.resolve('integration_ksef_direct', {
|
|
35
|
+
tenantId: auth.tenantId,
|
|
36
|
+
organizationId: auth.orgId,
|
|
37
|
+
})
|
|
38
|
+
: null
|
|
39
|
+
|
|
40
|
+
const { KsefDirectCredentialsSchema } = await import('../../../../data/validators')
|
|
41
|
+
if (!KsefDirectCredentialsSchema.safeParse(rawCreds).success) {
|
|
42
|
+
return NextResponse.json(
|
|
43
|
+
{ error: 'KSeF credentials not configured' },
|
|
44
|
+
{ status: 409 },
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const queue = createModuleQueue<SyncReceivedDocumentsPayload>('ksef_direct_receive')
|
|
49
|
+
const jobId = await queue.enqueue({
|
|
50
|
+
organizationId: auth.orgId,
|
|
51
|
+
tenantId: auth.tenantId,
|
|
52
|
+
dateFrom: parsed.data.dateFrom,
|
|
53
|
+
dateTo: parsed.data.dateTo,
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
return NextResponse.json({ jobId, message: 'Sync job queued' }, { status: 202 })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const openApi: OpenApiRouteDoc = {
|
|
60
|
+
tag: 'KSeF Direct',
|
|
61
|
+
summary: 'Sync received documents',
|
|
62
|
+
methods: {
|
|
63
|
+
POST: {
|
|
64
|
+
summary: 'Trigger date-range sync of received KSeF documents',
|
|
65
|
+
description: 'Enqueues a background worker that fetches all invoices received from KSeF for the given date range.',
|
|
66
|
+
requestBody: {
|
|
67
|
+
contentType: 'application/json',
|
|
68
|
+
schema: ReceivedDocumentSyncSchema,
|
|
69
|
+
},
|
|
70
|
+
responses: [
|
|
71
|
+
{
|
|
72
|
+
status: 202,
|
|
73
|
+
description: 'Sync job queued',
|
|
74
|
+
schema: z.object({
|
|
75
|
+
jobId: z.string(),
|
|
76
|
+
message: z.string(),
|
|
77
|
+
}),
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
errors: [
|
|
81
|
+
{ status: 400, description: 'Invalid date range', schema: z.object({ error: z.string() }) },
|
|
82
|
+
{ status: 409, description: 'KSeF credentials not configured', schema: z.object({ error: z.string() }) },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
}
|