We have a method getLocationsList() that takes a PurchasableEntityInterface and returns a list of locations. It currently returns all active locations. This is not the corect behavior as some locations may be active but not available for stock allocation.
#2671372: Manage stock locations as entities will make it easy to add an "Available for Stock allocation" field and this will make a better default. However we need to make sure we make it easy for site builders/developers to change this behavior so it can be by other criteria like by product type.

I believe the solution has two parts:

1) Global configuration.
Main location - all transactions & stock checking are against the main location and
All on hand stock - If set will checks all available stock not just main location (*1)
(*1) we should add another Boolean flag to the location table to say if its available for stock checking.

2) more granular configuration (by product/product type/ other) - I think that this is not needed at this point and any site needing such complexity can derive a class form LocalStockStorage to address this. We can also add rules support and or hooks at a later stage to allow applications to manage this logic.

Some discussion about this and related topics in https://github.com/BBGuy/commerce_stock/pull/9

Comments

guy_schneerson created an issue. See original summary.

olafkarsten’s picture

I feel the design is still flawed at this point. We have no good solution to filter the locations from which to calculate the level from.

I think that is not a case for a config item, as the list of locations to check against may depend on the order or the costumer or the commerce store or ... something else.

Commerceguys have a nice solution for filtering which product variants should be loaded in ProductVariationStorage->loadEnabled(). They dispatch an event, so some other modules can listen to that event and apply there own filter logic. Lets imagine we go with location content entities, we could do the same and easily dispatch a FilterStockLocationsEvent($activeLocations, $purchasableEntity). Plus it should be easy to let D8Rules act on this. As far as I remember D8 Rules react on events.

Per default wo could load all active locations and check against them.

Another idea is to force a relationship between a location and at least one commerce store. As each purchasableEntity belongs to at least one store, we could filter the locations by the purchasableEntity's store.

Just Ideas. Not ready for production :D.

steveoliver’s picture

Title: transactions & Stock checking using multiple locations for. » Support multiple (optional) stock locations
Related issues: +#2671372: Manage stock locations as entities

Updating title and linking related issue.

guy_schneerson’s picture

Hi @olafkarsten. I like the event aprouch especially if it enables using rules as we have two many use cases to support.
I think that out of the box we should support the following two options.

  1. Primary location only
  2. All available stock (all active locations)

In my experience this two should take care of most of the use cases. Option (1) should be the default
as option (2) can be confusing as we currently can only reserve stock from the primary location so Option (2) and can lead to negative stock. Although I know people will also use option (2) as they need to sell as long as they have stock anywhere. In such cases they will likely move stock between locations to balance things out and will not be confused by negative stock.

steveoliver’s picture

Title: Support multiple (optional) stock locations » Finalize the stock location API
Related issues: -#2671372: Manage stock locations as entities

I think this issue has started a substantial and important discussion that could be the basis for defining a stock location API. I am going to reference this as a parent from other important location related issues.

steveoliver’s picture

olafkarsten’s picture

Issue summary: View changes

Updated the issue summary.

olafkarsten’s picture

Title: Finalize the stock location API » Add further possibilities to filter stock locations available for stock allocation
olafkarsten’s picture

I think a flag for "Available for stock allocation" or stock level calculation is okay.

I wonder if we need the primary location thing at all. If I want only one location to be available, I have to set the above flag only on this very location. In this case the getLocations(List) will return only one location. If I have multiple locations enabled for allocation, for what reason I need a primary location?

Further filtering per Code is easy doing: Implement an event subsriber and do you filtering.

Further filtering per UI: I guess, we will need rules integration for this. This shouldn't be that complicated. As far as I have understand, rulesD8 is event based, too.

olafkarsten’s picture

@guy_schneerson I have another question. What is the reason for having an active location but don't want to use that location for stock level calculation?

guy_schneerson’s picture

The primary location is for creating transactions against. However some stores may want to allow purchesing of stock if it is not available in the primary location but is in other location. This is typically for locations that are not used for fulfilment but have stock that can be alocated.
This will tipacaly result in nagative stock in the fulfilment warehouse and a transfer from the oher locations to cover the shortfall.
Not all active locations are sutiable for allocations for example a repairs centar may hold damaged goods not sutiable for sale.
We may however have other cases where the primary location is not needed and stock can be alocated against any available locations. I think this is less common and we do not need to suport out of the box.

I am sure Rules will be a good aprouch for most of those issues but not expecting us to work on it untill we have a stable version first.

