@fast-white-cat/integration-ksef-direct 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +7 -0
- package/dist/modules/integration_ksef_direct/acl.js +13 -0
- package/dist/modules/integration_ksef_direct/acl.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].js +92 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.js +105 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.js +158 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].js +86 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.js +112 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.js +54 -0
- package/dist/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.js +64 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.js +104 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.js +41 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.js +172 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.js.map +7 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.js +80 -0
- package/dist/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.js +441 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.js +8 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.js +193 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.js +314 -0
- package/dist/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/backend/page.js +154 -0
- package/dist/modules/integration_ksef_direct/backend/page.js.map +7 -0
- package/dist/modules/integration_ksef_direct/commands/create-ksef-direct-document.js +80 -0
- package/dist/modules/integration_ksef_direct/commands/create-ksef-direct-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.js +43 -0
- package/dist/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/data/entities.js +224 -0
- package/dist/modules/integration_ksef_direct/data/entities.js.map +7 -0
- package/dist/modules/integration_ksef_direct/data/validators.js +103 -0
- package/dist/modules/integration_ksef_direct/data/validators.js.map +7 -0
- package/dist/modules/integration_ksef_direct/di.js +11 -0
- package/dist/modules/integration_ksef_direct/di.js.map +7 -0
- package/dist/modules/integration_ksef_direct/events.js +21 -0
- package/dist/modules/integration_ksef_direct/events.js.map +7 -0
- package/dist/modules/integration_ksef_direct/index.js +10 -0
- package/dist/modules/integration_ksef_direct/index.js.map +7 -0
- package/dist/modules/integration_ksef_direct/integration.js +56 -0
- package/dist/modules/integration_ksef_direct/integration.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/health.js +32 -0
- package/dist/modules/integration_ksef_direct/lib/health.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/invoiceNumberFormat.js +23 -0
- package/dist/modules/integration_ksef_direct/lib/invoiceNumberFormat.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefClient.js +523 -0
- package/dist/modules/integration_ksef_direct/lib/ksefClient.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefCrypto.js +103 -0
- package/dist/modules/integration_ksef_direct/lib/ksefCrypto.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefFa2Xml.js +123 -0
- package/dist/modules/integration_ksef_direct/lib/ksefFa2Xml.js.map +7 -0
- package/dist/modules/integration_ksef_direct/lib/ksefXmlParser.js +76 -0
- package/dist/modules/integration_ksef_direct/lib/ksefXmlParser.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.js +15 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.js +17 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.js +15 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.js +17 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.js +16 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.js.map +7 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.js +15 -0
- package/dist/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.js.map +7 -0
- package/dist/modules/integration_ksef_direct/setup.js +11 -0
- package/dist/modules/integration_ksef_direct/setup.js.map +7 -0
- package/dist/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.js +19 -0
- package/dist/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/workers/check-ksef-document-status.js +103 -0
- package/dist/modules/integration_ksef_direct/workers/check-ksef-document-status.js.map +7 -0
- package/dist/modules/integration_ksef_direct/workers/send-ksef-document.js +104 -0
- package/dist/modules/integration_ksef_direct/workers/send-ksef-document.js.map +7 -0
- package/dist/modules/integration_ksef_direct/workers/sync-received-documents.js +137 -0
- package/dist/modules/integration_ksef_direct/workers/sync-received-documents.js.map +7 -0
- package/dist/types/declarations.d.js +1 -0
- package/dist/types/declarations.d.js.map +7 -0
- package/package.json +98 -0
- package/src/index.ts +1 -0
- package/src/modules/integration_ksef_direct/__tests__/invoiceNumberFormat.test.ts +42 -0
- package/src/modules/integration_ksef_direct/__tests__/ksefFa2Xml.test.ts +407 -0
- package/src/modules/integration_ksef_direct/__tests__/ksefXmlParser.test.ts +230 -0
- package/src/modules/integration_ksef_direct/acl.ts +9 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents/[id].ts +94 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/documents.ts +111 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/health.ts +194 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents/[id].ts +88 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/received-documents.ts +119 -0
- package/src/modules/integration_ksef_direct/api/get/integration-ksef-direct/seller-info.ts +62 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents/[id]/send.ts +64 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/documents.ts +109 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/invoice-numbers.ts +40 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/fetch.ts +185 -0
- package/src/modules/integration_ksef_direct/api/post/integration-ksef-direct/received-documents/sync.ts +86 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.meta.ts +4 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/new/page.tsx +470 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/documents/page.tsx +233 -0
- package/src/modules/integration_ksef_direct/backend/integration-ksef-direct/received-documents/page.tsx +415 -0
- package/src/modules/integration_ksef_direct/backend/page.tsx +183 -0
- package/src/modules/integration_ksef_direct/commands/create-ksef-direct-document.ts +93 -0
- package/src/modules/integration_ksef_direct/commands/enqueue-ksef-direct-document.ts +57 -0
- package/src/modules/integration_ksef_direct/data/entities.ts +195 -0
- package/src/modules/integration_ksef_direct/data/validators.ts +115 -0
- package/src/modules/integration_ksef_direct/di.ts +9 -0
- package/src/modules/integration_ksef_direct/events.ts +18 -0
- package/src/modules/integration_ksef_direct/i18n/en.json +115 -0
- package/src/modules/integration_ksef_direct/i18n/pl.json +115 -0
- package/src/modules/integration_ksef_direct/index.ts +6 -0
- package/src/modules/integration_ksef_direct/integration.ts +54 -0
- package/src/modules/integration_ksef_direct/lib/health.ts +43 -0
- package/src/modules/integration_ksef_direct/lib/invoiceNumberFormat.ts +23 -0
- package/src/modules/integration_ksef_direct/lib/ksefClient.ts +668 -0
- package/src/modules/integration_ksef_direct/lib/ksefCrypto.ts +138 -0
- package/src/modules/integration_ksef_direct/lib/ksefFa2Xml.ts +147 -0
- package/src/modules/integration_ksef_direct/lib/ksefXmlParser.ts +97 -0
- package/src/modules/integration_ksef_direct/migrations/.snapshot-open-mercato.json +1028 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260519210000_integration_ksef_direct.ts +15 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260520120000_ksef_direct_documents.ts +17 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260520220000_ksef_direct_send_queue.ts +15 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260520230000_ksef_direct_seller_per_document.ts +17 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260521120000_ksef_direct_received_documents.ts +16 -0
- package/src/modules/integration_ksef_direct/migrations/Migration20260521130000_ksef_received_download_urls.ts +15 -0
- package/src/modules/integration_ksef_direct/setup.ts +9 -0
- package/src/modules/integration_ksef_direct/subscribers/auto-enqueue-ksef-document.ts +21 -0
- package/src/modules/integration_ksef_direct/workers/check-ksef-document-status.ts +129 -0
- package/src/modules/integration_ksef_direct/workers/send-ksef-document.ts +137 -0
- package/src/modules/integration_ksef_direct/workers/sync-received-documents.ts +171 -0
- package/src/types/declarations.d.ts +1 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import Link from 'next/link'
|
|
5
|
+
import { FileText, Inbox } from 'lucide-react'
|
|
6
|
+
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
7
|
+
import { SectionHeader } from '@open-mercato/ui/backend/SectionHeader'
|
|
8
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
9
|
+
import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
|
|
10
|
+
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
11
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
12
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
13
|
+
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
14
|
+
|
|
15
|
+
export const pageMetadata = {
|
|
16
|
+
requireAuth: true,
|
|
17
|
+
requireFeatures: ['integration_ksef_direct.manage'],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type HealthStatus = 'unconfigured' | 'checking' | 'connected' | 'error'
|
|
21
|
+
|
|
22
|
+
type ConnectionState = {
|
|
23
|
+
status: HealthStatus
|
|
24
|
+
lastCheckedAt: string | null
|
|
25
|
+
environment?: 'test' | 'production'
|
|
26
|
+
rateLimits?: { otherPerSecond?: number; otherPerMinute?: number }
|
|
27
|
+
error?: string
|
|
28
|
+
errorCode?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function KsefDirectPage() {
|
|
32
|
+
const t = useT()
|
|
33
|
+
const scopeVersion = useOrganizationScopeVersion()
|
|
34
|
+
const [state, setState] = React.useState<ConnectionState | null>(null)
|
|
35
|
+
const [isLoading, setLoading] = React.useState(false)
|
|
36
|
+
const [isChecking, setChecking] = React.useState(false)
|
|
37
|
+
const [fetchError, setFetchError] = React.useState<string | null>(null)
|
|
38
|
+
|
|
39
|
+
const fetchStatus = React.useCallback(async () => {
|
|
40
|
+
setLoading(true)
|
|
41
|
+
setFetchError(null)
|
|
42
|
+
try {
|
|
43
|
+
const result = await apiCall<ConnectionState>('/api/integration-ksef-direct/health')
|
|
44
|
+
if (result.ok && result.result) {
|
|
45
|
+
setState(result.result)
|
|
46
|
+
} else {
|
|
47
|
+
setFetchError('Failed to load connection status')
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
setFetchError('Failed to load connection status')
|
|
51
|
+
} finally {
|
|
52
|
+
setLoading(false)
|
|
53
|
+
}
|
|
54
|
+
}, [])
|
|
55
|
+
|
|
56
|
+
React.useEffect(() => { fetchStatus() }, [fetchStatus, scopeVersion])
|
|
57
|
+
|
|
58
|
+
React.useEffect(() => {
|
|
59
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
60
|
+
if (e.key === 'Escape') {
|
|
61
|
+
window.history.back()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
65
|
+
return () => window.removeEventListener('keydown', handleKeyDown)
|
|
66
|
+
}, [])
|
|
67
|
+
|
|
68
|
+
async function handleCheckConnection() {
|
|
69
|
+
setChecking(true)
|
|
70
|
+
try {
|
|
71
|
+
const result = await apiCall<ConnectionState>('/api/integration-ksef-direct/health')
|
|
72
|
+
if (result.ok && result.result) {
|
|
73
|
+
setState(result.result)
|
|
74
|
+
}
|
|
75
|
+
} finally {
|
|
76
|
+
setChecking(false)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getStatusVariant(status: HealthStatus): 'success' | 'error' | 'neutral' | 'warning' {
|
|
81
|
+
switch (status) {
|
|
82
|
+
case 'connected': return 'success'
|
|
83
|
+
case 'error': return 'error'
|
|
84
|
+
case 'checking': return 'warning'
|
|
85
|
+
default: return 'neutral'
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getStatusLabel(status: HealthStatus): string {
|
|
90
|
+
switch (status) {
|
|
91
|
+
case 'connected': return t('integration_ksef_direct.health.connected', 'Connected')
|
|
92
|
+
case 'error': return t('integration_ksef_direct.health.error', 'Connection error')
|
|
93
|
+
case 'checking': return t('integration_ksef_direct.health.checking', 'Checking...')
|
|
94
|
+
default: return t('integration_ksef_direct.health.unconfigured', 'Not configured')
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Page>
|
|
100
|
+
<PageBody>
|
|
101
|
+
<SectionHeader title={t('integration_ksef_direct.title', 'KSeF Direct Integration')} />
|
|
102
|
+
|
|
103
|
+
{isLoading && !state && <LoadingMessage label={t('integration_ksef_direct.health.loading', 'Loading...')} />}
|
|
104
|
+
{fetchError && !isLoading && <ErrorMessage label={fetchError} />}
|
|
105
|
+
|
|
106
|
+
<div className="grid grid-cols-2 gap-3 mt-2 mb-6">
|
|
107
|
+
<Link href="/backend/integration-ksef-direct/documents">
|
|
108
|
+
<div className="flex items-center gap-3 rounded-lg border border-border p-4 hover:bg-muted/50 transition-colors cursor-pointer">
|
|
109
|
+
<FileText className="h-5 w-5 text-muted-foreground shrink-0" />
|
|
110
|
+
<div>
|
|
111
|
+
<p className="text-sm font-medium">{t('integration_ksef_direct.nav.documents', 'Sent Documents')}</p>
|
|
112
|
+
<p className="text-xs text-muted-foreground">{t('integration_ksef_direct.nav.documents_desc', 'Outgoing invoices sent to KSeF')}</p>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</Link>
|
|
116
|
+
<Link href="/backend/integration-ksef-direct/received-documents">
|
|
117
|
+
<div className="flex items-center gap-3 rounded-lg border border-border p-4 hover:bg-muted/50 transition-colors cursor-pointer">
|
|
118
|
+
<Inbox className="h-5 w-5 text-muted-foreground shrink-0" />
|
|
119
|
+
<div>
|
|
120
|
+
<p className="text-sm font-medium">{t('integration_ksef_direct.nav.received_documents', 'Received Documents')}</p>
|
|
121
|
+
<p className="text-xs text-muted-foreground">{t('integration_ksef_direct.nav.received_documents_desc', 'Invoices received from KSeF')}</p>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</Link>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{state && (
|
|
128
|
+
<div className="space-y-4">
|
|
129
|
+
<div className="flex items-center gap-3">
|
|
130
|
+
<StatusBadge variant={getStatusVariant(state.status)}>
|
|
131
|
+
{getStatusLabel(state.status)}
|
|
132
|
+
</StatusBadge>
|
|
133
|
+
{state.environment && (
|
|
134
|
+
<span className="text-sm text-muted-foreground">
|
|
135
|
+
{t('integration_ksef_direct.health.environment', 'Environment:')} <strong>{state.environment.toUpperCase()}</strong>
|
|
136
|
+
</span>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
{state.lastCheckedAt && (
|
|
141
|
+
<p className="text-sm text-muted-foreground">
|
|
142
|
+
{t('integration_ksef_direct.health.last_checked', 'Last check:')}{' '}
|
|
143
|
+
{new Date(state.lastCheckedAt).toLocaleString()}
|
|
144
|
+
</p>
|
|
145
|
+
)}
|
|
146
|
+
|
|
147
|
+
{state.status === 'connected' && state.rateLimits && (
|
|
148
|
+
<p className="text-sm text-muted-foreground">
|
|
149
|
+
{t('integration_ksef_direct.health.rate_limits', 'Rate limits:')}{' '}
|
|
150
|
+
{[
|
|
151
|
+
state.rateLimits.otherPerSecond != null && `${state.rateLimits.otherPerSecond} req/s`,
|
|
152
|
+
state.rateLimits.otherPerMinute != null && `${state.rateLimits.otherPerMinute} req/min`,
|
|
153
|
+
]
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join(' · ')}
|
|
156
|
+
</p>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{state.status === 'error' && state.error && (
|
|
160
|
+
<ErrorMessage
|
|
161
|
+
label={state.error}
|
|
162
|
+
description={state.errorCode}
|
|
163
|
+
/>
|
|
164
|
+
)}
|
|
165
|
+
|
|
166
|
+
<div className="flex justify-end pt-2">
|
|
167
|
+
<Button
|
|
168
|
+
type="button"
|
|
169
|
+
onClick={handleCheckConnection}
|
|
170
|
+
disabled={isChecking}
|
|
171
|
+
variant="outline"
|
|
172
|
+
>
|
|
173
|
+
{isChecking
|
|
174
|
+
? t('integration_ksef_direct.health.checking', 'Checking...')
|
|
175
|
+
: t('integration_ksef_direct.health.check_button', 'Check connection')}
|
|
176
|
+
</Button>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
)}
|
|
180
|
+
</PageBody>
|
|
181
|
+
</Page>
|
|
182
|
+
)
|
|
183
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { CreateKsefDirectDocumentInput, KsefDirectLineItemInput } from '../data/validators'
|
|
2
|
+
import { KsefDirectDocument, type KsefDirectStoredLineItem } from '../data/entities'
|
|
3
|
+
import { emitKsefDirectEvent } from '../events'
|
|
4
|
+
|
|
5
|
+
export class KsefDirectNotConfiguredError extends Error {
|
|
6
|
+
constructor() {
|
|
7
|
+
super('KSEF_DIRECT_NOT_CONFIGURED')
|
|
8
|
+
this.name = 'KsefDirectNotConfiguredError'
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const VAT_RATE_MAP: Record<string, number> = { '0': 0, '5': 5, '8': 8, '23': 23, ZW: 0, NP: 0 }
|
|
13
|
+
|
|
14
|
+
function round2(value: number): number {
|
|
15
|
+
return Math.round(value * 100) / 100
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function computeLineItem(item: KsefDirectLineItemInput): KsefDirectStoredLineItem {
|
|
19
|
+
const netAmount = round2(item.quantity * item.unitNetPrice)
|
|
20
|
+
const rate = VAT_RATE_MAP[item.vatRate] ?? 0
|
|
21
|
+
const vatAmount = round2(netAmount * rate / 100)
|
|
22
|
+
return {
|
|
23
|
+
description: item.description,
|
|
24
|
+
quantity: item.quantity,
|
|
25
|
+
unit: item.unit,
|
|
26
|
+
unitNetPrice: item.unitNetPrice,
|
|
27
|
+
vatRate: item.vatRate,
|
|
28
|
+
netAmount,
|
|
29
|
+
vatAmount,
|
|
30
|
+
grossAmount: round2(netAmount + vatAmount),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function createKsefDirectDocument(
|
|
35
|
+
em: any,
|
|
36
|
+
tenantId: string,
|
|
37
|
+
organizationId: string,
|
|
38
|
+
input: CreateKsefDirectDocumentInput,
|
|
39
|
+
credentialsService: any,
|
|
40
|
+
): Promise<{ id: string; status: string; invoiceNumber: string; sellerNip: string }> {
|
|
41
|
+
const credentials = await credentialsService?.resolve('integration_ksef_direct', { tenantId, organizationId })
|
|
42
|
+
if (!credentials?.nip) {
|
|
43
|
+
throw new KsefDirectNotConfiguredError()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let sellerName = input.sellerName ?? null
|
|
47
|
+
if (!sellerName) {
|
|
48
|
+
const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')
|
|
49
|
+
const org = await em.findOne(Organization, { id: organizationId })
|
|
50
|
+
sellerName = org?.name ?? null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const storedLineItems = input.lineItems.map(computeLineItem)
|
|
54
|
+
const netAmount = round2(storedLineItems.reduce((sum, l) => sum + l.netAmount, 0))
|
|
55
|
+
const vatAmount = round2(storedLineItems.reduce((sum, l) => sum + l.vatAmount, 0))
|
|
56
|
+
const grossAmount = round2(netAmount + vatAmount)
|
|
57
|
+
const now = new Date()
|
|
58
|
+
|
|
59
|
+
const doc = em.create(KsefDirectDocument, {
|
|
60
|
+
organizationId,
|
|
61
|
+
tenantId,
|
|
62
|
+
source: 'manual' as const,
|
|
63
|
+
status: 'draft' as const,
|
|
64
|
+
sellerNip: credentials.nip,
|
|
65
|
+
sellerName,
|
|
66
|
+
sellerAddressL1: input.sellerAddressL1 ?? null,
|
|
67
|
+
sellerCity: input.sellerCity ?? null,
|
|
68
|
+
sellerCountry: input.sellerCountry ?? null,
|
|
69
|
+
buyerNip: input.buyerNip,
|
|
70
|
+
buyerName: input.buyerName ?? null,
|
|
71
|
+
invoiceNumber: input.invoiceNumber,
|
|
72
|
+
issueDate: new Date(input.issueDate),
|
|
73
|
+
saleDate: input.saleDate ? new Date(input.saleDate) : null,
|
|
74
|
+
netAmount: String(netAmount),
|
|
75
|
+
vatAmount: String(vatAmount),
|
|
76
|
+
grossAmount: String(grossAmount),
|
|
77
|
+
currency: input.currency ?? 'PLN',
|
|
78
|
+
lineItems: storedLineItems,
|
|
79
|
+
notes: input.notes ?? null,
|
|
80
|
+
createdAt: now,
|
|
81
|
+
updatedAt: now,
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
await em.persist(doc).flush()
|
|
85
|
+
|
|
86
|
+
await emitKsefDirectEvent('ksef_direct.document.created', {
|
|
87
|
+
documentId: doc.id,
|
|
88
|
+
organizationId,
|
|
89
|
+
tenantId,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return { id: doc.id, status: doc.status, invoiceNumber: doc.invoiceNumber, sellerNip: doc.sellerNip }
|
|
93
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createModuleQueue } from '@open-mercato/queue'
|
|
2
|
+
import { KsefDirectDocument } from '../data/entities'
|
|
3
|
+
import { emitKsefDirectEvent } from '../events'
|
|
4
|
+
|
|
5
|
+
export class KsefDocumentNotFoundError extends Error {
|
|
6
|
+
readonly status = 404
|
|
7
|
+
constructor() {
|
|
8
|
+
super('Document not found')
|
|
9
|
+
this.name = 'KsefDocumentNotFoundError'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class KsefDocumentNotQueueableError extends Error {
|
|
14
|
+
readonly status = 409
|
|
15
|
+
constructor(currentStatus: string) {
|
|
16
|
+
super(`Document cannot be queued: current status is '${currentStatus}'`)
|
|
17
|
+
this.name = 'KsefDocumentNotQueueableError'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SendKsefDocumentJobPayload = {
|
|
22
|
+
documentId: string
|
|
23
|
+
organizationId: string
|
|
24
|
+
tenantId: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function enqueueKsefDirectDocument(
|
|
28
|
+
em: any,
|
|
29
|
+
tenantId: string,
|
|
30
|
+
organizationId: string,
|
|
31
|
+
documentId: string,
|
|
32
|
+
): Promise<{ id: string; status: string; invoiceNumber: string }> {
|
|
33
|
+
const doc = await em.findOne(KsefDirectDocument, { id: documentId, organizationId, tenantId })
|
|
34
|
+
|
|
35
|
+
if (!doc) {
|
|
36
|
+
throw new KsefDocumentNotFoundError()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!['draft', 'failed'].includes(doc.status)) {
|
|
40
|
+
throw new KsefDocumentNotQueueableError(doc.status)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
doc.status = 'queued'
|
|
44
|
+
doc.updatedAt = new Date()
|
|
45
|
+
await em.flush()
|
|
46
|
+
|
|
47
|
+
await emitKsefDirectEvent('ksef_direct.document.queued', {
|
|
48
|
+
documentId: doc.id,
|
|
49
|
+
organizationId,
|
|
50
|
+
tenantId,
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const queue = createModuleQueue<SendKsefDocumentJobPayload>('ksef_direct_send')
|
|
54
|
+
await queue.enqueue({ documentId: doc.id, organizationId, tenantId })
|
|
55
|
+
|
|
56
|
+
return { id: doc.id, status: doc.status, invoiceNumber: doc.invoiceNumber }
|
|
57
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Entity, PrimaryKey, Property, Index, Unique } from '@mikro-orm/decorators/legacy'
|
|
2
|
+
|
|
3
|
+
export interface KsefDirectStoredLineItem {
|
|
4
|
+
description: string
|
|
5
|
+
quantity: number
|
|
6
|
+
unit: string
|
|
7
|
+
unitNetPrice: number
|
|
8
|
+
vatRate: string
|
|
9
|
+
netAmount: number
|
|
10
|
+
vatAmount: number
|
|
11
|
+
grossAmount: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Entity({ tableName: 'ksef_direct_documents' })
|
|
15
|
+
@Index({ properties: ['organizationId', 'tenantId'] })
|
|
16
|
+
@Index({ properties: ['organizationId', 'tenantId', 'status'] })
|
|
17
|
+
@Index({ properties: ['organizationId', 'tenantId', 'source'] })
|
|
18
|
+
@Index({ properties: ['ksefReferenceNumber'] })
|
|
19
|
+
export class KsefDirectDocument {
|
|
20
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
21
|
+
id!: string
|
|
22
|
+
|
|
23
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
24
|
+
organizationId!: string
|
|
25
|
+
|
|
26
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
27
|
+
tenantId!: string
|
|
28
|
+
|
|
29
|
+
@Property({ name: 'source', type: 'text', default: 'manual' })
|
|
30
|
+
source: 'manual' | 'ksef_sync' | 'sales_invoice' = 'manual'
|
|
31
|
+
|
|
32
|
+
@Property({ name: 'status', type: 'text', default: 'draft' })
|
|
33
|
+
status: 'draft' | 'queued' | 'sending' | 'sent' | 'failed' = 'draft'
|
|
34
|
+
|
|
35
|
+
@Property({ name: 'ksef_reference_number', type: 'text', nullable: true })
|
|
36
|
+
ksefReferenceNumber?: string | null
|
|
37
|
+
|
|
38
|
+
@Property({ name: 'seller_nip', type: 'text' })
|
|
39
|
+
sellerNip!: string
|
|
40
|
+
|
|
41
|
+
@Property({ name: 'buyer_nip', type: 'text' })
|
|
42
|
+
buyerNip!: string
|
|
43
|
+
|
|
44
|
+
@Property({ name: 'buyer_name', type: 'text', nullable: true })
|
|
45
|
+
buyerName?: string | null
|
|
46
|
+
|
|
47
|
+
@Property({ name: 'invoice_number', type: 'text' })
|
|
48
|
+
invoiceNumber!: string
|
|
49
|
+
|
|
50
|
+
@Property({ name: 'issue_date', type: Date })
|
|
51
|
+
issueDate!: Date
|
|
52
|
+
|
|
53
|
+
@Property({ name: 'sale_date', type: Date, nullable: true })
|
|
54
|
+
saleDate?: Date | null
|
|
55
|
+
|
|
56
|
+
@Property({ name: 'net_amount', type: 'string', columnType: 'numeric(15,2)' })
|
|
57
|
+
netAmount!: string
|
|
58
|
+
|
|
59
|
+
@Property({ name: 'vat_amount', type: 'string', columnType: 'numeric(15,2)' })
|
|
60
|
+
vatAmount!: string
|
|
61
|
+
|
|
62
|
+
@Property({ name: 'gross_amount', type: 'string', columnType: 'numeric(15,2)' })
|
|
63
|
+
grossAmount!: string
|
|
64
|
+
|
|
65
|
+
@Property({ name: 'currency', type: 'text', default: 'PLN' })
|
|
66
|
+
currency: string = 'PLN'
|
|
67
|
+
|
|
68
|
+
@Property({ name: 'line_items', type: 'json', columnType: 'jsonb' })
|
|
69
|
+
lineItems: KsefDirectStoredLineItem[] = []
|
|
70
|
+
|
|
71
|
+
@Property({ name: 'notes', type: 'text', nullable: true })
|
|
72
|
+
notes?: string | null
|
|
73
|
+
|
|
74
|
+
@Property({ name: 'ksef_processing_reference_number', type: 'text', nullable: true })
|
|
75
|
+
ksefProcessingReferenceNumber?: string | null
|
|
76
|
+
|
|
77
|
+
@Property({ name: 'seller_name', type: 'text', nullable: true })
|
|
78
|
+
sellerName?: string | null
|
|
79
|
+
|
|
80
|
+
@Property({ name: 'seller_address_l1', type: 'text', nullable: true })
|
|
81
|
+
sellerAddressL1?: string | null
|
|
82
|
+
|
|
83
|
+
@Property({ name: 'seller_city', type: 'text', nullable: true })
|
|
84
|
+
sellerCity?: string | null
|
|
85
|
+
|
|
86
|
+
@Property({ name: 'seller_country', type: 'text', nullable: true })
|
|
87
|
+
sellerCountry?: string | null
|
|
88
|
+
|
|
89
|
+
@Property({ name: 'error_message', type: 'text', nullable: true })
|
|
90
|
+
errorMessage?: string | null
|
|
91
|
+
|
|
92
|
+
@Property({ name: 'created_at', type: Date })
|
|
93
|
+
createdAt: Date = new Date()
|
|
94
|
+
|
|
95
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
96
|
+
updatedAt: Date = new Date()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Entity({ tableName: 'ksef_direct_received_documents' })
|
|
100
|
+
@Index({ properties: ['organizationId', 'tenantId'] })
|
|
101
|
+
@Index({ properties: ['organizationId', 'tenantId', 'status'] })
|
|
102
|
+
@Unique({ properties: ['organizationId', 'ksefReferenceNumber'] })
|
|
103
|
+
export class KsefDirectReceivedDocument {
|
|
104
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
105
|
+
id!: string
|
|
106
|
+
|
|
107
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
108
|
+
organizationId!: string
|
|
109
|
+
|
|
110
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
111
|
+
tenantId!: string
|
|
112
|
+
|
|
113
|
+
@Property({ name: 'ksef_reference_number', type: 'text' })
|
|
114
|
+
ksefReferenceNumber!: string
|
|
115
|
+
|
|
116
|
+
@Property({ name: 'raw_xml', type: 'text', nullable: true })
|
|
117
|
+
rawXml?: string | null
|
|
118
|
+
|
|
119
|
+
@Property({ name: 'invoice_number', type: 'text', nullable: true })
|
|
120
|
+
invoiceNumber?: string | null
|
|
121
|
+
|
|
122
|
+
@Property({ name: 'seller_nip', type: 'text', nullable: true })
|
|
123
|
+
sellerNip?: string | null
|
|
124
|
+
|
|
125
|
+
@Property({ name: 'seller_name', type: 'text', nullable: true })
|
|
126
|
+
sellerName?: string | null
|
|
127
|
+
|
|
128
|
+
@Property({ name: 'issue_date', type: 'string', columnType: 'date', nullable: true })
|
|
129
|
+
issueDate?: string | null
|
|
130
|
+
|
|
131
|
+
@Property({ name: 'currency', type: 'text', nullable: true })
|
|
132
|
+
currency?: string | null
|
|
133
|
+
|
|
134
|
+
@Property({ name: 'net_amount', type: 'string', columnType: 'numeric(15,2)', nullable: true })
|
|
135
|
+
netAmount?: string | null
|
|
136
|
+
|
|
137
|
+
@Property({ name: 'vat_amount', type: 'string', columnType: 'numeric(15,2)', nullable: true })
|
|
138
|
+
vatAmount?: string | null
|
|
139
|
+
|
|
140
|
+
@Property({ name: 'gross_amount', type: 'string', columnType: 'numeric(15,2)', nullable: true })
|
|
141
|
+
grossAmount?: string | null
|
|
142
|
+
|
|
143
|
+
@Property({ name: 'status', type: 'text', default: 'pending_download' })
|
|
144
|
+
status: 'pending_download' | 'downloaded' | 'failed' = 'pending_download'
|
|
145
|
+
|
|
146
|
+
@Property({ name: 'error_message', type: 'text', nullable: true })
|
|
147
|
+
errorMessage?: string | null
|
|
148
|
+
|
|
149
|
+
@Property({ name: 'upo_download_url', type: 'text', nullable: true })
|
|
150
|
+
upoDownloadUrl?: string | null
|
|
151
|
+
|
|
152
|
+
@Property({ name: 'invoice_download_url', type: 'text', nullable: true })
|
|
153
|
+
invoiceDownloadUrl?: string | null
|
|
154
|
+
|
|
155
|
+
@Property({ name: 'synced_at', type: Date, nullable: true })
|
|
156
|
+
syncedAt?: Date | null
|
|
157
|
+
|
|
158
|
+
@Property({ name: 'created_at', type: Date })
|
|
159
|
+
createdAt: Date = new Date()
|
|
160
|
+
|
|
161
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
162
|
+
updatedAt: Date = new Date()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@Entity({ tableName: 'ksef_direct_connections' })
|
|
166
|
+
@Index({ properties: ['status', 'updatedAt'] })
|
|
167
|
+
@Unique({ properties: ['organizationId', 'tenantId'] })
|
|
168
|
+
export class KsefDirectConnection {
|
|
169
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
170
|
+
id!: string
|
|
171
|
+
|
|
172
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
173
|
+
organizationId!: string
|
|
174
|
+
|
|
175
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
176
|
+
tenantId!: string
|
|
177
|
+
|
|
178
|
+
@Property({ name: 'status', type: 'text', default: 'unconfigured' })
|
|
179
|
+
status: 'unconfigured' | 'checking' | 'connected' | 'error' = 'unconfigured'
|
|
180
|
+
|
|
181
|
+
@Property({ name: 'last_checked_at', type: Date, nullable: true })
|
|
182
|
+
lastCheckedAt?: Date | null
|
|
183
|
+
|
|
184
|
+
@Property({ name: 'error_message', type: 'text', nullable: true })
|
|
185
|
+
errorMessage?: string | null
|
|
186
|
+
|
|
187
|
+
@Property({ name: 'error_code', type: 'text', nullable: true })
|
|
188
|
+
errorCode?: string | null
|
|
189
|
+
|
|
190
|
+
@Property({ name: 'created_at', type: Date })
|
|
191
|
+
createdAt: Date = new Date()
|
|
192
|
+
|
|
193
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
194
|
+
updatedAt: Date = new Date()
|
|
195
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
const dateString = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be YYYY-MM-DD')
|
|
4
|
+
const vatRates = ['0', '5', '8', '23', 'ZW', 'NP'] as const
|
|
5
|
+
|
|
6
|
+
export const KsefDirectLineItemSchema = z.object({
|
|
7
|
+
description: z.string().min(1),
|
|
8
|
+
quantity: z.number().positive(),
|
|
9
|
+
unit: z.string().min(1).default('szt'),
|
|
10
|
+
unitNetPrice: z.number().min(0),
|
|
11
|
+
vatRate: z.enum(vatRates),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export const CreateKsefDirectDocumentSchema = z.object({
|
|
15
|
+
buyerNip: z.string().regex(/^\d{10}$/, 'NIP must be 10 digits'),
|
|
16
|
+
buyerName: z.string().max(256).optional(),
|
|
17
|
+
invoiceNumber: z.string().min(1).max(256),
|
|
18
|
+
issueDate: dateString,
|
|
19
|
+
saleDate: dateString.optional(),
|
|
20
|
+
currency: z.string().length(3).default('PLN'),
|
|
21
|
+
lineItems: z.array(KsefDirectLineItemSchema).min(1),
|
|
22
|
+
notes: z.string().optional(),
|
|
23
|
+
sellerName: z.string().min(1).max(512).optional(),
|
|
24
|
+
sellerAddressL1: z.string().min(1).max(512),
|
|
25
|
+
sellerCity: z.string().min(1).max(256),
|
|
26
|
+
sellerCountry: z.string().length(2).optional(),
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export type CreateKsefDirectDocumentInput = z.infer<typeof CreateKsefDirectDocumentSchema>
|
|
30
|
+
export type KsefDirectLineItemInput = z.infer<typeof KsefDirectLineItemSchema>
|
|
31
|
+
|
|
32
|
+
export const KsefDirectConnectionStatusSchema = z.enum(['unconfigured', 'checking', 'connected', 'error'])
|
|
33
|
+
|
|
34
|
+
export const KsefDirectCredentialsSchema = z.object({
|
|
35
|
+
ksef_token: z.string().min(1),
|
|
36
|
+
nip: z.string().min(1),
|
|
37
|
+
environment: z.enum(['test', 'production']),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export const KsefDirectRateLimitsSchema = z.object({
|
|
41
|
+
otherPerSecond: z.number().optional(),
|
|
42
|
+
otherPerMinute: z.number().optional(),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
export const KsefDirectHealthResponseSchema = z.discriminatedUnion('status', [
|
|
46
|
+
z.object({
|
|
47
|
+
status: z.literal('connected'),
|
|
48
|
+
lastCheckedAt: z.string(),
|
|
49
|
+
environment: z.enum(['test', 'production']),
|
|
50
|
+
rateLimits: KsefDirectRateLimitsSchema.optional(),
|
|
51
|
+
}),
|
|
52
|
+
z.object({
|
|
53
|
+
status: z.literal('error'),
|
|
54
|
+
lastCheckedAt: z.string(),
|
|
55
|
+
error: z.string(),
|
|
56
|
+
errorCode: z.string(),
|
|
57
|
+
}),
|
|
58
|
+
z.object({
|
|
59
|
+
status: z.literal('unconfigured'),
|
|
60
|
+
lastCheckedAt: z.null(),
|
|
61
|
+
}),
|
|
62
|
+
z.object({
|
|
63
|
+
status: z.literal('checking'),
|
|
64
|
+
lastCheckedAt: z.string().nullable(),
|
|
65
|
+
}),
|
|
66
|
+
])
|
|
67
|
+
|
|
68
|
+
export type KsefDirectCredentials = z.infer<typeof KsefDirectCredentialsSchema>
|
|
69
|
+
export type KsefDirectHealthResponse = z.infer<typeof KsefDirectHealthResponseSchema>
|
|
70
|
+
export type KsefDirectRateLimits = z.infer<typeof KsefDirectRateLimitsSchema>
|
|
71
|
+
|
|
72
|
+
export const KsefSendInvoiceResponseSchema = z.object({
|
|
73
|
+
referenceNumber: z.string(),
|
|
74
|
+
processingCode: z.number().optional(),
|
|
75
|
+
processingDescription: z.string().optional(),
|
|
76
|
+
timestamp: z.string().optional(),
|
|
77
|
+
}).passthrough()
|
|
78
|
+
|
|
79
|
+
export const KsefInvoiceStatusResponseSchema = z.object({
|
|
80
|
+
referenceNumber: z.string(),
|
|
81
|
+
processingCode: z.number(),
|
|
82
|
+
processingDescription: z.string().optional(),
|
|
83
|
+
invoiceStatus: z.object({
|
|
84
|
+
ksefReferenceNumber: z.string().optional(),
|
|
85
|
+
invoiceNumber: z.string().optional(),
|
|
86
|
+
}).optional(),
|
|
87
|
+
}).passthrough()
|
|
88
|
+
|
|
89
|
+
export type KsefSendInvoiceResponse = z.infer<typeof KsefSendInvoiceResponseSchema>
|
|
90
|
+
export type KsefInvoiceStatusResponse = z.infer<typeof KsefInvoiceStatusResponseSchema>
|
|
91
|
+
|
|
92
|
+
const isoDate = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Must be YYYY-MM-DD')
|
|
93
|
+
|
|
94
|
+
export const ReceivedDocumentSyncSchema = z.object({
|
|
95
|
+
dateFrom: isoDate,
|
|
96
|
+
dateTo: isoDate,
|
|
97
|
+
}).refine(
|
|
98
|
+
(data) => data.dateTo >= data.dateFrom,
|
|
99
|
+
{ message: 'dateTo must be >= dateFrom', path: ['dateTo'] },
|
|
100
|
+
).refine(
|
|
101
|
+
(data) => {
|
|
102
|
+
const from = new Date(data.dateFrom)
|
|
103
|
+
const to = new Date(data.dateTo)
|
|
104
|
+
const diffDays = (to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24)
|
|
105
|
+
return diffDays <= 366
|
|
106
|
+
},
|
|
107
|
+
{ message: 'Date range must not exceed 366 days', path: ['dateTo'] },
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
export const ReceivedDocumentFetchSchema = z.object({
|
|
111
|
+
ksefReferenceNumber: z.string().min(1),
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
export type ReceivedDocumentSyncInput = z.infer<typeof ReceivedDocumentSyncSchema>
|
|
115
|
+
export type ReceivedDocumentFetchInput = z.infer<typeof ReceivedDocumentFetchSchema>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { asValue } from 'awilix'
|
|
2
|
+
import type { AppContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
|
+
import { ksefDirectHealthChecker } from './lib/health'
|
|
4
|
+
|
|
5
|
+
export function register(container: AppContainer) {
|
|
6
|
+
container.register({
|
|
7
|
+
ksefDirectHealthChecker: asValue(ksefDirectHealthChecker),
|
|
8
|
+
})
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
|
|
3
|
+
const events = [
|
|
4
|
+
{ id: 'ksef_direct.connection.connected', label: 'KSeF Direct Connection Established', entity: 'ksef_direct_connection', category: 'lifecycle' },
|
|
5
|
+
{ id: 'ksef_direct.connection.failed', label: 'KSeF Direct Connection Failed', entity: 'ksef_direct_connection', category: 'lifecycle' },
|
|
6
|
+
{ id: 'ksef_direct.connection.checked', label: 'KSeF Direct Connection Checked', entity: 'ksef_direct_connection', category: 'lifecycle' },
|
|
7
|
+
{ id: 'ksef_direct.document.created', label: 'KSeF Direct Document Created', entity: 'ksef_direct_document', category: 'lifecycle' },
|
|
8
|
+
{ id: 'ksef_direct.document.queued', label: 'KSeF Direct Document Queued', entity: 'ksef_direct_document', category: 'lifecycle', clientBroadcast: true },
|
|
9
|
+
{ id: 'ksef_direct.document.sent', label: 'KSeF Direct Document Sent', entity: 'ksef_direct_document', category: 'lifecycle', clientBroadcast: true },
|
|
10
|
+
{ id: 'ksef_direct.document.failed', label: 'KSeF Direct Document Failed', entity: 'ksef_direct_document', category: 'lifecycle', clientBroadcast: true },
|
|
11
|
+
{ id: 'ksef_direct.received_document.synced', label: 'KSeF Direct Received Documents Synced', entity: 'ksef_direct_received_document', category: 'lifecycle' },
|
|
12
|
+
{ id: 'ksef_direct.received_document.failed', label: 'KSeF Direct Received Document Failed', entity: 'ksef_direct_received_document', category: 'lifecycle' },
|
|
13
|
+
] as const
|
|
14
|
+
|
|
15
|
+
export const eventsConfig = createModuleEvents({ moduleId: 'integration_ksef_direct', events })
|
|
16
|
+
export const emitKsefDirectEvent = eventsConfig.emit
|
|
17
|
+
export type KsefDirectEventId = typeof events[number]['id']
|
|
18
|
+
export default eventsConfig
|