diff --git a/Solve Task 2/cart.py b/Solve Task 2/cart.py new file mode 100644 index 00000000..61036def --- /dev/null +++ b/Solve Task 2/cart.py @@ -0,0 +1,99 @@ +from typing import Final +from crud import ( + read_product, + update_product, +) +from storage import ( + QUANTITY_INDEX, + NAME_INDEX, + PRICE_INDEX, + Product) + +CartLine = tuple[int, int] +LINE_PRODUCT_ID_INDEX: Final = 0 +LINE_QUANTITY_INDEX: Final = 1 + + +def find_cart_line(cart: list[CartLine], + product_id: int,) -> CartLine | None: + for line in cart: + if line[LINE_PRODUCT_ID_INDEX] == product_id: + return line + print(f"Product {product_id} is not in the cart/") + return None + + +def add_to_cart(storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int) -> CartLine | None: + product = read_product(storage, product_id) + if product is None: + return product + storage_quantity = product[QUANTITY_INDEX] + if storage_quantity < quantity: + print(f"Not enough stock for product {product_id}: " + f"{storage_quantity} available, " + f"{quantity} requested.") + return None + + new_field = ( + product[NAME_INDEX], + product[PRICE_INDEX], + storage_quantity - quantity + ) + update_product(storage, product_id, new_field) + + for index, line in enumerate(cart): + if line[LINE_PRODUCT_ID_INDEX] == product_id: + new_line: CartLine = ( + product_id, + line[LINE_QUANTITY_INDEX] + quantity + ) + cart[index] = new_line + return new_line + new_line: CartLine = (product_id, quantity) + cart.append(new_line) + return new_line + + +def remove_from_cart(storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int) -> CartLine | None: + cart_line = find_cart_line(cart, product_id) + product = read_product(storage, product_id) + if cart_line is None: + return cart_line + if cart_line[LINE_QUANTITY_INDEX] < quantity: + print(f"Cart holds only {cart_line[LINE_QUANTITY_INDEX]} " + f"unit(s) of product {product_id}, cannot remove {quantity}.") + return None + if product is None: + return None + new_quantity = product[QUANTITY_INDEX] + quantity + new_fields = ( + product[NAME_INDEX], + product[PRICE_INDEX], + new_quantity, + ) + update_product( + storage, + product_id, + new_fields, + ) + new_line: CartLine = ( + product_id, + cart_line[LINE_QUANTITY_INDEX] - quantity, + ) + new_line: CartLine = ( + product_id, + cart_line[LINE_QUANTITY_INDEX]-quantity + ) + if new_line[LINE_QUANTITY_INDEX] == 0: + cart.remove(cart_line) + return new_line + for index, line in enumerate(cart): + if line == cart_line: + cart[index] = new_line + return new_line diff --git a/Solve Task 2/crud.py b/Solve Task 2/crud.py new file mode 100644 index 00000000..6202f484 --- /dev/null +++ b/Solve Task 2/crud.py @@ -0,0 +1,65 @@ +from decimal import Decimal +from storage import ( + PRODUCT_ID_MIN, + PRODUCT_ID_INDEX, + NAME_INDEX, + Product, +) +from utils import normalize_price + + +def create_product(storage: list[Product], + fields: tuple[str, Decimal, int]) -> int | None: + product_name, product_price, product_quantity = fields + for product in storage: + if product_name == product[NAME_INDEX]: + print(f"Product name {product_name} is already taken.") + return None + product_id = generate_product_id(storage) + storage.append(( + product_id, + product_name, + normalize_price(product_price), + product_quantity + )) + return product_id + + +def generate_product_id(storage: list[Product]) -> int: + if not storage: + return PRODUCT_ID_MIN + MAX_INDEX = max(product[PRODUCT_ID_INDEX] for product in storage) + return MAX_INDEX + 1 + + +def read_product(storage: list[Product], product_id: int) -> Product | None: + for product in storage: + if product[PRODUCT_ID_INDEX] == product_id: + return product + print(f"No product with id {product_id}") + return None + + +def update_product( + storage: list[Product], + product_id: int, + fields: tuple[str, Decimal, int], +) -> Product | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + new_name, new_price, new_quantity = fields + norm_price = normalize_price(new_price) + NEW_PRODUCT = (product_id, new_name, norm_price, new_quantity) + storage[index] = NEW_PRODUCT + return NEW_PRODUCT + print(f"No product with id {product_id}") + return None + + +def delete_product(storage: list[Product], product_id: int) -> int | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + storage.pop(index) + return product_id + print(f"No product with id {product_id}.") + return None diff --git a/Solve Task 2/storage.py b/Solve Task 2/storage.py new file mode 100644 index 00000000..cdd56923 --- /dev/null +++ b/Solve Task 2/storage.py @@ -0,0 +1,11 @@ +from decimal import Decimal +from typing import Final + +Product = tuple[int, str, Decimal, int] + +PRODUCT_ID_INDEX: Final[int] = 0 +NAME_INDEX: Final[int] = 1 +PRICE_INDEX: Final[int] = 2 +QUANTITY_INDEX: Final[int] = 3 + +PRODUCT_ID_MIN: Final[int] = 1 diff --git a/Solve Task 2/utils.py b/Solve Task 2/utils.py new file mode 100644 index 00000000..7d9077bb --- /dev/null +++ b/Solve Task 2/utils.py @@ -0,0 +1,9 @@ +from typing import Final +from decimal import Decimal, ROUND_HALF_UP + +PRICE_PRECISION: Final[int] = 2 +PRICE_STEP: Final = Decimal(1).scaleb(-PRICE_PRECISION) + + +def normalize_price(price: Decimal) -> Decimal: + return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP) diff --git a/src/part2/cart.py b/src/part2/cart.py new file mode 100644 index 00000000..cbdaa539 --- /dev/null +++ b/src/part2/cart.py @@ -0,0 +1,129 @@ +from typing import Final + +from .crud import read_product, update_product +from .storage import ( + NAME_INDEX, + PRICE_INDEX, + QUANTITY_INDEX, + Product, +) + + +CartLine = tuple[int, int] + +LINE_PRODUCT_ID_INDEX: Final[int] = 0 +LINE_QUANTITY_INDEX: Final[int] = 1 + + +def find_cart_line( + cart: list[CartLine], + product_id: int, +) -> CartLine | None: + for line in cart: + if line[LINE_PRODUCT_ID_INDEX] == product_id: + return line + + print(f"Product {product_id} is not in the cart") + return None + + +def add_to_cart( + storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int, +) -> CartLine | None: + product = read_product(storage, product_id) + + if product is None: + return None + + storage_quantity = product[QUANTITY_INDEX] + + if storage_quantity < quantity: + print( + f"Not enough stock for product {product_id}: " + f"{storage_quantity} available, " + f"{quantity} requested." + ) + return None + + new_fields = ( + product[NAME_INDEX], + product[PRICE_INDEX], + storage_quantity - quantity, + ) + + update_product( + storage, + product_id, + new_fields, + ) + + for index, line in enumerate(cart): + if line[LINE_PRODUCT_ID_INDEX] == product_id: + new_line = ( + product_id, + line[LINE_QUANTITY_INDEX] + quantity, + ) + cart[index] = new_line + return new_line + + new_line = (product_id, quantity) + cart.append(new_line) + return new_line + + +def remove_from_cart( + storage: list[Product], + cart: list[CartLine], + product_id: int, + quantity: int, +) -> CartLine | None: + cart_line = find_cart_line(cart, product_id) + + if cart_line is None: + return None + + if cart_line[LINE_QUANTITY_INDEX] < quantity: + print( + f"Cart holds only {cart_line[LINE_QUANTITY_INDEX]} " + f"unit(s) of product {product_id}, " + f"cannot remove {quantity}." + ) + return None + + product = read_product(storage, product_id) + + if product is None: + return None + + new_quantity = product[QUANTITY_INDEX] + quantity + + new_fields = ( + product[NAME_INDEX], + product[PRICE_INDEX], + new_quantity, + ) + + update_product( + storage, + product_id, + new_fields, + ) + + new_line: CartLine = ( + product_id, + cart_line[LINE_QUANTITY_INDEX] - quantity, + ) + + if new_line[LINE_QUANTITY_INDEX] == 0: + cart.remove(cart_line) + return new_line + + for index, line in enumerate(cart): + if line == cart_line: + cart[index] = new_line + return new_line + + return None diff --git a/src/part2/crud.py b/src/part2/crud.py new file mode 100644 index 00000000..a37746cc --- /dev/null +++ b/src/part2/crud.py @@ -0,0 +1,91 @@ +from decimal import Decimal + +from .storage import ( + NAME_INDEX, + PRODUCT_ID_INDEX, + PRODUCT_ID_MIN, + Product, +) +from .utils import normalize_price + + +def create_product( + storage: list[Product], + fields: tuple[str, Decimal, int], +) -> int | None: + product_name, product_price, product_quantity = fields + + for product in storage: + if product_name == product[NAME_INDEX]: + print(f"Product name {product_name} is already taken.") + return None + + product_id = generate_product_id(storage) + + storage.append( + ( + product_id, + product_name, + normalize_price(product_price), + product_quantity, + ) + ) + + return product_id + + +def generate_product_id(storage: list[Product]) -> int: + if not storage: + return PRODUCT_ID_MIN + + max_id = max(product[PRODUCT_ID_INDEX] for product in storage) + + return max_id + 1 + + +def read_product( + storage: list[Product], + product_id: int, +) -> Product | None: + for product in storage: + if product[PRODUCT_ID_INDEX] == product_id: + return product + + print(f"No product with id {product_id}") + return None + + +def update_product( + storage: list[Product], + product_id: int, + fields: tuple[str, Decimal, int], +) -> Product | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + new_name, new_price, new_quantity = fields + + new_product: Product = ( + product_id, + new_name, + normalize_price(new_price), + new_quantity, + ) + + storage[index] = new_product + return new_product + + print(f"No product with id {product_id}") + return None + + +def delete_product( + storage: list[Product], + product_id: int, +) -> int | None: + for index, product in enumerate(storage): + if product[PRODUCT_ID_INDEX] == product_id: + storage.pop(index) + return product_id + + print(f"No product with id {product_id}.") + return None diff --git a/src/part2/storage.py b/src/part2/storage.py new file mode 100644 index 00000000..726ab823 --- /dev/null +++ b/src/part2/storage.py @@ -0,0 +1,12 @@ +from decimal import Decimal +from typing import Final + + +Product = tuple[int, str, Decimal, int] + +PRODUCT_ID_INDEX: Final = 0 +NAME_INDEX: Final = 1 +PRICE_INDEX: Final = 2 +QUANTITY_INDEX: Final = 3 + +PRODUCT_ID_MIN: Final[int] = 1 diff --git a/src/part2/utils.py b/src/part2/utils.py new file mode 100644 index 00000000..28fe897f --- /dev/null +++ b/src/part2/utils.py @@ -0,0 +1,10 @@ +from decimal import ROUND_HALF_UP, Decimal +from typing import Final + + +PRICE_PRECISION: Final[int] = 2 +PRICE_STEP: Final[Decimal] = Decimal(1).scaleb(-PRICE_PRECISION) + + +def normalize_price(price: Decimal) -> Decimal: + return price.quantize(PRICE_STEP, rounding=ROUND_HALF_UP)