Skip to content

0.2.0

Choose a tag to compare

@nforst nforst released this 15 Oct 23:02
· 72 commits to master since this release
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 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.