Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ wrap it as a leaf dependency. Do not couple it to any framework.
(valibot). This is the single input type consumers construct.
- `src/lib/eslog/` — **owned** e-SLOG 2.0 serializer: domain model → e-SLOG XML
(UN/EDIFACT-INVOIC-derived, `urn:eslog:2.00`). This is the Slovenian delta the
upstream lib does not provide.
upstream lib does not provide. `validate-eslog.ts` validates produced XML
against the official XSD in `schema/` (`eSLOG20_INVOIC_v200.xsd` +
`xmldsig-core-schema.xsd`, vendored from epos.si) via `xmllint-wasm`. The XSDs
are loaded with bun text imports (`with { type: "text" }`; see `src/types.d.ts`)
so they ship in the package and work with no build step.
- `src/lib/einvoice/` — bridge to `@e-invoice-eu/core`: maps the domain model →
the lib's UBL-shaped internal JSON (`ubl:Invoice` / `cbc:`/`cac:`) → UBL / CII
output, plus EN16931 validation via the lib's `invoiceSchema`.
Expand Down
31 changes: 25 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ const program = Effect.gen(function* () {

`serializeEslog(invoice)` is a pure synchronous escape hatch for e-SLOG only.

### Validate produced e-SLOG against the official XSD

```ts
import { generateEInvoice, validateEslogXml } from "@grunt-it/fiscalize";

// validate on the way out…
const xml = await Effect.runPromise(
generateEInvoice(invoice, { format: "eslog", validateOutput: true }),
);

// …or validate any e-SLOG XML string standalone
yield* validateEslogXml(someEslogXml);
```

`validateEslogXml` checks the XML against the **official e-SLOG 2.0 XSD**
(`eSLOG20_INVOIC_v200.xsd` + `xmldsig-core-schema.xsd`, from the epos.si Aug-2020
package), using xmllint compiled to WebAssembly — no native bindings.

## The model

`Invoice` is a clean, EN16931-aligned **core invoice**: header (BT-1/2/3/5/9/72),
Expand All @@ -106,12 +124,13 @@ the mapping to each syntax stays auditable. Validated with `valibot`.

## What's covered vs deferred

P1 maps the **mandatory + common core**. Document-level allowances/charges,
line-level allowances, contacts, multiple payment means, e-SLOG XSD/schematron
output validation, and the long tail of optional BTs are deferred — see
[`ROADMAP.md`](./ROADMAP.md). The e-SLOG mapping is grounded in the official
spec (epos.si) and cross-checked against the MIT-licensed reference generator
`Media24si/eslog2`.
P1 maps the **mandatory + common core**, and produced e-SLOG XML is validated
against the official e-SLOG 2.0 **XSD**. Deferred (see [`ROADMAP.md`](./ROADMAP.md)):
business-rule (schematron-equivalent) validation — the official package ships no
`.sch`, so those rules are spec prose — plus document/line-level allowances,
contacts, multiple payment means, and the long tail of optional BTs. The e-SLOG
mapping is grounded in the official spec (epos.si) and cross-checked against the
MIT-licensed reference generator `Media24si/eslog2`.

## Development

Expand Down
19 changes: 15 additions & 4 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,23 @@ EN16931 core invoice → **e-SLOG 2.0** + **UBL / CII**, with validation.
- EN16931 structural validation via the lib's JSON Schema (Ajv 2019-09).
- Effect-native API + Promise boundary (`createEInvoice`).

## P1.x — e-SLOG XSD output validation ✅

- `validateEslogXml(xml)` validates produced e-SLOG XML against the **official
e-SLOG 2.0 XSD** (`eSLOG20_INVOIC_v200.xsd` + its `xmldsig-core-schema.xsd`
import), vendored from the epos.si Aug-2020 package, via xmllint compiled to
WebAssembly (`xmllint-wasm` — no native bindings, bun-friendly).
- `generateEInvoice(..., { validateOutput: true })` validates before returning.
- Confirms the P1 serializer is **XSD-conformant** (regression-locked in tests
against both our output and the official sample invoice).

### Deferred within the e-invoice core (next P1.x slices)

- **e-SLOG XSD + schematron validation** of *output*. P1 validates the EN16931
model structurally (UBL schema); it does not yet validate the produced e-SLOG
XML against the official e-SLOG 2.0 XSD or business-rule schematron. Vendor the
XSD (incl. `xmldsig-core-schema.xsd`) and add an output-validation pass.
- **Business-rule (schematron-equivalent) validation.** The official e-SLOG 2.0
package ships **no `.sch`** — the business rules (arithmetic, conditional
presence) live in the spec PDFs as prose. The XSD enforces structure/types,
not those rules. A future slice can encode the key rules (BR-CO/BR-S analogues)
as checks. Cross-check against the lib's EN16931 schematron where it overlaps.
- **Document-level allowances/charges** (BG-20/BG-21) → e-SLOG `G_SG16`, UBL
`cac:AllowanceCharge`. Model has the totals (BT-107/108) but not the detail.
- **Line-level allowances** (BG-27/BG-28) → e-SLOG `G_SG39`.
Expand Down
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"ajv-formats": "^3.0.1",
"effect": "^3.18.4",
"valibot": "^1.2.0",
"xmlbuilder2": "^4.0.3"
"xmlbuilder2": "^4.0.3",
"xmllint-wasm": "^5.2.0"
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export * from "./lib/foundation";
export * from "./lib/invoice";
export * from "./lib/einvoice";
export { type EslogOptions, serializeEslog } from "./lib/eslog/serialize";
export { validateEslogXml } from "./lib/eslog/validate-eslog";
export * as eslogCodes from "./lib/eslog/codes";
11 changes: 10 additions & 1 deletion src/lib/einvoice/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Effect } from "effect";
import { EInvoiceGenerationError, UnsupportedFormatError } from "../foundation/errors";
import type { EslogOptions } from "../eslog/serialize";
import { serializeEslog } from "../eslog/serialize";
import { validateEslogXml } from "../eslog/validate-eslog";
import type { Invoice } from "../invoice/model";
import { FORMATS, type Format, isFormat, LIB_FORMAT } from "./formats";
import { toEInvoiceInternal } from "./to-internal";
Expand All @@ -14,6 +15,12 @@ export interface GenerateOptions extends EslogOptions {
* Factur-X PDF metadata; irrelevant to the pure-XML formats here. Default `sl`.
*/
lang?: string;
/**
* For `eslog`: validate the produced XML against the official e-SLOG 2.0 XSD
* before returning (fails with `EslogValidationError` if non-conformant).
* Default `false`. Ignored for `ubl`/`cii` (the lib validates those itself).
*/
validateOutput?: boolean;
}

// `InvoiceService` is stateless across calls; build once.
Expand Down Expand Up @@ -42,10 +49,12 @@ export const generateEInvoice = Effect.fn("generateEInvoice")(function* (
}

if (format === "eslog") {
return yield* Effect.try({
const xml = yield* Effect.try({
try: () => serializeEslog(invoice, options),
catch: (cause) => new EInvoiceGenerationError("eslog", cause),
});
if (options.validateOutput) yield* validateEslogXml(xml);
return xml;
}

const internal = toEInvoiceInternal(invoice) as unknown as EInvoiceEUInvoice;
Expand Down
1 change: 1 addition & 0 deletions src/lib/eslog/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./serialize";
export * from "./validate-eslog";
export * as eslogCodes from "./codes";
19 changes: 19 additions & 0 deletions src/lib/eslog/schema/PROVENANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Vendored e-SLOG 2.0 schema — provenance

These files are the **official** e-SLOG 2.0 invoice schema, unmodified, vendored
from the e-SLOG 2.0 (August 2020) documentation package published by the
Slovenian national eBusiness centre (ePOS / GZS):

- Source: <https://www.epos.si/eslog> → `e-SLOG-2.0-08-2020-EN.zip`
- `eSLOG20_INVOIC_v200.xsd` — invoice (eRačun) XML schema, namespace `urn:eslog:2.00`.
- `xmldsig-core-schema.xsd` — W3C XML-Signature schema imported by the above
(`http://www.w3.org/2000/09/xmldsig#`).

They are used at runtime by `../validate-eslog.ts` to validate produced e-SLOG
XML. The official sample invoice from the same package is kept (as a test
fixture) at `src/test/sample-eslog20-with-bt.xml`, and the 1.6↔2.0 mapping
tables ship in the package as `.xlsx` (not vendored here).

e-SLOG is an open standard for Slovenian electronic business documents; these
schema files are redistributed for interoperability. They are not covered by
this project's MIT license — refer to ePOS/GZS for their terms.
Loading
Loading