ComplaintStockManager
in package
uses
SourceLockTransactionTrait
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'
COMPLAINTS_TABLE
private
string
COMPLAINTS_TABLE
= 'cms_mod_eshop_complaints'
DOCUMENTS_TABLE
private
string
DOCUMENTS_TABLE
= 'cms_mod_store_stock_documents'
LOCK_MAX_ATTEMPTS
private
int
LOCK_MAX_ATTEMPTS
= 3
LOCK_SCALE
private
int
LOCK_SCALE
= 3
LOCK_TIMEOUT
private
int
LOCK_TIMEOUT
= 5
MODULE
private
int
MODULE
= 4
MOVEMENTS_TABLE
private
string
MOVEMENTS_TABLE
= 'cms_mod_store_stock_movements'
SETTING_MODULE
private
int
SETTING_MODULE
= 4
SOURCE_TYPE
private
string
SOURCE_TYPE
= 'complaint'
Properties
$adminAlertManager read-only
private
AdminAlertManager
$adminAlertManager
$em read-only
private
EntityManagerInterface
$em
$setCompositionService read-only
private
SetCompositionService
$setCompositionService
$settingContext read-only
private
SettingContext
$settingContext
$stockDocumentManager read-only
private
StockDocumentManager
$stockDocumentManager
Methods
__construct()
public
__construct(EntityManagerInterface $em, StockDocumentManager $stockDocumentManager, SetCompositionService $setCompositionService, SettingContext $settingContext, AdminAlertManager $adminAlertManager) : mixed
Parameters
- $em : EntityManagerInterface
- $stockDocumentManager : StockDocumentManager
- $setCompositionService : SetCompositionService
- $settingContext : SettingContext
- $adminAlertManager : AdminAlertManager
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
ResultreceiveReturn()
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
ResultacquireSourceLock()
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 $complaintId) : string
Parameters
- $complaintId : 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
Return values
numeric-stringnotifyStuckReturn()
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)