Quotes, delivery notes and other documents

Documents share parties, lines, calculation, design and templates with invoices but are no invoices: they never produce e-invoice XML, are not validated and are rendered as PDF or HTML only.

Type DocumentType Builder Lines Prices and totals Payment block
Quote Quote (quote) DocumentBuilder::quote() required yes none (no bank details, QR code or amount due)
Order confirmation OrderConfirmation (order_confirmation) orderConfirmation() required yes none
Delivery note DeliveryNote (delivery_note) deliveryNote() required no none
Proforma invoice Proforma (proforma) proforma() required yes payment terms, bank details, QR code
Payment reminder PaymentReminder (payment_reminder) paymentReminder(int $level = 0) not allowed open items, fees, interest bank details, QR code over the total
Account statement AccountStatement (account_statement) accountStatement() not allowed open items, balance bank details

DocumentBuilder::make(DocumentType $type) creates a builder for any type (reminders then have level 0). DocumentType has showsPrices() (false for delivery notes), hasLineTotals() (quotes, order confirmations, proformas) and labelKey() (document_{value}).

use Quittio\Core\Documents\DocumentBuilder;
use Quittio\Country\DE\Tax\VatPreset;
use Quittio\Laravel\Facades\Quittio;
use Quittio\Laravel\Seller;

$quote = DocumentBuilder::quote()
    ->number('AN-2026-0007')
    ->issueDate('2026-10-06')
    ->seller(Seller::fromConfig())
    ->buyer(fn ($b) => $b->name('Kunde AG')->address('Weg 1', '54321', 'Beispielstadt', 'DE'))
    ->validUntil('2026-10-31')
    ->line(fn ($l) => $l->name('Consulting')->quantity(10, 'HUR')->netPrice('120.00')->vat(VatPreset::Standard19))
    ->optionalLine(fn ($l) => $l->name('Training')->quantity(1, 'DAY')->netPrice('800.00')->vat(VatPreset::Standard19))
    ->alternativeLine('1', fn ($l) => $l->name('Senior consulting')->quantity(10, 'HUR')->netPrice('150.00')->vat(VatPreset::Standard19))
    ->build();

Quittio::document($quote)->locale('de')->render()->download();

Builder methods

Parties, lines, notes, discounts and payment data work exactly like in the invoice builder. Dates accept Date, DateTimeInterface or a string; amounts are strings.

Method Effect
number(string $number) Document number
issueDate($date) Document date; default today (in the time zone of the document)
currency(string $currency) Currency; default EUR
seller(SellerParty\|callable $seller), buyer(BuyerParty\|callable $buyer) Parties; the callable receives a PartyBuilder
line(InvoiceLine\|ProvidesInvoiceLine\|callable $line, ?DocumentLineOptions $options = null) Line; the callable receives a LineBuilder
lines(iterable $lines) Several lines
optionalLine(InvoiceLine\|callable $line) Optional quote line: shown with its amounts, not in the totals
alternativeLine(string $alternativeTo, InvoiceLine\|callable $line) Alternative to the line with identifier $alternativeTo (BT-126; lines are numbered 1, 2, … unless set with LineBuilder::id()); shown, not in the totals
serialisedLine(InvoiceLine\|callable $line, array $serialNumbers) Delivery note line with serial numbers
note(string $content, ?NoteSubjectCode $subject = null) Note
discountPercent(string $percent, ?string $reason = null), discountAmount(string $amount, ?string $reason = null) Document discount, on the lines that count towards the totals
paymentTerms(PaymentTerms\|string\|callable $terms) Payment terms
creditTransfer(string $iban, ?string $bic = null, ?string $accountName = null) Bank account
buyerReference(?string $reference), purchaseOrderReference(?string $reference) Buyer’s references
reference(string $number, $date = null) Preceding document, e.g. the quote of an order confirmation or the order of a delivery note
validUntil($date) Validity of a quote
deliveryDate($date) Delivery date
deliverTo(?string $recipientName, ?Address $address = null) Deliver-to party
package(Package $package) Package of a delivery note
openItem(string $invoiceNumber, $invoiceDate, string $amount, $dueDate = null, ?string $paid = null, ?string $description = null) Invoice of a reminder or statement
fee(string $description, string $amount) Reminder fee
interest(string $base, BigDecimal\|string $ratePercent, $from, $until, ?string $description = null, ?int $dayCount = InterestCharge::ACTUAL_ACTUAL) Default interest of a reminder
deadline($date) Payment deadline of a reminder
statementPeriod($start, $end) Period of an account statement
openingBalance(string $amount) Opening balance of an account statement
presentation(Presentation\|callable $presentation) Design of this document (Templates)
locale(string $locale) Locale
custom(string $key, mixed $value) Custom value (meta.custom in the view model)
tap(callable $callback) Access to the underlying InvoiceBuilder
build(): DocumentData Builds the immutable document

