Skip to content

Commit

Permalink
Initial spares calculation when updating definition #417
Browse files Browse the repository at this point in the history
  • Loading branch information
joelvdavies committed Nov 29, 2024
1 parent 65a738a commit 254baa9
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 7 deletions.
22 changes: 20 additions & 2 deletions inventory_management_system_api/repositories/catalogue_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,9 @@ def has_child_elements(self, catalogue_item_id: CustomObjectId, session: ClientS
item = self._items_collection.find_one({"catalogue_item_id": catalogue_item_id}, session=session)
return item is not None

def list_ids(self, catalogue_category_id: str, session: ClientSession = None) -> List[ObjectId]:
def list_ids(self, catalogue_category_id: Optional[str] = None, session: ClientSession = None) -> List[ObjectId]:
# TODO: Update description and tests
# TODO: Make sure where it is used elsewhere specifies catalogue_category_id =
"""
Retrieve a list of all catalogue item ids with a specific catalogue_category_id from a MongoDB
database. Performs a projection to only include _id. (Required for mass updates of properties
Expand All @@ -150,14 +152,17 @@ def list_ids(self, catalogue_category_id: str, session: ClientSession = None) ->
"""
logger.info(
"Finding the id's of all catalogue items within the catalogue category with ID '%s' in the database",
# TODO: Update this when catalogue_category_id is None?
catalogue_category_id,
)

# Using distinct has a 16MB limit
# https://stackoverflow.com/questions/29771192/how-do-i-get-a-list-of-just-the-objectids-using-pymongo
# For 100000 documents, using list comprehension takes about 0.85 seconds vs 0.50 seconds for distinct
return self._catalogue_items_collection.find(
{"catalogue_category_id": CustomObjectId(catalogue_category_id)}, {"_id": 1}, session=session
{"catalogue_category_id": CustomObjectId(catalogue_category_id)} if catalogue_category_id else {},
{"_id": 1},
session=session,
).distinct("_id")

def insert_property_to_all_matching(
Expand Down Expand Up @@ -211,3 +216,16 @@ def update_names_of_all_properties_with_id(
array_filters=[{"elem._id": CustomObjectId(property_id)}],
session=session,
)

def update_number_of_spares(
self,
catalogue_item_id: ObjectId,
number_of_spares: Optional[int],
session: Optional[ClientSession] = None,
):

self._catalogue_items_collection.update_many(
{"_id": catalogue_item_id},
{"$set": {"number_of_spares": number_of_spares}},
session=session,
)
13 changes: 13 additions & 0 deletions inventory_management_system_api/repositories/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,16 @@ def update_names_of_all_properties_with_id(
)

# pylint:enable=duplicate-code

def count_with_usage_statuses_ids_in(
self,
catalogue_item_id: ObjectId,
usage_status_ids: List[CustomObjectId],
session: Optional[ClientSession] = None,
):
# TODO: Comment/log
# TODO: Look at https://stackoverflow.com/questions/60237515/mongodb-returning-wrong-count

return self._items_collection.count_documents(
{"catalogue_item_id": catalogue_item_id, "usage_status_id": {"$in": usage_status_ids}}, session=session
)
4 changes: 3 additions & 1 deletion inventory_management_system_api/repositories/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ def get(self, out_model_type: Type[SettingOutBaseT], session: ClientSession = No
# The spares definition contains a list of usage statuses - use an aggregate query here to obtain
# the actual usage status entities instead of just their stored ID

result = list(self._settings_collection.aggregate(SPARES_DEFINITION_GET_AGGREGATION_PIPELINE))
result = list(
self._settings_collection.aggregate(SPARES_DEFINITION_GET_AGGREGATION_PIPELINE, session=session)
)
setting = result[0] if len(result) > 0 else None
else:
setting = self._settings_collection.find_one({"_id": out_model_type.SETTING_ID}, session=session)
Expand Down
46 changes: 42 additions & 4 deletions inventory_management_system_api/services/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@

from fastapi import Depends

from inventory_management_system_api.core.custom_object_id import CustomObjectId
from inventory_management_system_api.core.database import start_session_transaction
from inventory_management_system_api.core.exceptions import MissingRecordError
from inventory_management_system_api.models.setting import SparesDefinitionIn, SparesDefinitionOut
from inventory_management_system_api.repositories.catalogue_item import CatalogueItemRepo
from inventory_management_system_api.repositories.item import ItemRepo
from inventory_management_system_api.repositories.setting import SettingRepo
from inventory_management_system_api.repositories.usage_status import UsageStatusRepo
from inventory_management_system_api.schemas.setting import SparesDefinitionPutSchema
Expand All @@ -24,15 +28,21 @@ class SettingService:
def __init__(
self,
setting_repository: Annotated[SettingRepo, Depends(SettingRepo)],
catalogue_item_repository: Annotated[CatalogueItemRepo, Depends(CatalogueItemRepo)],
item_repository: Annotated[ItemRepo, Depends(ItemRepo)],
usage_status_repository: Annotated[UsageStatusRepo, Depends(UsageStatusRepo)],
) -> None:
"""
Initialise the `SettingService` with a `SettingRepo` repository.
:param setting_repository: `SettingRepo` repository to use.
:param usage_status_repository: `UsageStatusRepo` repository to use
:param catalogue_item_repository: `CatalogueItemRepo` repository to use.
:param item_repository: `ItemRepo` repository to use.
:param usage_status_repository: `UsageStatusRepo` repository to use.
"""
self._setting_repository = setting_repository
self._catalogue_item_repository = catalogue_item_repository
self._item_repository = item_repository
self._usage_status_repository = usage_status_repository

def set_spares_definition(self, spares_definition: SparesDefinitionPutSchema) -> SparesDefinitionOut:
Expand All @@ -49,6 +59,34 @@ def set_spares_definition(self, spares_definition: SparesDefinitionPutSchema) ->
if not self._usage_status_repository.get(usage_status.id):
raise MissingRecordError(f"No usage status found with ID: {usage_status.id}")

return self._setting_repository.upsert(
SparesDefinitionIn(**spares_definition.model_dump()), SparesDefinitionOut
)
# Need all updates to the number of spares to succeed or fail together with assigning the new definition
# Also need to be able to write lock documents in the process
with start_session_transaction("updating spares definition") as session:
# Update spares definition first to ensure write locked to prevent further updates while calculating below
new_spares_definition_out = self._setting_repository.upsert(
SparesDefinitionIn(**spares_definition.model_dump()), SparesDefinitionOut, session=session
)

# Obtain a list of all catalogue item ids that will need to be recalculated
catalogue_item_ids = self._catalogue_item_repository.list_ids()

# Usage status id that constitute a spare in the new definition (obtain it now to save processing
# repeatedly)
usage_status_ids = [CustomObjectId(usage_status.id) for usage_status in spares_definition.usage_statuses]

for catalogue_item_id in catalogue_item_ids:
# Write lock the catalogue item to prevent any further item updates for it until the transaction
# completes
self._catalogue_item_repository.update_number_of_spares(catalogue_item_id, None, session=session)

# Now calculate the new number of spares
new_number_of_spares = self._item_repository.count_with_usage_statuses_ids_in(
catalogue_item_id, usage_status_ids, session=session
)

# Finally update
self._catalogue_item_repository.update_number_of_spares(
catalogue_item_id, new_number_of_spares, session=session
)

return new_spares_definition_out

0 comments on commit 254baa9

Please sign in to comment.