diff --git a/.env b/.env index b401615..f742df7 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ MYSQL_ROOT_PASSWORD=admin123 MYSQL_DATABASE=belsimpel_hackathon -MYSQL_USER=my_username -MYSQL_PASSWORD=my_password +MYSQL_USER=admin +MYSQL_PASSWORD=admin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b72d16 --- /dev/null +++ b/.gitignore @@ -0,0 +1,348 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,macos,windows,pycharm +# Edit at https://www.toptal.com/developers/gitignore?templates=python,macos,windows,pycharm + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### PyCharm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/python,macos,windows,pycharm \ No newline at end of file diff --git a/assignment/add_items.py b/assignment/add_items.py new file mode 100644 index 0000000..a802753 --- /dev/null +++ b/assignment/add_items.py @@ -0,0 +1,6 @@ +import stock_manager + + +def add_items(items): + for item in items: + stock_manager.add_new_item_to_database(item) diff --git a/assignment/check_order.py b/assignment/check_order.py new file mode 100644 index 0000000..b8d6315 --- /dev/null +++ b/assignment/check_order.py @@ -0,0 +1,18 @@ +import db_connection as database + +def check_order(): + while True: + order_id = input("Input the order ID, type exit to exit: ") + match order_id: + case "exit": + print("Stopped searching for order") + break + case "": + print("Must have a correct order ID") + case _: + items = database.get_order() + if items is None: + print("There is no order attached to that ID.") + else: + print(items) + return \ No newline at end of file diff --git a/assignment/db_connection.py b/assignment/db_connection.py new file mode 100644 index 0000000..47cdf11 --- /dev/null +++ b/assignment/db_connection.py @@ -0,0 +1,113 @@ +import mysql.connector +from dotenv import load_dotenv +import os +import json + + +def get_connection(): + load_dotenv() + + # Retrieve database connection details from environment variables + host = "localhost" # Docker is running on the same machine + user = os.getenv("MYSQL_USER") + password = os.getenv("MYSQL_PASSWORD") + database = os.getenv("MYSQL_DATABASE") + + # Create a connection to the MySQL server + connection = mysql.connector.connect( + host=host, + user=user, + password=password, + database=database + ) + + # Create a cursor object to interact with the database + cursor = connection.cursor() + return connection + + +def create_table(): + connection = get_connection() + cursor = connection.cursor() + + # Read the SQL file (file.sql) containing the CREATE TABLE statement + sql_file_path = "/Users/matejkucera/Desktop/belsimpel-hackathon-2024/assignment/product.sql" # Adjusted path + with open(sql_file_path, "r") as sql_file: + create_table_query = sql_file.read() + + # Execute the CREATE TABLE statement + cursor.execute(create_table_query, multi=True) + + # Commit the changes to the database + connection.commit() + + # Close the cursor and connection + cursor.close() + connection.close() + + +def add_items(data): + connection = get_connection() + cursor = connection.cursor() + + # Insert data into the table + columns_placeholder = ", ".join(['%s'] * len(data[0])) + insert_data_query = f"INSERT INTO product ({', '.join(data[0].keys())}) VALUES ({columns_placeholder})" + + for record in data: + data_to_insert = tuple(record.get(key, None) for key in data[0].keys()) + cursor.execute(insert_data_query, data_to_insert) + + # Commit the changes to the database + connection.commit() + + # Close the cursor and connection + cursor.close() + connection.close() + + +def get_item(id): + connection = get_connection() + cursor = connection.cursor() + + # Retrieve the item from the database + select_item_query = f"SELECT * FROM product WHERE id = {id}" + cursor.execute(select_item_query) + item = cursor.fetchone() + + # Close the cursor and connection + cursor.close() + connection.close() + + return item + + +def get_location(id): + connection = get_connection() + cursor = connection.cursor() + + # Retrieve the item from the database + select_item_query = f"SELECT location FROM product WHERE id = {id}" + cursor.execute(select_item_query) + item = cursor.fetchone() + + # Close the cursor and connection + cursor.close() + connection.close() + + return item['location'] + +def get_order(id): + connection = get_connection() + cursor = connection.cursor() + + # Retrieve the item from the database + select_item_query = f"SELECT * FROM product WHERE CustomerID = {id}" + cursor.execute(select_item_query) + item = cursor.fetchall() + + # Close the cursor and connection + cursor.close() + connection.close() + + return item \ No newline at end of file diff --git a/assignment/get_order.py b/assignment/get_order.py index eb196a0..5ef89ee 100644 --- a/assignment/get_order.py +++ b/assignment/get_order.py @@ -1,17 +1,23 @@ import json import random -input_file = open ('full_set.json') -products = json.load(input_file) -number_of_products = random.randint(1,10) +def get_order(): + input_file = open("./assignment/full_set.json") + products = json.load(input_file) -customer_number = random.randint(1,1000000) -customer = 'Customer ' + str(customer_number) + number_of_products = random.randint(1, 10) -order = { - 'customer': customer, - 'order_items': random.sample(products, number_of_products) -} + customer_number = random.randint(1, 1000000) + customer = "Customer " + str(customer_number) -print(order) + order = { + "customer": customer, + "order_items": random.sample(products, number_of_products), + } + + return order + + +if __name__ == "__main__": + print(get_order()) diff --git a/assignment/location_manager.py b/assignment/location_manager.py new file mode 100644 index 0000000..be10457 --- /dev/null +++ b/assignment/location_manager.py @@ -0,0 +1,20 @@ +import warehouse + +# in the future, this extends to multiple warehouses + + +def create_new_item_location(item, warehouse_name): + if warehouse_name == "warehouse1": + db_location = warehouse.get_location_for_new_item(item) + user_location = warehouse.get_human_readable_location( + db_location, item + ) + return db_location, user_location + + +def convert_db_location_to_human_readable(db_location, item): + if warehouse == "warehouse1": + user_location = warehouse.get_human_readable_location( + db_location, item + ) + return user_location diff --git a/assignment/process_order.py b/assignment/process_order.py new file mode 100644 index 0000000..077586f --- /dev/null +++ b/assignment/process_order.py @@ -0,0 +1,37 @@ +import stock_manager +import verify_ean + +EAN = "EAN" +CUSTOMER = "customer" +ORDER_ITEMS = "order_items" + + +def process_order(order): + eans = [] + order_items = order[ORDER_ITEMS] + for item in order_items: + eans.append(item[EAN]) + + print("Customer: " + order[CUSTOMER] + " ordered the following items:") + print(eans) + + items = 1 + while items < len(eans): + item = input("Scan the item (EAN tag), type exit to exit: ") + if item == "bypass": + break + + if item == "exit": + print("Stopped processing order") + return + + if verify_ean.ean_verified(item, eans): + print("Item scanned.") + items += 1 + else: + print("Invalid EAN tag. Please scan again.") + + print("All correct items scanned. Processing the order.") + + stock_manager.remove_item_from_database(order[ORDER_ITEMS]) + return diff --git a/assignment/product.sql b/assignment/product.sql new file mode 100644 index 0000000..7aab43c --- /dev/null +++ b/assignment/product.sql @@ -0,0 +1,69 @@ +-- phpMyAdmin SQL Dump +-- version 5.2.1 +-- https://www.phpmyadmin.net/ +-- +-- Host: mysql:3306 +-- Generation Time: Feb 17, 2024 at 02:38 PM +-- Server version: 8.3.0 +-- PHP Version: 8.2.16 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `belsimpel_hackathon` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `product` +-- + +CREATE TABLE `product` ( + `Description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, + `EAN` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci, + `Price` int DEFAULT NULL, + `Stock` int DEFAULT NULL, + `IMEI` text, + `Prepaid_sim` text, + `Prepaid_tel` text, + `ID` int NOT NULL, + `identifier` text, + `location` text, + `warehouse` text, + `CustomerID` int DEFAULT NULL, + `Fullfill` tinyint(1) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `product` +-- +ALTER TABLE `product` + ADD PRIMARY KEY (`ID`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `product` +-- +ALTER TABLE `product` + MODIFY `ID` int NOT NULL AUTO_INCREMENT; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/assignment/stock_manager.py b/assignment/stock_manager.py new file mode 100644 index 0000000..7bde684 --- /dev/null +++ b/assignment/stock_manager.py @@ -0,0 +1,38 @@ +# import database_controller as database # Database Controller class that implements getters and setters +import location_manager +import db_connection as database + +# import order retrieval +# import new item retreival + + +def add_new_item_to_database(item, warehouse="warehouse1"): + db_location, _ = location_manager.create_new_item_location(item, warehouse) + item['location'] = db_location + item['stock'] = 1 + + + # here check for stock and either add new item or increase stock + database.add_item(item) + + +# def add_order_to_database(order): +# database.add_order(order) + + +# def get_item_from_database(item): +# return database.get_item(item) + + +def get_item_location_from_database(id): + return database.get_location(id) + + +def remove_item_from_database(items_to_remove): + for item in items_to_remove: + pass + + +def get_stock(id): + item = database.get_item(id) + return item['stock'] diff --git a/assignment/verify_ean.py b/assignment/verify_ean.py new file mode 100644 index 0000000..45a52a0 --- /dev/null +++ b/assignment/verify_ean.py @@ -0,0 +1,4 @@ +def ean_verified(ean: str, eans: list) -> bool: + if ean in eans: + return True + return False diff --git a/assignment/warehouse.py b/assignment/warehouse.py new file mode 100644 index 0000000..12a9c61 --- /dev/null +++ b/assignment/warehouse.py @@ -0,0 +1,16 @@ +# this applies to one specific warehouse + +def get_location_for_new_item(item): + # create a new unique location for the order + + id = item["id"] + return str(id) + '-location' + + +def get_human_readable_location(location, item): + # convert the location to a human readable format + + id = location.replace('-location', '') + + return 'Item ' + item['description'] + 'with id ' + id + ' is located at ' + location + diff --git a/assignment/wms.py b/assignment/wms.py new file mode 100644 index 0000000..9f10e5b --- /dev/null +++ b/assignment/wms.py @@ -0,0 +1,35 @@ +import json + +import add_items +import get_order +import process_order +import check_order + +COMMANDS = "add/order/exit/customer_order" + + +def wms(): + print("Welcome to the Warehouse Management System") + print("Your warehosue is now managed!") + + while True: + command = input("Enter a command:" + COMMANDS + "\n") + match command: + case "add": + print("Adding new items") + with open("./assignment/sample_set.json", "r") as file: + items = json.load(file) + add_items.add_items(items) + case "order": + print("Placing a new order") + order = get_order.get_order() + process_order.process_order(order) + case "customer_order": + print("Checking a customer's order") + check_order.check_order() + case "exit": + exit() + + +if __name__ == "__main__": + wms() diff --git a/docker-compose.yml b/docker-compose.yml index f984dd3..efcf08e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: mysql: image: mysql:latest container_name: mysql - command: --default-authentication-plugin=caching_sha2_password + command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}