@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,94 @@
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 { KsefDirectDocument } from '../../../../data/entities'
7
+
8
+ export const metadata = {
9
+ path: '/integration-ksef-direct/documents/[id]',
10
+ GET: { requireAuth: true, requireFeatures: ['integration_ksef_direct.documents.view'] },
11
+ }
12
+
13
+ export async function GET(req: Request, { params }: { params: { id: string } }) {
14
+ const auth = await getAuthFromRequest(req)
15
+ if (!auth?.tenantId || !auth.orgId) {
16
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
17
+ }
18
+
19
+ const container = await createRequestContainer()
20
+ const em = container.resolve('em') as any
21
+
22
+ const doc = await em.findOne(KsefDirectDocument, {
23
+ id: params.id,
24
+ organizationId: auth.orgId,
25
+ tenantId: auth.tenantId,
26
+ })
27
+
28
+ if (!doc) {
29
+ return NextResponse.json({ error: 'Not found' }, { status: 404 })
30
+ }
31
+
32
+ return NextResponse.json({
33
+ id: doc.id,
34
+ source: doc.source,
35
+ status: doc.status,
36
+ invoiceNumber: doc.invoiceNumber,
37
+ sellerNip: doc.sellerNip,
38
+ buyerNip: doc.buyerNip,
39
+ buyerName: doc.buyerName ?? null,
40
+ issueDate: doc.issueDate.toISOString().split('T')[0],
41
+ saleDate: doc.saleDate ? doc.saleDate.toISOString().split('T')[0] : null,
42
+ netAmount: doc.netAmount,
43
+ vatAmount: doc.vatAmount,
44
+ grossAmount: doc.grossAmount,
45
+ currency: doc.currency,
46
+ lineItems: doc.lineItems,
47
+ notes: doc.notes ?? null,
48
+ ksefReferenceNumber: doc.ksefReferenceNumber ?? null,
49
+ errorMessage: doc.errorMessage ?? null,
50
+ createdAt: doc.createdAt.toISOString(),
51
+ updatedAt: doc.updatedAt.toISOString(),
52
+ })
53
+ }
54
+
55
+ export const openApi: OpenApiRouteDoc = {
56
+ tag: 'KSeF Direct',
57
+ summary: 'Get KSeF Direct document',
58
+ methods: {
59
+ GET: {
60
+ summary: 'Get a single KSeF Direct document',
61
+ description: 'Returns the full document record including line items.',
62
+ responses: [
63
+ {
64
+ status: 200,
65
+ description: 'Document record',
66
+ schema: z.object({
67
+ id: z.string().uuid(),
68
+ source: z.string(),
69
+ status: z.string(),
70
+ invoiceNumber: z.string(),
71
+ sellerNip: z.string(),
72
+ buyerNip: z.string(),
73
+ buyerName: z.string().nullable(),
74
+ issueDate: z.string(),
75
+ saleDate: z.string().nullable(),
76
+ netAmount: z.string(),
77
+ vatAmount: z.string(),
78
+ grossAmount: z.string(),
79
+ currency: z.string(),
80
+ lineItems: z.array(z.record(z.unknown())),
81
+ notes: z.string().nullable(),
82
+ ksefReferenceNumber: z.string().nullable(),
83
+ errorMessage: z.string().nullable(),
84
+ createdAt: z.string(),
85
+ updatedAt: z.string(),
86
+ }),
87
+ },
88
+ ],
89
+ errors: [
90
+ { status: 404, description: 'Document not found', schema: z.object({ error: z.string() }) },
91
+ ],
92
+ },
93
+ },
94
+ }
@@ -0,0 +1,111 @@
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 { KsefDirectDocument } from '../../../data/entities'
7
+
8
+ export const metadata = {
9
+ path: '/integration-ksef-direct/documents',
10
+ GET: { requireAuth: true, requireFeatures: ['integration_ksef_direct.documents.view'] },
11
+ }
12
+
13
+ const querySchema = z.object({
14
+ page: z.coerce.number().min(1).default(1),
15
+ pageSize: z.coerce.number().min(1).max(100).default(50),
16
+ status: z.string().optional(),
17
+ source: z.string().optional(),
18
+ search: z.string().optional(),
19
+ })
20
+
21
+ export async function GET(req: Request) {
22
+ const auth = await getAuthFromRequest(req)
23
+ if (!auth?.tenantId || !auth.orgId) {
24
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
25
+ }
26
+
27
+ const url = new URL(req.url)
28
+ const parsed = querySchema.safeParse(Object.fromEntries(url.searchParams.entries()))
29
+ if (!parsed.success) {
30
+ return NextResponse.json({ error: 'Invalid query' }, { status: 400 })
31
+ }
32
+ const query = parsed.data
33
+
34
+ const container = await createRequestContainer()
35
+ const em = container.resolve('em') as any
36
+
37
+ const where: Record<string, unknown> = {
38
+ organizationId: auth.orgId,
39
+ tenantId: auth.tenantId,
40
+ }
41
+
42
+ if (query.status) where.status = query.status
43
+ if (query.source) where.source = query.source
44
+
45
+ const [docs, total] = await em.findAndCount(
46
+ KsefDirectDocument,
47
+ where,
48
+ {
49
+ orderBy: { createdAt: 'DESC' },
50
+ limit: query.pageSize,
51
+ offset: (query.page - 1) * query.pageSize,
52
+ },
53
+ )
54
+
55
+ const items = docs.map((doc: KsefDirectDocument) => ({
56
+ id: doc.id,
57
+ source: doc.source,
58
+ status: doc.status,
59
+ invoiceNumber: doc.invoiceNumber,
60
+ buyerNip: doc.buyerNip,
61
+ buyerName: doc.buyerName ?? null,
62
+ issueDate: doc.issueDate.toISOString().split('T')[0],
63
+ sellerNip: doc.sellerNip,
64
+ netAmount: doc.netAmount,
65
+ vatAmount: doc.vatAmount,
66
+ grossAmount: doc.grossAmount,
67
+ currency: doc.currency,
68
+ ksefReferenceNumber: doc.ksefReferenceNumber ?? null,
69
+ createdAt: doc.createdAt.toISOString(),
70
+ }))
71
+
72
+ return NextResponse.json({ items, total, page: query.page, pageSize: query.pageSize })
73
+ }
74
+
75
+ export const openApi: OpenApiRouteDoc = {
76
+ tag: 'KSeF Direct',
77
+ summary: 'List KSeF Direct documents',
78
+ methods: {
79
+ GET: {
80
+ summary: 'List KSeF Direct documents',
81
+ description: 'Returns a paginated list of KSeF Direct document records for the current organization.',
82
+ responses: [
83
+ {
84
+ status: 200,
85
+ description: 'Paginated list',
86
+ schema: z.object({
87
+ items: z.array(z.object({
88
+ id: z.string().uuid(),
89
+ source: z.string(),
90
+ status: z.string(),
91
+ invoiceNumber: z.string(),
92
+ buyerNip: z.string(),
93
+ buyerName: z.string().nullable(),
94
+ issueDate: z.string(),
95
+ sellerNip: z.string(),
96
+ netAmount: z.string(),
97
+ vatAmount: z.string(),
98
+ grossAmount: z.string(),
99
+ currency: z.string(),
100
+ ksefReferenceNumber: z.string().nullable(),
101
+ createdAt: z.string(),
102
+ })),
103
+ total: z.number(),
104
+ page: z.number(),
105
+ pageSize: z.number(),
106
+ }),
107
+ },
108
+ ],
109
+ },
110
+ },
111
+ }
@@ -0,0 +1,194 @@
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 { verifyAccess, KsefAuthError, KsefNetworkError } from '../../../lib/ksefClient'
7
+ import type { KsefCredentials } from '../../../lib/ksefClient'
8
+ import { KsefDirectCredentialsSchema } from '../../../data/validators'
9
+
10
+ export const metadata = {
11
+ path: '/integration-ksef-direct/health',
12
+ GET: { requireAuth: true, requireFeatures: ['integration_ksef_direct.manage'] },
13
+ }
14
+
15
+ export async function GET(req: Request) {
16
+ const auth = await getAuthFromRequest(req)
17
+ if (!auth?.tenantId || !auth.orgId) {
18
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
19
+ }
20
+
21
+ const container = await createRequestContainer()
22
+ const credentialsService = container.resolve('integrationCredentialsService') as any
23
+ const em = container.resolve('em') as any
24
+
25
+ const { KsefDirectConnection } = await import('../../../data/entities')
26
+ const { emitKsefDirectEvent } = await import('../../../events')
27
+
28
+ const rawCredentials = credentialsService
29
+ ? await credentialsService.resolve('integration_ksef_direct', {
30
+ tenantId: auth.tenantId,
31
+ organizationId: auth.orgId,
32
+ })
33
+ : null
34
+
35
+ const credentialsParsed = rawCredentials
36
+ ? KsefDirectCredentialsSchema.safeParse(rawCredentials)
37
+ : null
38
+
39
+ if (!credentialsParsed?.success) {
40
+ await upsertConnection(em, KsefDirectConnection, auth.orgId, auth.tenantId, {
41
+ status: 'unconfigured',
42
+ lastCheckedAt: null,
43
+ errorMessage: null,
44
+ errorCode: null,
45
+ })
46
+ return NextResponse.json({ status: 'unconfigured', lastCheckedAt: null })
47
+ }
48
+
49
+ const credentials: KsefCredentials = {
50
+ ksefToken: credentialsParsed.data.ksef_token,
51
+ nip: credentialsParsed.data.nip,
52
+ environment: credentialsParsed.data.environment,
53
+ }
54
+
55
+ await upsertConnection(em, KsefDirectConnection, auth.orgId, auth.tenantId, {
56
+ status: 'checking',
57
+ lastCheckedAt: null,
58
+ errorMessage: null,
59
+ errorCode: null,
60
+ })
61
+
62
+ try {
63
+ const rateLimits = await verifyAccess(credentials)
64
+ const now = new Date()
65
+
66
+ await upsertConnection(em, KsefDirectConnection, auth.orgId, auth.tenantId, {
67
+ status: 'connected',
68
+ lastCheckedAt: now,
69
+ errorMessage: null,
70
+ errorCode: null,
71
+ })
72
+
73
+ await emitKsefDirectEvent('ksef_direct.connection.checked', {
74
+ organizationId: auth.orgId,
75
+ tenantId: auth.tenantId,
76
+ })
77
+ await emitKsefDirectEvent('ksef_direct.connection.connected', {
78
+ organizationId: auth.orgId,
79
+ tenantId: auth.tenantId,
80
+ })
81
+
82
+ return NextResponse.json({
83
+ status: 'connected',
84
+ lastCheckedAt: now.toISOString(),
85
+ environment: credentials.environment,
86
+ rateLimits,
87
+ })
88
+ } catch (err) {
89
+ const now = new Date()
90
+ const errorMessage = err instanceof Error ? err.message : 'Unknown error'
91
+ const errorCode =
92
+ err instanceof KsefAuthError
93
+ ? err.errorCode
94
+ : err instanceof KsefNetworkError
95
+ ? err.errorCode
96
+ : 'UNKNOWN_ERROR'
97
+
98
+ await upsertConnection(em, KsefDirectConnection, auth.orgId, auth.tenantId, {
99
+ status: 'error',
100
+ lastCheckedAt: now,
101
+ errorMessage,
102
+ errorCode,
103
+ })
104
+
105
+ await emitKsefDirectEvent('ksef_direct.connection.checked', {
106
+ organizationId: auth.orgId,
107
+ tenantId: auth.tenantId,
108
+ })
109
+ await emitKsefDirectEvent('ksef_direct.connection.failed', {
110
+ organizationId: auth.orgId,
111
+ tenantId: auth.tenantId,
112
+ })
113
+
114
+ return NextResponse.json({
115
+ status: 'error',
116
+ lastCheckedAt: now.toISOString(),
117
+ error: errorMessage,
118
+ errorCode,
119
+ })
120
+ }
121
+ }
122
+
123
+ interface UpsertPayload {
124
+ status: 'unconfigured' | 'checking' | 'connected' | 'error'
125
+ lastCheckedAt: Date | null
126
+ errorMessage: string | null
127
+ errorCode: string | null
128
+ }
129
+
130
+ async function upsertConnection(
131
+ em: any,
132
+ KsefDirectConnection: any,
133
+ organizationId: string,
134
+ tenantId: string,
135
+ payload: UpsertPayload,
136
+ ): Promise<void> {
137
+ const existing = await em.findOne(KsefDirectConnection, { organizationId, tenantId })
138
+ if (existing) {
139
+ existing.status = payload.status
140
+ existing.lastCheckedAt = payload.lastCheckedAt
141
+ existing.errorMessage = payload.errorMessage
142
+ existing.errorCode = payload.errorCode
143
+ existing.updatedAt = new Date()
144
+ await em.flush()
145
+ } else {
146
+ const connection = em.create(KsefDirectConnection, {
147
+ organizationId,
148
+ tenantId,
149
+ status: payload.status,
150
+ lastCheckedAt: payload.lastCheckedAt,
151
+ errorMessage: payload.errorMessage,
152
+ errorCode: payload.errorCode,
153
+ })
154
+ await em.persist(connection).flush()
155
+ }
156
+ }
157
+
158
+ export const openApi: OpenApiRouteDoc = {
159
+ tag: 'KSeF Direct',
160
+ summary: 'KSeF Direct health check',
161
+ methods: {
162
+ GET: {
163
+ summary: 'Check KSeF Direct connection health',
164
+ description: 'Performs a full authentication flow against the KSeF MF API v2 and returns connection status.',
165
+ responses: [
166
+ {
167
+ status: 200,
168
+ description: 'Health check result',
169
+ schema: z.union([
170
+ z.object({
171
+ status: z.literal('connected'),
172
+ lastCheckedAt: z.string(),
173
+ environment: z.enum(['test', 'production']),
174
+ rateLimits: z.object({
175
+ otherPerSecond: z.number().optional(),
176
+ otherPerMinute: z.number().optional(),
177
+ }).optional(),
178
+ }),
179
+ z.object({
180
+ status: z.literal('error'),
181
+ lastCheckedAt: z.string(),
182
+ error: z.string(),
183
+ errorCode: z.string(),
184
+ }),
185
+ z.object({
186
+ status: z.literal('unconfigured'),
187
+ lastCheckedAt: z.null(),
188
+ }),
189
+ ]),
190
+ },
191
+ ],
192
+ },
193
+ },
194
+ }
@@ -0,0 +1,88 @@
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 { KsefDirectReceivedDocument } from '../../../../data/entities'
7
+
8
+ export const metadata = {
9
+ path: '/integration-ksef-direct/received-documents/[id]',
10
+ GET: { requireAuth: true, requireFeatures: ['integration_ksef_direct.received_documents.view'] },
11
+ }
12
+
13
+ export async function GET(req: Request, { params }: { params: { id: string } }) {
14
+ const auth = await getAuthFromRequest(req)
15
+ if (!auth?.tenantId || !auth.orgId) {
16
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
17
+ }
18
+
19
+ const container = await createRequestContainer()
20
+ const em = container.resolve('em') as any
21
+
22
+ const doc = await em.findOne(KsefDirectReceivedDocument, {
23
+ id: params.id,
24
+ organizationId: auth.orgId,
25
+ tenantId: auth.tenantId,
26
+ })
27
+
28
+ if (!doc) {
29
+ return NextResponse.json({ error: 'Not found' }, { status: 404 })
30
+ }
31
+
32
+ return NextResponse.json({
33
+ id: doc.id,
34
+ ksefReferenceNumber: doc.ksefReferenceNumber,
35
+ invoiceNumber: doc.invoiceNumber ?? null,
36
+ sellerNip: doc.sellerNip ?? null,
37
+ sellerName: doc.sellerName ?? null,
38
+ issueDate: doc.issueDate ?? null,
39
+ currency: doc.currency ?? null,
40
+ netAmount: doc.netAmount ?? null,
41
+ vatAmount: doc.vatAmount ?? null,
42
+ grossAmount: doc.grossAmount ?? null,
43
+ rawXml: doc.rawXml ?? null,
44
+ status: doc.status,
45
+ errorMessage: doc.errorMessage ?? null,
46
+ syncedAt: doc.syncedAt?.toISOString() ?? null,
47
+ createdAt: doc.createdAt.toISOString(),
48
+ updatedAt: doc.updatedAt.toISOString(),
49
+ })
50
+ }
51
+
52
+ export const openApi: OpenApiRouteDoc = {
53
+ tag: 'KSeF Direct',
54
+ summary: 'Get received KSeF document',
55
+ methods: {
56
+ GET: {
57
+ summary: 'Get a single received KSeF document',
58
+ description: 'Returns the full received document record including raw FA(2) XML.',
59
+ responses: [
60
+ {
61
+ status: 200,
62
+ description: 'Document record',
63
+ schema: z.object({
64
+ id: z.string().uuid(),
65
+ ksefReferenceNumber: z.string(),
66
+ invoiceNumber: z.string().nullable(),
67
+ sellerNip: z.string().nullable(),
68
+ sellerName: z.string().nullable(),
69
+ issueDate: z.string().nullable(),
70
+ currency: z.string().nullable(),
71
+ netAmount: z.string().nullable(),
72
+ vatAmount: z.string().nullable(),
73
+ grossAmount: z.string().nullable(),
74
+ rawXml: z.string().nullable(),
75
+ status: z.string(),
76
+ errorMessage: z.string().nullable(),
77
+ syncedAt: z.string().nullable(),
78
+ createdAt: z.string(),
79
+ updatedAt: z.string(),
80
+ }),
81
+ },
82
+ ],
83
+ errors: [
84
+ { status: 404, description: 'Not found', schema: z.object({ error: z.string() }) },
85
+ ],
86
+ },
87
+ },
88
+ }
@@ -0,0 +1,119 @@
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 { KsefDirectReceivedDocument } from '../../../data/entities'
7
+
8
+ export const metadata = {
9
+ path: '/integration-ksef-direct/received-documents',
10
+ GET: { requireAuth: true, requireFeatures: ['integration_ksef_direct.received_documents.view'] },
11
+ }
12
+
13
+ const querySchema = z.object({
14
+ page: z.coerce.number().min(1).default(1),
15
+ pageSize: z.coerce.number().min(1).max(100).default(50),
16
+ status: z.string().optional(),
17
+ search: z.string().optional(),
18
+ })
19
+
20
+ export async function GET(req: Request) {
21
+ const auth = await getAuthFromRequest(req)
22
+ if (!auth?.tenantId || !auth.orgId) {
23
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
24
+ }
25
+
26
+ const url = new URL(req.url)
27
+ const parsed = querySchema.safeParse(Object.fromEntries(url.searchParams.entries()))
28
+ if (!parsed.success) {
29
+ return NextResponse.json({ error: 'Invalid query' }, { status: 400 })
30
+ }
31
+ const query = parsed.data
32
+
33
+ const container = await createRequestContainer()
34
+ const em = container.resolve('em') as any
35
+
36
+ const where: Record<string, unknown> = {
37
+ organizationId: auth.orgId,
38
+ tenantId: auth.tenantId,
39
+ }
40
+
41
+ if (query.status) where.status = query.status
42
+
43
+ if (query.search) {
44
+ const pattern = `%${query.search}%`
45
+ where.$or = [
46
+ { ksefReferenceNumber: { $ilike: pattern } },
47
+ { invoiceNumber: { $ilike: pattern } },
48
+ { sellerNip: { $ilike: pattern } },
49
+ { sellerName: { $ilike: pattern } },
50
+ ]
51
+ }
52
+
53
+ const [docs, total] = await em.findAndCount(
54
+ KsefDirectReceivedDocument,
55
+ where,
56
+ {
57
+ orderBy: { createdAt: 'DESC' },
58
+ limit: query.pageSize,
59
+ offset: (query.page - 1) * query.pageSize,
60
+ },
61
+ )
62
+
63
+ const items = docs.map((doc: KsefDirectReceivedDocument) => ({
64
+ id: doc.id,
65
+ ksefReferenceNumber: doc.ksefReferenceNumber,
66
+ invoiceNumber: doc.invoiceNumber ?? null,
67
+ sellerNip: doc.sellerNip ?? null,
68
+ sellerName: doc.sellerName ?? null,
69
+ issueDate: doc.issueDate ?? null,
70
+ currency: doc.currency ?? null,
71
+ netAmount: doc.netAmount ?? null,
72
+ vatAmount: doc.vatAmount ?? null,
73
+ grossAmount: doc.grossAmount ?? null,
74
+ status: doc.status,
75
+ errorMessage: doc.errorMessage ?? null,
76
+ syncedAt: doc.syncedAt?.toISOString() ?? null,
77
+ createdAt: doc.createdAt.toISOString(),
78
+ }))
79
+
80
+ return NextResponse.json({ items, total, page: query.page, pageSize: query.pageSize })
81
+ }
82
+
83
+ export const openApi: OpenApiRouteDoc = {
84
+ tag: 'KSeF Direct',
85
+ summary: 'List received KSeF documents',
86
+ methods: {
87
+ GET: {
88
+ summary: 'List received KSeF documents',
89
+ description: 'Returns a paginated list of received KSeF document records for the current organization.',
90
+ responses: [
91
+ {
92
+ status: 200,
93
+ description: 'Paginated list',
94
+ schema: z.object({
95
+ items: z.array(z.object({
96
+ id: z.string().uuid(),
97
+ ksefReferenceNumber: z.string(),
98
+ invoiceNumber: z.string().nullable(),
99
+ sellerNip: z.string().nullable(),
100
+ sellerName: z.string().nullable(),
101
+ issueDate: z.string().nullable(),
102
+ currency: z.string().nullable(),
103
+ netAmount: z.string().nullable(),
104
+ vatAmount: z.string().nullable(),
105
+ grossAmount: z.string().nullable(),
106
+ status: z.string(),
107
+ errorMessage: z.string().nullable(),
108
+ syncedAt: z.string().nullable(),
109
+ createdAt: z.string(),
110
+ })),
111
+ total: z.number(),
112
+ page: z.number(),
113
+ pageSize: z.number(),
114
+ }),
115
+ },
116
+ ],
117
+ },
118
+ },
119
+ }
@@ -0,0 +1,62 @@
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 { KsefDirectCredentialsSchema } from '../../../data/validators'
7
+
8
+ export const metadata = {
9
+ path: '/integration-ksef-direct/seller-info',
10
+ GET: { requireAuth: true, requireFeatures: ['integration_ksef_direct.documents.create'] },
11
+ }
12
+
13
+ export async function GET(req: Request) {
14
+ const auth = await getAuthFromRequest(req)
15
+ if (!auth?.tenantId || !auth.orgId) {
16
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
17
+ }
18
+
19
+ const container = await createRequestContainer()
20
+ const credentialsService = container.resolve('integrationCredentialsService') as any
21
+ const em = container.resolve('em') as any
22
+
23
+ const rawCredentials = credentialsService
24
+ ? await credentialsService.resolve('integration_ksef_direct', {
25
+ tenantId: auth.tenantId,
26
+ organizationId: auth.orgId,
27
+ })
28
+ : null
29
+
30
+ const credentialsParsed = rawCredentials
31
+ ? KsefDirectCredentialsSchema.safeParse(rawCredentials)
32
+ : null
33
+
34
+ const sellerNip = credentialsParsed?.success ? credentialsParsed.data.nip : null
35
+
36
+ const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')
37
+ const org = await em.findOne(Organization, { id: auth.orgId })
38
+ const sellerName = org?.name ?? null
39
+
40
+ return NextResponse.json({ sellerNip, sellerName })
41
+ }
42
+
43
+ export const openApi: OpenApiRouteDoc = {
44
+ tag: 'KSeF Direct',
45
+ summary: 'Get KSeF Direct seller info',
46
+ methods: {
47
+ GET: {
48
+ summary: 'Get seller NIP and name for the current tenant',
49
+ description: 'Returns seller NIP from KSeF credentials and seller name from the current Organization.',
50
+ responses: [
51
+ {
52
+ status: 200,
53
+ description: 'Seller info',
54
+ schema: z.object({
55
+ sellerNip: z.string().nullable(),
56
+ sellerName: z.string().nullable(),
57
+ }),
58
+ },
59
+ ],
60
+ },
61
+ },
62
+ }