Skip to content

Releases: Quittio/quittio

0.5.0

Choose a tag to compare

@nforst nforst released this 18 Oct 14:55
v0.5.0
cc7f2e5

Added

  • MimeType::Xml (application/xml) for supporting documents (BT-125-1) in the profile xrechnung-extension, which allows XML attachments (BR-DEX-01). The PHP rules now check the MIME code with BR-CL-24: other profiles report MimeType::Xml as error, as the official Schematron does (BR-CL-24, in Peppol also PEPPOL-EN16931-CL001); the extension accepts it through ExtendsCodeLists::additionalCodes().

Changed

  • The VAT rows of the PDF and HTML name IGIC (category L, Canary Islands) and IPSI (category M, Ceuta and Melilla) instead of VAT, e.g. "7 % IGIC auf 50,00 €" or "7% IGIC on 50.00 €" (labels vat_row_igic and vat_row_ipsi). Rows of the other categories are unchanged.
  • The package rules QUITTIO-DECLARED-AMOUNTS, QUITTIO-VAT-ROUNDING and QUITTIO-CANCELLATION-VAT-ALIGNED read the declared amounts state through the internal Calculation\DeclaredAmountsState like the calculator, the builder, the reader and the cancellations. The results are the same.

0.4.0

Choose a tag to compare

@nforst nforst released this 17 Oct 22:03
0d822c6