DocumentBuilder is Macroable. tap() reaches every invoice builder method, but the document keeps only number, date, currency, parties, lines, allowances, charges, notes, buyer reference, purchase order reference, delivery, payment terms, payment instructions, presentation and meta; other values set through tap() are dropped.

build() throws an InvalidArgumentException when a quote, order confirmation, proforma or delivery note has no line, or when a reminder or account statement has lines, allowances or charges.

Quotes and order confirmations

Quotes show validUntil, mark optional lines (“[Optional]”) and alternative lines (“[Alternative to pos. 1]”) and end with an acceptance field (“Order placed:” with a signature line). Optional and alternative lines get their line amounts but are excluded from the totals, the VAT breakdown and InvoiceBuilder::fromDocument(); document discounts apply to the remaining lines. Offers are no request to pay: the view model has no payment block, no amount due and no due date text.

Order confirmations reference the quote with reference('AN-2026-0007', '2026-10-06') and state the delivery date with deliveryDate().

Delivery notes

Delivery notes print quantities without prices or totals. Lines given as callable start with net price 0 and VAT category O (not subject to VAT), so they need only name, quantity and unit; the callable may still set prices. InvoiceLine objects and ProvidesInvoiceLine models are taken as they are.

use Quittio\Core\Documents\Package;
use Brick\Math\BigDecimal;

$note = DocumentBuilder::deliveryNote()
    ->number('LS-2026-0012')
    ->seller(Seller::fromConfig())
    ->buyer($buyer)
    ->reference('AB-2026-0003')
    ->deliveryDate('2026-10-02')
    ->serialisedLine(fn ($l) => $l->name('Router')->quantity(2, 'C62'), ['SN-1001', 'SN-1002'])
    ->line(fn ($l) => $l->name('Cable')->quantity(5, 'MTR'))
    ->package(new Package('P1', type: 'Box', weightKg: BigDecimal::of('2.5'), dimensions: '30 × 20 × 10 cm', trackingNumber: '00340434161094042557'))
    ->build();

Package has id, type, weightKg (BigDecimal), dimensions and trackingNumber. The template lists the packages and the serial numbers below each line.

Proforma invoices

A proforma is not an invoice for tax purposes. Build it with DocumentBuilder::proforma(): it is rendered like an invoice (prices, totals, payment terms, bank details and QR code) with the title “Proforma invoice” and the notice “This proforma invoice is not an invoice for tax purposes.” among the legal notes, and never carries e-invoice XML. An invoice with type code 325 (InvoiceTypeCode::ProformaInvoice) in the invoice pipeline fails validation with QUITTIO-PROFORMA, because e-invoice XML with VAT amounts would be booked as an invoice.

Payment reminders

$reminder = DocumentBuilder::paymentReminder(1)          // 0 = payment reminder (Zahlungserinnerung), 1 = 1. Mahnung, 2 = 2. Mahnung …
    ->number('M-2026-0003')
    ->seller(Seller::fromConfig())
    ->buyer($buyer)
    ->openItem('RE-2026-0042', '2026-08-01', '1190.00', dueDate: '2026-08-15', paid: '190.00')
    ->fee('Reminder costs', '2.50')
    ->interest('1000.00', '10.27', from: '2026-08-16', until: '2026-10-04')   // until is exclusive: last day 03.10.
    ->deadline('2026-10-18')
    ->creditTransfer('DE02120300000000202051', 'BYLADEM1001')
    ->build();

