CoolCMS API

InvoiceGenerator
in package

FinalYes

Builds and saves one document of a given type (regular invoice or credit note) from a payment.

Called by InvoiceManager::processPaidPayments() from the payment cron (cron/invoicer/payment/generate), so generation never delays the gateway callback and runs serialized. The caller picks the type from PaymentStatusRule; the runner owns the master switch (7/invoice.payment.auto) and Payment.invoicedStatus.

  • InvoiceType::isCreditNote() decides between buildFromPayment() (invoice from payment items) and buildCreditNote() (negated mirror of the source invoice).
  • The billing line comes from the type (InvoiceType::getBillingLineId()), fallback 7/invoice.default.billingLineId; without a line nothing is issued, because the number always comes from a line.
  • Idempotent through Payment.invoicedStatus and existingInvoice(). Invoice.paymentId is not unique: one payment may have an invoice and a credit note.
  • Seller comes from settings (7/invoice.default.sellerId), customer from the user profile. userType and paymentRegion are derived from the user/payment (default: non-payer private person, default region), because the PDF variant is resolved from the invoice type matrix.

IMPORTANT: payment path only. This class is single-seller and always takes a Payment. A module that issues documents from its own data must use InvoiceComposer (seller as parameter, returns an unsaved object graph saved via InvoiceManager::save()); extending this class instead would force the payment flow to change with it.

Tags
phpstan-import-type

TCreditNoteLedger from CreditNoteLedger

Table of Contents

Constants