Added

  • Typed view model: every node that templates receive has a class in Quittio\Core\View\Model (PartyView, AddressView, LineView, VatRowView, TotalsView, PaymentView, DesignView, DocumentInfoView …; lists are ViewList<T>, e.g. ViewList<LineView>) with its keys as typed properties, so IDEs and PHPStan know that $invoice->totals->amountDue is a ?FormattedValue. The nodes are ViewNode objects over the same data: published Blade and PHP templates, array syntax, toArray() and the mandatory field guard work unchanged (Typed nodes). The compatibility promise now names the keys and types of the view model.
  • The PHP rules check the XRechnung extension like the official validator: BR-DEX-02 (warning), BR-DEX-04 to BR-DEX-10 and BR-DEX-12 to BR-DEX-14 for identifier schemes with XR01 to XR03, the amount due with third party payments and the third party payments themselves. In the profile xrechnung-extension they replace BR-CL-10, -11, -21, -25, -26 and, in UBL, BR-CO-16 and QUITTIO-SUBLINES, so a violation is reported once. Profiles declare such replacements with the capability ReplacesRules.
  • The Peppol profile checks all Peppol BIS Billing 3.0 rules that the data of an invoice can break, with readable messages instead of only the official validation: the format of the business process (PEPPOL-EN16931-R007), values of only whitespace (-R008), net price against gross price and discount (-R046), the sign of BT-111 (-R055), line periods within the invoicing period (-R110, -R111), currencies and dates (-CL007, -F001), the type codes of business process 01 (-P0100, -P0101), the type codes 326 and 384 outside Germany (-P0112), VATEX codes with the wrong VAT category (-P0104 to -P0111) and the format and check digits of GLN, Norwegian, Danish, Belgian, Italian, Swedish and Australian identifiers (PEPPOL-COMMON-R040 to -R053). The code lists CodeList::PEPPOL_CURRENCIES, PEPPOL_INVOICE_TYPES and PEPPOL_CREDIT_NOTE_TYPES are generated from the Peppol rules with CodeListGenerator::generatePeppol() (CodeListGenerator::PEPPOL_LISTS). The rules the UBL writer always satisfies are listed in the validation docs (Peppol rules).
  • The PHP validation checks the VAT categories L (IGIC, Canary Islands: BR-AF-01, -02, -05, -08, -09, -10), M (IPSI, Ceuta and Melilla: BR-AG-01, -02, -05, -08, -09, -10) and B (split payment, Italy: BR-B-01, BR-B-02). Like S, L and M allow one VAT breakdown per rate with VAT; their rate may also be 0. The warning QUITTIO-IGIC-ZERO-RATE-CII reports IGIC with the rate 0 in CII, which the official CII Schematron rejects.
  • XRechnung CVD (Clean Vehicles Directive) in the German pack: the profile xrechnung-cvd (XRechnung30Cvd, specification identifier …#compliant#urn:xeinkauf.de:kosit:xrechnung:cvd_0.9) for xrechnung-cii, xrechnung-ubl and zugferd, with the rules BR-DE-CVD-01 to BR-DE-CVD-06-b: contract (BT-12) and tender or lot reference (BT-17) are required, and vehicle lines carry the category as item classification with scheme CVD (BT-158) and the item attribute cva (BT-160, BT-161). The profile accepts the scheme CVD in the code list check BR-CL-13.

Changed

  • The Laravel settings of the German pack live under quittio.countries.de (config('quittio.countries.de.base_rates'), ….base_rate_file) instead of the configuration quittio-de. php artisan vendor:publish --tag=quittio-de-config publishes them to config/quittio/countries/de.php, which Laravel loads as quittio.countries.de; they can also be set in config/quittio.php under 'countries' => ['de' => […]]. quittio:install publishes the tag quittio-{code}-config of every installed country pack with config/quittio.php. GermanyServiceProvider no longer fills quittio-de with defaults: code that reads config('quittio-de.…') reads quittio.countries.de. Lower-case codes under quittio.countries hold the settings of a pack, the convention for further packs (quittio.countries.{code}); a pack class listed under 'de' is moved to a numeric key.
  • The calculator, the builder, the reader and the cancellations read and write the declared amounts flags together as one typed state (the internal Calculation\DeclaredAmountsState) instead of single flags. The flags stay the stored format with the same strings, so stored data of earlier versions is read unchanged; they are now always written after the other flags in a fixed order (declared_amounts, declared_amounts_recalculated, cancellation_vat_aligned, declared_vat_in_tax_currency:, amounts_fingerprint: last), and duplicates or an unreadable declared_vat_in_tax_currency: value are dropped when data is calculated again.
  • ViewModelFactory builds the view model with one internal factory per section (header, parties, references, lines, allowances and charges, VAT breakdown, totals, payment, notes, design, documents; View\Internal\Sections). The view model is the same as before.

Deprecated

  • The configuration quittio-de (config/quittio-de.php): move its keys to quittio.countries.de and delete the file. Until 1.0 it is still read: its keys fill those that quittio.countries.de leaves out or keeps at the default, and the provider reports an E_USER_DEPRECATED on every start while it is set (The old file).

Fixed

  • The KoSIT validator (L5) reports a rule with the level the validator scenario gives it (customLevel), as the validator itself does when it decides on acceptance. Before, the level of the Schematron was used, so e.g. BR-CO-16 in an XRechnung extension invoice with third party payments was reported as error although the validator accepted the document.
  • The official validation in CI now checks PDF/A-3b with veraPDF: the job pulls the verapdf/cli image (pinned by digest), and the veraPDF tests fail instead of being skipped when the image is missing in CI. tools/validate-fixtures.sh also renders every EN 16931 scenario as ZUGFeRD PDF and checks the PDFs with veraPDF; before, it generated no PDFs, and its veraPDF call would have received no files.

0.3.0

Choose a tag to compare

@nforst nforst released this 16 Oct 15:46
fe22ebb

Added

  • Single PHP rules can be switched off, re-rated or replaced by their code: validation.rules.disabled, validation.rules.severity and Quittio::extend('rule', …) in Laravel, RuleOverrides with Quittio::configureRules() in the core. Official EN 16931 and Peppol rules need allow_official; unknown codes are refused. Quittio::ruleCodes() lists the known codes.
  • The calculation policy decides what happens with declared amounts of other systems: calculation.declared_amounts (keep, recalculate, reject), CalculationPolicy and DeclaredAmountsMode in the core. An own calculator is configured with calculation.calculator or passed to Quittio::create(calculator: …); Quittio::calculator() returns it.
  • Quittio::edit() (core and Laravel) and InvoiceBuilder::fromUsing() continue existing invoice data with the calculator of the instance; cancellations and the document calculator of QuittioFake use it as well. InvoiceFactory::calculated() takes a calculator.
  • Tax presets whose rate depends on the date (Quittio\Core\Tax\DatedTaxPreset) are resolved with the supply date of each line (SupplyDate: line period, VAT point date, delivery date, invoicing period, issue date). DatedValues holds values that change on dates; TaxPresetCollection::alias() finds presets under further keys.
  • VatPreset::Standard (de.standard) and VatPreset::Reduced (de.reduced) take the German rate of the supply date (TaxTables), e.g. 16 % and 5 % from July to December 2020. The plausibility rule DE-VAT-RATE warns about rates that applied neither as standard nor as reduced rate on the supply date. The small amount limit of DE-USTG-08 is that of the issue date (150.00 EUR before 2017).
  • Country packs register output formats, aliases and serializers (Countries\Capabilities\ProvidesFormats, implemented by AbstractCountryPack); the German pack brings XRechnung (XRechnungFormats). Profiles declare sub lines, extension fields and third party payments with capabilities (SupportsSubLines, SupportsExtensionFields, SupportsThirdPartyPayments) instead of the core checking XRechnung keys.
  • Default interest for every country pack with a reference rate: Quittio\Core\Dunning\DefaultInterest (forCountry($pack)), the contract Dunning\ReferenceRates and the capability ProvidesReferenceRates; the German pack provides its base rates (BaseRateTable implements ReferenceRates) and its DefaultInterest uses the core calculation. DocumentBuilder::interestCharge() adds calculated charges with their day count and texts.
  • Documents can be queued in Laravel: Quittio::document($quote)->queue()->save($disk, $path) with the job RenderDocument, the events QueuedDocumentStored and QueuedDocumentFailed and QuittioManager::queueDocument(); QuittioFake records them (assertDocumentQueued(), queuedDocuments()). Documents are stored as versioned snapshots (DocumentData::toSnapshot(), fromSnapshot(), toArray(), toJson() …) with their own upcasters (quittio.snapshots.document_upcasters, SnapshotUpcasterChain::documentDefault()); Mapping\Attributes\MapOf keeps keyed arrays.
  • The German special cases are reached with the builder macro de(): ->de()->smallAmount(), ->de()->selfBilled(), ->de()->deductAdvance($advance) … (GermanInvoice).

Changed

  • Lines without VAT treatment of a German seller get the preset key de.standard instead of de.standard_19, and quittio.defaults.vat = 'de.reduced_7' gives de.reduced. Applications that compare preset keys use VatPreset::fromKey($key)?->canonical(). Supplies from 2020-07-01 to 2020-12-31 get 16 % and 5 %.
  • CiiWriter, UblWriter, CiiReader and UblReader keep no state between calls: each call works with its own internal worker, so one instance can be used reentrantly. The readers no longer extend the internal XPathReader and therefore no longer expose its constants.
  • The render run resolves the logo of the presentation before it builds the view model (ViewContext::$logo, ResolvedLogo), so the view layer does no I/O; ViewModelFactory keeps no state between calls.
  • The documentation website is versioned: the root shows the latest release, /next/ the development version and /X.Y/ older minor versions, with a version switcher (docs/_versioning/build.mjs). Every version has a generated API reference under api/ (phpDocumentor, tools/docs/phpdoc.sh); the builder traits under Builder\Concerns are no longer @internal, so their methods appear there.
  • Pull requests opened in the read-only split repositories quittio/core, quittio/laravel and quittio/country-de are closed automatically with a pointer to the monorepo.

Deprecated

  • VatPreset::Standard19 and VatPreset::Reduced7: use VatPreset::Standard and VatPreset::Reduced; the keys de.standard_19 and de.reduced_7 stay readable. Flags::SMALL_AMOUNT_LIMIT: use TaxTables::smallAmountLimitOn().
  • The builder macros smallAmount(), propertyRelatedService(), newVehicle(), selfBilled(), deductAdvance() and consumerDefaultNotice() directly on the builder: use ->de()->…. GermanInvoiceMacros::ADVANCE_TYPES: use GermanInvoice::ADVANCE_TYPES. FormatRegistry::XRECHNUNG_CII and XRECHNUNG_UBL: use XRechnungFormats::CII and UBL of the German pack, which registers the formats; the core keeps them until 1.0.

Fixed

  • Peppol invoices with an electronic address scheme that Peppol does not accept, e.g. EM (e-mail), are rejected with the new rule PEPPOL-EN16931-CL008 instead of passing the PHP rules and failing the official Peppol validation. The allowed schemes are the code list CodeList::PEPPOL_EAS (peppol-eas-codes.json, from the Peppol BIS rules 3.0.20); tools/codelists/update.php regenerates it with CodeListGenerator::generatePeppol().

0.2.0

Choose a tag to compare

@nforst nforst released this 15 Oct 23:02
d2ca3d3

Added

  • The legal texts of the German pack (LegalTexts) and the text of default interest come from its language files (resources/lang/{de,en}/legal.php), so lang/vendor/quittio-de overrides them in Laravel; LegalTexts::DEFAULT_INTEREST is new.
  • quittio/country-de ships stubs/InvoiceBuilder.stub.php, which describes its builder macros (smallAmount(), selfBilled(), deductAdvance(), …) for IDEs and PHPStan.
  • Tests: generated round trips through CII and UBL, fuzzing of the XML, PDF and array input, and tests for formats, mapping, serialisation, the PDF parser, mail attachments, a second boot and the escaping of the Blade templates.
  • Documentation: Versioning and compatibility with the backwards compatibility promise; ARCHITECTURE.md, decision records in docs/adr/, RELEASING.md, a reworked CONTRIBUTING.md, an FAQ page; issue and pull request templates, CODEOWNERS and response times in SECURITY.md. Classes outside the public API (XPathReader, XmlBuilder, Pdf\Parsing, CarryOver, ConfigMapper, the command and preview classes, …) are marked @internal.
  • Documents (quotes, reminders, …) dispatch DocumentRendering and DocumentRendered in Laravel; PendingDocument has option() (core and Laravel), data(), options() and is Macroable (Laravel).
  • Quittio::setPostProcessor() replaces the PDF/A and XML embedding for renderers that cannot embed. Profiles describe their embedded XML with the capability DescribesEmbeddedXml (XMP description and fx:Version) instead of the file name deciding it.
  • Amounts of prepaid(), addPrepaid(), rounding(), discountPercent(), discountAmount() and of the document builder's fee() and openingBalance() accept BigDecimal, int or string like line prices (floats are rejected). DocumentBuilder::note(), discountPercent() and discountAmount() take the same subject and reason codes as the invoice builder.
  • QuittioFake dispatches the events of a real run (InvoiceRendering, InvoiceValidated, InvoiceRendered) and records queued invoices: assertQueued(), assertNothingQueued(), queued(). Its decisions follow configuration changes without Quittio::flush().
  • Events QueuedInvoiceStored (a queued job stored the file, with disk and path) and QueuedInvoiceFailed (a queued job failed for good).
  • Quittio\Core\Quittio::resetState() forgets the process-wide state (translator bridge, default snapshot upcasters, application default VAT, base rates of the German pack); extensions register their own state with onResetState(). The test trait InteractsWithQuittio calls it after each test.

Changed

  • README and documentation mark the Managed layer quittio/managed as not released yet; its documentation page is a preview and is left out of llms.txt, llms-full.txt and the MCP index.
  • The documentation is built and link-checked on pull requests; every PHP code block of the documentation must parse.
  • quittio/core, quittio/laravel and quittio/country-de declare their test dependencies and ship a phpunit.xml.dist, so their split repositories run composer test on their own.
  • quittio/quittio registers the Quittio facade alias and suggests ext-intl, ext-zip and opis/json-schema like the single packages.
  • Development: Composer scripts test:fast, test:official, test:visual, test:coverage, test:mutation, docs:generate, codelists:update and ci; a weekly CI run with composer audit, line coverage and mutation testing; PHPStan also checks tools/*.php and reports calls of deprecated code.
  • Visual baselines are written only with QUITTIO_UPDATE_BASELINES=1; a missing baseline fails the test. The default Blade partials that print pre-escaped HTML carry a warning comment.
  • Queued RenderInvoice jobs store the render options as versioned JSON (RenderOptions::toArray()/fromArray()) instead of a PHP-serialised object, so an update of Quittio no longer breaks jobs in the queue; jobs queued by 0.1.0 still run. Errors that another attempt cannot fix (validation, invalid data, configuration) fail the job at once instead of three times; other errors are retried after 10 and 60 seconds. Inside a database transaction the job is dispatched after the commit (quittio.queue.after_commit).
  • Every exception that Quittio throws implements QuittioException: invalid arguments and runtime errors are now Quittio\Core\Exceptions\InvalidArgumentException, RuntimeException or LogicException, which extend the PHP classes of the same name, so existing catch blocks keep working.
  • Country pack discovery keeps its result in bootstrap/cache/quittio-countries.php (and per process) until Composer changes the installed packages, instead of reading vendor/composer/installed.json whenever the core is built.
  • The Laravel configuration no longer defaults to Germany: defaults.country (QUITTIO_COUNTRY) and seller.address.country are null, which means the only installed country pack (Germany with quittio/quittio), so existing applications keep their behaviour. With several packs, set defaults.country or the seller's country. Quittio::registerCountry() makes the next call build the core again.
  • The Laravel manager builds a new core when the quittio configuration changes, so Quittio::flush() is no longer needed after config()->set(). Under Octane it reads the configuration of the current request instead of the application that created the singleton; so does the default VAT of lines without VAT.

Deprecated

  • Environment variables are named after their configuration keys: QUITTIO_DEFAULT_COUNTRY, QUITTIO_VALIDATION_MODE, QUITTIO_MANDATORY_FIELD_GUARD, QUITTIO_STORAGE_DISK and QUITTIO_PREVIEW_ENABLED. The names of 0.1.0 (QUITTIO_COUNTRY, QUITTIO_VALIDATION, QUITTIO_GUARD, QUITTIO_DISK, QUITTIO_PREVIEW) are still read when the new one is not set, until 1.0.
  • The methods of Quittio\Core\Quittio that change the instance are named after what they do: addSerializer(), addValidator(), clearValidators(), setRenderer(), setTemplateEngine(), setPaymentQrGenerator() and setFormatResolver(). The with…() names, which elsewhere in Quittio return a changed copy, remain as deprecated aliases until 1.0.

Security

  • quittio:schemas:download checks the KoSIT validator, the XRechnung configuration, the Peppol rules and SchXslt against pinned SHA-256 checksums before it unpacks or runs them; so do the download scripts in tools/.
  • Decimal strings are only accepted in plain notation (Decimal::of(), builders, InvoiceData::fromArray()/fromJson(), snapshots): an exponent such as 1e999999999 was expanded to a billion digits and exhausted the memory. Other notations throw a NumberFormatException (a HydrationException for array and JSON input).
  • The preview route refuses requests outside the local environment unless the gate viewQuittioPreview allows them; before, QUITTIO_PREVIEW=true made it public behind the web middleware only. Requests are limited to 30 per minute (quittio.preview.throttle). QuittioServiceProvider::registerPreviewRoutes() is no longer public.

Fixed

  • Default interest of payment reminders was described in English for German locales with a region such as de_AT.
  • InvoiceData::fromJson() threw PHP's JsonException for JSON that cannot be parsed (empty, too deep, invalid UTF-8); it now throws a HydrationException. JsonSchemaValidator::validate() reports such input as a finding.
  • DE-USTG-08 names the limit of Flags::SMALL_AMOUNT_LIMIT (250.00 EUR) instead of a second, hard-coded value. The English fallback messages of four UStG rules matched their translations no longer; a test now compares every rule's fallback with lang/en/rules.php and requires a German message.
  • The field reference documents the UBL credit note path of BT-129 and BT-130 (cbc:CreditedQuantity) and no longer shows pseudo-XPath for BT-110 and BT-111. A test now evaluates every documented CII and UBL path against the XML of the sample invoices and of the official XRechnung test suite.
  • A custom TotalsCalculator was bypassed by the VAT alignment of partial cancellations; Cancellation::of() and DeclaredAmounts::needed() accept the calculator, and Quittio::cancellation() passes its own.
  • Texts with control characters (e.g. a vertical tab copied from a spreadsheet cell) or invalid UTF-8 produced XML that was not well-formed and was rejected by every receiver. The new rule QUITTIO-INVALID-CHARACTERS names the field, and the CII and UBL writers refuse such data with an InvalidInvoiceData exception.
  • A UBL credit note without payment instructions (BG-16) left out its due date (BT-9) silently: the UBL binding transmits it only in cac:PaymentMeans. The new warning QUITTIO-CREDIT-NOTE-DUE-DATE points this out; the field reference names the credit note path.
  • A percentage document allowance or charge without base amount whose VAT category and rate matched no invoice line became 0.00 silently (data from JSON, readers or with()); the calculator now throws a CalculationException. Drafts without any lines still calculate with a zero base.
  • Reading an invoice and the check for declared amounts no longer turn errors such as a TypeError of a custom TotalsCalculator into the warning QUITTIO-READER-RECALCULATION; only exceptions are reported.
  • The post-processing of PDFs from renderers that cannot embed (PDF/A and XML) uses the configured temp_dir instead of the system temporary directory.
  • quittio:validate: an unknown --profile printed a stack trace; files that cannot be checked (missing, not an e-invoice, unknown profile) now exit with 2 instead of 1, so scripts can tell them from invalid files; with --json such errors are printed as {"valid": false, "error": …} and the result names expectedProfile and profileMismatch.
  • Failed writes are reported w...
Read more

v0.1.0

Choose a tag to compare

@nforst nforst released this 15 Oct 13:50
4d7c268

First release.

Added

  • Monorepo quittio/quittio (the complete package) with the read-only splits quittio/core, quittio/laravel and quittio/country-de.
  • EN 16931 data model with mapping attributes (BT, cardinality, profile level, CII and UBL paths), builders, JSON conversion, generated JSON schema and field reference.
  • Calculation according to EN 16931 including document allowances split by VAT rate, gross prices, foreign currency and third party payments.
  • Profiles MINIMUM, BASIC WL, BASIC, EN 16931, EXTENDED, Peppol BIS 3; XRechnung 3.0 and the XRechnung extension in the country pack Germany.
  • Validation levels L1 to L7: PHP rules (EN 16931, Peppol, BR-DE, UStG), XSD, KoSIT validator (daemon and CLI), veraPDF (CLI and Docker), plausibility checks.
  • CII and UBL writers and readers with protection against XXE, entity expansion and oversized input; PDF reader for embedded invoices with decompression limits.
  • ZUGFeRD/Factur-X PDF/A-3b with mPDF, XMP metadata and associated files; post processing of foreign PDFs; EPC GiroCode.
  • View model with formatted and raw values, mandatory field guard, plain PHP and Blade default templates with partials, DIN 5008 letter layouts.
  • Format resolver, entry point Quittio\Core\Quittio and Laravel facade with several formats per call, storage, downloads, mail attachments, queue, events, preview route and Artisan commands.
  • Quotes, order confirmations, delivery notes, proforma invoices, payment reminders and account statements.
  • Country pack Germany: tax presets, UStG rules, format rules, legal texts, BMF error classes, numbering, retention, dunning law with base rate table, correction policy, builder helpers.
  • Testing helpers: fake, assertions, invoice factory, sample scenarios, visual regression (identical baselines on macOS and the Linux CI image).
  • quittio:schemas:download compiles the Peppol BIS Billing 3.0 rules; KoSIT configuration accepts several scenario sets.
  • Base rates up to 1 July 2026; small business invoices according to section 34a UStDV.
  • Full cancellations (Quittio::cancellation()) with configurable sign strategy: type code 381 with positive amounts (default) or 380 with negative amounts (corrections.cancellation_strategy).
  • Quittio::prepare() / PendingInvoice::prepare(): calculation, format and profile choice and validation without rendering.
  • InvoiceBuilder::updateLine(), removeLine() and InvoiceLine::withNetPrice().
  • Complete reference documentation: new pages Building invoices, Laravel integration and Configuration; every builder method, config key (both packages), command, event, exception, tax preset and rule is documented, the Managed layer page covers models, numbering, archive, immutability, delivery, audit log, authorisation and retention in full.
  • Documentation website built from docs/ with Jekyll and Just the Docs (navigation, search), deployed to Cloudflare by .github/workflows/docs.yml; the Markdown files stay the only source.
  • Country pack Germany: tax preset VatPreset::TriangularTransaction for intra-community triangular transactions (section 25b UStG, wording of section 14a (7) UStG).
  • Country pack Germany: rule DE-USTG-11 requires the VAT identifiers of seller and buyer for reverse charge services in another member state and for triangular transactions (section 14a (1) and (7) UStG); the tax number or a legal registration identifier is not sufficient there.
  • Country pack Germany: ->deductAdvance($advanceInvoice) for final invoices (section 14 (5) sentence 2 UStG): references the paid advance invoice, adds it to BT-113 and itemises its net amounts and VAT per rate in a note. InvoiceBuilder::addPrepaid() adds to BT-113.
  • quittio.seller.small_business now takes effect: Seller::apply() marks the invoice (Meta::FLAG_SMALL_BUSINESS) and the German pack rejects VAT on it (DE-USTG-09).
  • Official KoSIT test for every German tax preset in XRechnung CII and UBL; the XRechnung golden-file tests run in the official CI job; CI validates every package composer.json.
  • Laravel: format_resolver.peppol_lookup (invokable class or callable receiving the BuyerParty) for the automatic Peppol choice; Quittio::coreWithLevels(); RenderedDocument::hasPdf(); Quittio::fake()->documents().
  • Core: LineBuilder::showGrossPrice(); InvoiceBuilder::payee() accepts a PayeeParty, identifier schemes or a PartyBuilder callable; Quittio::cancellation() takes an optional $title; country pack readers (CountryPack::readers()) are used by read() and validateFile().
  • Country pack Germany: info DE-LEITWEG-ID-SUSPECT for buyer references shaped like a Leitweg-ID with wrong check digits; warning DE-USTG-13 when newVehicle() is used without a line of category K.
  • Country pack Germany: the base rate (§ 247 BGB) can be updated without a release: quittio-de.base_rates in the new configuration config/quittio-de.php, php artisan quittio:base-rate:update fetching it from the statistics API of the Deutsche Bundesbank (BundesbankBaseRates), or BaseRates::register(); DefaultInterest uses these rates by default. The pack has a Laravel service provider of its own (GermanyServiceProvider, discovered automatically).
  • Country pack Germany: tax numbers (BT-32) of German sellers are checked for format and check digit according to the ELSTER procedures of every federal state (warning DE-TAX-NUMBER); TaxNumber parses the ELSTER, federal and state notations.
  • Country pack Germany: rule DE-USTG-14 rejects intra-community supplies (category K) of small businesses except for new vehicles (section 19 (1) sentence 2 and (6) sentence 1 UStG); warning DE-USTG-15 for invoices on intra-community supplies and on services in another member state issued after the 15th of the month following the supply (section 14a (1) sentence 2 and (3) sentence 1 UStG); UstgRules::issuingDeadline().
  • Documentation for AI assistants: llms.txt, llms-full.txt and every page as Markdown on the website, a read-only MCP server at https://docs.quittio.dev/mcp (tools search_docs, get_page, list_pages) and Laravel Boost guidelines and the skill quittio-development in quittio/quittio and quittio/laravel.
  • Core: shorter invoice code. Lines can be given as name with named values (->line('Consulting', netPrice: '120.00', quantity: 4, unit: UnitCode::Hour), also vat, description and priceInclVat; the same in Line::make(), subLine() and the document builder), as Line::make('Consulting', …)->… or as arrays whose keys are the LineBuilder methods (LineBuilder::ARRAY_KEYS, fill(), Line::fromArray()), parties as Party::make('Kunde AG')->… or as application models: ->buyer($customer) for models implementing ProvidesParty. subLine() and the document builder accept the same forms.
  • Core: default VAT for lines without vat(): InvoiceBuilder::defaultVat() and DocumentBuilder::defaultVat() per invoice, InvoiceBuilder::useDefaultVat() for the application, CountryDefaultVat (configured preset, otherwise the standard preset of the seller's country pack, for small business invoices its small business preset), CountryDefaults::$vatPreset and $smallBusinessVatPreset (Germany: de.standard_19, de.small_business), CountryRegistry::taxPreset().
  • Core: InvoiceBuilder::applyDefaults() remembers the defaults of a seller; seller() with another seller removes them again, except values the caller set. Quittio::invoice() and the document methods use it, so another seller gets neither the bank account nor the small business flag (nor, with quittio/managed, the payment terms, design, locale, time zone and currency) of the configured seller.
  • validation.locale: the language of validation messages, independent of the invoice language. In Laravel the default is the language of the application (app()->getLocale()), 'invoice' restores the language of the invoice; without Laravel the default stays the invoice language, 'app' uses the translator's locale. ValidationContext::messageLocale().
  • Laravel: Quittio::invoice()->…->check() validates without rendering.
  • Core: fewer callbacks. dueInDays(), cashDiscount() and lateFee() on the invoice and document builder replace paymentTerms(fn ($t) => …); presentation() takes an array of changes; updateLine('2', netPrice: '80.00', quantity: 3) takes named values; payee() takes a PartyBuilder. The callbacks keep working.
  • Laravel: QUITTIO_SELLER_SMALL_BUSINESS for quittio.seller.small_business.
  • Laravel: config/quittio.php explains every key; pdf.colors.secondary is listed (default null, as before).
  • PDF: carry-over across pages. When the line table continues on the next page, the page ends with "Fortsetzung auf Seite 2 · Übertrag" and the sum of the lines so far, the next page starts with "Übertrag von Seite 1" (English "Continued on page 2 · Carried forward", "Brought forward from page 1"). The mPDF renderer lays a document out again only when the lines span pages; own templates enable it with markers (templates), view model lineGroups[].lines[].carryOver. Following pages get a header with logo or name, title and number, recipient, customer number and date.
  • Fonts: Geist (SIL Open Font License) is bundled and the default font, with the family geistmedium (Medium, SemiBold) for medium weights; Inter stays bundled (pdf.font = 'inter', also for Greek).
  • View model: design.fontMedium, design.continuationMarginTop, design.logoMaxHeight, design.footerHasTaxIds, totals.dueEqualsGross; labels payment, transfer_until, remittance_short, vat_row_single, your_vat_id, your_tax_number, tax_number_short, carry_over, carry_over_from, continued_on.
  • Documentation for first-time users: new pages E-invoicing basics (EN 16931, CII and UBL, formats, profiles, rule codes, which format you need, German terms) and Troubleshooting (co...
Read more