# @ksefuj/validator

KSeF FA(3) XML validator with full XSD schema validation and semantic business rules.

## Features

- **Full XSD validation** using libxml2-wasm (WebAssembly)
- **Semantic business rules** that catch errors XSD can't express
- **NBP currency rate validation** — checks `KursWaluty`, and the advance-invoice rates
  `KursWalutyZ` and `KursWalutyZW`, against official NBP Table A mid-rates (Art. 31a VAT Act)
- **Works everywhere** - Node.js, browser, CLI
- **Zero network requests** - bundled schemas for offline use
- **TypeScript support** with full type definitions

## Installation

```bash
npm install @ksefuj/validator@0.4.0
# or
pnpm add @ksefuj/validator@0.4.0
# or
yarn add @ksefuj/validator@0.4.0
```

## Usage

```typescript
import { validate } from "@ksefuj/validator";

const result = await validate(xmlString, {
  locale: "en", // 'pl' (default) | 'en' | 'uk'
  enableXsdValidation: true, // default: true
  enableSemanticValidation: true, // default: true
});

if (!result.valid) {
  // Separate XSD and semantic errors
  const xsdErrors = result.errors.filter((e) => e.source === "xsd");
  const semanticErrors = result.errors.filter((e) => e.source === "semantic");

  for (const error of result.errors) {
    console.error(`${error.source}: ${error.message}`, error.path);
  }
}
```

### Currency rate validation

Optionally validate that the invoice's exchange rates match the official NBP Table A mid-rate (Art.
31a VAT Act). Pass a rate table per currency — the validator picks the correct date itself.
`FaWiersz/KursWaluty` is checked per line; advance invoices are covered too, through
`Fa/KursWalutyZ` and `ZaliczkaCzesciowa/KursWalutyZW`.

The reference date is **not** simply `P_1`. Art. 31a ust. 1 takes the rate from the last business
day before the day the tax obligation arises; the issue date governs only when the invoice is issued
_before_ that (ust. 2). The validator infers the tax point from `FaWiersz/P_6A`, `Fa/P_6`,
`Fa/OkresFa/P_6_Do` or `ZaliczkaCzesciowa/P_6Z`, and falls back to `P_1` when the invoice carries
none. On an advance invoice the tax point is the day the payment was received (Art. 19a ust. 8), so
each `KursWalutyZW` is checked against its own `P_6Z` — unless the invoice was issued first, which
Art. 106i ust. 7 permits up to 60 days ahead, in which case ust. 2 puts the reference date back on
`P_1` like everywhere else.

```typescript
import { validate, type CurrencyRate } from "@ksefuj/validator";

// Pass an array of rates for each currency (e.g. fetched from the NBP range API).
// A July service invoiced on 2026-08-05 takes the rate from before 2026-07-31,
// so the table has to reach back to the tax point, not just to P_1.
const currencyRates: Record<string, CurrencyRate[] | null> = {
  EUR: [
    { currency: "EUR", date: "2026-07-29", mid: 4.21 },
    { currency: "EUR", date: "2026-07-30", mid: 4.2856 }, // picked for a July settlement period
  ],
  USD: null, // looked up but unavailable → emits CURRENCY_RATE_UNVERIFIABLE
};

const result = await validate(xmlString, { currencyRates });
// → CURRENCY_RATE_MISMATCH  if KursWaluty ≠ the selected NBP rate
// → CURRENCY_RATE_UNVERIFIABLE  if the rate table is null or has no rate within 10 days
```

Use `rateReferenceCandidates(p1, taxPointDate)` from `@ksefuj/validator/currency-date` (a
dependency-free subpath, safe in any bundle) to work out which dates a rate table has to cover
before fetching it.

| `currencyRates[currency]` | behaviour                                                                               |
| ------------------------- | --------------------------------------------------------------------------------------- |
| key absent                | skipped silently — currency was never looked up                                         |
| `null`                    | `CURRENCY_RATE_UNVERIFIABLE` — lookup was attempted but failed                          |
| `CurrencyRate[]`          | validator picks the most recent rate strictly before the reference date, within 10 days |

If `currencyRates` is omitted entirely, all currency checks are skipped (backwards-compatible with
`0.2.0`).

## CLI Usage

```bash
npx @ksefuj/validator invoice.xml
```

## Architecture

### Schema Management

The validator uses bundled XSD schemas from the Polish Ministry of Finance to ensure:

- **No CORS issues** - schemas are embedded, not fetched
- **Offline capability** - works without internet connection
- **Consistent validation** - always uses the same schema version

#### How schemas are organized:

