diff --git a/streamlit_app/pages/create_stock.py b/streamlit_app/pages/create_stock.py new file mode 100644 index 0000000..1281455 --- /dev/null +++ b/streamlit_app/pages/create_stock.py @@ -0,0 +1,63 @@ +from datetime import datetime + +import requests +import streamlit as st + + +def create_stock_item(payload): + try: + # Post data to FastAPI endpoint + response = requests.post("http://fastapi:8000/inventory", json=payload) + response.raise_for_status() + return response.json(), None + except requests.exceptions.RequestException as e: + return None, str(e) + + +def add_stock_page(): + st.title("Add Stock Item") + + # Define the form for stock creation + with st.form(key="add_stock_form"): + st.header("Stock Information") + + # Input fields for stock data + ingredient_id = st.number_input("Ingredient ID", min_value=1, step=1) + stock_quantity = st.number_input( + "Quantity", min_value=0.0, format="%.2f" + ) # Float with 2 decimal places + stock_unit = st.selectbox( + "Unit", ["liter", "deciliter", "centiliter", "milliliter"] + ) # Example units + cost = st.number_input( + "Cost per Unit", min_value=0.0, format="%.2f" + ) # Float with 2 decimal places + delivery_date = st.date_input( + "Delivery Date", min_value=datetime(2000, 1, 1) + ) # Date input + + # Create the stock data payload + payload = { + "stock": { + "ingredient_id": ingredient_id, + "unit": stock_unit, + "quantity": stock_quantity, + "cost": cost, + "delivery_date": delivery_date.isoformat(), + # Convert date to ISO format string + } + } + + # Submit button + submit_button = st.form_submit_button(label="Add Stock") + + if submit_button: + result, error = create_stock_item(payload) + if error: + st.error(f"Failed to add stock: {error}") + else: + st.success(f"Stock item added successfully: {result}") + + +if __name__ == "__main__": + add_stock_page() diff --git a/streamlit_app/pages/ingredients.py b/streamlit_app/pages/ingredients.py new file mode 100644 index 0000000..7829833 --- /dev/null +++ b/streamlit_app/pages/ingredients.py @@ -0,0 +1,41 @@ +import pandas as pd +import requests +import streamlit as st + + +def display_ingredient_items(): + st.title("Ingredient Items Report") + + ingredient_id = st.number_input("Enter Ingredient ID:", min_value=1, step=1) + + if st.button("Get Ingredient"): + try: + # Fetch ingredient data from the FastAPI endpoint + response = requests.get( + f"http://fastapi:8000/inventory/ingredient/{ingredient_id}" + ) + response.raise_for_status() + data = response.json() + + # Extract items from the response data + ingredient_items = data.get("items", []) + df_ingredient = pd.DataFrame(ingredient_items) + + # Check if the DataFrame is empty + if df_ingredient.empty: + st.write(f"No data found for Ingredient ID {ingredient_id}.") + else: + # Ensure 'delivery_date' is treated as a datetime object for sorting + df_ingredient["delivery_date"] = pd.to_datetime( + df_ingredient["delivery_date"] + ) + # Sort the DataFrame by 'delivery_date' + df_ingredient_sorted = df_ingredient.sort_values(by="delivery_date") + st.table(df_ingredient_sorted) + + except requests.exceptions.RequestException as e: + st.write("Failed to connect to FastAPI:", e) + + +if __name__ == "__main__": + display_ingredient_items() diff --git a/streamlit_app/pages/report_stock.py b/streamlit_app/pages/report_stock.py new file mode 100644 index 0000000..98bb56e --- /dev/null +++ b/streamlit_app/pages/report_stock.py @@ -0,0 +1,30 @@ +import pandas as pd +import requests +import streamlit as st + + +def display_stock_items(): + st.title("Stock Items Report") + + try: + # Fetch stock data from the FastAPI endpoint + response = requests.get("http://fastapi:8000/inventory/") + response.raise_for_status() + data = response.json() + + # Extract items from the response data + stock_items = data.get("items", []) + df_stock = pd.DataFrame(stock_items) + + # Check if the DataFrame is empty + if df_stock.empty: + st.write("No stock data available.") + else: + st.table(df_stock) + + except requests.exceptions.RequestException as e: + st.write("Failed to connect to FastAPI:", e) + + +if __name__ == "__main__": + display_stock_items() diff --git a/weird_salads/api/app.py b/weird_salads/api/app.py index a4313b3..dedfe48 100644 --- a/weird_salads/api/app.py +++ b/weird_salads/api/app.py @@ -3,13 +3,20 @@ from weird_salads.api.schemas import ( CreateOrderSchema, + CreateStockSchema, GetMenuItemAvailabilitySchema, GetMenuItemSchema, GetOrderSchema, GetOrdersSchema, GetSimpleMenuSchema, + GetStockItemSchema, + GetStockSchema, +) +from weird_salads.inventory.inventory_service.exceptions import ( + IngredientNotFoundError, + MenuItemNotFoundError, + StockItemNotFoundError, ) -from weird_salads.inventory.inventory_service.exceptions import MenuItemNotFoundError from weird_salads.inventory.inventory_service.inventory_service import MenuService from weird_salads.inventory.repository.inventory_repository import MenuRepository from weird_salads.orders.orders_service.orders_service import OrdersService @@ -61,6 +68,76 @@ def get_availability(item_id: int): ) +@app.get("/inventory", response_model=GetStockSchema, tags=["Inventory"]) +def get_stock(): + with UnitOfWork() as unit_of_work: + repo = MenuRepository(unit_of_work.session) + inventory_service = MenuService(repo) + results = inventory_service.list_stock() + return {"items": [result.dict() for result in results]} + + +@app.get( + "/inventory/stock/{stock_id}", response_model=GetStockItemSchema, tags=["Inventory"] +) +def get_stock_item(stock_id: str): + try: + with UnitOfWork() as unit_of_work: + repo = MenuRepository(unit_of_work.session) + inventory_service = MenuService(repo) + order = inventory_service.get_stock_item(stock_id=stock_id) + return order + except StockItemNotFoundError: + raise HTTPException( + status_code=404, detail=f"Stock Item with ID {stock_id} not found" + ) + + +@app.get( + "/inventory/ingredient/{ingredient_id}", + response_model=GetStockSchema, + tags=["Inventory"], +) +def get_ingredient(ingredient_id: int): + try: + with UnitOfWork() as unit_of_work: + repo = MenuRepository(unit_of_work.session) + inventory_service = MenuService(repo) + ingredient = inventory_service.get_ingredient(ingredient_id=ingredient_id) + return {"items": [record.dict() for record in ingredient]} + except IngredientNotFoundError: + raise HTTPException( + status_code=404, detail=f"Ingredient Item with ID {ingredient_id} not found" + ) + + +@app.post( + "/inventory", + status_code=status.HTTP_201_CREATED, + response_model=GetStockItemSchema, + tags=["Inventory"], +) +def create_stock(payload: CreateStockSchema): + with UnitOfWork() as unit_of_work: + repo = MenuRepository(unit_of_work.session) + stock_service = MenuService(repo) + stock_item = payload.model_dump()["stock"] + stock_item["unit"] = stock_item["unit"].value # necessary? + + # Check if the ingredient exists + ingredient = stock_service.get_ingredient(stock_item["ingredient_id"]) + if ingredient is None: + raise HTTPException( + status_code=404, + detail=f"Ingredient with ID {stock_item['ingredient_id']} not found", + ) + + order = stock_service.ingest_stock(stock_item) + unit_of_work.commit() + return_payload = order.dict() + return return_payload + + # Orders @app.get( "/order", diff --git a/weird_salads/api/schemas.py b/weird_salads/api/schemas.py index 821529e..13b8f91 100644 --- a/weird_salads/api/schemas.py +++ b/weird_salads/api/schemas.py @@ -151,3 +151,31 @@ class GetOrdersSchema(BaseModel): class Config: extra = "forbid" + + +class StockSchema(BaseModel): + ingredient_id: int + unit: UnitOfMeasure + quantity: Annotated[float, Field(ge=0.0, strict=True)] + cost: Annotated[float, Field(ge=0.0, strict=True)] + # expiry_date: datetime + delivery_date: Optional[datetime] = datetime.now(timezone.utc) + created_on: Optional[datetime] = datetime.now(timezone.utc) + + class Config: + extra = "forbid" + + +class GetStockItemSchema(StockSchema): + id: str + + +class GetStockSchema(BaseModel): + items: List[GetStockItemSchema] + + +class CreateStockSchema(BaseModel): + stock: StockSchema + + class Config: + extra = "forbid" diff --git a/weird_salads/inventory/inventory_service/exceptions.py b/weird_salads/inventory/inventory_service/exceptions.py index baafa43..92d8d84 100644 --- a/weird_salads/inventory/inventory_service/exceptions.py +++ b/weird_salads/inventory/inventory_service/exceptions.py @@ -4,3 +4,11 @@ class MenuItemNotFoundError(Exception): class UnitConversionError(Exception): pass + + +class StockItemNotFoundError(Exception): + pass + + +class IngredientNotFoundError(Exception): + pass diff --git a/weird_salads/inventory/inventory_service/inventory_service.py b/weird_salads/inventory/inventory_service/inventory_service.py index aded268..8903422 100644 --- a/weird_salads/inventory/inventory_service/inventory_service.py +++ b/weird_salads/inventory/inventory_service/inventory_service.py @@ -5,7 +5,11 @@ from typing import Any, Dict, List from weird_salads.api.schemas import UnitOfMeasure -from weird_salads.inventory.inventory_service.exceptions import MenuItemNotFoundError +from weird_salads.inventory.inventory_service.exceptions import ( + IngredientNotFoundError, + MenuItemNotFoundError, + StockItemNotFoundError, +) from weird_salads.inventory.inventory_service.inventory import ( MenuItem, MenuItemIngredient, @@ -133,19 +137,19 @@ def get_recipe_item_availability(self, item_id: int) -> Dict[str, Any]: # ---- ingredient_id queries def get_ingredient(self, ingredient_id: int): ingredient_item = self.menu_repository.get_ingredient(ingredient_id) - if ingredient_item is not None: + if ingredient_item: return ingredient_item - raise ValueError(f"items with id {ingredient_id} not found") # fix + raise IngredientNotFoundError(f"items with id {ingredient_id} not found") # fix def ingest_stock(self, item): - return self.stock_repository.add_stock(item) + return self.menu_repository.add_stock(item) # ---- stock_id queries def get_stock_item(self, stock_id: str): stock_item = self.menu_repository.get_stock(stock_id) if stock_item is not None: return stock_item - raise ValueError(f"stock with id {stock_id} not found") # fix + raise StockItemNotFoundError(f"stock with id {stock_id} not found") # fix def list_stock(self): # needs options for filtering return self.menu_repository.list_stock() diff --git a/weird_salads/inventory/repository/inventory_repository.py b/weird_salads/inventory/repository/inventory_repository.py index de5c3da..83fb992 100644 --- a/weird_salads/inventory/repository/inventory_repository.py +++ b/weird_salads/inventory/repository/inventory_repository.py @@ -106,18 +106,18 @@ def _get_ingredient(self, id: int): def get_ingredient(self, id: int) -> List[StockItem]: ingredients = self._get_ingredient(id) - if ingredients is not None: + if ingredients: # is not None: return [StockItem(**ingredient.dict()) for ingredient in ingredients] def _get_stock(self, id: str): return self.session.query(StockModel).filter(StockModel.id == id).first() - def get_stock(self, id_: str): - order = self._get(id) + def get_stock(self, id: str): + order = self._get_stock(id) if order is not None: return StockItem(**order.dict()) - def list_stock(self, limit=None): + def list_stock(self, limit=None): # need to implement limits query = self.session.query(StockModel) records = query.all() return [StockItem(**record.dict()) for record in records]