Przejdź do głównej zawartości

System pluginów

Ksefnik ma modularną architekturę opartą na pluginach. Core zawiera 6 passów reconciliation (order 100-500), a pluginy dodają kolejne (order 600+). Architektura “plugin by absence” oznacza, że brak pakietu = darmowa wersja, bez żadnego DRM.

Ksefnik nie sprawdza licencji, nie dzwoni do żadnego serwera, nie blokuje funkcji. Zamiast tego:

// Wewnątrz ksefnik — ładowanie pluginu
try {
const proPlugin = await import('@ksefnik/pro-reconciliation');
registry.register(proPlugin.default);
} catch {
// Pakiet nie zainstalowany — brak dodatkowych passów
// Działa darmowa wersja z 6 core passami
}

Każdy plugin implementuje interfejs KsefPlugin:

interface KsefPlugin {
/** Unikalna nazwa pluginu */
name: string;
/** Wersja pluginu (semver) */
version: string;
/** Dodatkowe passy reconciliation */
reconciliationPasses?(): ReconciliationPass[];
/** Hook wywoływany po zakończeniu reconciliation */
onReconciliationComplete?(report: ReconciliationReport): Promise<void>;
/** Dodatkowe narzędzia MCP */
mcpTools?(): McpTool[];
/** Dodatkowe komendy CLI */
cliCommands?(): CliCommand[];
}

Każdy pass reconciliation implementuje:

interface ReconciliationPass {
/** Unikalna nazwa passa */
name: string;
/** Kolejność wykonania (core: 100-500, pluginy: 600+) */
order: number;
/** Wykonaj dopasowanie */
execute(
unmatchedInvoices: Invoice[],
unmatchedTransactions: BankTransaction[],
): Promise<Match[]>;
}
import { createKsefnik } from '@ksefnik/core';
import { ecommercePlugin } from './my-ecommerce-plugin';
const ksef = createKsefnik({
config: { nip: '5213456784' },
});
ksef.plugins.register(ecommercePlugin);
const ksef = createKsefnik({
config: { nip: '5213456784' },
});
// Załaduj plugin z pakietu npm — jeśli nie jest zainstalowany, loguje warning
await ksef.plugins.load('@ksefnik/pro-reconciliation');

Registry scala passy z core i pluginów, sortując je po order:

ŹródłoPassOrder
CoreksefRef100
CoreexactNip200
CoreinvoiceRef300
Corefuzzy400
Corepartial450
Coreproximity500
PluginecommerceOrderId600
PluginrecurringPayment650
PluginmultiCurrency700
// Sprawdź zarejestrowane passy
const passes = ksef.reconciliation.getPasses();
for (const pass of passes) {
console.log(`${pass.name} (order: ${pass.order})`);
}
import type { KsefPlugin, ReconciliationPass, Match } from '@ksefnik/core';
/** Pass dopasowujący po numerze zamówienia e-commerce w tytule przelewu */
const ecommerceOrderPass: ReconciliationPass = {
name: 'ecommerceOrderId',
order: 600,
async execute(invoices, transactions): Promise<Match[]> {
const matches: Match[] = [];
for (const inv of invoices) {
// Szukaj numeru zamówienia w metadanych faktury
const orderId = inv.metadata?.orderId;
if (!orderId) continue;
const tx = transactions.find(t =>
t.title.includes(orderId)
);
if (tx && Math.abs(tx.amount) === inv.grossAmount) {
matches.push({
invoice: inv,
transaction: tx,
pass: 'ecommerceOrderId',
confidence: 0.92,
});
}
}
return matches;
},
};
export const ecommercePlugin: KsefPlugin = {
name: 'ecommerce-reconciliation',
version: '1.0.0',
reconciliationPasses() {
return [ecommerceOrderPass];
},
async onReconciliationComplete(report) {
const ecommerceMatches = report.matched.filter(
m => m.pass === 'ecommerceOrderId'
);
console.log(
`E-commerce plugin dopasował ${ecommerceMatches.length} faktur po numerze zamówienia`
);
},
};
import { createKsefnik } from '@ksefnik/core';
import { ecommercePlugin } from './ecommerce-plugin';
const ksef = createKsefnik({
config: { nip: '8971234561' },
});
ksef.plugins.register(ecommercePlugin);
const invoices = await ksef.invoices.fetch({
from: '2026-03-01',
to: '2026-03-31',
});
const transactions = await ksef.bank.importStatement('./wyciag.csv');
const report = await ksef.reconciliation.run(invoices, transactions);
// passBreakdown zawiera też passy z pluginów
for (const pb of report.passBreakdown) {
console.log(`${pb.pass}: ${pb.matchCount} dopasowań`);
}
export const advancedPlugin: KsefPlugin = {
name: 'advanced-analytics',
version: '2.0.0',
mcpTools() {
return [
{
name: 'analyze_trends',
description: 'Analiza trendów dopasowań w czasie',
parameters: {
months: { type: 'number', description: 'Liczba miesięcy do analizy' },
},
handler: async (params) => {
// logika analizy trendów
return { trend: 'improving', matchRateChange: '+5%' };
},
},
];
},
cliCommands() {
return [
{
name: 'trends',
description: 'Pokaż trendy dopasowań',
handler: async (args) => {
// logika komendy CLI
},
},
];
},
};
  1. Firma sprzedaje online i klienci podają numer zamówienia w tytule przelewu (np. Zamówienie #ORD-2026-1234).
  2. Core passes nie dopasowują tych przelewów, bo tytuł nie zawiera NIP ani numeru faktury.
  3. Firma tworzy plugin z passem ecommerceOrderId (order 600), który szuka numerów zamówień.
  4. Po zainstalowaniu pluginu match rate rośnie z 72% do 91%.
  5. Plugin działa obok core passes — nie zastępuje ich, tylko uzupełnia pipeline.