CREDIT_SOURCE_INCLUDES  : array<string|int, mixed> = ['items', 'shippers', 'paymentMethods', 'vouche...
Source collections that must be loaded for the ledger and for the credit note mirror.
DEFAULT_DUE_PERIOD  : int = 14
DEFAULT_UNIT  : string = 'ks'
GATE_FEE_LINE_NAME  : string = 'Poplatek platební brány'
Gateway fee line (with a rate suffix such as "(21 %)" for several rates); localized by the template, not the generator.
MONEY_SCALE  : int = 3
Money scale of `decimal(10,3)` columns and of intermediate results. Mirrors the private constants of {@see InvoiceCalculator}: the fallback in {@see addLinesFromPayment()} must use the same arithmetic, otherwise its exact base would not match after the recalculation in `InvoiceManager::beforeSave()`.
ORDER_CALLBACK_KEY  : string = 'ESHOP_ORDER'
Callback key of an e-shop payment; mirrors `OrderPaymentManager::CALLBACK_KEY` (Invoicer does not depend on the e-shop).
PER_LINE_ROUNDING_LIMIT  : string = '0.010'
Cap of the cent balancing of the per-line breakdown. The payment form rounds the due amount to the currency precision ({@see \App\UI\Front\Base\Components\PaymentFormComponent\Traits\PriceTrait::roundToPrecision()}), the document header to 3 decimals, so for a two-decimal currency they differ by at most `0.005 + 0.0005`.
ROUNDING_LINE_NAME  : string = 'Haléřové vyrovnání'
Balancing line name, hardcoded like the gateway fee line (the document is localized by the template).
SETTING_MODULE  : int = 7
WORK_SCALE  : int = 12

Properties

$bazaarPaymentItemManager  : PaymentItemManager
$bazaarTariffManager  : TariffManager
$callbackKeyCache  : array<int, string|null>
Cache of callback keys (`callback_id` to `callback_key`): there are few callbacks while the runner walks a whole batch of payments, and without the cache each payment would query the DB.
$comcatPaymentItemManager  : PaymentItemManager
$comcatTariffManager  : TariffManager
$creditNoteLedger  : CreditNoteLedger
$discussionPaymentItemManager  : PaymentItemManager
$discussionTariffManager  : TariffManager
$em  : EntityManagerInterface
$emailManager  : EmailManager
$gateCache  : array<int, PaymentGate|null>
$invoiceManager  : InvoiceManager
$invoiceTypeManager  : InvoiceTypeManager
$languageManager  : LanguageManager
$lateFallbackVat  : numeric-string|null
Rate of the summary line of a legacy payment without items, set by the engine at the payment date (null = emergency rate).
$paymentCallbackManager  : PaymentCallbackManager
$paymentGateManager  : PaymentGateManager
$paymentItemManager  : PaymentItemManager
$paymentRegionManager  : PaymentRegionManager
$sellerManager  : SellerManager
$settingContext  : SettingContext
$supplyKindManager  : SupplyKindManager
$tariffManager  : TariffManager
$taxAmountCalculator  : TaxAmountCalculator
$taxManager  : TaxManager
$translator  : Translator
$userManager  : UserManager
$userPaymentManager  : UserPaymentManager
$userTypeManager  : UserTypeManager

Methods

__construct()  : mixed
generateForPayment()  : Result
Issues a document of the given type from a payment, idempotently per type.
generateManualCreditNote()  : Result
Issues a manual credit note (corrective tax document) for an issued document from the admin.
isDocumentedByOrder()  : bool
Tells whether the payment belongs to an e-shop order (callback `ESHOP_ORDER`). Only the e-shop issues its documents, by order state (`OrderInvoiceGenerator`, invoice without `paymentId`, credit notes via {@see CreditNoteLedger}); the payment cron would build a second document through the fallback (one line, one rate) and double the cancellation and credit note.
isPaymentWithoutInvoice()  : bool
Tells whether the payment issues no document to the customer: marker `INTERMEDIARY` (SMS via an intermediary in its own name; the aggregator document is made manually) or `CREDIT_REDEMPTION` (credit spending). A legacy payment without a snapshot through a gate with `intermediary_supply` is the same. A credit payment without a marker is already filtered out by `InvoiceManager::noInvoicingCurrencyIds()` (currency).
manualCreditNoteSource()  : Result
Returns the source of a manual credit note: an issued (numbered) non-credit document not owned by an e-shop order (the order has its own credit note under the order lock). Shared by the preview and the issuing, so the UI never offers a credit note that issuing would reject.
addLinesFromPayment()  : void
Builds the billed lines: either a per-line breakdown from price list bases, or the authoritative fallback from the paid amount.
allocateGateFee()  : array<int, array{name: string, base: float, vat: numeric-string}>
Returns gateway fee lines: base `payment_gate_fee_total` allocated proportionally to the bases of the line rate groups (remainder to the largest group); an ancillary supply shares the treatment of the main one. One rate gives one gateway fee line, several rates one line per rate. Without tariff lines the emergency rate applies.
applyCustomer()  : void
applyLateTaxTreatment()  : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
Treats a legacy payment without a snapshot: runs the engine at the payment date (`RESOLVED_LATE`). See {@see applyTaxTreatment()}.
applySeller()  : void
applyTaxTreatment()  : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
Applies the VAT treatment of a payment document: returns lines with final rates and sets the tax snapshot of the document and the header `use_vat` (derived from chargeVat).
bazaarTariffLines()  : array{0: list, 1: list}
Returns tariff lines of a Bazaar payment (`cms_mod_bazaar_payment_items` + Bazaar price list). Mirrors {@see systemTariffLines()} with the module types; items without a tariff (category payments, `tariffId` NULL) are skipped and the document goes through the fallback from the total amount.
buildCreditNote()  : Invoice
Builds a credit note: header mirror of the source (non-credit) invoice of the payment plus the source lines with price x -1 (derived fields are recomputed by {@see InvoiceCalculator}).
buildFromPayment()  : Invoice|null
comcatTariffLines()  : array{0: list, 1: list}
Returns tariff lines of a Comcat payment; mirrors {@see systemTariffLines()} with the module types.
creditQuantity()  : int
Returns the pieces of a source line on the credit note: the chosen quantity (manual partial credit note), else the ledger remainder.
defaultPaymentRegionId()  : int|null
Default region (is_default), typically the Czech one.
deriveUserTypeId()  : int|null
Derives the user type from the profile (payer/non-payer x company/person) via the `UserType` flags `requiresCompanyData` and `requiresVatId`. Default (unknown user) = non-payer private person (both false).
discussionTariffLines()  : array{0: list, 1: list}
Returns tariff lines of a Discussion payment (topping); mirrors {@see systemTariffLines()} with the module types.
exhaustsSource()  : bool
Tells whether this credit note zeroes the remainder of the whole invoice (then the fixed discount is its whole remainder).
existingInvoice()  : Invoice|null
Idempotence check: does a document of this type already exist for the payment?
fallbackBase()  : array{0: numeric-string, 1: ?numeric-string}
Splits the paid amount into a base without VAT (plus an optional cent balancing) so that `invoice.total_price_with_vat` after the {@see InvoiceCalculator} recalculation equals `payment.total_price` exactly. Returns `[base, balancing|null]` as 3-decimal numeric-strings.
findPaymentGate()  : PaymentGate|null
Payment gate (cached per id) with the flags `final_prices` and `intermediary_supply`.
findSourceInvoiceForCreditNote()  : Invoice|null
Returns the existing non-credit invoice of the payment (with lines): the source of a credit note (nothing to credit without it) and the gate for types with `requires_invoice`. Non-credit types come from the code list {@see InvoiceType} (credit_note = false).
line()  : InvoiceItem
lineGrossPrice()  : numeric-string
Returns the unrounded intermediate result of one line (`base x (1 + rate/100)`), exactly what {@see InvoiceCalculator::recalculateLine()} feeds into the header sum.
localizationRef()  : Localization|null
Returns a managed reference without a DB query. Source documents come from Api managers that end with `clearDoctrine()`, so their entities are detached and assigning them to the association of a new invoice would fail on flush. The scalar id from the read-only mirror is always at hand and needs no lazy load.
mainLineVat()  : numeric-string|null
Returns the rate of the main supply (line with the highest base, the first on a tie), used for the summary line of the fallback.
mirrorLine()  : void
Mirrors a credit note line: copies name/unit/vat/useVat and discounts, price x -1, quantity = ledger remainder.
money()  : numeric-string
Rounds money half away from zero to {@see MONEY_SCALE}. Mirrors the private `InvoiceCalculator::money()` (bcmath truncates, hence the +-0.0005). The fallback must predict exactly what the calculator computes in `beforeSave()`; if the semantics change there, they must change here too.
negatePrice()  : string
nonCreditInvoiceTypeIds()  : array<int, int>
Returns ids of document types that are not credit notes (credit_note = false), the candidates for a credit note source.
normalizedTotal()  : numeric-string
Returns the paid amount as a 3-decimal numeric-string safe for bcmath. Shared by {@see predictedPerLineTotal()} (difference against the header prediction) and {@see fallbackBase()} (back-calculated base), so the normalization lives once.
normalizeRequestedLines()  : array<string, array<int, int>>
Normalizes form input: known collections only, positive integer quantities. The ledger enforces the cap.
paymentCallbackKey()  : string|null
Returns the payment callback key (`BAZAAR_PAYMENT`, `SYSTEM_USER_PAYMENT`, ...) that decides whose price list the document reads ({@see addLinesFromPayment()}). NULL = payment without a callback (legacy), system path.
paymentItems()  : array<int, UserPayment>
paymentRegionLanguageId()  : int|null
Default document language of the payment region (null when the region has none set).
predictedPerLineTotal()  : numeric-string
Predicts the header amount with VAT that {@see InvoiceCalculator::recalculate()} computes from per-line rows, as a literal mirror: bases are taken in the form {@see line()} stores them (`number_format($price, 3)`), multiplied unrounded by the rate multiplier at {@see WORK_SCALE}, and only the sum is rounded. Quantity is 1 and the discount off for all these lines, so the calculator does nothing more.
proportionalVoucherShare()  : string
Returns the proportional part of the fixed discount for a partial credit note: `discount x returned base / source base`, capped by the ledger remainder of the discount (rounding up would over-credit). Base = `totalPrice` of the billed lines (items, shipping, payment) scaled by the ratio of the chosen pieces.
remainingLines()  : array<string, array<int, int>>
Returns the whole ledger remainder as a line selection.
remainingQuantity()  : int
Returns the ledger remainder of a source line (pieces nobody has credited yet). A line outside the ledger (no id) yields 0: better to credit nothing than to credit twice.
resolveBillingLineId()  : int|null
Line for the document: the line of the type, else `7/invoice.default.billingLineId`; null when neither exists.
sendInvoiceEmail()  : void
settingBool()  : bool
settingInt()  : int
settingIntOrNull()  : int|null
settingStr()  : string
settingVat()  : numeric-string
Rate setting (`7/invoice.default.vatPercents`, stored as text) → `DECIMAL(5,2)` string; invalid/missing → `0.00`.
snapshotTariffLines()  : array{0: list, 1: list}
Returns tariff lines from the accounting snapshot of checkout (`cms_system_payment_items`): name, base, rate and productType as actually billed. An empty result means the payment has no snapshot (legacy) and the caller falls back to the live price list. The snapshot rate is always resolved (checkout stores the result of the chain price list row, tariff, default); `?? $defaultVat` is only a safeguard.
systemTariffLines()  : array{0: list, 1: list}
Returns tariff lines of a system payment (membership/credits, `cms_system_user_payment_items`).
translateTaxNote()  : string|null
VAT clause in the document language plus Czech; null when the code has no clause.

Constants

CREDIT_SOURCE_INCLUDES

Source collections that must be loaded for the ledger and for the credit note mirror.

public array<string|int, mixed> CREDIT_SOURCE_INCLUDES = ['items', 'shippers', 'paymentMethods', 'vouchers']

GATE_FEE_LINE_NAME

Gateway fee line (with a rate suffix such as "(21 %)" for several rates); localized by the template, not the generator.

private string GATE_FEE_LINE_NAME = 'Poplatek platební brány'

MONEY_SCALE

Money scale of `decimal(10,3)` columns and of intermediate results. Mirrors the private constants of {@see InvoiceCalculator}: the fallback in {@see addLinesFromPayment()} must use the same arithmetic, otherwise its exact base would not match after the recalculation in `InvoiceManager::beforeSave()`.

private int MONEY_SCALE = 3

ORDER_CALLBACK_KEY

Callback key of an e-shop payment; mirrors `OrderPaymentManager::CALLBACK_KEY` (Invoicer does not depend on the e-shop).

private string ORDER_CALLBACK_KEY = 'ESHOP_ORDER'

PER_LINE_ROUNDING_LIMIT

Cap of the cent balancing of the per-line breakdown. The payment form rounds the due amount to the currency precision ({@see \App\UI\Front\Base\Components\PaymentFormComponent\Traits\PriceTrait::roundToPrecision()}), the document header to 3 decimals, so for a two-decimal currency they differ by at most `0.005 + 0.0005`.

private string PER_LINE_ROUNDING_LIMIT = '0.010'

A larger difference (currency with precision 0) is no longer cent balancing: such a document is built by the fallback, which is exact to the paid amount by construction.

ROUNDING_LINE_NAME

Balancing line name, hardcoded like the gateway fee line (the document is localized by the template).

private string ROUNDING_LINE_NAME = 'Haléřové vyrovnání'

Used by both branches of addLinesFromPayment(): the fallback (fallbackBase()) and the per-line breakdown (predictedPerLineTotal()).

Properties

$callbackKeyCache

Cache of callback keys (`callback_id` to `callback_key`): there are few callbacks while the runner walks a whole batch of payments, and without the cache each payment would query the DB.

private array<int, string|null> $callbackKeyCache = []

$lateFallbackVat

Rate of the summary line of a legacy payment without items, set by the engine at the payment date (null = emergency rate).

private numeric-string|null $lateFallbackVat = null

Methods

__construct()

public __construct(SettingContext $settingContext, InvoiceManager $invoiceManager, InvoiceTypeManager $invoiceTypeManager, SellerManager $sellerManager, UserManager $userManager, UserPaymentManager $userPaymentManager, TariffManager $tariffManager, EmailManager $emailManager, UserTypeManager $userTypeManager, PaymentRegionManager $paymentRegionManager, PaymentCallbackManager $paymentCallbackManager, PaymentItemManager $paymentItemManager, PaymentItemManager $bazaarPaymentItemManager, TariffManager $bazaarTariffManager, PaymentItemManager $comcatPaymentItemManager, TariffManager $comcatTariffManager, PaymentItemManager $discussionPaymentItemManager, TariffManager $discussionTariffManager, EntityManagerInterface $em, TaxManager $taxManager, TaxAmountCalculator $taxAmountCalculator, SupplyKindManager $supplyKindManager, PaymentGateManager $paymentGateManager, LanguageManager $languageManager, Translator $translator, CreditNoteLedger $creditNoteLedger) : mixed
Parameters
$settingContext : SettingContext
$invoiceManager : InvoiceManager
$invoiceTypeManager : InvoiceTypeManager
$sellerManager : SellerManager
$userManager : UserManager
$userPaymentManager : UserPaymentManager
$tariffManager : TariffManager
$emailManager : EmailManager
$userTypeManager : UserTypeManager
$paymentRegionManager : PaymentRegionManager
$paymentCallbackManager : PaymentCallbackManager
$paymentItemManager : PaymentItemManager
$bazaarPaymentItemManager : PaymentItemManager
$bazaarTariffManager : TariffManager
$comcatPaymentItemManager : PaymentItemManager
$comcatTariffManager : TariffManager
$discussionPaymentItemManager : PaymentItemManager
$discussionTariffManager : TariffManager
$em : EntityManagerInterface
$taxManager : TaxManager
$taxAmountCalculator : TaxAmountCalculator
$supplyKindManager : SupplyKindManager
$paymentGateManager : PaymentGateManager
$languageManager : LanguageManager
$translator : Translator
$creditNoteLedger : CreditNoteLedger

generateForPayment()

Issues a document of the given type from a payment, idempotently per type.

public generateForPayment(Payment $payment, int $invoiceTypeId) : Result

The caller picks the type from the payment status rules. Success carries the document (new, or an already existing one healed without a second e-mail); success with null data means settled without a document (nothing to reverse: the payment was never paid, or the source is already fully credited). Failures differ by code:

  • ResultCode::DB_NOT_FOUND is permanent (type from the rule missing, credit note without a source invoice): retrying never succeeds, so the runner settles the payment, otherwise it would eat the batch forever and starve the others;
  • other codes are transient (missing seller/line configuration, failed save): the runner leaves invoicedStatus unset and the payment is retried, so fixing the configuration heals it.

Does not set invoicedStatus nor negate invoice.payment.auto; the runner does.

Parameters
$payment : Payment
$invoiceTypeId : int
Return values
Result

generateManualCreditNote()

Issues a manual credit note (corrective tax document) for an issued document from the admin.

public generateManualCreditNote(int $sourceInvoiceId, int $creditTypeId[, array<string|int, mixed>|null $requested = null ]) : Result

Built like a credit note from a payment (buildCreditNote(): header mirror, negated lines linked via related_*_id, number from the line of the credit note type, PDF in completedSave) and capped by the same CreditNoteLedger, so it never exceeds what nobody has credited yet (neither payment cron nor e-shop). InvoiceManager::afterSave() locks the source in the same transaction.

$requested = null issues a full credit note (whole remainder); otherwise a partial one for the chosen lines/pieces. The e-mail is never sent (even with invoice.email.autoSend); sending is a separate admin action.

IMPORTANT: call under the source lock (InvoiceCreditNoteManager), otherwise two concurrent requests read the same remainder and over-credit.

Failure errorCode: everything from manualCreditNoteSource() plus creditNoteTypeInvalid, billingLineMissing, decimalQuantityUnsupported, unattributedCreditLines, nothingToCredit, creditLimitExceeded, saveFailed.

Parameters
$sourceInvoiceId : int
$creditTypeId : int
$requested : array<string|int, mixed>|null = null

collection => (source line id => pieces); cleaned by normalizeRequestedLines()

Return values
Result —

data: ['invoiceId' => ?int, 'invoiceNumber' => ?string, 'pdf' => ?string, 'final' => bool]

isDocumentedByOrder()

Tells whether the payment belongs to an e-shop order (callback `ESHOP_ORDER`). Only the e-shop issues its documents, by order state (`OrderInvoiceGenerator`, invoice without `paymentId`, credit notes via {@see CreditNoteLedger}); the payment cron would build a second document through the fallback (one line, one rate) and double the cancellation and credit note.

public isDocumentedByOrder(Payment $payment) : bool
Parameters
$payment : Payment
Return values
bool

isPaymentWithoutInvoice()

Tells whether the payment issues no document to the customer: marker `INTERMEDIARY` (SMS via an intermediary in its own name; the aggregator document is made manually) or `CREDIT_REDEMPTION` (credit spending). A legacy payment without a snapshot through a gate with `intermediary_supply` is the same. A credit payment without a marker is already filtered out by `InvoiceManager::noInvoicingCurrencyIds()` (currency).

public isPaymentWithoutInvoice(Payment $payment) : bool
Parameters
$payment : Payment
Return values
bool

manualCreditNoteSource()

Returns the source of a manual credit note: an issued (numbered) non-credit document not owned by an e-shop order (the order has its own credit note under the order lock). Shared by the preview and the issuing, so the UI never offers a credit note that issuing would reject.

public manualCreditNoteSource(int $sourceInvoiceId) : Result

Failure errorCode: sourceNotFound, sourceIsCreditNote, sourceNotIssued, orderOwned.

Parameters
$sourceInvoiceId : int
Return values
Result —

data = source Invoice loaded with CREDIT_SOURCE_INCLUDES

addLinesFromPayment()

Builds the billed lines: either a per-line breakdown from price list bases, or the authoritative fallback from the paid amount.

private addLinesFromPayment(Invoice $invoice, Payment $payment, int $paymentId, User|null $user, int $sellerId, DateTimeImmutable $taxDate) : void

The per-line branch needs balancing too: the payment form rounds the due amount to the currency precision (two places for CZK), while document lines are 3-decimal price list bases and InvoiceCalculator rounds the header to 3 places, so the two may differ by a fraction of a cent (payment 494.900, header 494.905). The difference is billed as a ROUNDING_LINE_NAME line with rate 0, the same way as in fallbackBase(). When the difference exceeds PER_LINE_ROUNDING_LIMIT, the per-line breakdown is dropped and the document is built by the fallback, which hits the paid amount by construction.

Parameters
$invoice : Invoice
$payment : Payment
$paymentId : int
$user : User|null
$sellerId : int
$taxDate : DateTimeImmutable

allocateGateFee()

Returns gateway fee lines: base `payment_gate_fee_total` allocated proportionally to the bases of the line rate groups (remainder to the largest group); an ancillary supply shares the treatment of the main one. One rate gives one gateway fee line, several rates one line per rate. Without tariff lines the emergency rate applies.

private allocateGateFee(string $feeTotal, array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}> $lines, numeric-string $defaultVat) : array<int, array{name: string, base: float, vat: numeric-string}>
Parameters
$feeTotal : string
$lines : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
$defaultVat : numeric-string
Return values
array<int, array{name: string, base: float, vat: numeric-string}>