olafkarsten’s picture

Wow! That we will see commerce + stock customers, which will need such a setup was far behind my imagination. But you are the maintainer. You surely know better what the support and feature request are about.

So we need somehow

A) determine the primary location
B) determine locations (if any) "Available for allocation" if primary falls short

First I thought we can add both as basefields to the location entity, like the status/active field. But quickly developed doubts, as I don't think these are static settings.

So I talked on IRC with bojanz about that. Here is a quote:

[15:30:57] olafkarsten: stores are billing locations
[15:31:16] company registered in multiple countries OR different vendors altogether
[15:31:33] you'd want the primary stock location to be configurable per-store
[15:31:41] (field on the store)
[15:31:44]
yepp, that was my feeling to
[15:32:48]
bojanz: we can do that the other way around - reference a store on the location entity - as "act as primary for that store"?
[15:33:01] olafkarsten: what if you have a hundred stores?
[15:33:47] I guess you could, since each store would have own locations (stores reference field)
[15:33:51] just not sure it makes more sense
[15:35:58]
bojanz: a location can be the primary for multiple stores but one store can only have one primary
[15:36:22] olafkarsten: in that case you'll get better UX if you place the field on the store
[15:36:36] otherwise you'd need to do an autocomplete field on the location
[15:37:05] that said, you might have too many locations to list at once on the store too
[15:37:09] but at least it's a single autocomplete
[15:37:22] so, no opinion for now :)
[15:37:54]
I don't think we will see customers with hundreds of stock locations
[15:38:37]
if so, they will have the money for better ux

So the idea is, to use a reference field.
Either at the store entity "Select a location as primary for this store" or we put it on the location entity "Select store(s) for which this location act as primary"

Same holds true for the "Available for allocation".

I think we should keep both together. Either at the store settings or at the stock location settings.

And how do we allocate stock in case of multiple "secondary locations"? I have no idea how to do this without user interaction.

olafkarsten’s picture

Purchasable entities can belong to multiple stores, i.e. reference multiple stores.

So I'm afraid we have to pass it as third parameter to getPrimaryTransactionLocation(PurchasableEntityInterface $entity, $quantity, Store $store)

guy_schneerson’s picture

Hi @olafkarsten
bojanz is absolutely correct and I have accidentally neglected the multi store concept. and I agree that we need to add the store to all methods that return locations, so that both the getPrimaryTransactionLocation() and getLocationsList() (as in "Available for stock allocation").
I think it would be best to do the following:
1) Stock configuration: primary stock location (to act as a default)
2) Add to the store entity two new fields "primary store location" + "Available locations for allocation" . I think this is more logical. If the primary location is on the location entity it will need to check if another location is the primary as we can have only one and it will get to confusing.

guy_schneerson’s picture

I missed one impotent point above and that is that we have two versions of getLocationList()
1) In StockCheckInterface: getLocationList($return_active_only = TRUE)
This returns all locations or all active locations. Used by updateLocationStockLevel() and the UI to show a list of all available locations.
2) In StockServiceConfigInterface: getLocationList(PurchasableEntityInterface $entity)
This returns all locations available the available locations for allocation for the $entity and is the one we should be adding the store parameter.

Names and locations are open to negotiation but the general logic is:
The location checker and updater are the Read / Write interfaces and the Configuration is the buisnes logic that tells you how the checker and updater should be used.

olafkarsten’s picture

I really don't get the need for that separation. But maybe if I see some code from someone that does more than proxy the calls from one getLocationList to the other getLocationsList() I will understand the need for that. I will see if I have some time to issue an new PR for the location entity, without all that stuff.

guy_schneerson’s picture

@olafkarsten An example may help.
We have 10 stores each having 2 or 3 locations for holding stock (the stores may be in different countries or its a market place site to help understand the separation).

A call is made to check if Store A has available stock:
The StockAvailabilityChecker calls the configuration getLocationList($entity, $store) to get the list of locations it can check.

For this to work we will need a configuration screen that can list all currently active locations to allow an admin to select available locations for Store A.
The interface will call the stock checker getLocationList(TRUE) to get all active locations and allow him to select the ones he wants.

Transactions will build up over time and will need to be summed into the location level table
The cron job will call updateLocationStockLevel() that will also call the stock checker getLocationList(TRUE) to get a list of all the active locations as all hose need processing.

olafkarsten’s picture

I understand the need of two different functions.

It just feels somehow strange at the StockCheckInterface. But to be honest. I have no better idea.

