You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 with a clear message: quittio:render, quittio:preview and quittio:base-rate:update exit with 1, RenderedInvoice::save() of the Laravel package throws a RuntimeException when the disk does not store the file. save() of the core results and quittio:render --output treat a path ending in / as directory and create missing directories (RenderedDocument::save() did not create them).
The CII and UBL readers accept every xs:boolean spelling of ChargeIndicator (1/0, surrounding white space). Before, such document allowances and charges were dropped and line charges were read as allowances; a missing or invalid indicator is now reported as QUITTIO-READER-CHARGE-INDICATOR. The documented CII and UBL paths of BG-20 and BG-21 are valid XPath.