Skip to content

Commit

Permalink
Initial working spares calculation for items updates #417
Browse files Browse the repository at this point in the history
  • Loading branch information
joelvdavies committed Dec 5, 2024
1 parent 1bebf69 commit 6de588c
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 21 deletions.
89 changes: 84 additions & 5 deletions inventory_management_system_api/services/item.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

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 (
DatabaseIntegrityError,
InvalidActionError,
Expand All @@ -16,9 +18,11 @@
)
from inventory_management_system_api.models.catalogue_item import PropertyOut
from inventory_management_system_api.models.item import ItemIn, ItemOut
from inventory_management_system_api.models.setting import SparesDefinitionOut
from inventory_management_system_api.repositories.catalogue_category import CatalogueCategoryRepo
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.system import SystemRepo
from inventory_management_system_api.repositories.usage_status import UsageStatusRepo
from inventory_management_system_api.schemas.catalogue_item import PropertyPostSchema
Expand All @@ -42,6 +46,7 @@ def __init__(
catalogue_item_repository: Annotated[CatalogueItemRepo, Depends(CatalogueItemRepo)],
system_repository: Annotated[SystemRepo, Depends(SystemRepo)],
usage_status_repository: Annotated[UsageStatusRepo, Depends(UsageStatusRepo)],
setting_repository: Annotated[SettingRepo, Depends(SettingRepo)],
) -> None:
"""
Initialise the `ItemService` with an `ItemRepo`, `CatalogueCategoryRepo`,
Expand All @@ -52,12 +57,14 @@ def __init__(
:param catalogue_item_repository: The `CatalogueItemRepo` repository to use.
:param system_repository: The `SystemRepo` repository to use.
:param usage_status_repository: The `UsageStatusRepo` repository to use.
:param setting_repository: The `SettingRepo` repository to use.
"""
self._item_repository = item_repository
self._catalogue_category_repository = catalogue_category_repository
self._catalogue_item_repository = catalogue_item_repository
self._system_repository = system_repository
self._usage_status_repository = usage_status_repository
self._setting_repository = setting_repository

def create(self, item: ItemPostSchema) -> ItemOut:
"""
Expand Down Expand Up @@ -94,9 +101,34 @@ def create(self, item: ItemPostSchema) -> ItemOut:
defined_properties = catalogue_category.properties
properties = utils.process_properties(defined_properties, supplied_properties)

return self._item_repository.create(
ItemIn(**{**item.model_dump(), "properties": properties, "usage_status": usage_status.value})
)
# Need to recalculate number of spares after adding these need to either succeed or fail together. Also need
# to be able to write lock the catalogue item document in the process to prevent further changes while
# recalculating
with start_session_transaction("adding item") as session:
# Write lock the catalogue item to prevent any further item updates for it until the transaction
# completes
# TODO: Change to just str in the prepare_for_number_of_spares_update? Same below
utils.prepare_for_number_of_spares_update(
CustomObjectId(catalogue_item_id), self._catalogue_item_repository, session
)

new_item = self._item_repository.create(
ItemIn(**{**item.model_dump(), "properties": properties, "usage_status": usage_status.value}),
session=session,
)

# Obtain the current spares definition
spares_definition = self._setting_repository.get(SparesDefinitionOut, session=session)
if spares_definition:
utils.perform_number_of_spares_update(
CustomObjectId(catalogue_item_id),
utils.get_usage_status_ids(spares_definition),
self._catalogue_item_repository,
self._item_repository,
session=session,
)

return new_item

def get(self, item_id: str) -> Optional[ItemOut]:
"""
Expand Down Expand Up @@ -139,12 +171,16 @@ def update(self, item_id: str, item: ItemPatchSchema) -> ItemOut:

if "catalogue_item_id" in update_data and item.catalogue_item_id != stored_item.catalogue_item_id:
raise InvalidActionError("Cannot change the catalogue item the item belongs to")
catalogue_item_id = CustomObjectId(stored_item.catalogue_item_id)

if "system_id" in update_data and item.system_id != stored_item.system_id:
system = self._system_repository.get(item.system_id)
if not system:
raise MissingRecordError(f"No system found with ID: {item.system_id}")

updating_usage_status = False
if "usage_status_id" in update_data and item.usage_status_id != stored_item.usage_status_id:
updating_usage_status = True
usage_status_id = item.usage_status_id
usage_status = self._usage_status_repository.get(usage_status_id)
if not usage_status:
Expand Down Expand Up @@ -173,15 +209,58 @@ def update(self, item_id: str, item: ItemPatchSchema) -> ItemOut:

update_data["properties"] = utils.process_properties(defined_properties, supplied_properties)

