Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delete manufacturer by id #64 #99

Merged
merged 11 commits into from
Nov 1, 2023
4 changes: 4 additions & 0 deletions inventory_management_system_api/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ class ChildrenElementsExistError(DatabaseError):
"""


class PartOfCatalogueItemError(DatabaseError):
"""
Exception raised when attempting to delete a manufacturer that is a part of a catalogue item
"""
class DatabaseIntegrityError(DatabaseError):
"""
Exception raised when something is found in the database that shouldn't have been
Expand Down
22 changes: 11 additions & 11 deletions inventory_management_system_api/models/catalogue_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,11 @@
"""
from typing import Optional, List, Any

from pydantic import BaseModel, Field, validator
from pydantic import BaseModel, Field, HttpUrl, validator

from inventory_management_system_api.models.custom_object_id_data_types import CustomObjectIdField, StringObjectIdField


class Manufacturer(BaseModel):
"""
Model representing a catalogue item manufacturer.
"""

name: str
address: str
web_url: str


class Property(BaseModel):
"""
Model representing a catalogue item property.
Expand All @@ -28,6 +18,14 @@ class Property(BaseModel):
unit: Optional[str] = None


class Manufacturer(BaseModel):
"""Input database model for a manufacturer"""

name: str
url: HttpUrl
address: str


class CatalogueItemIn(BaseModel):
"""
Input database model for a catalogue item.
Expand All @@ -37,6 +35,8 @@ class CatalogueItemIn(BaseModel):
name: str
description: str
properties: List[Property] = []
# pylint: disable=fixme
# TODO - Change from manufacturer to manufacturer id
manufacturer: Manufacturer

@validator("properties", pre=True, always=True)
Expand Down
34 changes: 33 additions & 1 deletion inventory_management_system_api/repositories/manufacturer.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@

from inventory_management_system_api.core.custom_object_id import CustomObjectId
from inventory_management_system_api.core.database import get_database
from inventory_management_system_api.core.exceptions import DuplicateRecordError, MissingRecordError
from inventory_management_system_api.core.exceptions import (
DuplicateRecordError,
MissingRecordError,
PartOfCatalogueItemError,
)

from inventory_management_system_api.models.manufacturer import ManufacturerIn, ManufacturerOut

Expand All @@ -28,6 +32,7 @@ def __init__(self, database: Database = Depends(get_database)) -> None:

self._database = database
self._collection: Collection = self._database.manufacturer
self._catalogue_item_collection: Collection = self._database.catalogue_items

def create(self, manufacturer: ManufacturerIn) -> ManufacturerOut:
"""
Expand Down Expand Up @@ -104,6 +109,23 @@ def update(self, manufacturer_id: str, manufacturer: ManufacturerIn) -> Manufact
manufacturer = self.get(str(manufacturer_id))
return manufacturer

def delete(self, manufacturer_id: str) -> None:
"""
Delete a manufacturer by its ID from MongoDB database.
Checks if manufactuer is a part of an item, and does not delete if it is

:param manufacturer_id: The ID of the manufacturer to delete
:raises ExistIn
"""
manufacturer_id = CustomObjectId(manufacturer_id)
if self.is_manufacturer_in_catalogue_item(str(manufacturer_id)):
raise PartOfCatalogueItemError("The specified manufacturer is a part of a Catalogue Item")

logger.info("Deleting manufacturer with ID %s from the database", manufacturer_id)
result = self._collection.delete_one({"_id": manufacturer_id})
if result.deleted_count == 0:
raise MissingRecordError(f"No manufacturer found with ID: {str(manufacturer_id)}")

def _is_duplicate_manufacturer(self, code: str) -> bool:
"""
Check if manufacturer with the same url already exists in the manufacturer collection
Expand All @@ -114,3 +136,13 @@ def _is_duplicate_manufacturer(self, code: str) -> bool:
logger.info("Checking if manufacturer with code '%s' already exists", code)
count = self._collection.count_documents({"code": code})
return count > 0

def is_manufacturer_in_catalogue_item(self, manufacturer_id: str) -> bool:
"""Checks to see if any of the documents in the database have a specific manufactuer id