applyLateTaxTreatment()

Treats a legacy payment without a snapshot: runs the engine at the payment date (`RESOLVED_LATE`). See {@see applyTaxTreatment()}.

private applyLateTaxTreatment(Invoice $invoice, Payment $payment, User|null $user, int $sellerId, DateTimeImmutable $taxDate, DateTime $taxDateValue, array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}> $lines) : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
Parameters
$invoice : Invoice
$payment : Payment
$user : User|null
$sellerId : int
$taxDate : DateTimeImmutable
$taxDateValue : DateTime
$lines : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
Return values
array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>

applySeller()

private applySeller(Invoice $invoice, int $sellerId, DateTimeInterface $taxDate) : void
Parameters
$invoice : Invoice
$sellerId : int
$taxDate : DateTimeInterface

applyTaxTreatment()

Applies the VAT treatment of a payment document: returns lines with final rates and sets the tax snapshot of the document and the header `use_vat` (derived from chargeVat).

private applyTaxTreatment(Invoice $invoice, Payment $payment, User|null $user, int $sellerId, DateTimeImmutable $taxDate, array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}> $lines) : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
  • Payment with a snapshot (tax_treatment): code, country, VAT id verification, commodity code and flags are copied, line rates come from the item snapshot. tax_date of the document = day the payment was received (tax point). When the rate on that day differs from the rate on the form day (bank transfer paid after a rate change), the rate of the receipt day applies, the paid amount holds (the base is back-calculated by the fallback) and the document carries RATE_DATE_MISMATCH; a company with prices without VAT may owe a top-up, resolved by an admin.
  • Legacy payment without a snapshot: the engine at the payment date (VAT id evidence only, no VIES) plus RESOLVED_LATE. When the engine gives no answer (missing seller status / rate / items), the previous behavior applies (seller use_vat at the payment date, rates from the price list) without a snapshot.