```
src/schemas/
├── *.xsd                    # Source XSD files (development only)
├── schemas-data.ts          # Generated bundle with all schemas
└── bundle.ts                # Bundle loader
```

#### Development vs Runtime:

- **Development**: Individual `.xsd` files are the source of truth, downloaded from government
  servers
- **Build time**: TypeScript compiles `schemas-data.ts` → `schemas-data.js`
- **Runtime**: Only `schemas-data.js` is used - XSD files are never accessed
- **npm package**: Excludes `.xsd` and `.ts` files to minimize size (~760KB saved)

#### Updating schemas:

From the project root:

```bash
pnpm update-schemas
```

This downloads the latest schemas from crd.gov.pl and regenerates the bundle.

### Package Optimization

The published npm package is optimized to exclude redundant files:

| Files          | Purpose            | Included in npm?    |
| -------------- | ------------------ | ------------------- |
| `*.xsd`        | Source schemas     | ❌ No (saved 442KB) |
| `*.ts` in dist | TypeScript sources | ❌ No (saved 318KB) |
| `*.js`         | Compiled code      | ✅ Yes              |
| `*.d.ts`       | Type definitions   | ✅ Yes              |

**Result**: Package size reduced from ~1.4MB to ~100KB (compressed).

## Validation Layers

### 1. XSD Schema Validation

Full compliance with official Ministry of Finance FA(3) schemas using libxml2-wasm.

### 2. Semantic Business Rules

**44 comprehensive validation rules** based on the official FA(3) information sheet from the
Ministry of Finance.

#### Rule Categories

1. **Podmiot Rules** (8 rules) — Entity validation, JST/GV requirements, NIP placement
2. **Fa Core Rules** (5 rules) — P_15 requirement, mutual exclusions, currency handling
3. **Adnotacje Rules** (11 rules) — All mandatory fields, selection logic for exemptions/margin
   procedures
4. **FaWiersz Rules** (4 rules) — Tax rate validation, GTU format, decimal precision
5. **Corrective Invoice Rules** (2 rules) — KSeF number consistency, reverse charge validation
6. **Payment & Transaction Rules** (8 rules) — Payment dates, bank accounts, currency pairs, NBP
   rate validation
7. **Format Rules** (2 rules) — Number formatting, separator validation
8. **Additional Business Logic Rules** (4 rules) — Tax calculations, bank account format, line
   number uniqueness, negative quantities

#### Key Validations

- **Constitution-based**: Each rule references specific sections from the official FA(3) information
  sheet
- **Precise error reporting**: Exact XPath locations, error context, and fix suggestions
- **Common gotchas**: NIP field placement, GTU format, decimal precision, selection logic
- **Business logic**: Currency conversions, tax rate consistency, entity relationships

### 3. Strict XML checks (KSeF API 2.4.0)

Raw-text checks that KSeF PROD enforces from **2026-10-19** (until then the API only returns
`X-System-Warning`). An invoice is rejected when it contains XML processing instructions
(`XML_PROCESSING_INSTRUCTION`), Unicode characters discouraged by the W3C XML spec
(`XML_DISCOURAGED_CHARACTER`: `[#x7F-#x84]`, `[#x86-#x9F]`, `[#xFDD0-#xFDEF]`, `[#xNFFFE-#xNFFFF]`),
a leading BOM (`XML_BOM_PRESENT`) or an XML declaration with a non-UTF-8 encoding
(`XML_ENCODING_NOT_UTF8`). Issues carry line/column, and the code point or PI target. Source:
[Weryfikacja faktury](https://github.com/CIRFMF/ksef-api/blob/main/faktury/weryfikacja-faktury.md)

## Testing

Comprehensive test suite ensuring 100% compliance with government standards:

### Test Coverage

The validator is tested against official MF examples and common invoice patterns:

- Standard VAT invoices
- Correction invoices (KOR) - both universal and difference methods
- Advance invoices (ZAL) and settlements (ROZ)
- Simplified invoices (≤450 PLN)
- WDT (intra-community) and export invoices
- VAT margin procedures
- Foreign currency invoices
- Invoices with attachments and edge cases

### Semantic Rule Testing

**100+ targeted test cases** for each semantic validation rule:

- Positive tests (valid cases that should pass)
- Negative tests (invalid cases with specific error expectations)
- Edge cases and boundary conditions
- Constitution-based test scenarios from FA(3) information sheet

### Test Framework

- **Vitest** for fast, modern testing
- **XSD + Semantic dual validation** for each test case
- **Detailed error assertions** with exact error codes and locations
- **CI/CD integration** ensuring all tests pass before deployment

All tests guarantee compatibility with the KSeF 2.0 system and adherence to Ministry of Finance
specifications.

## License

Apache 2.0
