Wlasne passy
Interfejs ReconciliationPass
Dział zatytułowany „Interfejs ReconciliationPass”Kazdy pass musi implementowac trzy elementy:
interface ReconciliationPass { name: string order: number run(context: MatchingContext): Promise<MatchResult[]>}| Pole | Typ | Opis |
|---|---|---|
name | string | Unikalna nazwa passa (pojawi sie w match.passName) |
order | number | Kolejnosc wykonania (nizszy = wczesniej) |
run | function | Logika dopasowania |
MatchingContext
Dział zatytułowany „MatchingContext”Kontekst przekazywany do metody run():
interface MatchingContext { invoices: Invoice[] // niesparowane faktury transactions: BankTransaction[] // niesparowane transakcje alreadyMatched: Match[] // dotychczasowe dopasowania}MatchResult
Dział zatytułowany „MatchResult”Wynik zwracany z run():
interface MatchResult { invoiceId: string transactionId: string transactionIds?: string[] // opcjonalne, dla dopasowan czesciowych confidence: number // 0-100 reasons: string[] // uzasadnienie dopasowania}Konwencja numeracji order
Dział zatytułowany „Konwencja numeracji order”| Zakres | Przeznaczenie |
|---|---|
| 100-500 | Passy core (ksef-ref, exact, invoice-ref, fuzzy, partial, proximity) |
| 600+ | Passy z pluginow |
Pelny przyklad: pass po numerze zamowienia
Dział zatytułowany „Pelny przyklad: pass po numerze zamowienia”Scenariusz: twoj system ERP dodaje numer zamowienia (order ID) do tytulu przelewu. Chcesz dopasowywac faktury po tym numerze.
- Zdefiniuj pass
- Zarejestruj go przez plugin
- Uruchom pipeline
Krok 1: Definicja passa
Dział zatytułowany „Krok 1: Definicja passa”import type { ReconciliationPass, MatchingContext, MatchResult } from '@ksefnik/shared'
const ORDER_ID_PATTERN = /ORD-\d{6,}/i
export const orderIdPass: ReconciliationPass = { name: 'order-id', order: 600,
async run(context: MatchingContext): Promise<MatchResult[]> { const results: MatchResult[] = [] const usedTxIds = new Set<string>()
for (const invoice of context.invoices) { // Zakladamy, ze numer zamowienia jest w polu description lub customFields const orderId = extractOrderId(invoice) if (!orderId) continue
for (const tx of context.transactions) { if (usedTxIds.has(tx.id)) continue
if (tx.description.toUpperCase().includes(orderId.toUpperCase())) { const amountMatch = Math.abs(tx.amount) === invoice.grossAmount results.push({ invoiceId: invoice.id, transactionId: tx.id, confidence: amountMatch ? 88 : 70, reasons: [ `Order ID "${orderId}" found in transaction description`, ...(amountMatch ? ['Amount also matches'] : []), ], }) usedTxIds.add(tx.id) break } } }
return results },}
function extractOrderId(invoice: { invoiceNumber: string }): string | null { const match = invoice.invoiceNumber.match(ORDER_ID_PATTERN) return match ? match[0] : null}Krok 2: Rejestracja przez plugin
Dział zatytułowany „Krok 2: Rejestracja przez plugin”import type { KsefPlugin } from '@ksefnik/shared'import { orderIdPass } from './order-id.pass.js'
export const orderIdPlugin: KsefPlugin = { name: 'order-id-matcher', version: '1.0.0',
reconciliationPasses() { return [orderIdPass] },}Krok 3: Uzycie
Dział zatytułowany „Krok 3: Uzycie”import { createKsefnik } from '@ksefnik/core'import { orderIdPlugin } from './order-id-plugin.js'
const ksefnik = createKsefnik({ config })ksefnik.plugins.register(orderIdPlugin)
const report = await ksefnik.reconciliation.run(invoices, transactions)// report.summary.passBreakdown['order-id'] === 3import { runPipeline, defaultPasses } from '@ksefnik/core'import { orderIdPass } from './order-id.pass.js'
const report = await runPipeline( invoices, transactions, [...defaultPasses, orderIdPass],)Dobre praktyki
Dział zatytułowany „Dobre praktyki”- Zawsze uzywaj
usedTxIds— zapobiega przypisaniu tej samej transakcji do wielu faktur w ramach jednego passa - Breakuj po pierwszym trafieniu — jedna faktura = jedna transakcja (chyba ze piszesz pass partial)
- Dodawaj reasons — kazde dopasowanie powinno miec czytelne uzasadnienie
- Testuj z Vitest — pass to czysta funkcja, latwa do unit testowania
import { describe, it, expect } from 'vitest'import { orderIdPass } from './order-id.pass.js'
describe('orderIdPass', () => { it('matches invoice with order ID in transaction description', async () => { const results = await orderIdPass.run({ invoices: [{ id: 'inv-1', invoiceNumber: 'ORD-123456', grossAmount: 50000 }], transactions: [{ id: 'tx-1', amount: -50000, description: 'Zaplata ORD-123456' }], alreadyMatched: [], })
expect(results).toHaveLength(1) expect(results[0].confidence).toBe(88) })})