The clause is translated again into the language of the document (plus Czech), not taken from the payment (which is in the page language).

Parameters
$invoice : Invoice
$payment : Payment
$user : User|null
$sellerId : int
$taxDate : DateTimeImmutable
$lines : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
Return values
array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>

bazaarTariffLines()

Returns tariff lines of a Bazaar payment (`cms_mod_bazaar_payment_items` + Bazaar price list). Mirrors {@see systemTariffLines()} with the module types; items without a tariff (category payments, `tariffId` NULL) are skipped and the document goes through the fallback from the total amount.

private bazaarTariffLines(int $paymentId, int|null $region, int|null $gate, int|null $currency, int $langId, numeric-string $defaultVat) : array{0: list, 1: list}
Parameters
$paymentId : int
$region : int|null
$gate : int|null
$currency : int|null
$langId : int
$defaultVat : numeric-string
Return values
array{0: list, 1: list}

buildCreditNote()

Builds a credit note: header mirror of the source (non-credit) invoice of the payment plus the source lines with price x -1 (derived fields are recomputed by {@see InvoiceCalculator}).

private buildCreditNote(Invoice $source, TCreditNoteLedger $ledger, int|null $paymentId, int $creditTypeId, int $billingLineId[, array<string, array<int, int>>|null $lines = null ][, bool $isFinal = true ]) : Invoice