At least we should rename the function at the StockServiceConfigInterface. Maybe the same name for different stuff confuses me. getLocations() for StockServiceConfigInterface and getLocationList() for the checker.

And we should put more beef on our doc comments. Such things

general logic is:
The location checker and updater are the Read / Write interfaces and the Configuration is the buisnes logic that tells you how the checker and updater should be used.

shouldn't be hidden in the issue queue.

If someone looks at the StockCheckInterface it isn't clear at all, that the locations he need to use in getTotalStockLevel() or getIsInStock() should not come from checker->getLocationList(). In fact we do that wrong ourself, e.g in StockServiceManager::getStockLevel()

guy_schneerson’s picture

Yes we should rename. Once location as entity as is in we will clean this up and add the store in.
doc comments +1 :)
checking if somthing is in stock is not the same as checking how much is in stock (although can be).
Naming aside I don't see what StockServiceManager does wrong?
Once we get locations as entities in. I will create a PR to rename and add the store in and will take feedback.

olafkarsten’s picture

For renaming I opened another issue #2857796: Rename getLocationList in StockServiceConfigInterface, as it is not really related to this issue. Maybe we end with another name.

The StockServiceManager ask the Checker for the locations, when it should ask the ServiceConfig. In its current implementation the results are identical, but that will propably change.

Further we should keep en eye whenand where we need the stock level calculation. (Thats the performance critical part - right?) We check that often, so it should be fast. If we need to load another entity (e.g. store) to ask for the primaryLocation or the available locations it may not the fastest solution. But it may well be, that we have the store in context anytime we calculate the level. So it's only something we should keep in mind during coding and testing.

guy_schneerson’s picture

@olafkarsten was also thinking about the performance issue re stores and in have the following thoughts:

  • I would love it to be a single serialized array stored by stock - but we lose out on exposing the data to entities & views
  • Cache the data maybe even on a store entity update so it is a single query to get the config data
  • it will still probably be a single entity query without needing to load any entities and will not add any/significant overheads so nothing to worry about :)

lets keep an eye

guy_schneerson’s picture

I have made the architectural changes to support this #2858391: Add Support for multiple stores & clean up locations store relationship . Feedback welcomed

steveoliver’s picture

Status: Active » Needs work

OK, It's been a few months, and I haven't worked on it recently, but I'm posting my Work in Progress (WIP) PR -- https://github.com/BBGuy/commerce_stock/pull/35/files -- which addresses at least initially most, if not all, of the issues touched on in this issue.

  1. config/schema/commerce_stock.schema.yml - stock service configurations are sets of stock services with weights, and filters for entity types, order types, stores, locations, etc. This way stock services (here plugins) can be instantiated multiple times, in a list of configured services, where business logic can loop through and find availability and transaction locations.
  2. src/Resolver/ChainStockServiceResolver.php - stock services are resolved at runtime for a given entity, context and quantity.
  3. src/StockAvailabilityChecker.php - stock availability is checked through 'getAvailabilityLocations' method we identified as needing attention in this issue.
  4. src/StockServiceConfig.php (deleted) - stock service configuration is no longer a static object, it is a set of stock service plugins paired with properties for determining at runtime the correct service for a given entity, context, and quantity.
  5. src/StockServiceManager.php - stock service manager is no longer just a static list of services but the entrypoint for getting stock services at runtime.
  6. Each stock service implements one or more interfaces related to the types of transactions supported
  7. Each stock service, when instantiated in a stock service configuration, can be configured to support the available transaction(s) for each case (again, based on criteria in the stock service configuration, e.g. store, customer, order or product type)

I may have forgotten a few notes, but those are the big ones. It still needs more work to finish, but this is the direction I've headed and would love to discuss / get help on. I probably won't be able to work on this for at least another few months. If anyone would like to discuss, please find me on #commerce in drupal.slack.com or in the issue queue here.

olafkarsten’s picture

I'm a little sad about this, as the pr is closed. I somehow think the PR introduces some interesting ideas. But I understand, that it is difficult in a limited time to review a half baked pr with nearly no comment why all that stuff (59 files!) should be changed. I guess it addresses somewhat more than the possibilities to filter stock locations ;)

Maybe @steveoliver can help us a little bit und explain the decisions he made. I would like to get @guy_schneerson on board, before investing more time.

olafkarsten’s picture

Category: Task » Plan
Status: Needs work » Postponed
Issue tags: +2.x

lets keep the idea.