StockLedgerService

Manager class used to create appropriate stock history entries for the various stock actions.

The following stock actions are supported:

1) PURCHASE New items of an article are purchased for a given purchase price and stored within the warehouse.

2) STOCKTAKE All available items of an article within the warehouse are counted and the article's instock quantity is updated accordingly.

3) SALE A given amount of items of an article has been sold and is removed from the warehouse.

4) RETURN A given amount of items of an article has been returned by the customer and is added to the warehouse.

5) MANUAL The instock quantity of an article is changed manually.

6) INCOMING An arbitrary incoming amount is added to the stock.

7) OUTGOING An arbitrary outgoing amount is removed from the stock.

These actions can be roughly divided into two groups: "incoming" and "outgoing", whereas "incoming" refers to all history entries, which are related to the storage of items within the warehouse and "outgoing" refers to all entries, which are concerned with the removal of items from the warehouse:

a) "incoming":

  • purchase entry
  • stocktake entry with positive change amount
  • return entry
  • manual entry with positive change amount

    b) "outgoing"

  • stocktake entry with negative change amount
  • sale entry
  • manual entry with negative change amount

History entries for "neutral" stocktake actions (change amount is zero) play a minor role and therefore are not listed above, since they "have no effect" and are created only for informational purposes.

To enable the computation of the inventory value (Bewerteter Warenbestand) for a given reference date, we need to link "outgoing" and "incoming" entries in a suitable way. If possible, each "outgoing" entry is therefore related to an "incoming" entry, which is called it's source lot entry. "Incoming" entries are assigned to "outgoing" entries based on their "free capacity" and in accordance with the LIFO (Last In First Out) principle, meaning "incoming" entries are assigned in their reversed creation order. The assignment process is (roughly) as follows:

1) get a list of all "incoming" entries for the given article in their reversed creation order 2) compute the "free capacity" for each "incoming" entry in the list, whereas the "free capacity" is calculated as follows: change amount of "incoming" entry minus the sum of the change amounts of all already assigned "outgoing" entries 3) Remove all entries from the list, whose "free capacity" = 0 4) Check "free capacity" of the first list entry: a) "free capacity" >= change amount of "outgoing" entry? Create a new "outgoing" entry for the given change amount and assign it to the "incoming" entry b) "free capacity" < change amount of "outgoing" entry? Create a new "outgoing" entry with change amount = "free capacity" and assign the "incoming" entry to the newly created "outgoing" entry. Remove the "incoming" entry from the list and repeat step 4) until no unprocessed change amount remains or the list is empty 5) If some unprocessed change amount remains, create a new unassigned "outgoing" entry for the remaining change amount (purchase price = default purchase price)

"Outgoing" entries created in step 4) are called assigned, whereas those created in step 5) are called unassigned. If the assignment process results in a single "outgoing" entry, this entry is called a singlepart entry otherwise the set of newly created entries is called a multipart entry.

The instock quantity of each article, which is tracked within the stock history and the one maintained by Shopware (within the article details) may differ, since Shopware updates his counter as soon as a new order has been created, whereas the one maintained within the stock history is intended to track the physical item movements and is therefore (usually) not updated before the order is completely fulfilled and all items have been shipped. To keep these counters in sync the manager class takes care of updating the Shopware counter, when creating new history entries.

Remark: The purchase price of the default customer group of the active shop is used as default purchase price.

package

Default

Methods

__construct

__construct(\Shopware\Components\Model\ModelManager $entityManager, \Shopware_Components_Snippet_Manager $snippetManager, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\StockChangeListFactory $stockChangeListFactory, \Shopware\Plugins\ViisonPickwareERP\Components\DerivedPropertyUpdater\DerivedPropertyUpdater $derivedPropertyUpdater, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\ArticleDetailStockInfoProvider $articleDetailStockInfoProvider, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\ArticleDetailConcurrencyCoordinator $articleDetailConcurrencyCoordinator, \Psr\Log\LoggerInterface $logger) 

Arguments

$entityManager

\Shopware\Components\Model\ModelManager

$snippetManager

\Shopware_Components_Snippet_Manager

$logger

\Psr\Log\LoggerInterface

Returns the bin locations changed by a stock ledger entry in comparison to its direct predecessor.

calculateChangedBinLocations(integer $stockLedgerEntryId) : array

Tries to find the stock entry with the given $stockLedgerEntryId as well as the entry directly preceding that stock entry to calculate the difference between their snapshots, which is then returned.

Arguments

$stockLedgerEntryId

integer

Response

array

Finds all bin location mappings of the selected article detail in the selected warehouse and both unsets any 'default mapping' flags and removes any non-zero stock from the bin location mappings by logging manual stock entries using the default purchase price.