Header copying follows ScheduleManager::buildFromInvoice(). IMPORTANT: id, number, order, pdf and qr are never carried over; paymentId, relatedInvoiceId and the line are set anew, otherwise the credit note would reuse the identity of the source.

Lines carry only the remainder from CreditNoteLedger (invoiced minus already credited), the voucher fixed discount only its not yet dissolved part. Without earlier credit notes the result equals a full mirror; after a cancellation or partial credit note it never exceeds the remainder.

A manual credit note (generateManualCreditNote()) passes the chosen lines ($lines, already capped) and the $paymentId of the source (may be null).

Parameters
$source : Invoice
$ledger : TCreditNoteLedger
$paymentId : int|null
$creditTypeId : int
$billingLineId : int
$lines : array<string, array<int, int>>|null = null

collection => (source line id => pieces); null = whole remainder

$isFinal : bool = true

the credit note zeroes the remainder of the whole invoice, so the fixed discount is its whole remainder

Return values
Invoice

buildFromPayment()

private buildFromPayment(Payment $payment, int $paymentId, int $invoiceTypeId, int $billingLineId) : Invoice|null
Parameters
$payment : Payment
$paymentId : int
$invoiceTypeId : int
$billingLineId : int
Return values
Invoice|null

comcatTariffLines()

