CoolCMS API

InvoiceCreditNoteManager
in package
uses SourceLockTransactionTrait

FinalYes

Issues a manual credit note for an issued invoice from the Invoicer admin.

A thin layer over InvoiceGenerator::generateManualCreditNote() that holds a per-invoice advisory lock (cms_invoicer_invoice_{id}), so two concurrent requests (double click, two admins) cannot read the same balance and credit the goods twice. The cap and the document build live in the generator; preview() reads the same ledger and source as the issue.

Credit notes of e-shop orders (orderOwned) do not belong here; they have their own path under the order lock (OrderCreditNoteManager).

Table of Contents

Constants

LOCK_MAX_ATTEMPTS  : int = 3
LOCK_SCALE  : int = 3
LOCK_TIMEOUT  : int = 5

Properties

$creditNoteLedger  : CreditNoteLedger
$em  : EntityManagerInterface
$invoiceGenerator  : InvoiceGenerator
$invoiceTypeManager  : InvoiceTypeManager

Methods

__construct()  : mixed
issue()  : Result
Issues a credit note: the whole remainder when `$full` is true (selected lines are ignored), otherwise a partial one for `$requestedLines`.
preview()  : Result
Builds the form data: source lines with invoiced, already credited and remaining quantity, plus the offered credit note types.
acquireSourceLock()  : bool
Calls `GET_LOCK(name, timeout)` and returns true only on a definite 1 (0 = timed out or busy, null = error). `$timeout` is in seconds; 0 is non-blocking (try-lock, used by the expirer).
connection()  : Connection
lockLogChannel()  : string
releaseSourceLock()  : void
Releases the advisory lock.
runInSourceTransaction()  : T
Runs a closure in one raw-DBAL transaction with a deadlock retry (`SQLSTATE` 40001 / errno 1213).
ladd()  : numeric-string
lcmp()  : int
ldec()  : numeric-string
ldiv()  : numeric-string
Divides with half-up rounding to scale 3 (bcmath itself truncates; same rounding as {@see \App\UI\Api\Eshop\Models\Helpers\OrderCalculator::round3()}).
lInt()  : int
lmul()  : numeric-string
ln()  : numeric-string
lockName()  : string
lStr()  : string
lStrN()  : string|null
lsub()  : numeric-string

Constants

Properties

Methods

issue()

Issues a credit note: the whole remainder when `$full` is true (selected lines are ignored), otherwise a partial one for `$requestedLines`.

public issue(int $invoiceId, int $creditTypeId, bool $full[, array<string|int, mixed> $requestedLines = [] ]) : Result

Failure errorCode: nestedTransaction, invoiceBusy, noLinesSelected and everything from InvoiceGenerator::generateManualCreditNote().

Parameters
$invoiceId : int
$creditTypeId : int
$full : bool
$requestedLines : array<string|int, mixed> = []

collection => (source line id => pieces)

Return values
Result

preview()

Builds the form data: source lines with invoiced, already credited and remaining quantity, plus the offered credit note types.

public preview(int $invoiceId) : Result

Reads without a lock; only issue() is authoritative.

Failure errorCode: everything from InvoiceGenerator::manualCreditNoteSource() and decimalQuantityUnsupported.

Parameters
$invoiceId : int
Return values
Result —

data: array{sourceInvoiceId: int, sourceInvoiceNumber: ?string, locked: bool, lines: array<string, list<array<string, mixed>>>, hasRemainder: bool, blocked: bool, creditTypes: array<int, string>}

acquireSourceLock()

Calls `GET_LOCK(name, timeout)` and returns true only on a definite 1 (0 = timed out or busy, null = error). `$timeout` is in seconds; 0 is non-blocking (try-lock, used by the expirer).

protected acquireSourceLock(string $name, int $timeout) : bool
Parameters
$name : string
$timeout : int
Return values
bool

releaseSourceLock()

Releases the advisory lock.

protected releaseSourceLock(string $name) : void

IMPORTANT: it is called from a finally, so a failure here (a gone-away connection after a fatal query) is swallowed and logged; rethrowing would mask the original exception being unwound. MySQL drops session locks when the connection dies anyway, so nothing leaks past the process.

Parameters
$name : string

runInSourceTransaction()

Runs a closure in one raw-DBAL transaction with a deadlock retry (`SQLSTATE` 40001 / errno 1213).

protected runInSourceTransaction(callable(Connection): T $fn) : T

The advisory lock (held by the caller across this call) is not released by a rollback, so every attempt runs under the same serialization.

Parameters
$fn : callable(Connection): T
Tags
template
Return values
T

ladd()

private ladd(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string

ldiv()

Divides with half-up rounding to scale 3 (bcmath itself truncates; same rounding as {@see \App\UI\Api\Eshop\Models\Helpers\OrderCalculator::round3()}).

private ldiv(string $a, string $b) : numeric-string

IMPORTANT: truncation would systematically drop up to 0.001 from every unit on large quantities, so the invariant sum(unitPrice x quantity) ~ sum(rowBase) would drift in one direction (an issue document undervalued against the order).

Parameters
$a : string
$b : string
Return values
numeric-string

lmul()

private lmul(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string

lsub()

private lsub(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string
On this page

Search results