discardStock(\Shopware\Models\Article\Detail $articleDetail, \Shopware\CustomModels\ViisonPickwareERP\Warehouse\Warehouse $warehouse, string|null $comment = null, \Shopware\Models\User\User|null $user = null) 

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$comment

string|null

$user

\Shopware\Models\User\User|null

getTransactionId

getTransactionId() : string

Response

string

Initializes the managed article detail by updating its total, physical stock to the given `$actualStock`.

initializeStockOfArticleDetail(\Shopware\Models\Article\Detail $articleDetail, \Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation $binLocation, integer $actualStock, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>
Throws
\Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockLedgerConcurrencyException

If the managed article detail has already been initialized.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$binLocation

\Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation

The bin location that should be changed when adjusting the stock.

$actualStock

integer

The actual, physical stock across all warehouses.

$comment

string

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Tries to acquire a global stock change lock for the article detail managed by this instance.

performWithLockForArticleDetail(\Shopware\Models\Article\Detail $articleDetail, \Closure $closure) : mixed
Throws
\Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockLedgerException

If acquiring the lock failed.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$closure

\Closure

Response

mixed

Creates a new history entry for arbitrary incoming stock changes.

recordIncomingStock(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\PositiveStockChangeList $stockChanges, float|null $purchasePrice = null, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

This happens when the stock amount for an individual article is increased through the app.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$purchasePrice

float|null

The price the incoming quantity was purchased for

$comment

string

An optional comment

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for arbitrary outgoing stock changes.

recordOutgoingStock(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\NegativeStockChangeList $stockChanges, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

This happens when the stock amount for an individual article is decreased through the app.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$comment

string

An optional comment

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a purchase action.

recordPurchasedStock(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\PositiveStockChangeList $stockChanges, float|null $purchasePrice = null, string $comment = '', \Shopware\CustomModels\ViisonPickwareERP\SupplierOrder\SupplierOrderItem|null $supplierOrderItem = null, \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

This happens when a supplier order is received and processed in the warehouse.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$purchasePrice

float|null

The price the quantity was purchased for

$comment

string

An optional comment

$supplierOrderItem

\Shopware\CustomModels\ViisonPickwareERP\SupplierOrder\SupplierOrderItem|null

An optional supplier order article to be associated with the purchase

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

First validates the given $stockChanges to make sure that all source bin locations are currently mapped to the article detail and their stock is sufficient for the relocation. The respective stock change quantity does not exceed the stock currently residing on that bin location. The latter validation prevents any bin location from containing negative stock after the relocation, which would be a violation of our data model.

recordRelocatedStock(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\RelocationStockChangeList $stockChanges, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Those validations apply to all source bin locations, except for the warehouse's null bin location, if present. We allow this exception, because we have to acknowledge that stocks might be corrupt in a way, that in the real world stock exists, but that stock is not reflected in the database, which results in zero or negative stock on the null bin location (see \self::updateBinLocationMappings()). In that case, relocating that stock in the real world must still be possible (see https://github.com/VIISON/ShopwarePickwareMobile/issues/163). Once validated, depending on the warehouses of the source and destination bin locations, the relocation is performed by either logging a 'relocation' in the managed warehouse or by logging a sequence of stock entries for a relocation to a different warehouse than the one managed by this stock manager.

Throws
\Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockLedgerValidationException

if any of the source bin locations is currently not mapped to the variant or its stock is not sufficient for the relocation.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$comment

string

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a return action.

recordReturnedStock(\Shopware\Models\Article\Detail $articleDetail, \Shopware\CustomModels\ViisonPickwareERP\ReturnShipment\ReturnShipmentItem $returnShipmentItem, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\PositiveStockChangeList $stockChanges, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

This happens when a return shipment is received from a customer and unpacked in the warehouse.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$returnShipmentItem

\Shopware\CustomModels\ViisonPickwareERP\ReturnShipment\ReturnShipmentItem

An optional reshipment item to be associated with the return

$comment

string

An optional comment

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a return correction.

recordReturnedStockCorrection(\Shopware\Models\Article\Detail $articleDetail, \Shopware\CustomModels\ViisonPickwareERP\ReturnShipment\ReturnShipmentItem $returnShipmentItem, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\NegativeStockChangeList $stockChanges, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Correction for \self::recordReturnedStock()

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$returnShipmentItem

\Shopware\CustomModels\ViisonPickwareERP\ReturnShipment\ReturnShipmentItem

An optional reshipment item to be associated with the return

$comment

string

An optional comment

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Create a history entry for a return action without a ReturnShipment.

recordReturnedStockWithoutReturnShipment(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Models\Order\Detail $orderDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\PositiveStockChangeList $stockChanges, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

A special function made exclusively for POS, because POS does not create ReturnShipments.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$orderDetail

\Shopware\Models\Order\Detail

$comment

string

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a sale action.

recordSoldStock(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Models\Order\Detail $orderDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\NegativeStockChangeList $stockChanges, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

This happens when a customer order is shipped.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$orderDetail

\Shopware\Models\Order\Detail

The order detail, which was sold

$comment

string

An optional comment

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a sale correction.

recordSoldStockCorrection(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Models\Order\Detail $orderDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\PositiveStockChangeList $stockChanges, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Correction for \self::recordSoldStock()

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$orderDetail

\Shopware\Models\Order\Detail

$comment

string

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a stocktake action.

recordStocktake(\Shopware\Models\Article\Detail $articleDetail, \Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation $binLocation, integer $actualStock, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Happens when a stocktake is performed using the app.

Throws
\Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockLedgerException

If the managed article detail has not been initialized yet.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$binLocation

\Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation

The bin location, whose actual stock shall be logged

$actualStock

integer

The actual physical stock quantity

$comment

string

An optional comment

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a write-off action.

recordWriteOff(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\NegativeStockChangeList $stockChanges, \Shopware\CustomModels\ViisonPickwareERP\ReturnShipment\ReturnShipmentItem|null $returnShipmentItem = null, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

That is when an item from a return must be written-off or an item in the warehouse must be written-off.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$comment

string

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Creates a new history entry for a write-off correction.

recordWriteOffCorrection(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\PositiveStockChangeList $stockChanges, \Shopware\CustomModels\ViisonPickwareERP\ReturnShipment\ReturnShipmentItem|null $returnShipmentItem = null, string $comment = '', \Shopware\Models\User\User|null $user = null) : array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Correction for \self::recordSoldStock()

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$comment

string

$user

\Shopware\Models\User\User|null

Response

array<mixed,\Shopware\CustomModels\ViisonPickwareERP\StockLedger\StockLedgerEntry>

Reinstates the actual stock of the article detail onto the default warehouse null bin location.

reinstateStock(\Shopware\Models\Article\Detail $articleDetail,  $comment = null, \Shopware\Models\User\User|null $user = null) : void
Throws
\Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockLedgerException

Arguments

$articleDetail

\Shopware\Models\Article\Detail

$comment

$user

\Shopware\Models\User\User|null

Marks the $newDefaultBinLocation as the default bin location in the $warehouse for the $articleDetail.

selectDefaultBinLocation(\Shopware\Models\Article\Detail $articleDetail, \Shopware\CustomModels\ViisonPickwareERP\Warehouse\Warehouse $warehouse, \Shopware\CustomModels\ViisonPickwareERP\Warehouse\BinLocation|null $newDefaultBinLocation = null) 

Uses the given $newDefaultBinLocation to either create or update a mapping from the managed article detail to that bin location and mark it as the default mapping in the managed warehouse. If any other bin location is currently mapped as the default location, the respective mapping is updated and removed, if it does not contain any stock. Passing null as the new default bin location causes the current default bin location mapping to be deselected as the default, effectively leaving the managed article detail without a default bin location mapping. In any case, the bin location mappings of the managed article detail are updated to ensure that any obsolete bin location mappings are removed or new mappings are created as necessary.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

Updates the given `$articleDetail`'s attributes by setting its `pickwareStockManagementDisabled` flag to `false`, if the `$articleDetail`'s stock is not being recorded in the stock ledger.

startRecordingStockChangesForArticleDetail(\Shopware\Models\Article\Detail $articleDetail) 

Arguments

$articleDetail

\Shopware\Models\Article\Detail

Checks whether the given `$articleDetail`' stock is not recorded in the stock ledger and, if it is not, removes all physical stock and relocates the `$articleDetail` to the null bin location in all warehouses. Finally the `$articleDetail`'s attributes are updated by setting its `pickwareStockManagementDisabled` to `true`.

stopRecordingStockChangesForArticleDetail(\Shopware\Models\Article\Detail $articleDetail) 

Arguments

$articleDetail

\Shopware\Models\Article\Detail

Updates the bin location mappings for an $articleDetail in the $warehouse based on the made $stockChanges.

updateBinLocationMappings(\Shopware\Models\Article\Detail $articleDetail, \Shopware\Plugins\ViisonPickwareERP\Components\StockLedger\StockChangeList\AbstractStockChangeList $stockChanges) 

For all bin locations affected by the given $stockChanges either an existing mapping is found and updated/removed or a new mapping is created. Then it is asserted that at least one bin location mapping exists and all negative stock resulting from the $stockChanges is moved to the null bin location mapping.

Arguments

$articleDetail

\Shopware\Models\Article\Detail

Constants

The comment, which is appended to a stock entry comment when changing the physical stock from a negative to a positive value.

ENTRIES

The divider which is inserted between given and generated stock entry comments.

POSTFIXES