:param manufacturer_id: The ID of the manufacturer that is looked for
:return Returns True if 1 or more documents have the manufacturer ID, false if none do
"""
manufacturer_id = CustomObjectId(manufacturer_id)
count = self._catalogue_item_collection.count_documents({"manufacturer_id": manufacturer_id})
return count > 0
24 changes: 24 additions & 0 deletions inventory_management_system_api/routers/v1/manufacturer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DuplicateRecordError,
InvalidObjectIdError,
MissingRecordError,
PartOfCatalogueItemError,
)

from inventory_management_system_api.schemas.manufacturer import (
Expand Down Expand Up @@ -110,3 +111,26 @@ def edit_manufacturer(
message = "A manufacturer with the same name has been found"
logger.exception(message)
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=message) from exc


@router.delete(
path="/{manufacturer_id}",
summary="Delete a manufacturer by its ID",
response_description="Manufacturer deleted successfully",
status_code=status.HTTP_204_NO_CONTENT,
)
def delete_manufacturer(manufacturer_id: str, manufacturer_service: ManufacturerService = Depends()) -> None:
# pylint: disable=missing-function-docstring
logger.info("Deleting manufacturer with ID: %s", manufacturer_id)
try:
manufacturer_service.delete(manufacturer_id)
except (MissingRecordError, InvalidObjectIdError) as exc:
logger.exception("The specified manufacturer does not exist")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="The specified manufacturer does not exist"
) from exc
except PartOfCatalogueItemError as exc:
logger.exception("The specified manufacturer is a part of a Catalogue Item")
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="The specified manufacturer is a part of a Catalogue Item"
) from exc
20 changes: 11 additions & 9 deletions inventory_management_system_api/schemas/catalogue_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@
from pydantic import BaseModel, Field, HttpUrl


class ManufacturerSchema(BaseModel):
"""
Schema model for a catalogue item manufacturer creation request.
"""
class ManufacturerPostRequestSchema(BaseModel):
"""Schema model for a manufacturer creation request"""

name: str = Field(description="The name of the manufacturer")
address: str = Field(description="The address of the manufacturer")
web_url: HttpUrl = Field(description="The website URL of the manufacturer")
name: str
url: HttpUrl
address: str


class PropertyPostRequestSchema(BaseModel):
Expand Down Expand Up @@ -44,7 +42,9 @@ class CatalogueItemPostRequestSchema(BaseModel):
name: str = Field(description="The name of the catalogue item")
description: str = Field(description="The catalogue item description")
properties: Optional[List[PropertyPostRequestSchema]] = Field(description="The catalogue item properties")
manufacturer: ManufacturerSchema = Field(description="The details of the manufacturer")
# pylint: disable=fixme
# TODO - Change from manufacturer to manufacturer id
manufacturer: ManufacturerPostRequestSchema = Field(description="The details of the manufacturer")


class CatalogueItemPatchRequestSchema(CatalogueItemPostRequestSchema):
Expand All @@ -57,7 +57,9 @@ class CatalogueItemPatchRequestSchema(CatalogueItemPostRequestSchema):
)
name: Optional[str] = Field(description="The name of the catalogue item")
description: Optional[str] = Field(description="The catalogue item description")
manufacturer: Optional[ManufacturerSchema] = Field(description="The details of the manufacturer")
# pylint: disable=fixme
# TODO - Change from manufacturer to manufacturer id
manufacturer: Optional[ManufacturerPostRequestSchema] = Field(description="The details of the manufacturer")


class CatalogueItemSchema(CatalogueItemPostRequestSchema):
Expand Down
15 changes: 13 additions & 2 deletions inventory_management_system_api/services/manufacturer.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from fastapi import Depends
from inventory_management_system_api.core.exceptions import MissingRecordError
from inventory_management_system_api.models.manufacturer import ManufacturerIn, ManufacturerOut

from inventory_management_system_api.repositories.manufacturer import ManufacturerRepo
from inventory_management_system_api.schemas.manufacturer import (
ManufacturerPatchRequstSchema,
Expand All @@ -21,7 +20,10 @@
class ManufacturerService:
"""Service for managing manufacturers"""

def __init__(self, manufacturer_repository: ManufacturerRepo = Depends(ManufacturerRepo)) -> None:
def __init__(
self,
manufacturer_repository: ManufacturerRepo = Depends(ManufacturerRepo),
) -> None:
"""
Initialise the manufacturer service with a ManufacturerRepo

