@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.
Files changed (140) hide show
  1. package/README.md +36 -0
  2. package/dist/index.js +2 -0
  3. package/dist/index.js.map +7 -0
  4. package/dist/modules/integration_ksef_direct/acl.js +13 -0
  5. package/dist/modules/integration_ksef_direct/acl.js.map +7 -0
  6. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].js +92 -0
  7. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].js.map +7 -0
  8. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.js +105 -0
  9. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.js.map +7 -0
  10. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.js +158 -0
  11. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.js.map +7 -0
  12. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].js +86 -0
  13. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].js.map +7 -0
  14. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.js +112 -0
  15. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.js.map +7 -0
  16. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.js +54 -0
  17. package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.js.map +7 -0
  18. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.js +64 -0
  19. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.js.map +7 -0
  20. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.js +104 -0
  21. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.js.map +7 -0
  22. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.js +41 -0
  23. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.js.map +7 -0
  24. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.js +172 -0
  25. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.js.map +7 -0
  26. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.js +80 -0
  27. package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.js.map +7 -0
  28. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.js +441 -0
  29. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.js.map +7 -0
  30. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.js +8 -0
  31. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.js.map +7 -0
  32. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.js +193 -0
  33. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.js.map +7 -0
  34. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.js +314 -0
  35. package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.js.map +7 -0
  36. package/dist/modules/integration_ksef_direct/backend/page.js +154 -0
  37. package/dist/modules/integration_ksef_direct/backend/page.js.map +7 -0
  38. package/dist/modules/integration_ksef_direct/commands/create-ksef-direct-document.js +80 -0
  39. package/dist/modules/integration_ksef_direct/commands/create-ksef-direct-document.js.map +7 -0
  40. package/dist/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.js +43 -0
  41. package/dist/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.js.map +7 -0
  42. package/dist/modules/integration_ksef_direct/data/entities.js +224 -0
  43. package/dist/modules/integration_ksef_direct/data/entities.js.map +7 -0
  44. package/dist/modules/integration_ksef_direct/data/validators.js +103 -0
  45. package/dist/modules/integration_ksef_direct/data/validators.js.map +7 -0
  46. package/dist/modules/integration_ksef_direct/di.js +11 -0
  47. package/dist/modules/integration_ksef_direct/di.js.map +7 -0
  48. package/dist/modules/integration_ksef_direct/events.js +21 -0
  49. package/dist/modules/integration_ksef_direct/events.js.map +7 -0
  50. package/dist/modules/integration_ksef_direct/index.js +10 -0
  51. package/dist/modules/integration_ksef_direct/index.js.map +7 -0
  52. package/dist/modules/integration_ksef_direct/integration.js +56 -0
  53. package/dist/modules/integration_ksef_direct/integration.js.map +7 -0
  54. package/dist/modules/integration_ksef_direct/lib/health.js +32 -0
  55. package/dist/modules/integration_ksef_direct/lib/health.js.map +7 -0
  56. package/dist/modules/integration_ksef_direct/lib/invoiceNumberFormat.js +23 -0
  57. package/dist/modules/integration_ksef_direct/lib/invoiceNumberFormat.js.map +7 -0
  58. package/dist/modules/integration_ksef_direct/lib/ksefClient.js +523 -0
  59. package/dist/modules/integration_ksef_direct/lib/ksefClient.js.map +7 -0
  60. package/dist/modules/integration_ksef_direct/lib/ksefCrypto.js +103 -0
  61. package/dist/modules/integration_ksef_direct/lib/ksefCrypto.js.map +7 -0
  62. package/dist/modules/integration_ksef_direct/lib/ksefFa2Xml.js +123 -0
  63. package/dist/modules/integration_ksef_direct/lib/ksefFa2Xml.js.map +7 -0
  64. package/dist/modules/integration_ksef_direct/lib/ksefXmlParser.js +76 -0
  65. package/dist/modules/integration_ksef_direct/lib/ksefXmlParser.js.map +7 -0
  66. package/dist/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.js +15 -0
  67. package/dist/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.js.map +7 -0
  68. package/dist/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.js +17 -0
  69. package/dist/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.js.map +7 -0
  70. package/dist/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.js +15 -0
  71. package/dist/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.js.map +7 -0
  72. package/dist/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.js +17 -0
  73. package/dist/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.js.map +7 -0
  74. package/dist/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.js +16 -0
  75. package/dist/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.js.map +7 -0
  76. package/dist/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.js +15 -0
  77. package/dist/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.js.map +7 -0
  78. package/dist/modules/integration_ksef_direct/setup.js +11 -0
  79. package/dist/modules/integration_ksef_direct/setup.js.map +7 -0
  80. package/dist/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.js +19 -0
  81. package/dist/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.js.map +7 -0
  82. package/dist/modules/integration_ksef_direct/workers/check-ksef-document-status.js +103 -0
  83. package/dist/modules/integration_ksef_direct/workers/check-ksef-document-status.js.map +7 -0
  84. package/dist/modules/integration_ksef_direct/workers/send-ksef-document.js +104 -0
  85. package/dist/modules/integration_ksef_direct/workers/send-ksef-document.js.map +7 -0
  86. package/dist/modules/integration_ksef_direct/workers/sync-received-documents.js +137 -0
  87. package/dist/modules/integration_ksef_direct/workers/sync-received-documents.js.map +7 -0
  88. package/dist/types/declarations.d.js +1 -0
  89. package/dist/types/declarations.d.js.map +7 -0
  90. package/package.json +98 -0
  91. package/src/index.ts +1 -0
  92. package/src/modules/integration_ksef_direct/__tests__/invoiceNumberFormat.test.ts +42 -0
  93. package/src/modules/integration_ksef_direct/__tests__/ksefFa2Xml.test.ts +407 -0
  94. package/src/modules/integration_ksef_direct/__tests__/ksefXmlParser.test.ts +230 -0
  95. package/src/modules/integration_ksef_direct/acl.ts +9 -0
  96. package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].ts +94 -0
  97. package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.ts +111 -0
  98. package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.ts +194 -0
  99. package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].ts +88 -0
  100. package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.ts +119 -0
  101. package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.ts +62 -0
  102. package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.ts +64 -0
  103. package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.ts +109 -0
  104. package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.ts +40 -0
  105. package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.ts +185 -0
  106. package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.ts +86 -0
  107. package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.ts +4 -0
  108. package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.tsx +470 -0
  109. package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.tsx +233 -0
  110. package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.tsx +415 -0
  111. package/src/modules/integration_ksef_direct/backend/page.tsx +183 -0
  112. package/src/modules/integration_ksef_direct/commands/create-ksef-direct-document.ts +93 -0
  113. package/src/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.ts +57 -0
  114. package/src/modules/integration_ksef_direct/data/entities.ts +195 -0
  115. package/src/modules/integration_ksef_direct/data/validators.ts +115 -0
  116. package/src/modules/integration_ksef_direct/di.ts +9 -0
  117. package/src/modules/integration_ksef_direct/events.ts +18 -0
  118. package/src/modules/integration_ksef_direct/i18n/en.json +115 -0
  119. package/src/modules/integration_ksef_direct/i18n/pl.json +115 -0
  120. package/src/modules/integration_ksef_direct/index.ts +6 -0
  121. package/src/modules/integration_ksef_direct/integration.ts +54 -0
  122. package/src/modules/integration_ksef_direct/lib/health.ts +43 -0
  123. package/src/modules/integration_ksef_direct/lib/invoiceNumberFormat.ts +23 -0
  124. package/src/modules/integration_ksef_direct/lib/ksefClient.ts +668 -0
  125. package/src/modules/integration_ksef_direct/lib/ksefCrypto.ts +138 -0
  126. package/src/modules/integration_ksef_direct/lib/ksefFa2Xml.ts +147 -0
  127. package/src/modules/integration_ksef_direct/lib/ksefXmlParser.ts +97 -0
  128. package/src/modules/integration_ksef_direct/migrations/.snapshot-open-mercato.json +1028 -0
  129. package/src/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.ts +15 -0
  130. package/src/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.ts +17 -0
  131. package/src/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.ts +15 -0
  132. package/src/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.ts +17 -0
  133. package/src/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.ts +16 -0
  134. package/src/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.ts +15 -0
  135. package/src/modules/integration_ksef_direct/setup.ts +9 -0
  136. package/src/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.ts +21 -0
  137. package/src/modules/integration_ksef_direct/workers/check-ksef-document-status.ts +129 -0
  138. package/src/modules/integration_ksef_direct/workers/send-ksef-document.ts +137 -0
  139. package/src/modules/integration_ksef_direct/workers/sync-received-documents.ts +171 -0
  140. package/src/types/declarations.d.ts +1 -0
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ export const metadata = {
2
+ navHidden: true,
3
+ requireFeatures: ['integration_ksef_direct.documents.create'],
4
+ }