Returns tariff lines of a Comcat payment; mirrors {@see systemTariffLines()} with the module types.

private comcatTariffLines(int $paymentId, int|null $region, int|null $gate, int|null $currency, int $langId, numeric-string $defaultVat) : array{0: list, 1: list}
Parameters
$paymentId : int
$region : int|null
$gate : int|null
$currency : int|null
$langId : int
$defaultVat : numeric-string
Return values
array{0: list, 1: list}

creditQuantity()

Returns the pieces of a source line on the credit note: the chosen quantity (manual partial credit note), else the ledger remainder.

private creditQuantity(TCreditNoteLedger $ledger, array<string, array<int, int>>|null $lines, string $collection, int|null $sourceLineId) : int
Parameters
$ledger : TCreditNoteLedger
$lines : array<string, array<int, int>>|null
$collection : string
$sourceLineId : int|null
Return values
int

defaultPaymentRegionId()

Default region (is_default), typically the Czech one.

private defaultPaymentRegionId() : int|null
Return values
int|null

deriveUserTypeId()

Derives the user type from the profile (payer/non-payer x company/person) via the `UserType` flags `requiresCompanyData` and `requiresVatId`. Default (unknown user) = non-payer private person (both false).

private deriveUserTypeId(User|null $user) : int|null

Returns null when the code list has no matching row (the caller falls back).

Parameters
$user : User|null
Return values
int|null

discussionTariffLines()

Returns tariff lines of a Discussion payment (topping); mirrors {@see systemTariffLines()} with the module types.

private discussionTariffLines(int $paymentId, int|null $region, int|null $gate, int|null $currency, int $langId, numeric-string $defaultVat) : array{0: list, 1: list}
Parameters
$paymentId : int
$region : int|null
$gate : int|null
$currency : int|null
$langId : int
$defaultVat : numeric-string
Return values
array{0: list, 1: list}

exhaustsSource()

Tells whether this credit note zeroes the remainder of the whole invoice (then the fixed discount is its whole remainder).

private exhaustsSource(TCreditNoteLedger $ledger, array<string, array<int, int>> $lines) : bool
Parameters
$ledger : TCreditNoteLedger
$lines : array<string, array<int, int>>
Return values
bool

existingInvoice()

Idempotence check: does a document of this type already exist for the payment?

private existingInvoice(int $paymentId, int $invoiceTypeId) : Invoice|null
Parameters
$paymentId : int
$invoiceTypeId : int
Return values
Invoice|null

fallbackBase()

Splits the paid amount into a base without VAT (plus an optional cent balancing) so that `invoice.total_price_with_vat` after the {@see InvoiceCalculator} recalculation equals `payment.total_price` exactly. Returns `[base, balancing|null]` as 3-decimal numeric-strings.

private fallbackBase(string|null $totalPrice, float $fallbackTotal, numeric-string $vat, bool $useVat) : array{0: numeric-string, 1: ?numeric-string}

Plain total / (1 + rate/100) is not enough: the base is stored in decimal(10,3), so it is rounded, and that rounding is not invertible. The grid of reachable amounts money(b x mult) has step 0.001 x mult, so some target amounts fall between two neighbouring points and no 3-decimal base yields them; the document would then differ from the paid amount by 0.001.

