InvoiceCreditNoteManager
in package
uses
SourceLockTransactionTrait
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
LOCK_MAX_ATTEMPTS
private
int
LOCK_MAX_ATTEMPTS
= 3
LOCK_SCALE
private
int
LOCK_SCALE
= 3
LOCK_TIMEOUT
private
int
LOCK_TIMEOUT
= 5
Properties
$creditNoteLedger read-only
private
CreditNoteLedger
$creditNoteLedger
$em read-only
private
EntityManagerInterface
$em
$invoiceGenerator read-only
private
InvoiceGenerator
$invoiceGenerator
$invoiceTypeManager read-only
private
InvoiceTypeManager
$invoiceTypeManager
Methods
__construct()
public
__construct(EntityManagerInterface $em, InvoiceGenerator $invoiceGenerator, CreditNoteLedger $creditNoteLedger, InvoiceTypeManager $invoiceTypeManager) : mixed
Parameters
- $em : EntityManagerInterface
- $invoiceGenerator : InvoiceGenerator
- $creditNoteLedger : CreditNoteLedger
- $invoiceTypeManager : InvoiceTypeManager
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
Resultpreview()
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
boolconnection()
protected
connection() : Connection
Return values
ConnectionlockLogChannel()
protected
lockLogChannel() : string
Return values
stringreleaseSourceLock()
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
Return values
Tladd()
private
ladd(string $a, string $b) : numeric-string
Parameters
- $a : string
- $b : string
Return values
numeric-stringlcmp()
private
lcmp(string $a, string $b) : int
Parameters
- $a : string
- $b : string
Return values
intldec()
private
ldec(string $value) : numeric-string
Parameters
- $value : string
Return values
numeric-stringldiv()
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-stringlInt()
private
lInt(mixed $value) : int
Parameters
- $value : mixed
Return values
intlmul()
private
lmul(string $a, string $b) : numeric-string
Parameters
- $a : string
- $b : string
Return values
numeric-stringln()
private
ln(string $value) : numeric-string
Parameters
- $value : string
Return values
numeric-stringlockName()
private
lockName(int $invoiceId) : string
Parameters
- $invoiceId : int
Return values
stringlStr()
private
lStr(mixed $value) : string
Parameters
- $value : mixed
Return values
stringlStrN()
private
lStrN(mixed $value) : string|null
Parameters
- $value : mixed
Return values
string|nulllsub()
private
lsub(string $a, string $b) : numeric-string
Parameters
- $a : string
- $b : string