$reminder->dunning->total('EUR');            // open items + fees + interest, no VAT
$reminder->dunning->remittanceText();        // "RE-2026-0042" – open invoice numbers for the transfer

The title is “Payment reminder” for level 0 and “Reminder no. :level” (“1. Mahnung”) from level 1. The template lists the open items (invoice number, date, due date, amount, paid, open), the fees, the interest lines and “Total to pay”, then the deadline text (“Please pay the total amount by …”). The payment block uses the open invoice numbers as remittance information (the document number if none is open) and the GiroCode carries the total of the reminder.

  • Reminders have no lines, allowances or charges; the builder rejects them. The amount to pay is Dunning::total(): open items plus fees plus interest.
  • Fees and default interest carry no VAT. They are damages, not consideration for a supply (UStAE 1.3 Abs. 6); the same applies to the flat rate of section 288 (5) BGB.
  • Only actual costs may be claimed as reminder fees; Quittio has no default fee. The documents layer does not track which claims already carried a flat rate.
  • interest() takes the first day with interest (from, e.g. the day after the due date) and the day after the last day with interest (until, exclusive). InterestCharge::lastDay() is the last day with interest; the default description uses it (“Default interest 10.27% p.a. on 1,000.00 € from … to …”).
  • Interest is simple interest counted actual/actual: the days of each calendar year are divided by 365 or 366, so that a full year yields exactly the annual rate. Pass dayCount: 365 or dayCount: 360 for a fixed basis; other values and a period that ends before it starts throw an InvalidArgumentException. The amount is rounded half up once.

Dunning ($document->dunning): level, openItems, deadline, fees (DunningFee: description, amount), interest, openTotal($currency), feeTotal($currency), interestTotal($currency), total($currency), openInvoiceNumbers() (unsettled items, without duplicates) and remittanceText(int $maxLength = 140) (as many complete numbers as fit, comma separated). A negative level throws an InvalidArgumentException.

InterestCharge: base, ratePercent, from, until, description, amount (a stated amount replaces the calculation), dayCount, days(), lastDay(), total().

OpenItem: invoiceNumber, invoiceDate, amount, dueDate, paid, description, open() (amount minus paid; negative for overpaid items and credits) and isSettled() (nothing open).

German default interest

The statutory rates of section 288 BGB (base rate plus 9 or 5 percentage points, periods split at every change of the base rate) come from the German country pack; add its charges to the reminder:

use Brick\Money\Money;
use Quittio\Core\Support\Date;
use Quittio\Country\DE\Dunning\DefaultInterest;

foreach ((new DefaultInterest)->calculate(Money::of('1000.00', 'EUR'), Date::parse('2026-06-16'), Date::parse('2026-10-05'), consumer: false) as $charge) {
    $builder->interest((string) $charge->base->getAmount(), $charge->ratePercent, $charge->from, $charge->until, $charge->description);
}

Base rate table, flat rate and the conditions of default are described in Country packs. The country-neutral contract is CountryPack::dunningLaw() (DunningLaw: b2bInterestPoints, b2cInterestPoints, b2bFlatRate, dayCount, consumerDefaultAfterDays, referenceRateName).

Account statements

$statement = DocumentBuilder::accountStatement()
    ->number('KA-2026-09')
    ->seller(Seller::fromConfig())
    ->buyer($buyer)
    ->statementPeriod('2026-09-01', '2026-09-30')
    ->openingBalance('100.00')
    ->openItem('RE-2026-0050', '2026-09-10', '595.00', dueDate: '2026-09-24', paid: '595.00')
    ->openItem('RE-2026-0061', '2026-09-28', '238.00', dueDate: '2026-10-12')
    ->build();

$statement->statement->balance('EUR');       // 338.00