return self._item_repository.update(item_id, ItemIn(**{**stored_item.model_dump(), **update_data}))
# TODO: Comment and cleanup
if not updating_usage_status:
return self._item_repository.update(item_id, ItemIn(**{**stored_item.model_dump(), **update_data}))
else:
with start_session_transaction("updating item") as session:
utils.prepare_for_number_of_spares_update(catalogue_item_id, self._catalogue_item_repository, session)

new_item = self._item_repository.update(
item_id, ItemIn(**{**stored_item.model_dump(), **update_data}), session=session
)

# Obtain the current spares definition
spares_definition = self._setting_repository.get(SparesDefinitionOut, session=session)
if spares_definition:
utils.perform_number_of_spares_update(
catalogue_item_id,
utils.get_usage_status_ids(spares_definition),
self._catalogue_item_repository,
self._item_repository,
session=session,
)
return new_item

def delete(self, item_id: str) -> None:
"""
Delete an item by its ID.
:param item_id: The ID of the item to delete.
"""
return self._item_repository.delete(item_id)
# TODO: Update comment and tests for raising error instead of repo delete
item = self.get(item_id)
if item is None:
raise MissingRecordError(f"No item found with ID: {str(item_id)}")

# TODO: Comment below
with start_session_transaction("deleting item") as session:
catalogue_item_id = CustomObjectId(item.catalogue_item_id)

utils.prepare_for_number_of_spares_update(catalogue_item_id, self._catalogue_item_repository, session)

self._item_repository.delete(item_id, session=session)

# Obtain the current spares definition
spares_definition = self._setting_repository.get(SparesDefinitionOut, session=session)
if spares_definition:
utils.perform_number_of_spares_update(
catalogue_item_id,
utils.get_usage_status_ids(spares_definition),
self._catalogue_item_repository,
self._item_repository,
session=session,
)

def _merge_missing_properties(
self, properties: List[PropertyOut], supplied_properties: List[PropertyPostSchema]
Expand Down
26 changes: 10 additions & 16 deletions inventory_management_system_api/services/setting.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
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
from inventory_management_system_api.services import utils

logger = logging.getLogger()

Expand Down Expand Up @@ -60,10 +61,11 @@ def update_spares_definition(self, spares_definition: SparesDefinitionPutSchema)
raise MissingRecordError(f"No usage status found with ID: {usage_status.id}")

# 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
# Also need to be able to write lock the catalogue item documents in the process to prevent further changes
# while recalculating.
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(
new_spares_definition = self._setting_repository.upsert(
SparesDefinitionIn(**spares_definition.model_dump()), SparesDefinitionOut, session=session
)

Expand All @@ -72,21 +74,13 @@ def update_spares_definition(self, spares_definition: SparesDefinitionPutSchema)

# 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]
usage_status_ids = utils.get_usage_status_ids(new_spares_definition)

# Recalculate for each catalogue item
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
utils.prepare_for_number_of_spares_update(catalogue_item_id, self._catalogue_item_repository, session)
utils.perform_number_of_spares_update(
catalogue_item_id, usage_status_ids, self._catalogue_item_repository, self._item_repository, session
)

return new_spares_definition_out
return new_spares_definition
39 changes: 39 additions & 0 deletions inventory_management_system_api/services/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
import re
from typing import Dict, List, Union

from bson import ObjectId
from pymongo.client_session import ClientSession

from inventory_management_system_api.core.custom_object_id import CustomObjectId
from inventory_management_system_api.core.exceptions import (
DuplicateCatalogueCategoryPropertyNameError,
InvalidPropertyTypeError,
MissingMandatoryProperty,
)
from inventory_management_system_api.models.catalogue_category import CatalogueCategoryPropertyOut
from inventory_management_system_api.models.setting import 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.schemas.catalogue_category import CatalogueCategoryPostPropertySchema
from inventory_management_system_api.schemas.catalogue_item import PropertyPostSchema

Expand Down Expand Up @@ -241,3 +248,35 @@ def _merge_non_mandatory_properties(
elif not defined_property["mandatory"]:
properties[defined_property_id] = {"id": defined_property_id, "value": None}
return properties


def get_usage_status_ids(spares_definition: SparesDefinitionOut) -> list[CustomObjectId]:
# TODO: Comment

return [CustomObjectId(usage_status.id) for usage_status in spares_definition.usage_statuses]


def prepare_for_number_of_spares_update(
catalogue_item_id: ObjectId, catalogue_item_repository: CatalogueItemRepo, session: ClientSession
) -> None:
# TODO: Comment

catalogue_item_repository.update_number_of_spares(catalogue_item_id, None, session=session)


def perform_number_of_spares_update(
catalogue_item_id: ObjectId,
usage_status_ids: list[CustomObjectId],
catalogue_item_repository: CatalogueItemRepo,
item_repository: ItemRepo,
session: ClientSession,
) -> None:
# TODO: Comment - include must use prepare first

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

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

0 comments on commit 6de588c

Please sign in to comment.