CoolCMS API

InvoicePdfGenerator
in package

FinalYes

Domain wrapper over {@see PdfGenerator} for invoices.

Builds the exact $args key contract that the migrated Latte templates in the database depend on, generates the QR payment, prepares the target folder and picks the PDF variant. Returns relative paths of the PDF and QR for invoice.pdf / invoice.qrCode; the caller persists them (InvoiceManager::completedSave()) through a post-commit updateColumn.

Best-effort: generate() never throws. A partial failure (QR without currency, missing variant, render error) is only logged and yields null in the matching key, so the already saved invoice stays untouched.

IMPORTANT: args are materialized from the invoice into a plain array before any find(). A later clearDoctrine in the variant generator would otherwise detach the object graph being read.

Table of Contents

Constants

EXEMPT_CODES  : array<string|int, mixed> = [\App\UI\Api\System\Models\Enums\TaxTreatmentCo...
Exempt supplies: the VAT column shows 0 percent on the document (section 29/2).
MONEY_SCALE  : int = 3
Scale of the Invoicer money columns (`decimal(10,3)`); see {@see formatMoney()}.
PDF_DIR_KEY  : string = 'invoicerInvoicePdfDir'
PDF_DIR_MODULE  : int = 7
QR_FILE  : string = 'qrcode.png'
SETTING_MODULE  : int = 7
Module index of Invoicer in the settings table (`SettingPresenter::MODULE`).
TAX_DOCUMENT_CODES  : array<string|int, mixed> = [\App\UI\Api\System\Models\Enums\TaxTreatmentCo...
Treatment codes for which the document is a tax document (sections 28/29 of the VAT Act).

Properties

$config  : Config
$countryManager  : CountryManager
$currencyManager  : CurrencyManager
$invoiceTypeManager  : InvoiceTypeManager
$languageManager  : LanguageManager
$localizationManager  : LocalizationManager
$orderNumberResolver  : InvoiceOrderNumberResolver|null
$pdfGenerator  : PdfGenerator
$priceFormatContext  : PriceFormatContext
$settingContext  : SettingContext
$taxManager  : TaxManager
$translator  : Translator

Methods

__construct()  : mixed
formatMoney()  : string|null
Formats a money value for display in the form, with the rules of {@see formatQuantity()} and the `decimal(10,3)` scale.
formatQuantity()  : string|null
Formats a line quantity for printing in a PDF template.
generate()  : array{pdf: ?string, qrCode: ?string, pdfTemplateVariantId: ?int}
Generates the QR and PDF into the invoice folder and returns their relative paths (or `null`).
splitTaxNote()  : array{0: ?string, 1: ?string}
Splits the snapshot `tax_note` into [document language, Czech].
storedFilesPresent()  : bool
Tells whether the files referenced by the `pdf` / `qr_code` columns exist on disk.
applySymbol()  : void
buildArgs()  : array<string, mixed>
Builds the `$args` key contract of the legacy `InvoiceService::createVariablesForPdf`.
buildFileName()  : string
Builds the file name `{prefix_}{invoiceId}_{timestamp}.pdf`.
buildVatSummary()  : array<int, array{rate: string, rateKey: string, base: float, vat: float, total: float}>
Builds the VAT breakdown per rate for the footer of the modern PDF template.
folderPath()  : string
generateQrCode()  : string|null
Generates the QR payment into the invoice folder and returns the relative path to `qrcode.png`.
invoiceLocalization()  : object|null
Returns the document localization for the amount format, or null (historical documents) to fall back to the currency.
lineToArray()  : array<string, mixed>
Converts a line (shipper, payment method or item; they share getters) to a plain array of the legacy contract.
localizeArgs()  : array<string, mixed>
Applies the language and localization dependent parts of the args.
orderNumber()  : string|null
Returns the order number for `$orderNumber`, or null without an order or without the order module.
resolveVariant()  : int|null
Resolves the PDF variant for a document without a `pdf_template_variant_id` snapshot.
resolveVariantFromMatrix()  : int|null
settingIntOrNull()  : int|null
Returns the PDF layout setting (see {@see InvoiceGenerator::settingIntOrNull()}); `null` means the default layout, which `PdfGenerator` resolves itself.
spreadMixedRateVoucher()  : array<string|int, array{rate: string, rateKey: string, base: float, vat: float, total: float}>
Spreads a mixed-rate voucher across buckets proportionally to the item bases (fallback: bases of all buckets).
taxArgs()  : array<string, mixed>
Adds the `$tax` template args (VAT treatment of the document).
typeArgs()  : array<string, mixed>
Adds the document type to the template args.

Constants

EXEMPT_CODES

Exempt supplies: the VAT column shows 0 percent on the document (section 29/2).

private array<string|int, mixed> EXEMPT_CODES = [\App\UI\Api\System\Models\Enums\TaxTreatmentCode::IntraCommunitySupply, \App\UI\Api\System\Models\Enums\TaxTreatmentCode::Export]

MONEY_SCALE

Scale of the Invoicer money columns (`decimal(10,3)`); see {@see formatMoney()}.

private int MONEY_SCALE = 3

SETTING_MODULE

Module index of Invoicer in the settings table (`SettingPresenter::MODULE`).

private int SETTING_MODULE = 7

NOTE: a different constant from PDF_DIR_MODULE, which is for the config path, not settings, even though the number is the same.

TAX_DOCUMENT_CODES

Treatment codes for which the document is a tax document (sections 28/29 of the VAT Act).

private array<string|int, mixed> TAX_DOCUMENT_CODES = [\App\UI\Api\System\Models\Enums\TaxTreatmentCode::Domestic, \App\UI\Api\System\Models\Enums\TaxTreatmentCode::ReverseCharge, \App\UI\Api\System\Models\Enums\TaxTreatmentCode::IntraCommunitySupply, \App\UI\Api\System\Models\Enums\TaxTreatmentCode::Export, \App\UI\Api\System\Models\Enums\TaxTreatmentCode::DomesticReverseCharge]

Drives $tax['taxDocument'], the "corrective tax document" sentence on cancellations and credit notes. NON_PAYER (domestic non-payer = commercial document), OUTSIDE_SCOPE and VOUCHER_ISSUE are not tax documents.

Properties

Methods

__construct()

public __construct(PdfGenerator $pdfGenerator, InvoiceTypeManager $invoiceTypeManager, CurrencyManager $currencyManager, Config $config, SettingContext $settingContext, PriceFormatContext $priceFormatContext, LocalizationManager $localizationManager, TaxManager $taxManager, LanguageManager $languageManager, CountryManager $countryManager, Translator $translator[, InvoiceOrderNumberResolver|null $orderNumberResolver = null ]) : mixed
Parameters
$pdfGenerator : PdfGenerator
$invoiceTypeManager : InvoiceTypeManager
$currencyManager : CurrencyManager
$config : Config
$settingContext : SettingContext
$priceFormatContext : PriceFormatContext
$localizationManager : LocalizationManager
$taxManager : TaxManager
$languageManager : LanguageManager
$countryManager : CountryManager
$translator : Translator
$orderNumberResolver : InvoiceOrderNumberResolver|null = null

formatMoney()

Formats a money value for display in the form, with the rules of {@see formatQuantity()} and the `decimal(10,3)` scale.

public static formatMoney(int|float|string|null $price) : string|null

Only what the admin sees changes; input still accepts both separators (Decimal::fromInput() normalizes the comma and spaces).

IMPORTANT: do not pass the computed line fields (priceWithVat, totalPrice, ...) here. The client invoice-calc.js overwrites them via toFixed(3), so the format would revert to a dot after the first change and just flicker.

Parameters
$price : int|float|string|null
Return values
string|null

formatQuantity()

Formats a line quantity for printing in a PDF template.

public static formatQuantity(int|float|string|null $quantity[, string $decimalSeparator = ',' ]) : string|null

The Latte variants live in the database and are never edited (the content passes through TinyMCE, which would break the markup), so the format has to be produced here when building $args:

  • a whole number is printed as is ("2"), so renders of existing documents stay byte-identical;
  • a decimal uses the given separator (comma by default) without trailing zeros ("2,5", "2,25");
  • null (missing shipper or payment) stays null, so n:if="!empty(...)" in templates still works.

Static and public on purpose: a pure function over a scalar that must be testable without the whole PDF pipeline and usable in admin listings. Delegates to QuantityFormat::quantity(), the single format of a document quantity (form, listing, PDF, e-mail).

Parameters
$quantity : int|float|string|null
$decimalSeparator : string = ','
Return values
string|null

generate()

Generates the QR and PDF into the invoice folder and returns their relative paths (or `null`).

public generate(Invoice $invoice) : array{pdf: ?string, qrCode: ?string, pdfTemplateVariantId: ?int}

pdfTemplateVariantId is the variant the PDF was actually generated with (the document snapshot or a fresh matrix resolve). The caller (InvoiceManager::completedSave()) stores it on a document without a snapshot, so later re-edits stop resolving the live matrix.

Parameters
$invoice : Invoice
Return values
array{pdf: ?string, qrCode: ?string, pdfTemplateVariantId: ?int}

splitTaxNote()

Splits the snapshot `tax_note` into [document language, Czech].

public static splitTaxNote(string|null $taxNote) : array{0: ?string, 1: ?string}

A bilingual clause is stored as two lines (document language, then Czech); a Czech document has one line, so the Czech copy is null.

Parameters
$taxNote : string|null
Return values
array{0: ?string, 1: ?string}

storedFilesPresent()

Tells whether the files referenced by the `pdf` / `qr_code` columns exist on disk.

public storedFilesPresent(Invoice $invoice, string|null $pdfRel, string|null $qrRel) : bool

Used to decide that a save without changes must not regenerate the PDF (InvoiceManager::completedSave()). The lookup uses the same folder generate() writes to (invoice folder plus the file name of the stored relative path), so the check cannot point elsewhere than the write. An empty pdf means the file is missing; the QR may be null when there was nothing to encode, but a filled qr_code must exist.

Parameters
$invoice : Invoice
$pdfRel : string|null
$qrRel : string|null
Return values
bool

applySymbol()

private applySymbol(QRPlatba $qr, string $setter, string|null $value) : void
Parameters
$qr : QRPlatba
$setter : string
$value : string|null

buildArgs()

Builds the `$args` key contract of the legacy `InvoiceService::createVariablesForPdf`.

private buildArgs(Invoice $invoice, Currency|null $currency, string|null $qrAbsPath) : array<string, mixed>

IMPORTANT: the templates migrated from the legacy database depend on these exact keys. They are scalars plus seller, customer, shipperTariff, paymentMethod, deliveryData, items[], giftList[], voucherList[], qrCodeImg, meta_title and vatSummary (see buildVatSummary()).

Effective VAT: for a non-payer (header useVat = 0) the vat rates of lines and vouchers go to the PDF as null (database values stay, the PDF just does not use them). This mirrors InvoiceCalculator and invoice-calc.js, which also ignore VAT when the header has it off.

Parameters
$invoice : Invoice
$currency : Currency|null
$qrAbsPath : string|null
Return values
array<string, mixed>

buildFileName()

Builds the file name `{prefix_}{invoiceId}_{timestamp}.pdf`.

private buildFileName(int $invoiceId, InvoiceType|null $invoiceType, int|null $languageId) : string

The prefix comes from the per-language InvoiceTypeText.filename, sanitized to [A-Za-z0-9_-]. The timestamp separates regenerations in the same folder.

Parameters
$invoiceId : int
$invoiceType : InvoiceType|null
$languageId : int|null
Return values
string

buildVatSummary()

Builds the VAT breakdown per rate for the footer of the modern PDF template.

private buildVatSummary(bool $headerUseVat, array<int, array<string, mixed>> $lines, array<int, array<string, mixed>> $vouchers[, int $itemCount = null ]) : array<int, array{rate: string, rateKey: string, base: float, vat: float, total: float}>

Sums the lines (items, shipperTariff, paymentMethod, all already through lineToArray()) by their vat rate, then subtracts the vouchers so the breakdown matches the final header amounts after discounts (the calculator computes totalPrice / totalPriceWithVat after vouchers too):

  • the fixed part of a voucher (price / priceWithVat) is subtracted from the bucket of its own vat rate;
  • the percentage part is percents / 100 of the sum of all bucket bases, split across buckets proportionally to their base. The calculator takes percents from the sum of the original lines, so the share uses the base before this voucher is subtracted.

Lines with name === null (missing shipper or payment method, neutral zeros) and lines with vat === null (non-payer header or a line with useVat off) are skipped, otherwise a false bucket of rate 0 appears. A voucher with vat === null is not subtracted. For a non-payer ($headerUseVat false) the result is always an empty list.

A voucher of an e-shop document may carry a mixed effective rate (for example 20.0847 from a discount over items with 21 and 15 percent). No bucket has such a rate, so it is spread across buckets proportionally to the item bases (a percentage discount comes from items, not from shipping or payment), and the cent remainder of VAT goes to the largest bucket. The breakdown then matches the voucher price / priceWithVat and the header exactly. A rate that is a whole number after rounding to 4 places is subtracted from its own bucket.

A group is keyed by the normalized rate string (VatRate::key(): '21', '5.5'), so the rates 5 and 5.5 never merge (a float or int key would cut 5.5 to 5). rate is the template text, rateKey the canonical group key.

Parameters
$headerUseVat : bool
$lines : array<int, array<string, mixed>>

items[] + shipperTariff + paymentMethod

$vouchers : array<int, array<string, mixed>>

voucherList from buildArgs()

$itemCount : int = null

how many leading elements of $lines are items (the rest is shipping and payment)

Return values
array<int, array{rate: string, rateKey: string, base: float, vat: float, total: float}>

folderPath()

private folderPath(int $invoiceId, DateTime $issuedAt, bool $abs) : string
Parameters
$invoiceId : int
$issuedAt : DateTime
$abs : bool
Return values
string

generateQrCode()

Generates the QR payment into the invoice folder and returns the relative path to `qrcode.png`.

private generateQrCode(Invoice $invoice, Currency|null $currency, string $folderAbs, string $folderRel) : string|null

The amount is useVat ? totalWithVat : total. Returns null (no QR) for an amount of 0 or less, a missing currency, or neither bank account nor IBAN.

Parameters
$invoice : Invoice
$currency : Currency|null
$folderAbs : string
$folderRel : string
Return values
string|null

invoiceLocalization()

Returns the document localization for the amount format, or null (historical documents) to fall back to the currency.

private invoiceLocalization(Invoice $invoice) : object|null

The invoice comes from the Api manager (clearDoctrine()), so a lazy association cannot be relied on: the scalar id is read from the read-only mirror and the entity is loaded separately.

Parameters
$invoice : Invoice
Return values
object|null

lineToArray()

Converts a line (shipper, payment method or item; they share getters) to a plain array of the legacy contract.

private lineToArray(InvoiceShipper|InvoicePaymentMethod|InvoiceItem|null $line, bool $headerUseVat) : array<string, mixed>

A null line (missing shipper or payment method) yields neutral zeros so template n:if="!empty(...)" checks work.

Parameters
$line : InvoiceShipper|InvoicePaymentMethod|InvoiceItem|null
$headerUseVat : bool

effective VAT switch, mirroring InvoiceCalculator; for a non-payer (header off) or a line without useVat, vat goes to the PDF as null (the *WithVat amounts were already computed without VAT)

Return values
array<string, mixed>

localizeArgs()

Applies the language and localization dependent parts of the args.

private localizeArgs(array<string, mixed> $args, Currency|null $currency, object|null $localization) : array<string, mixed>
  • dateFormat is the short date format of the document localization; templates use {$dueDate|printDate:($dateFormat ?? 'd.m.Y')}, and without a localization it stays d.m.Y.
  • Line quantity uses the localization decimal separator (whole numbers are unchanged, so a Czech document renders byte-identically).
Parameters
$args : array<string, mixed>
$currency : Currency|null
$localization : object|null
Return values
array<string, mixed>

orderNumber()

Returns the order number for `$orderNumber`, or null without an order or without the order module.

private orderNumber(int|null $orderId) : string|null
Parameters
$orderId : int|null
Return values
string|null

resolveVariant()

Resolves the PDF variant for a document without a `pdf_template_variant_id` snapshot.

private resolveVariant(Invoice $invoice, InvoiceType|null $invoiceType) : int|null

Two modes: a document with tax_treatment uses the matrix of document type by treatment code (exact match, then *); a document without it (legacy) uses the old matrix unchanged. An existing snapshot is handled by the caller (generate()) and always wins.

Parameters
$invoice : Invoice
$invoiceType : InvoiceType|null
Return values
int|null

settingIntOrNull()

Returns the PDF layout setting (see {@see InvoiceGenerator::settingIntOrNull()}); `null` means the default layout, which `PdfGenerator` resolves itself.

private settingIntOrNull(string $key) : int|null
Parameters
$key : string
Return values
int|null

spreadMixedRateVoucher()

Spreads a mixed-rate voucher across buckets proportionally to the item bases (fallback: bases of all buckets).

private spreadMixedRateVoucher(array<string|int, array{rate: string, rateKey: string, base: float, vat: float, total: float}> $buckets, array<string|int, float> $itemBases, float $price, float $priceWithVat) : array<string|int, array{rate: string, rateKey: string, base: float, vat: float, total: float}>

The VAT remainder (bucket rates times shares differ from the voucher VAT by cents) goes to the bucket with the largest weight, so the subtracted sums equal price / priceWithVat exactly.

Parameters
$buckets : array<string|int, array{rate: string, rateKey: string, base: float, vat: float, total: float}>
$itemBases : array<string|int, float>
$price : float
$priceWithVat : float
Return values
array<string|int, array{rate: string, rateKey: string, base: float, vat: float, total: float}>

taxArgs()

Adds the `$tax` template args (VAT treatment of the document).

private taxArgs(array<string, mixed> $args, Invoice $invoice, InvoiceType|null $invoiceType) : array<string, mixed>

IMPORTANT: the key is always present (Latte reports an undefined variable without it). For a document without tax_treatment all values are empty and templates branch on !empty($tax['code']), so a legacy document renders unchanged.

Taken from the tax snapshot of the document (not recomputed): code, note / noteCs (the clause in the document language and in Czech; tax_note stores it as two lines, see DocumentLocale::taxNote()), notes (all distinct clauses; items carry no code of their own, so at most one), rateCountry, chargeVat, customerVatIdVerified(At), summaryReportCode, flags. Derived: rateCountryName, exempt, taxDocument, sellerStatus (seller status on tax_date from the status history) and title, the document name by type, code and status (document.invoicer.tax.title.*, in the document language). The title also replaces the PDF meta_title, which would otherwise carry the internal variant name.

Parameters
$args : array<string, mixed>
$invoice : Invoice
$invoiceType : InvoiceType|null
Return values
array<string, mixed>

typeArgs()

Adds the document type to the template args.

private typeArgs(array<string, mixed> $args, InvoiceType|null $invoiceType, int|null $languageId) : array<string, mixed>
  • invoiceTypeKey: cancellation and credit note share the PDF variants, so the heading and closing text branch in the template by this key ({if ($invoiceTypeKey ?? '') === 'DOBROPIS'}).
  • meta_title of a corrective document is the type name in the document language plus the number; the variant name would be wrong in the PDF title of a credit note.
Parameters
$args : array<string, mixed>
$invoiceType : InvoiceType|null
$languageId : int|null
Return values
array<string, mixed>
On this page

Search results