CoolCMS API

ComplaintStockManager
in package
uses SourceLockTransactionTrait

FinalYes

Returns the approved items of a complaint to the warehouse.

It performs a single operation, DocumentService::receive() with SourceRef(4,'complaint',$complaintId), priced at COGS (the stock price at the time the original order was issued); pricing at the current or selling price would distort the weighted stock price.

Locking: per complaint GET_LOCK('cms_eshop_complaint_{id}'), reentrant, so it is safe to call nested from ComplaintTransitionManager::apply(). Store keeps its own SourceRef lock; the lock order is always eshop first, then store.

Idempotent: once complaints.stock_document_id is written the return has happened and a repeated apply() is a no-op.

Table of Contents

Constants

STUCK_NOTIFICATION_TYPE  : string = 'COMPLAINT_RETURN_STUCK'
Admin notification type: the stock return needs manual intervention.
COMPLAINTS_TABLE  : string = 'cms_mod_eshop_complaints'
DOCUMENTS_TABLE  : string = 'cms_mod_store_stock_documents'
LOCK_MAX_ATTEMPTS  : int = 3
LOCK_SCALE  : int = 3
LOCK_TIMEOUT  : int = 5
MODULE  : int = 4
MOVEMENTS_TABLE  : string = 'cms_mod_store_stock_movements'
SETTING_MODULE  : int = 4
SOURCE_TYPE  : string = 'complaint'

Properties

$adminAlertManager  : AdminAlertManager
$em  : EntityManagerInterface
$setCompositionService  : SetCompositionService
$settingContext  : SettingContext
$stockDocumentManager  : StockDocumentManager

Methods

__construct()  : mixed
compensateReturn()  : Result
Compensates a return created by the current transition when the following status/queue write failed.
receiveReturn()  : Result
Returns the approved complaint items (`ComplaintItem::$approved === true`) to the warehouse.
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
notifyStuckReturn()  : void
Notifies admins once per complaint (bell, `COMPLAINT_RETURN_STUCK`) that the complaint points at a canceled return document.
originalCogsPerStockItem()  : array<int, string>
Returns the weighted average COGS price per stock item.

Constants

STUCK_NOTIFICATION_TYPE

Admin notification type: the stock return needs manual intervention.

public string STUCK_NOTIFICATION_TYPE = 'COMPLAINT_RETURN_STUCK'

Properties

Methods

compensateReturn()

Compensates a return created by the current transition when the following status/queue write failed.

public compensateReturn(int $complaintId, int $documentId[, int|null $userId = null ]) : Result

Store receive() owns its transaction and cannot be nested, so the return is undone by a counter document, not a rollback: StockDocumentManager::cancel() (audited cancellation), then stock_document_id = NULL only if the complaint still points at the same document, so a repeated transition creates the whole return again.

When the cancellation fails, stock_document_id stays: a repeated transition skips the return idempotently and only completes the status (the goods are already in stock). Both failures are logged loudly to eshop.general so an operator can trace the state.

Result data ['cancelled' => bool]. Failure errorCode: compensationFailed.

Parameters
$complaintId : int
$documentId : int
$userId : int|null = null
Return values
Result

receiveReturn()

Returns the approved complaint items (`ComplaintItem::$approved === true`) to the warehouse.

public receiveReturn(Complaint $complaint[, int|null $userId = null ]) : Result

The $complaint must be saved (have an id) and have its items collection loaded; use a fresh load, not an object graph built without includes.

Result data: ['stockDocumentId' => ?int, 'noop' => bool]. Failure errorCode: complaintLocked, nestedTransaction, warehouseNotConfigured, nothingToReceive, unmappedItems, receiveFailed.

Parameters
$complaint : Complaint
$userId : int|null = null
Return values
Result

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 $complaintId) : string
Parameters
$complaintId : int
Return values
string

lsub()

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

notifyStuckReturn()

Notifies admins once per complaint (bell, `COMPLAINT_RETURN_STUCK`) that the complaint points at a canceled return document.

private notifyStuckReturn(int $complaintId, int $documentId) : void

A repeated status transition would see a numeric stock_document_id in receiveReturn(), return a no-op and never create the stock return. Manual fix: set the complaint's stock_document_id to null (UPDATE cms_mod_eshop_complaints SET stock_document_id = NULL WHERE complaint_id = …) and run the transition again; the return is then created in full. Best effort: a failure reading the order or sending is only logged.

Parameters
$complaintId : int
$documentId : int

originalCogsPerStockItem()

Returns the weighted average COGS price per stock item.

private originalCogsPerStockItem(int $orderId) : array<int, string>

It aggregates the posted issue movements paired with the original order (SourceRef(4,'order',$orderId)), like MarginReporter. Issue movements have a negative quantity, hence -quantity in both the weight and the sum. A stock item without a traceable issue (older order, manual SQL) is missing from the result, and the caller treats it as unitPrice=null (Store auto-prices the return).

Parameters
$orderId : int
Return values
array<int, string> —

stock item id mapped to its weighted COGS price (decimal string)

On this page

Search results