Expand Down Expand Up @@ -91,3 +93,12 @@ def update(self, manufacturer_id: str, manufacturer: ManufacturerPatchRequstSche

logger.info(stored_manufacturer.address)
return self._manufacturer_repository.update(manufacturer_id, ManufacturerIn(**stored_manufacturer.dict()))

def delete(self, manufacturer_id: str) -> None:
"""
Delete a manufacturer by its ID

:param manufacturer_id: The ID of the manufacturer to delete

"""
return self._manufacturer_repository.delete(manufacturer_id)
11 changes: 6 additions & 5 deletions test/e2e/test_catalogue_category.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,9 +319,10 @@ def test_delete_catalogue_category_with_child_catalogue_items(test_client):
"manufacturer": {
"name": "Manufacturer A",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-a.co.uk",
"url": "https://www.manufacturer-a.co.uk",
},
}

test_client.post("/v1/catalogue-items", json=catalogue_item_post)

response = test_client.delete(f"/v1/catalogue-categories/{catalogue_category_id}")
Expand Down Expand Up @@ -574,7 +575,7 @@ def test_partial_update_catalogue_category_change_valid_when_has_child_catalogue
"manufacturer": {
"name": "Manufacturer A",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-a.co.uk",
"url": "https://www.manufacturer-a.co.uk",
},
}
test_client.post("/v1/catalogue-items", json=catalogue_item_post)
Expand Down Expand Up @@ -694,7 +695,7 @@ def test_partial_update_catalogue_category_change_from_leaf_to_non_leaf_has_chil
"manufacturer": {
"name": "Manufacturer A",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-a.co.uk",
"url": "https://www.manufacturer-a.co.uk",
},
}
test_client.post("/v1/catalogue-items", json=catalogue_item_post)
Expand Down Expand Up @@ -819,7 +820,7 @@ def test_partial_update_catalogue_category_change_parent_id_has_child_catalogue_
"manufacturer": {
"name": "Manufacturer A",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-a.co.uk",
"url": "https://www.manufacturer-a.co.uk",
},
}
test_client.post("/v1/catalogue-items", json=catalogue_item_post)
Expand Down Expand Up @@ -1010,7 +1011,7 @@ def test_partial_update_catalogue_category_change_catalogue_item_properties_has_
"manufacturer": {
"name": "Manufacturer A",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-a.co.uk",
"url": "https://www.manufacturer-a.co.uk",
},
}
test_client.post("/v1/catalogue-items", json=catalogue_item_post)
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/test_catalogue_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def get_catalogue_item_a_dict(catalogue_category_id: str) -> Dict:
"manufacturer": {
"name": "Manufacturer A",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-a.co.uk",
"url": "https://www.manufacturer-a.co.uk",
},
}

Expand All @@ -66,7 +66,7 @@ def get_catalogue_item_b_dict(catalogue_category_id: str) -> Dict:
"manufacturer": {
"name": "Manufacturer A",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-a.co.uk",
"url": "https://www.manufacturer-a.co.uk",
},
}

Expand Down Expand Up @@ -831,7 +831,7 @@ def test_partial_update_catalogue_item_change_manufacturer(test_client):
"manufacturer": {
"name": "Manufacturer B",
"address": "1 Address, City, Country, Postcode",
"web_url": "https://www.manufacturer-b.co.uk",
"url": "https://www.manufacturer-b.co.uk",
}
}
response = test_client.patch(f"/v1/catalogue-items/{response.json()['id']}", json=catalogue_item_patch)
Expand Down
Loading