Two steps:

  1. Candidates are money(total / mult) and its neighbours +-0.001. The one whose money(candidate x mult) hits total exactly wins, ties go to the closest to the exact quotient. Most amounts resolve with a single line, so the document looks unchanged. The neighbours are a safety net: the verification is the authority, not a proof of the grid density.
  2. When no candidate matches, the closest base is returned with the remainder total - money(base x mult), always +-0.001, which goes to its own line with rate 0.

Why it always matches with the balancing: the calculator sums unrounded lines and rounds only the header. For m = money(b x mult), b x mult = m + e with e in [-0.0005; 0.0005). With the remainder r = total - m the header sum is total + e, and since total has exactly 3 decimals, money(total + e) = total. Without the remainder the result would be m, off by a cent.

For a non-payer and for rate 0 the fallback is the identity (base = paid amount, no balancing).

Parameters
$totalPrice : string|null
$fallbackTotal : float

fallback for a non-numeric/NULL decimal from the DB (bcmath would fail on it)

$vat : numeric-string
$useVat : bool
Return values
array{0: numeric-string, 1: ?numeric-string}

findPaymentGate()

Payment gate (cached per id) with the flags `final_prices` and `intermediary_supply`.

private findPaymentGate(int|null $paymentGateId) : PaymentGate|null
Parameters
$paymentGateId : int|null
Return values
PaymentGate|null

findSourceInvoiceForCreditNote()

Returns the existing non-credit invoice of the payment (with lines): the source of a credit note (nothing to credit without it) and the gate for types with `requires_invoice`. Non-credit types come from the code list {@see InvoiceType} (credit_note = false).

private findSourceInvoiceForCreditNote(int $paymentId) : Invoice|null
Parameters
$paymentId : int
Return values
Invoice|null

line()

private line(string $name, float $price, string $vat, bool $useVat, string $unit) : InvoiceItem
Parameters
$name : string
$price : float
$vat : string
$useVat : bool
$unit : string
Return values
InvoiceItem

lineGrossPrice()

Returns the unrounded intermediate result of one line (`base x (1 + rate/100)`), exactly what {@see InvoiceCalculator::recalculateLine()} feeds into the header sum.

private lineGrossPrice(float $base, numeric-string $vat, bool $useVat) : numeric-string
Parameters
$base : float
$vat : numeric-string
$useVat : bool
Return values
numeric-string

localizationRef()

Returns a managed reference without a DB query. Source documents come from Api managers that end with `clearDoctrine()`, so their entities are detached and assigning them to the association of a new invoice would fail on flush. The scalar id from the read-only mirror is always at hand and needs no lazy load.

private localizationRef(int|null $localizationId) : Localization|null
Parameters
$localizationId : int|null
Return values
Localization|null

mainLineVat()

Returns the rate of the main supply (line with the highest base, the first on a tie), used for the summary line of the fallback.

private mainLineVat(array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}> $lines) : numeric-string|null
Parameters
$lines : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId: int|null}>
Return values
numeric-string|null

mirrorLine()

Mirrors a credit note line: copies name/unit/vat/useVat and discounts, price x -1, quantity = ledger remainder.

private mirrorLine(InvoiceItem|InvoiceShipper|InvoicePaymentMethod $from, InvoiceItem|InvoiceShipper|InvoicePaymentMethod $to, int $quantity) : void

The calculator recomputes derived fields (priceAfterDiscount/total*); being linear, it yields exactly the negative of the not yet credited part of the line.

Parameters
$from : InvoiceItem|InvoiceShipper|InvoicePaymentMethod
$to : InvoiceItem|InvoiceShipper|InvoicePaymentMethod
$quantity : int

money()

Rounds money half away from zero to {@see MONEY_SCALE}. Mirrors the private `InvoiceCalculator::money()` (bcmath truncates, hence the +-0.0005). The fallback must predict exactly what the calculator computes in `beforeSave()`; if the semantics change there, they must change here too.

private money(numeric-string $value) : numeric-string
Parameters
$value : numeric-string
Return values
numeric-string

negatePrice()

private negatePrice(string $price) : string
Parameters
$price : string
Return values
string

nonCreditInvoiceTypeIds()

Returns ids of document types that are not credit notes (credit_note = false), the candidates for a credit note source.

private nonCreditInvoiceTypeIds() : array<int, int>
Return values
array<int, int>

normalizedTotal()

Returns the paid amount as a 3-decimal numeric-string safe for bcmath. Shared by {@see predictedPerLineTotal()} (difference against the header prediction) and {@see fallbackBase()} (back-calculated base), so the normalization lives once.

private normalizedTotal(string|null $totalPrice, float $fallbackTotal) : numeric-string

Exponent notation ('1.0e3') passes is_numeric() but bcmath rejects it (ValueError); like a NULL/non-numeric column it falls back to the float from the payment header. The is_numeric() in the ternary below only serves static analysis (the regex already guarantees the shape).

Parameters
$totalPrice : string|null
$fallbackTotal : float

fallback for a non-numeric/NULL decimal from the DB

Return values
numeric-string

normalizeRequestedLines()

Normalizes form input: known collections only, positive integer quantities. The ledger enforces the cap.

private normalizeRequestedLines(array<string|int, mixed> $requested) : array<string, array<int, int>>
Parameters
$requested : array<string|int, mixed>
Return values
array<string, array<int, int>>

