Przejdź do głównej zawartości

Schematy Zod

Wszystkie modele domenowe w ksefnik są definiowane jako schematy Zod w pakiecie @ksefnik/shared. Typy TypeScript są generowane automatycznie przez z.infer.

import {
InvoiceSchema, LineItemSchema,
BankTransactionSchema,
MatchSchema,
ReconciliationReportSchema,
nipSchema,
} from '@ksefnik/shared'

Schemat faktury KSeF.

PoleTypWymaganeOpis
idz.string().uuid()takUnikalne ID faktury
invoiceNumberz.string().min(1)takNumer faktury
sellerNIPnipSchematakNIP sprzedawcy (10 cyfr + checksum)
buyerNIPnipSchemanieNIP nabywcy
grossAmountz.number().int().positive()takKwota brutto w groszach
netAmountz.number().int().positive()nieKwota netto w groszach
vatAmountz.number().int().nonnegative()nieKwota VAT w groszach
currencyz.literal('PLN')takWaluta (zawsze PLN)
issueDatez.string().date()takData wystawienia (YYYY-MM-DD)
salesDatez.string().date()nieData sprzedaży
dueDatez.string().date()nieTermin płatności
ksefReferencez.string()nieNumer referencyjny KSeF
sellerNamez.string()nieNazwa sprzedawcy
buyerNamez.string()nieNazwa nabywcy
descriptionz.string()nieOpis faktury
lineItemsz.array(LineItemSchema)niePozycje faktury
rawXmlz.string()nieSurowy XML z KSeF
createdAtz.string().datetime()takTimestamp utworzenia (ISO)

Schemat pozycji faktury.

PoleTypWymaganeOpis
lineNumberz.number().int().positive()takNumer pozycji
descriptionz.string().min(1)takOpis pozycji
unitz.string()nieJednostka miary (szt., kg, godz.)
quantityz.number().positive()nieIlość
unitNetPricez.number().int()nieCena jednostkowa netto w groszach
netAmountz.number().int().positive()takKwota netto pozycji w groszach
vatRatez.number().int().min(0).max(100)nieStawka VAT (0, 5, 8, 23)

Schemat transakcji bankowej.

PoleTypWymaganeOpis
idz.string().uuid()takUnikalne ID transakcji
datez.string().date()takData transakcji (YYYY-MM-DD)
amountz.number().int()takKwota w groszach (ujemna = wydatek)
descriptionz.string()takOpis transakcji z banku
senderNamez.string()nieNazwa nadawcy
senderNIPz.string().regex(/^\d{10}$/)nieNIP nadawcy
recipientNamez.string()nieNazwa odbiorcy
recipientNIPz.string().regex(/^\d{10}$/)nieNIP odbiorcy
bankz.enum([...])takŹródło: mt940, mbank, ing, pko, santander, other
rawz.string()takSurowy wiersz z wyciągu
createdAtz.string().datetime()takTimestamp importu (ISO)

Schemat dopasowania faktury do transakcji.

PoleTypWymaganeOpis
idz.string().uuid()takUnikalne ID dopasowania
invoiceIdz.string().uuid()takID faktury
transactionIdz.string().uuid()takID głównej transakcji
transactionIdsz.array(z.string().uuid())nieID transakcji (przy partial match)
confidencez.number().int().min(0).max(100)takPewność dopasowania (0-100)
passNamez.string()takNazwa passa, który dokonał dopasowania
reasonsz.array(z.string())takPowody dopasowania
confirmedz.boolean().default(false)takCzy potwierdzone przez użytkownika
createdAtz.string().datetime()takTimestamp utworzenia (ISO)

Schemat raportu uzgadniania.

PoleTypWymaganeOpis
idz.string().uuid()takUnikalne ID raportu
matchedz.array(MatchSchema)takLista dopasowań
unmatchedInvoicesz.array(InvoiceSchema)takNiedopasowane faktury
unmatchedTransactionsz.array(BankTransactionSchema)takNiedopasowane transakcje
summarySummarySchematakPodsumowanie statystyczne
runAtz.string().datetime()takTimestamp uruchomienia (ISO)
durationMsz.number().int().nonnegative()takCzas trwania w milisekundach
PoleTypOpis
totalInvoicesz.number().int().nonnegative()Łączna liczba faktur
totalTransactionsz.number().int().nonnegative()Łączna liczba transakcji
matchedCountz.number().int().nonnegative()Liczba dopasowań
unmatchedInvoiceCountz.number().int().nonnegative()Niedopasowane faktury
unmatchedTransactionCountz.number().int().nonnegative()Niedopasowane transakcje
averageConfidencez.number().min(0).max(100)Średni confidence
passBreakdownz.record(z.string(), z.number())Dopasowania per pass

Schemat walidujący polski NIP.

const nipSchema = z
.string()
.regex(/^\d{10}$/, 'NIP must be exactly 10 digits')
.refine(isValidNIP, { message: 'Invalid NIP checksum' })

Walidacja sumy kontrolnej NIP z wagami [6, 5, 7, 2, 3, 4, 5, 6, 7]:

function isValidNIP(nip: string): boolean {
const digits = nip.split('').map(Number)
const sum = NIP_WEIGHTS.reduce((acc, w, i) => acc + w * digits[i]!, 0)
return sum % 11 === digits[9]
}