CoolCMS API

OrderCreditNoteManager
in package
uses SourceLockTransactionTrait

FinalYes

Issues a partial credit note as a standalone action on an invoice.

This is not a state transition. A whole-order credit note is born from the transition to state 9 ("returned"); routing a partial one the same way would make the state machine lie, because returning 1 of 3 items does not make the order returned. Hence a separate manager-tier entry point and its own RBAC key.

Both paths share the limit and the lock:

The lock is taken here, not in the generator: the generator also runs under the transition lock, so the lock belongs to the entry point that owns the operation. A nested GET_LOCK of the same name on one session is counted (MariaDB keeps it until every RELEASE_LOCK), so nesting would be harmless, just redundant.

Table of Contents

Constants

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

Properties

$complaintManager  : ComplaintManager
$creditNoteLedger  : CreditNoteLedger
$em  : EntityManagerInterface
$invoiceGenerator  : OrderInvoiceGenerator
$orderManagerFactory  : OrderManagerFactory

Methods

__construct()  : mixed
issuePartial()  : Result
Issues a partial credit note for selected lines of the order's source invoice.
preview()  : Result
Returns the data for the partial credit note form: source invoice lines with label, invoiced and already credited quantity and the remainder.
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
normalizeLines()  : array<string, array<int, int>>
Drops zero/negative quantities and foreign collection keys.
prefillFromComplaint()  : array<string, array<int, int>>
Prefills the form from the approved complaint items.

Constants

Properties

Methods

issuePartial()

Issues a partial credit note for selected lines of the order's source invoice.

public issuePartial(int $orderId, array<string, array<int, int>> $requestedLines) : Result

Always returns a Result; the admin form builds its flash messages on it. Failure errorCode: orderLocked, nestedTransaction, orderNotFound, noLinesSelected, plus permanent ones from the generator (sourceInvoiceMissing, unattributedCreditLines, nothingToCredit, creditLimitExceeded, creditNoteTypeMissing, decimalQuantityUnsupported).

IMPORTANT: decimalQuantityUnsupported arrives as a failed Result, not as an exception; OrderInvoiceGenerator::buildCreditNoteWithinLimit() converts it at the ledger. Otherwise the generic catch below would swallow it as an unexpected error. The conversion lives in the generator because the whole-order credit note (state transition, cron) goes through the same place. preview() keeps its own equivalent catch since it calls the ledger directly.

Parameters
$orderId : int
$requestedLines : array<string, array<int, int>>

collection => (source invoice line id => pieces)

Return values
Result

preview()

Returns the data for the partial credit note form: source invoice lines with label, invoiced and already credited quantity and the remainder.

public preview(int $orderId[, int|null $complaintId = null ]) : Result

The source invoice comes from OrderInvoiceGenerator::findCreditNoteSource(), the same method the credit note is really built from; a separate "latest invoice" lookup would drift and the UI would offer lines of another document.

No lock (read only). The remainder may change before the submit; the authoritative check is the one in issuePartial() under the lock.

Failure errorCode: sourceInvoiceMissing, decimalQuantityUnsupported (the source invoice has a line with a decimal quantity; the credit limit supports whole pieces only).

Parameters
$orderId : int
$complaintId : int|null = null
Return values
Result —

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

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

lockName()

private lockName(int $orderId) : string
Parameters
$orderId : int
Return values
string

lsub()

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

normalizeLines()

Drops zero/negative quantities and foreign collection keys.

private normalizeLines(array<string, array<int, int>> $lines) : array<string, array<int, int>>

The limit itself is enforced by CreditNoteLedger::violations(); this only cleans the form input so an empty "return" field does not send zeros that look like an attempt at an empty credit note.

Parameters
$lines : array<string, array<int, int>>
Return values
array<string, array<int, int>>

prefillFromComplaint()

Prefills the form from the approved complaint items.

private prefillFromComplaint(int $complaintId, array<string, array<int, array<string, mixed>>> $lines) : array<string, array<int, int>>

A credit note from a complaint is issued manually; this is only a suggested quantity that the admin sees and confirms. A complaint does not lower the limit: the suggestion is clipped to the ledger remainder and never exceeds it.

Mapping goes through invoice_items.order_item_id. IMPORTANT: it is not unique, an item fee has its own invoice line with the same order_item_id, so both lines get prefilled (returning goods also returns the packaging fee for those pieces). The admin can reduce them before confirming.

Parameters
$complaintId : int
$lines : array<string, array<int, array<string, mixed>>>

lines from preview()

Return values
array<string, array<int, int>> —

collection => (invoice line id => suggested pieces)

On this page

Search results