The balance is the opening balance plus the open amounts (amount minus paid) of the listed items. The opening balance is the balance at the start of the period and already contains every invoice dated before it, so:

  • with openingBalance(), only items dated within statementPeriod() are accepted;
  • without an opening balance the statement is an open item list and may list older items;
  • items dated after the end of the period are always rejected;
  • without statementPeriod() the period ends on the document date.

Violations throw an InvalidArgumentException, as does a period that ends before it starts. AccountStatement ($document->statement) has period, items, openingBalance, openItems() (unsettled items) and balance($currency). The template lists all items, the period, the opening balance and the balance; the payment block shows the bank details without QR code.

DocumentData

build() returns Quittio\Core\Documents\DocumentData (immutable, with()): type, number, issueDate, currency, seller, buyer, lines, lineOptions (DocumentLineOptions keyed by line identifier), allowances, charges, notes, validUntil, reference, referenceDate, buyerReference, purchaseOrderReference, delivery, packages, dunning, statement, paymentTerms, paymentInstructions, totals, vatBreakdown, presentation and meta.

Method Returns
options(string $lineId) DocumentLineOptions of a line (optional, alternativeTo, serialNumbers, isBillable())
billableLines() Lines that count towards the totals (without optional and alternative lines)
isCalculated() Totals are set (or there is nothing to calculate)
toInvoiceDraft(bool $billableOnly = true) The document as InvoiceData for calculation and rendering; never use it as an invoice

Documents are calculated by DocumentCalculator with the invoice calculator when they are rendered: every line gets its amounts, the totals and the VAT breakdown come from the billable lines.

Rendering

$result = Quittio::document($quote)          // DocumentData; in Laravel also a DocumentBuilder
    ->locale('en')
    ->country('DE')
    ->template('invoices.my-design.layout')
    ->presentation(new Presentation(title: 'Offer'))
    ->render();

$html = Quittio::document($quote)->htmlOnly()->render()->html();
PendingDocument method Effect
template(string $template) Template (selection rules)
locale(string $locale) Locale; otherwise the document’s locale, the configured locale, the country default
country(string $country) Country pack
presentation(Presentation $presentation) Design defaults of this call; the document’s own presentation wins
htmlOnly() HTML instead of PDF
render() RenderedDocument

Documents use the invoice templates and the same view model; the node documentInfo holds the document specific values (view model), document.kind the type. The PDF is PDF/A-3b when pdf.pdfa is set and no watermark is used, never with embedded XML. Documents run no validation, no mandatory field guard and dispatch no events.

RenderedDocument (core): data(), html(), hasPdf(), pdf() (LogicException after htmlOnly()), contents() (PDF, otherwise HTML), mimeType(), filename(?string $extension = null) and save(string $path) (a file, or a directory for the generated file name). The Laravel class adds core(), download(?string $filename = null), stream(?string $filename = null), save(?string $disk = null, ?string $path = null) (default disk quittio.storage.disk; a path ending in / gets the file name appended), is Responsable (download) and Attachable for mails (Laravel integration).

The file name follows pdf.filename ({title}_{number}.{ext}; {format} is the document type, {date} the document date): Angebot_AN-2026-0007.pdf, 2._Mahnung_M-2026-0003.pdf.

From document to invoice

use Quittio\Core\Builder\InvoiceBuilder;

$invoice = InvoiceBuilder::fromDocument($quote)
    ->number('RE-2026-0100')
    ->issueDate('2026-11-16')
    ->deliveryDate('2026-11-15')
    ->build();

fromDocument() starts an invoice from a quote, order confirmation, delivery note or proforma: it takes over the parties, the billable lines (amounts are recalculated), document allowances and charges, payment terms and instructions, delivery, buyer reference, purchase order reference, presentation and locale. Number and notes are not taken over; set the number, and the issue date (otherwise today). The number of an order confirmation becomes the sales order reference (BT-14), the number of a delivery note the despatch advice reference (BT-16); quotes and proformas are not referenced. Lines of a delivery note have no prices (net price 0, category O) and need prices and VAT before invoicing. The result is a normal InvoiceBuilder; validation and e-invoice formats apply when it is rendered.