paymentCallbackKey()

Returns the payment callback key (`BAZAAR_PAYMENT`, `SYSTEM_USER_PAYMENT`, ...) that decides whose price list the document reads ({@see addLinesFromPayment()}). NULL = payment without a callback (legacy), system path.

private paymentCallbackKey(Payment $payment) : string|null
Parameters
$payment : Payment
Return values
string|null

paymentRegionLanguageId()

Default document language of the payment region (null when the region has none set).

private paymentRegionLanguageId(int|null $paymentRegionId) : int|null
Parameters
$paymentRegionId : int|null
Return values
int|null

predictedPerLineTotal()

Predicts the header amount with VAT that {@see InvoiceCalculator::recalculate()} computes from per-line rows, as a literal mirror: bases are taken in the form {@see line()} stores them (`number_format($price, 3)`), multiplied unrounded by the rate multiplier at {@see WORK_SCALE}, and only the sum is rounded. Quantity is 1 and the discount off for all these lines, so the calculator does nothing more.

private predictedPerLineTotal(array<int, array{name: string, base: float, vat: numeric-string, supplyKindId?: int|null}> $lines, array<int, array{name: string, base: float, vat: numeric-string}> $feeLines, bool $useVat) : numeric-string
Parameters
$lines : array<int, array{name: string, base: float, vat: numeric-string, supplyKindId?: int|null}>
$feeLines : array<int, array{name: string, base: float, vat: numeric-string}>

allocated gateway fee lines

$useVat : bool
Return values
numeric-string

proportionalVoucherShare()

Returns the proportional part of the fixed discount for a partial credit note: `discount x returned base / source base`, capped by the ledger remainder of the discount (rounding up would over-credit). Base = `totalPrice` of the billed lines (items, shipping, payment) scaled by the ratio of the chosen pieces.

private proportionalVoucherShare(string $fixed, string $remaining, Invoice $source, array<string, array<int, int>> $lines) : string
Parameters
$fixed : string
$remaining : string
$source : Invoice
$lines : array<string, array<int, int>>
Return values
string

remainingLines()

Returns the whole ledger remainder as a line selection.

private remainingLines(TCreditNoteLedger $ledger) : array<string, array<int, int>>
Parameters
$ledger : TCreditNoteLedger
Return values
array<string, array<int, int>>

remainingQuantity()

Returns the ledger remainder of a source line (pieces nobody has credited yet). A line outside the ledger (no id) yields 0: better to credit nothing than to credit twice.

private remainingQuantity(TCreditNoteLedger $ledger, string $collection, int|null $sourceLineId) : int
Parameters
$ledger : TCreditNoteLedger
$collection : string
$sourceLineId : int|null
Return values
int

resolveBillingLineId()

Line for the document: the line of the type, else `7/invoice.default.billingLineId`; null when neither exists.

private resolveBillingLineId(InvoiceType $invoiceType) : int|null
Parameters
$invoiceType : InvoiceType
Return values
int|null

settingBool()

private settingBool(string $key) : bool
Parameters
$key : string
Return values
bool

settingInt()

private settingInt(string $key, int $default) : int
Parameters
$key : string
$default : int
Return values
int

settingIntOrNull()

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

settingStr()

private settingStr(string $key, string $default) : string
Parameters
$key : string
$default : string
Return values
string

settingVat()

Rate setting (`7/invoice.default.vatPercents`, stored as text) → `DECIMAL(5,2)` string; invalid/missing → `0.00`.

private settingVat(string $key) : numeric-string
Parameters
$key : string
Return values
numeric-string

snapshotTariffLines()

Returns tariff lines from the accounting snapshot of checkout (`cms_system_payment_items`): name, base, rate and productType as actually billed. An empty result means the payment has no snapshot (legacy) and the caller falls back to the live price list. The snapshot rate is always resolved (checkout stores the result of the chain price list row, tariff, default); `?? $defaultVat` is only a safeguard.

private snapshotTariffLines(int $paymentId, numeric-string $defaultVat) : array{0: list, 1: list}
Parameters
$paymentId : int
$defaultVat : numeric-string
Return values
array{0: list, 1: list}

systemTariffLines()

Returns tariff lines of a system payment (membership/credits, `cms_system_user_payment_items`).

private systemTariffLines(int $paymentId, int|null $region, int|null $gate, int|null $currency, int $langId, numeric-string $defaultVat) : array{0: list, 1: list}

IMPORTANT: the rate chain is the same in all four collectors, so changing one means changing all: price list row (region x gate, TariffPrice::vatPercents), then flat tariff override, then global setting. The rate belongs to a concrete tariff x region combination (different services have different VAT in one region), so it comes from the price list row, not from the payment region alone.

Parameters
$paymentId : int
$region : int|null
$gate : int|null
$currency : int|null
$langId : int
$defaultVat : numeric-string
Return values
array{0: list, 1: list}

translateTaxNote()

VAT clause in the document language plus Czech; null when the code has no clause.

private translateTaxNote(string|null $noteKey, Invoice $invoice) : string|null
Parameters
$noteKey : string|null
$invoice : Invoice
Return values
string|null
On this page

Search results