Repository navigation
Releases: StackForge-EU/factur-x
Release list
v1.4.3
- Fixed half-cent rounding.
Math.round(n * 100) / 100turns
402.50 × 19%(76.475) into76.47, because76.475 * 100is
7647.499999999999, sovalidateInputrejected the correctly rounded
76.48(BR-CO-17). Amounts are now rounded half away from zero on a
noise-free value and compared in integer cents, in the validator and in
the XML builder alike. Reported by @johannesspohr (#21).
v1.4.2
- Fixed the embedded XML MIME type back to
text/xml. 1.2.1 switched the
factur-x.xmlfilespec/Subtypetoapplication/xml(#7), but the
Factur-X specification requirestext/xml, and the official FNFE-MPE
validator rejects anything else. PDFs generated with 1.2.1 through 1.4.1
should be regenerated. A test now pins the value (#19).
v1.4.1
- Added the rounding amount (BT-114). A new optional
roundingAmount
field onInvoiceTotalsInputemitsram:RoundingAmountbetween the tax
total and the grand total, so an invoice can state the amount that takes
the grand total (BT-112) to the payable figure (BT-115). Emitted for
EN 16931 and EXTENDED only, as the lower-profile XSDs do not define the
element.validateInputnow also checks BR-CO-16
(duePayableAmount = grandTotal - prepaidAmount + roundingAmount), which
the schematron already enforced. Omitting the field keeps the output
byte-for-byte identical (#17).
v1.4.0
- Added the item price discount (BT-147). A new optional
priceDiscount
field onInvoiceLineInputemitsram:AppliedTradeAllowanceChargeinside
the gross price element, so a line can state the discount that connects the
gross unit price (BT-148) to the net unit price (BT-146) instead of leaving
it implied by their difference. RequiresgrossUnitPriceto be set; the
amount keeps up to four decimals for sub-cent discounts. Omitting the field
keeps the output byte-for-byte identical (#15).
v1.3.1
- Dropped the unused
schema/cii-d22b/code-list XSDs from the npm and JSR
packages (~100 files, ~800 KB). Nothing references them at runtime — the
per-profile schema directories are self-contained. One of these files also
has a path longer than 100 characters, which a JSR server-side tar
regression truncated, breaking the 1.3.0 JSR publish. - The release workflow now dry-runs
npm packanddeno publishbefore
tagging, and publishes to npm only after JSR has succeeded — npm versions
are effectively immutable, so the unretractable registry ships last.
v1.3.0
- Added item price base quantity (BT-149/BT-150). New optional
basisQuantityandbasisQuantityUnitCodefields onInvoiceLineInput
emitram:BasisQuantityinside the net (and, when present, gross) price
element, so a unit price can state the quantity it refers to — e.g.
12.50per 10 m². The unit code defaults to the line'sunitCode, as
EN 16931 requires BT-150 to match BT-130. Omitting the field keeps the
output byte-for-byte identical (#12). - CI now requires every PR to add a
CHANGELOG.mdentry (or carry the
skip-changeloglabel), so contributor fixes always reach the release notes.
v1.2.1
- Fixed XSD validation on Windows. Schema
<xsd:import>resolution built a
malformedfile://URL from backslash paths and keyed the schema lookup by
a percent-encoded href that libxml2 normalises differently. Now uses
pathToFileURL()and matches imported schemas by filename. Windows added
to the CI matrix (thanks @muratkorkmaztr-cmyk, #6). - Embedded
factur-x.xmlnow uses theapplication/xmlMIME type instead
of the deprecatedtext/xml(RFC 7303), which current Factur-X conformance
checkers flag (thanks @muratkorkmaztr-cmyk, #7). - Expanded npm
keywords(e.g.e-rechnung,pdfa,chorus-pro,un-cefact,
spelling variants) so the package is easier to find on npm (#8).
v1.2.0
-
Updated to Factur-X 1.09 / ZUGFeRD 2.5 (spec dated 2026-06-10). Factur-X
1.09 keeps the same UN/CEFACT CII D22B syntax, namespaces, and profile URNs
(urn:factur-x.eu:1p0:*) as 1.08, so this is a backward-compatible refresh:
XML produced by previous versions remains valid, and no input/output changes
are required.- Bundled XSD schemas under
schema/swapped to the official 1.09 set
(filenames normalized to the repo convention, internalschemaLocation
references rewritten to match). For MINIMUM / BASIC WL / BASIC / EN 16931 the
XSDs are content-identical to 1.08; the EXTENDED XSD gains only optional
additions (debited-account name, financial adjustment / charges on behalf of
a third party, an optionallistIDattribute on the allowance/charge reason
code). - Reference
.sch(Schematron) and_codedb.xmlfiles updated to the 1.09
code lists and CEN validation rules (effective 2026-05-15). These are not
shipped in the npm package and not used at runtime — this library performs
XSD validation only. - Documentation and code comments updated from "1.08 / 2.4" to "1.09 / 2.5".
- Bundled XSD schemas under
-
New optional EXTENDED-profile input fields for selected Factur-X 1.09
business terms (all additive, only emitted atProfile.EXTENDED):PaymentInput.debtorAccountName(BT-216) andPaymentInput.debtorBic
(BT-215) — debited-account name and its payment service provider BIC.AllowanceChargeInput.exemptionReason/exemptionReasonCode
(BT-173–176) — VAT exemption reason/code on document-level allowances and
charges.FacturXInvoiceInput.financialAdjustments(BG-34, new
FinancialAdjustmentInputwithreasonBT-180 +amountBT-179) —
charges collected on behalf of a third party.InvoiceLineInput.manufacturer(BG-X-94) — line-level product manufacturer
party (ram:ManufacturerTradeParty).- Remaining 1.09 EXTENDED terms (e.g. document-level non-VAT tax codes,
logistic-service-charge exemption reasons) are still not emitted and can be
added later without breaking changes.
-
Fixed:
ram:FormattedIssueDateTimenow emits aqdt:DateTimeStringchild
instead ofudt:DateTimeString. It is typed asqdt:FormattedDateTimeTypein
the CII XSD, so the previousudt:prefix failed XSD/Schematron validation for
every credit note or cancellation invoice referencing a dated preceding
invoice (#5). -
Fixed: XSD validation no longer exhausts libxml2's fixed-size input-callback
table.validateXsdnow clears the table on each call, so validating many
documents in one process no longer causes spurious failures once ~15
validations have run.
v1.1.0
- BREAKING: Every
ram:GlobalIDand the legal-organizationram:IDnow
carry the@schemeIDattribute required by EN 16931. The XSD-canonical CII
mapping makes the scheme mandatory onram:GlobalID(BT-29-1 / BT-46-1 for
parties, BT-157-1 for line items) and conditional on
ram:SpecifiedLegalOrganization/ram:ID(BT-30-1 / BT-47-1); without these
attributes Mustang / KoSIT / veraPDF's Factur-X profile reject the invoice
even though the embedded CII looked superficially correct. Three input
fields changed:TradePartyInput.globalIdis nowIdentifierWithSchemeInput
({ value: string; schemeID: string }) instead of a barestringInvoiceLineInput.standardIdentifieris nowIdentifierWithSchemeInput
instead of a barestring(sibling fix on top of #4 — the line-item
ram:GlobalIDhad the same defect as the party one)LegalOrganizationInput.schemeID?: stringadded (optional, mirrors the
conditional spec rule)
Migration:globalId: "4000001000005"→
globalId: { value: "4000001000005", schemeID: "0088" }(and the analogous
rewrite forstandardIdentifier). The newIdentifierWithSchemeInputtype
is exported fromsrc/index.ts. Regression test added in
tests/xsd-validator.test.tsthat drives all three schemed identifiers
through the EN16931 XSD in one go (thanks @sco-indy, #4).
v1.0.8
- PDF trailer was missing the
/IDentry required by PDF/A-3. ISO 19005-3
(and PDF 1.7 §14.4) require the trailer dictionary to carry an/IDarray of
two byte strings: the first immutable identifier of the original document,
the second updated on every modification.pdf-libinitializes
trailerInfo = {}and never writes one on save, so veraPDF and the FNFE-MPE
Factur-X profile rejected every PDF this library produced as non-conformant.
AddedensureIdTrailerinsrc/core/embed.tswhich runs after the first
pdfDoc.save()whenaddPdfA3Metadatais on: it computes an MD5 over the
serialized bytes (per PDF 1.7 §14.4 — uniqueness, not cryptographic strength,
is what matters), preserves any pre-existing first identifier, and writes the
pair before re-saving. Regression tests added intests/embed.test.ts
covering both the no-existing-trailer and existing-trailer paths (thanks
@sco-indy, #3).