From 7a60b4b152eea4492e034252603401872b896655 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 05:40:43 +0100 Subject: [PATCH 01/12] add final project --- submissions/Ali-Salad/final_project/README.md | 75 ++++ .../Ali-Salad/final_project/data/products.txt | 2 + .../Ali-Salad/final_project/data/staff.txt | 2 + submissions/Ali-Salad/final_project/main.py | 333 ++++++++++++++++++ .../__pycache__/inventory.cpython-314.pyc | Bin 0 -> 8184 bytes .../models/__pycache__/staff.cpython-314.pyc | Bin 0 -> 5703 bytes .../final_project/models/inventory.py | 93 +++++ .../Ali-Salad/final_project/models/staff.py | 68 ++++ .../python-for-everyone-bootcamp/README.md | 22 ++ .../python-for-everyone-bootcamp/music.txt | 1 + .../python-for-everyone-bootcamp/README.md | 22 ++ .../python-for-everyone-bootcamp/music.txt | 1 + .../submissions/README.md | 52 +++ .../submissions/README.md | 52 +++ .../utils/__pycache__/helpers.cpython-314.pyc | Bin 0 -> 2654 bytes .../utils/__pycache__/reports.cpython-314.pyc | Bin 0 -> 5971 bytes .../utils/__pycache__/storage.cpython-314.pyc | Bin 0 -> 6329 bytes .../Ali-Salad/final_project/utils/helpers.py | 42 +++ .../Ali-Salad/final_project/utils/reports.py | 70 ++++ .../Ali-Salad/final_project/utils/storage.py | 95 +++++ 20 files changed, 930 insertions(+) create mode 100644 submissions/Ali-Salad/final_project/README.md create mode 100644 submissions/Ali-Salad/final_project/data/products.txt create mode 100644 submissions/Ali-Salad/final_project/data/staff.txt create mode 100644 submissions/Ali-Salad/final_project/main.py create mode 100644 submissions/Ali-Salad/final_project/models/__pycache__/inventory.cpython-314.pyc create mode 100644 submissions/Ali-Salad/final_project/models/__pycache__/staff.cpython-314.pyc create mode 100644 submissions/Ali-Salad/final_project/models/inventory.py create mode 100644 submissions/Ali-Salad/final_project/models/staff.py create mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md create mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt create mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md create mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt create mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md create mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md create mode 100644 submissions/Ali-Salad/final_project/utils/__pycache__/helpers.cpython-314.pyc create mode 100644 submissions/Ali-Salad/final_project/utils/__pycache__/reports.cpython-314.pyc create mode 100644 submissions/Ali-Salad/final_project/utils/__pycache__/storage.cpython-314.pyc create mode 100644 submissions/Ali-Salad/final_project/utils/helpers.py create mode 100644 submissions/Ali-Salad/final_project/utils/reports.py create mode 100644 submissions/Ali-Salad/final_project/utils/storage.py diff --git a/submissions/Ali-Salad/final_project/README.md b/submissions/Ali-Salad/final_project/README.md new file mode 100644 index 00000000..2f441d63 --- /dev/null +++ b/submissions/Ali-Salad/final_project/README.md @@ -0,0 +1,75 @@ +# Store Manager — final project + +A professional **Inventory & Staff Management System** CLI built in Python 3.10+. +Covers all concepts from Sections 1–6 of the Python bootcamp: comments, `print`/`input`, +conditions, lists and loops, functions and `main()`, files (UTF-8), `try`/`except` on bad +input, `__str__` on a class, `@dataclass`, and composition. + +--- + +## Run + +```bash +cd store_manager +python main.py +``` + +--- + +## What's where + +| Path | Purpose | +|---|---| +| `main.py` | Main menu + sub-menu functions | +| `models/inventory.py` | `Product` (one item) + `Store` (all items in memory) | +| `models/staff.py` | `Employee` (one person) + `Roster` (all staff in memory) | +| `utils/storage.py` | Load / save `data/products.txt` and `data/staff.txt` | +| `utils/reports.py` | Formatted terminal reports | +| `utils/helpers.py` | Safe `ask()`, `ask_float()`, `ask_int()`, `confirm()` | +| `data/products.txt` | Saved product roster | +| `data/staff.txt` | Saved staff roster | + +--- + +## Menus + +### Main +``` +[1] Inventory +[2] Staff +[3] Reports +[0] Save & Quit +``` + +### Inventory +Add, list, search by name, browse by category, update, restock, remove. + +### Staff +Hire, list, search by name, browse by role, update, fire. +Valid roles: `manager`, `cashier`, `stock_clerk`, `supervisor`, `intern` + +### Reports +- Inventory summary (with low-stock ⚠ flag) +- Low stock alert (configurable threshold, default ≤ 5 units) +- Category breakdown (units + value per category) +- Staff summary (roles + monthly payroll total) + +--- + +## Data files + +### `data/products.txt` +UTF-8 text. Lines starting with `#` are comments. +Header row: `id|name|category|price|quantity|supplier` +Supplier is optional (empty string if absent). + +### `data/staff.txt` +UTF-8 text. Lines starting with `#` are comments. +Header row: `id|name|role|salary|phone` +Phone is optional (empty string if absent). + +--- + +## Requirements +- Python 3.10+ (uses `str | None` union syntax) +- No third-party libraries — standard library only diff --git a/submissions/Ali-Salad/final_project/data/products.txt b/submissions/Ali-Salad/final_project/data/products.txt new file mode 100644 index 00000000..397c3d2f --- /dev/null +++ b/submissions/Ali-Salad/final_project/data/products.txt @@ -0,0 +1,2 @@ +# Store Manager — products data +id|name|category|price|quantity|supplier diff --git a/submissions/Ali-Salad/final_project/data/staff.txt b/submissions/Ali-Salad/final_project/data/staff.txt new file mode 100644 index 00000000..faa83433 --- /dev/null +++ b/submissions/Ali-Salad/final_project/data/staff.txt @@ -0,0 +1,2 @@ +# Store Manager — staff data +id|name|role|salary|phone diff --git a/submissions/Ali-Salad/final_project/main.py b/submissions/Ali-Salad/final_project/main.py new file mode 100644 index 00000000..938f49b5 --- /dev/null +++ b/submissions/Ali-Salad/final_project/main.py @@ -0,0 +1,333 @@ +""" +main.py — Store Manager CLI +============================= +A professional inventory and staff management system. +Covers Python concepts from Sections 1–6: + comments, print/input, conditions, loops, functions, + main(), files (UTF-8), try/except, __str__, @dataclass, composition. + +Python 3.10+ (uses X | Y union type hints) + +Run: + cd store_manager + python main.py +""" +from __future__ import annotations +import sys +from models.inventory import Store +from models.staff import Roster, ROLES +from utils.storage import load_products, save_products, load_staff, save_staff +from utils.reports import ( + inventory_summary, low_stock_report, + staff_summary, category_breakdown, +) +from utils.helpers import ask, ask_float, ask_int, confirm + +_BANNER = r""" + ╔══════════════════════════════════════════╗ + ║ STORE MANAGER v1.0 ║ + ║ Inventory & Staff Management System ║ + ╚══════════════════════════════════════════╝ +""" + +_MAIN_MENU = """ + [1] Inventory + [2] Staff + [3] Reports + [0] Save & Quit +""" + +_INV_MENU = """ + ── Inventory ────────────────────── + [1] Add product + [2] List all products + [3] Search by name + [4] Browse by category + [5] Update product + [6] Restock product + [7] Remove product + [0] Back +""" + +_STAFF_MENU = """ + ── Staff ─────────────────────────── + [1] Hire employee + [2] List all staff + [3] Search by name + [4] Browse by role + [5] Update employee + [6] Fire employee + [0] Back +""" + +_REPORTS_MENU = """ + ── Reports ───────────────────────── + [1] Inventory summary + [2] Low stock alert + [3] Category breakdown + [4] Staff summary + [0] Back +""" + + +# ── Inventory sub-menu ───────────────────────────────────────────────────── + +def menu_inventory(store: Store) -> None: + while True: + print(_INV_MENU) + choice = input(" > ").strip() + + if choice == "1": + print("\n ── Add product ──") + name = ask(" Name : ") + category = ask(" Category : ") + price = ask_float(" Price $ : ", min_val=0.01) + quantity = ask_int(" Qty : ", min_val=0) + supplier = ask(" Supplier (optional, press Enter to skip): ", required=False) or None + p = store.add_product(name, category, price, quantity, supplier) + print(f"\n ✓ Added [{p.id}] {p.name}") + + elif choice == "2": + inventory_summary(store) + + elif choice == "3": + query = ask(" Name query: ") + results = store.find_by_name(query) + if not results: + print(" No products found.") + else: + for p in results: + print(f"\n{p}") + + elif choice == "4": + cats = store.categories() + if not cats: + print(" No categories yet.") + continue + print(" Categories:", ", ".join(cats)) + cat = ask(" Category: ") + results = store.find_by_category(cat) + if not results: + print(" No products in that category.") + else: + for p in results: + print(f"\n{p}") + + elif choice == "5": + pid = ask_int(" Product ID: ", min_val=1) + p = store.find_by_id(pid) + if not p: + print(" ✗ Product not found.") + continue + print(f"\n{p}\n") + new_name = ask(f" New name [{p.name}] (Enter to keep): ", required=False) + new_cat = ask(f" New category [{p.category}] (Enter to keep): ", required=False) + new_price_raw = input(f" New price [${p.price:.2f}] (Enter to keep): ").strip() + new_qty_raw = input(f" New qty [{p.quantity}] (Enter to keep): ").strip() + new_supplier = input(f" New supplier [{p.supplier or 'none'}] (Enter to keep, 'none' to clear): ").strip() + + if new_name: + p.name = new_name + if new_cat: + p.category = new_cat + if new_price_raw: + try: + p.price = float(new_price_raw) + except ValueError: + print(" ✗ Invalid price — skipped.") + if new_qty_raw: + try: + p.quantity = int(new_qty_raw) + except ValueError: + print(" ✗ Invalid quantity — skipped.") + if new_supplier.lower() == "none": + p.supplier = None + elif new_supplier: + p.supplier = new_supplier + print(" ✓ Product updated.") + + elif choice == "6": + pid = ask_int(" Product ID: ", min_val=1) + amount = ask_int(" Add how many units: ", min_val=1) + if store.restock(pid, amount): + p = store.find_by_id(pid) + print(f" ✓ {p.name} now has {p.quantity} units.") # type: ignore[union-attr] + else: + print(" ✗ Product not found.") + + elif choice == "7": + pid = ask_int(" Product ID: ", min_val=1) + p = store.find_by_id(pid) + if not p: + print(" ✗ Product not found.") + continue + print(f"\n{p}\n") + if confirm(" Remove this product? (y/n): "): + store.remove_product(pid) + print(" ✓ Product removed.") + else: + print(" Cancelled.") + + elif choice == "0": + break + else: + print(" ✗ Invalid option.") + + +# ── Staff sub-menu ───────────────────────────────────────────────────────── + +def menu_staff(roster: Roster) -> None: + while True: + print(_STAFF_MENU) + choice = input(" > ").strip() + + if choice == "1": + print("\n ── Hire employee ──") + name = ask(" Name : ") + print(f" Roles : {', '.join(sorted(ROLES))}") + role = ask(" Role : ").lower() + if role not in ROLES: + print(f" ✗ '{role}' is not a valid role. Please choose from the list.") + continue + salary = ask_float(" Salary (monthly $): ", min_val=0) + phone = ask(" Phone (optional, Enter to skip): ", required=False) or None + e = roster.hire(name, role, salary, phone) + print(f"\n ✓ Hired [{e.id}] {e.name} as {e.role}") + + elif choice == "2": + staff_summary(roster) + + elif choice == "3": + query = ask(" Name query: ") + results = roster.find_by_name(query) + if not results: + print(" No employees found.") + else: + for e in results: + print(f"\n{e}") + + elif choice == "4": + print(f" Roles: {', '.join(sorted(ROLES))}") + role = ask(" Role: ").lower() + results = roster.by_role(role) + if not results: + print(" No employees with that role.") + else: + for e in results: + print(f"\n{e}") + + elif choice == "5": + eid = ask_int(" Employee ID: ", min_val=1) + e = roster.find_by_id(eid) + if not e: + print(" ✗ Employee not found.") + continue + print(f"\n{e}\n") + new_name = ask(f" New name [{e.name}] (Enter to keep): ", required=False) + print(f" Roles: {', '.join(sorted(ROLES))}") + new_role = ask(f" New role [{e.role}] (Enter to keep): ", required=False).lower() + new_salary_raw = input(f" New salary [${e.salary:,.2f}] (Enter to keep): ").strip() + new_phone = input(f" New phone [{e.phone or 'none'}] (Enter to keep, 'none' to clear): ").strip() + + if new_name: + e.name = new_name + if new_role: + if new_role not in ROLES: + print(f" ✗ '{new_role}' is not valid — role unchanged.") + else: + e.role = new_role + if new_salary_raw: + try: + e.salary = float(new_salary_raw) + except ValueError: + print(" ✗ Invalid salary — skipped.") + if new_phone.lower() == "none": + e.phone = None + elif new_phone: + e.phone = new_phone + print(" ✓ Employee updated.") + + elif choice == "6": + eid = ask_int(" Employee ID: ", min_val=1) + e = roster.find_by_id(eid) + if not e: + print(" ✗ Employee not found.") + continue + print(f"\n{e}\n") + if confirm(" Remove this employee? (y/n): "): + roster.fire(eid) + print(" ✓ Employee removed.") + else: + print(" Cancelled.") + + elif choice == "0": + break + else: + print(" ✗ Invalid option.") + + +# ── Reports sub-menu ─────────────────────────────────────────────────────── + +def menu_reports(store: Store, roster: Roster) -> None: + while True: + print(_REPORTS_MENU) + choice = input(" > ").strip() + + if choice == "1": + inventory_summary(store) + elif choice == "2": + threshold = ask_int(" Low-stock threshold (default 5): ", min_val=1) if \ + input(" Custom threshold? (y/n): ").strip().lower() == "y" else 5 + low_stock_report(store, threshold) + elif choice == "3": + category_breakdown(store) + elif choice == "4": + staff_summary(roster) + elif choice == "0": + break + else: + print(" ✗ Invalid option.") + + +# ── Entry point ──────────────────────────────────────────────────────────── + +def main() -> None: + store = Store() + roster = Roster() + + print(_BANNER) + print(" Loading data...") + try: + load_products(store) + load_staff(roster) + print(f" ✓ {len(store.products)} product(s) and " + f"{len(roster.employees)} employee(s) loaded.\n") + except Exception as e: + print(f" ⚠ Could not load data: {e}") + + while True: + print(_MAIN_MENU) + choice = input(" > ").strip() + + if choice == "1": + menu_inventory(store) + elif choice == "2": + menu_staff(roster) + elif choice == "3": + menu_reports(store, roster) + elif choice == "0": + print("\n Saving data...", end=" ") + try: + save_products(store) + save_staff(roster) + print("✓") + except Exception as e: + print(f"\n ✗ Save failed: {e}") + print(" Goodbye!\n") + sys.exit(0) + else: + print(" ✗ Choose 0–3.") + + +if __name__ == "__main__": + main() diff --git a/submissions/Ali-Salad/final_project/models/__pycache__/inventory.cpython-314.pyc b/submissions/Ali-Salad/final_project/models/__pycache__/inventory.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5021060f4fc79e0311b4df93a47fc70634a9b06 GIT binary patch literal 8184 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 literal 0 HcmV?d00001 diff --git a/submissions/Ali-Salad/final_project/models/__pycache__/staff.cpython-314.pyc b/submissions/Ali-Salad/final_project/models/__pycache__/staff.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e102395cdd61b4832a276403a4463477ab93fed GIT binary patch literal 5703 zcmcIoU2t2)72dr+(v_?~I~ER!6DxLN5Cj{7DIu|4Oq~!n#DoioPO+`INY_LlOUm9W zz;1_XN@sA|DM>p_BlA$3mVvl^AWR=J(cJ zDV=s_boT7e-QDx|on74-3i=6@f4w#Q-0!`F{2d!^k!^<7p97{uRAQ1DqS6U^N-%{U zHfKVTF)AThN;cY4ix5NvA>mGkHt>R6|(txU5{zGbWF_~rp{t%&D2s^%`kLhz#pep zo0iMvP0h^Yb4FaSe2sBSp3UglbjmpmuSB~+>)!>YM5c*JK~iC+%@hxUw9}F*^uvfM z0w*;%nR6hk>gAkb%Bl}qe%@*W=>lCO8!P9vH8g_{4gQ*{n%ta2V;eEP^BoG27g#ky&wRscLP%*2{I!b z{ShH)=<6nr(Q{O#sxU}ld+}48Yk8P%7Fmw@ps`9oSDx8Co6}4KSM0SOOdXqg($JYP zHI~ib8PZda=Jauvc|o5V*Nx}Rd|_%flhd-v0?R+Ar_3qaN>i?ttUxm9XkSkzt*9&V zKy&vl*vmjtBAY~!{WT=zUA(G8H{miOg9VQY^7!h11O{b1i@i6L+njbff|9FWGAlri z2xmYi1<(qt<2YyYQUNeDj7ZIWHV-2bjCfj($S|Vp98o<%qI!?`67+D>AXr0L2>D~N zvuMv4lw&cAECge*iE(T}ITqs+Cc!b9+bXz-+YZA5k8>~K^A9dahX;<#E{KD3`MAu& zuph${F^q54h9n3C!a*|h?5ySI&Xmk%a=L+f=#Q}|RF*fH1eXKTMVd^_)fRlDv9vff*CcB((?h2F>k3j^ZI0815B zL6uZl^{9ZIs&7z+)A(6Cd}6yZLc*3$vj8@^$A^&XK*H_DJ%_SR=&%8f`#8G=+SV>x zwr#-FhZFAsg23dF+iOV5yQ)~Ne2Z6Cqr9qg?gtN*+bee*sbS4copLfJEI1q2icm)Ty}33EF4?+7*Jp7cmPacT^jtdbVSBL;P1u{}gHPo<;hJ61W(;5c){jx2EhXYab}F3iJej z94xngsO@3gd$wc`XFiUhb-7fR_SD1;AZ0nj*GU`%?suw^_ zay1@Al!vvh*;-q19@4`joJTqZBom%E8J8dr=ZbSW)8Ji%V9lf65)1i)>kz0ui)LCmgamh|Er`Yj zSDOvdA9wC6r0P-?Feuy1v9J5#olw zYY9xC+g>av9;lhtj{?&Rb}Er4$VqsaoNsT1@Gp@PJfBa4j4o>5CuXP}*p%PeR|QKL zxZ5@vF7D=3-|(|KT(b&n)Vj@qYZD&6@H*4x;W?Qd7|!NXTGkjHXiOY|&ka=Pr*Nw4 z9Yb%UWFfmSKU5q^E@x1R{XZF^Xqt#;;S=z{f1 ztOG@yXWBj_3-#on2|y^y*2zX&16Bu<-4jCm7a+{Nx;#O`+!tBI@T>WmB8!`GEz0PSG| zFM}652Bo(>gWD1NzlvN-!v8OBiqQ|jrUu^4@%Fuyw95yZleOzEFpW4aeqGz>*FOFUuT^#+myJxLuyT=yCUprG1Nnj5T zt~j-~;dXb=YEeW5Qb|&k?PJX_-k^2S`L^E~*$zw>KL^6%!AQH?K zd^h{qhfipR1TTihNg%J1zs3$-O@9zOUYb~Zd0jeQ>5P_6EvD9`=%xg(x{U~2-!}s2 ziyI2u6gEPTOg20K6q}wc2er4rg(pW5CoB*Ay{EIBIOsqwN)dlk3MqZ zar<$`McXc}r3~ADl8mwpe1magJPM>LQc7!rK&1^2p?!ZLw}0j#zLT^hS3N{%+Z4jo dUnM~5A~dVKd5{krq7Tt3LCS|d6OiS?{~HC+9<2ZX literal 0 HcmV?d00001 diff --git a/submissions/Ali-Salad/final_project/models/inventory.py b/submissions/Ali-Salad/final_project/models/inventory.py new file mode 100644 index 00000000..838598c2 --- /dev/null +++ b/submissions/Ali-Salad/final_project/models/inventory.py @@ -0,0 +1,93 @@ +""" +models/inventory.py — Product and Store dataclasses. +""" +from __future__ import annotations +from dataclasses import dataclass, field + + +@dataclass +class Product: + id: int + name: str + category: str + price: float + quantity: int + supplier: str | None = None + + def __str__(self) -> str: + supplier_info = f" Supplier : {self.supplier}" if self.supplier else "" + return ( + f" ID : {self.id}\n" + f" Name : {self.name}\n" + f" Category : {self.category}\n" + f" Price : ${self.price:.2f}\n" + f" Qty : {self.quantity}" + + (f"\n{supplier_info}" if supplier_info else "") + ) + + def total_value(self) -> float: + return self.price * self.quantity + + def is_low_stock(self, threshold: int = 5) -> bool: + return self.quantity <= threshold + + +@dataclass +class Store: + products: list[Product] = field(default_factory=list) + _next_id: int = 1 + + def add_product(self, name: str, category: str, price: float, + quantity: int, supplier: str | None = None) -> Product: + p = Product( + id=self._next_id, + name=name, + category=category, + price=price, + quantity=quantity, + supplier=supplier, + ) + self.products.append(p) + self._next_id += 1 + return p + + def find_by_id(self, pid: int) -> Product | None: + for p in self.products: + if p.id == pid: + return p + return None + + def find_by_name(self, query: str) -> list[Product]: + q = query.lower() + return [p for p in self.products if q in p.name.lower()] + + def find_by_category(self, category: str) -> list[Product]: + c = category.lower() + return [p for p in self.products if c in p.category.lower()] + + def remove_product(self, pid: int) -> bool: + for i, p in enumerate(self.products): + if p.id == pid: + self.products.pop(i) + return True + return False + + def restock(self, pid: int, amount: int) -> bool: + p = self.find_by_id(pid) + if p: + p.quantity += amount + return True + return False + + def low_stock_report(self, threshold: int = 5) -> list[Product]: + return [p for p in self.products if p.is_low_stock(threshold)] + + def inventory_value(self) -> float: + return sum(p.total_value() for p in self.products) + + def categories(self) -> list[str]: + return sorted(set(p.category for p in self.products)) + + def sync_ids(self) -> None: + if self.products: + self._next_id = max(p.id for p in self.products) + 1 diff --git a/submissions/Ali-Salad/final_project/models/staff.py b/submissions/Ali-Salad/final_project/models/staff.py new file mode 100644 index 00000000..cd366861 --- /dev/null +++ b/submissions/Ali-Salad/final_project/models/staff.py @@ -0,0 +1,68 @@ +""" +models/staff.py — Employee and Roster dataclasses. +""" +from __future__ import annotations +from dataclasses import dataclass, field + + +ROLES = {"manager", "cashier", "stock_clerk", "supervisor", "intern"} + + +@dataclass +class Employee: + id: int + name: str + role: str + salary: float + phone: str | None = None + + def __str__(self) -> str: + phone_line = f"\n Phone : {self.phone}" if self.phone else "" + return ( + f" ID : {self.id}\n" + f" Name : {self.name}\n" + f" Role : {self.role}\n" + f" Salary : ${self.salary:,.2f}/mo" + + phone_line + ) + + +@dataclass +class Roster: + employees: list[Employee] = field(default_factory=list) + _next_id: int = 1 + + def hire(self, name: str, role: str, salary: float, + phone: str | None = None) -> Employee: + e = Employee(id=self._next_id, name=name, role=role, + salary=salary, phone=phone) + self.employees.append(e) + self._next_id += 1 + return e + + def find_by_id(self, eid: int) -> Employee | None: + for e in self.employees: + if e.id == eid: + return e + return None + + def find_by_name(self, query: str) -> list[Employee]: + q = query.lower() + return [e for e in self.employees if q in e.name.lower()] + + def fire(self, eid: int) -> bool: + for i, e in enumerate(self.employees): + if e.id == eid: + self.employees.pop(i) + return True + return False + + def total_payroll(self) -> float: + return sum(e.salary for e in self.employees) + + def by_role(self, role: str) -> list[Employee]: + return [e for e in self.employees if e.role == role] + + def sync_ids(self) -> None: + if self.employees: + self._next_id = max(e.id for e in self.employees) + 1 diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md new file mode 100644 index 00000000..7deba036 --- /dev/null +++ b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md @@ -0,0 +1,22 @@ +# Python for Everyone Bootcamp + +Welcome to the Python for Everyone Bootcamp repository. + +This is the main starting point for the bootcamp. All lessons, practice files, and project work will be added here as we move through the program. + +**Lesson sections (in order):** + +| Folder | Focus | +|--------|--------| +| `01_python_foundations` | Setup, variables, types, I/O, debugging basics | +| `02_operators_and_conditions` | Operators, comparisons, logical `and`/`or`, `if` / `elif` / `else`, nested conditions | +| `03_collections_and_loops` | Collections, `for` / `while` / `break`–`continue`, patterns (`enumerate`, `.items()`), methods | +| `04_functions` | Functions, parameters, return values, defaults, `*args` / `**kwargs`, scope | +| `05_file_handling_and_error_handling` | File I/O (read / write / append), `try` / `except` / `finally` | +| `06_object_oriented_programming` | Classes, methods, inheritance, dataclasses, composition | + +If you're joining the bootcamp, start by cloning this repository and opening it in VS Code or Cursor. + +More content will be organized into sections as the bootcamp progresses. + +Happy learning. diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt new file mode 100644 index 00000000..b097bbd5 --- /dev/null +++ b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt @@ -0,0 +1 @@ +degmo | m.bakaal ciro | 2013 diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md new file mode 100644 index 00000000..7deba036 --- /dev/null +++ b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md @@ -0,0 +1,22 @@ +# Python for Everyone Bootcamp + +Welcome to the Python for Everyone Bootcamp repository. + +This is the main starting point for the bootcamp. All lessons, practice files, and project work will be added here as we move through the program. + +**Lesson sections (in order):** + +| Folder | Focus | +|--------|--------| +| `01_python_foundations` | Setup, variables, types, I/O, debugging basics | +| `02_operators_and_conditions` | Operators, comparisons, logical `and`/`or`, `if` / `elif` / `else`, nested conditions | +| `03_collections_and_loops` | Collections, `for` / `while` / `break`–`continue`, patterns (`enumerate`, `.items()`), methods | +| `04_functions` | Functions, parameters, return values, defaults, `*args` / `**kwargs`, scope | +| `05_file_handling_and_error_handling` | File I/O (read / write / append), `try` / `except` / `finally` | +| `06_object_oriented_programming` | Classes, methods, inheritance, dataclasses, composition | + +If you're joining the bootcamp, start by cloning this repository and opening it in VS Code or Cursor. + +More content will be organized into sections as the bootcamp progresses. + +Happy learning. diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt new file mode 100644 index 00000000..b097bbd5 --- /dev/null +++ b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt @@ -0,0 +1 @@ +degmo | m.bakaal ciro | 2013 diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md new file mode 100644 index 00000000..e206a55d --- /dev/null +++ b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md @@ -0,0 +1,52 @@ +# 📥 Assignment Submissions + +Welcome! This folder is dedicated to **assignment** submissions on GitHub. + +**Homework** (each section’s **`homework.md`**) is **not** submitted here—only **assignments** from **`assignments/*.md`**. See **`assignments/README.md`** for the list. + +Please follow the rules below carefully so your work is accepted. + +--- + +## 🚀 How to Submit + +1. **Fork this repository** to your GitHub account. +2. Navigate to the `submissions/` folder. +3. **Create a folder with your exact GitHub username** (case-sensitive). + - Example: If your username is `sharafdin`, your folder must be named `sharafdin/`. +4. Place all your assignment files inside your folder: + - `main.py` + - `answers.md` + - any additional files required (datasets, images, etc.) +5. Commit and push your changes. +6. Open a **Pull Request (PR)** to this repository. + - PR title format: `Assignment Submission - ` (`Assignment Submission - sharafdin`) + +--- + +## ✅ Rules + +- Your folder **must exactly match your GitHub username** (uppercase/lowercase matters). +- Only edit your own folder. Do **not** modify or delete others’ work. +- Submissions outside the correct folder will be **rejected**. +- Each assignment should be kept inside your folder. For multiple assignments, you can structure like: + +``` + +submissions/ +├── sharafdin/ +│ ├── assignment1/ +│ │ ├── main.py +│ │ └── answers.md +│ └── assignment2/ +│ ├── main. +│ └── report.pdf +└── omartood/ +├── assignment1/ +└── assignment2/ + +``` + +--- + +Happy coding! 🚀 diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md new file mode 100644 index 00000000..e206a55d --- /dev/null +++ b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md @@ -0,0 +1,52 @@ +# 📥 Assignment Submissions + +Welcome! This folder is dedicated to **assignment** submissions on GitHub. + +**Homework** (each section’s **`homework.md`**) is **not** submitted here—only **assignments** from **`assignments/*.md`**. See **`assignments/README.md`** for the list. + +Please follow the rules below carefully so your work is accepted. + +--- + +## 🚀 How to Submit + +1. **Fork this repository** to your GitHub account. +2. Navigate to the `submissions/` folder. +3. **Create a folder with your exact GitHub username** (case-sensitive). + - Example: If your username is `sharafdin`, your folder must be named `sharafdin/`. +4. Place all your assignment files inside your folder: + - `main.py` + - `answers.md` + - any additional files required (datasets, images, etc.) +5. Commit and push your changes. +6. Open a **Pull Request (PR)** to this repository. + - PR title format: `Assignment Submission - ` (`Assignment Submission - sharafdin`) + +--- + +## ✅ Rules + +- Your folder **must exactly match your GitHub username** (uppercase/lowercase matters). +- Only edit your own folder. Do **not** modify or delete others’ work. +- Submissions outside the correct folder will be **rejected**. +- Each assignment should be kept inside your folder. For multiple assignments, you can structure like: + +``` + +submissions/ +├── sharafdin/ +│ ├── assignment1/ +│ │ ├── main.py +│ │ └── answers.md +│ └── assignment2/ +│ ├── main. +│ └── report.pdf +└── omartood/ +├── assignment1/ +└── assignment2/ + +``` + +--- + +Happy coding! 🚀 diff --git a/submissions/Ali-Salad/final_project/utils/__pycache__/helpers.cpython-314.pyc b/submissions/Ali-Salad/final_project/utils/__pycache__/helpers.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be44151c32128ce2fa9f465dc7cba13adbd3ecd8 GIT binary patch literal 2654 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 str: + while True: + val = input(prompt).strip() + if val or not required: + return val + print(" ✗ This field cannot be empty.") + + +def ask_float(prompt: str, *, min_val: float = 0.0) -> float: + while True: + raw = input(prompt).strip() + try: + val = float(raw) + if val < min_val: + print(f" ✗ Must be ≥ {min_val}.") + continue + return val + except ValueError: + print(" ✗ Enter a valid number (e.g. 9.99).") + + +def ask_int(prompt: str, *, min_val: int = 0) -> int: + while True: + raw = input(prompt).strip() + try: + val = int(raw) + if val < min_val: + print(f" ✗ Must be ≥ {min_val}.") + continue + return val + except ValueError: + print(" ✗ Enter a whole number.") + + +def confirm(prompt: str = "Are you sure? (y/n): ") -> bool: + return input(prompt).strip().lower() in {"y", "yes"} diff --git a/submissions/Ali-Salad/final_project/utils/reports.py b/submissions/Ali-Salad/final_project/utils/reports.py new file mode 100644 index 00000000..5862c30c --- /dev/null +++ b/submissions/Ali-Salad/final_project/utils/reports.py @@ -0,0 +1,70 @@ +""" +utils/reports.py — Print formatted reports to the terminal. +""" +from __future__ import annotations +from models.inventory import Store +from models.staff import Roster + +_SEP = "─" * 54 + + +def _header(title: str) -> None: + print(f"\n{_SEP}") + print(f" {title.upper()}") + print(_SEP) + + +def inventory_summary(store: Store) -> None: + _header("inventory summary") + if not store.products: + print(" No products on file.") + return + print(f" {'ID':<5} {'Name':<22} {'Category':<14} {'Qty':>5} {'Price':>8}") + print(" " + "·" * 52) + for p in store.products: + flag = " ⚠ LOW" if p.is_low_stock() else "" + print(f" {p.id:<5} {p.name:<22} {p.category:<14} " + f"{p.quantity:>5} ${p.price:>7.2f}{flag}") + print(" " + "·" * 52) + print(f" Total products : {len(store.products)}") + print(f" Inventory value : ${store.inventory_value():,.2f}") + print(f" Categories : {', '.join(store.categories()) or 'none'}") + + +def low_stock_report(store: Store, threshold: int = 5) -> None: + _header(f"low stock report (≤ {threshold} units)") + items = store.low_stock_report(threshold) + if not items: + print(" All products are sufficiently stocked.") + return + for p in items: + print(f" [{p.id}] {p.name} — {p.quantity} left") + + +def staff_summary(roster: Roster) -> None: + _header("staff summary") + if not roster.employees: + print(" No employees on file.") + return + print(f" {'ID':<5} {'Name':<22} {'Role':<14} {'Salary':>10}") + print(" " + "·" * 52) + for e in roster.employees: + print(f" {e.id:<5} {e.name:<22} {e.role:<14} ${e.salary:>9,.2f}") + print(" " + "·" * 52) + print(f" Total staff : {len(roster.employees)}") + print(f" Monthly payroll: ${roster.total_payroll():,.2f}") + + +def category_breakdown(store: Store) -> None: + _header("category breakdown") + if not store.products: + print(" No products on file.") + return + cats: dict[str, tuple[int, float]] = {} + for p in store.products: + qty, val = cats.get(p.category, (0, 0.0)) + cats[p.category] = (qty + p.quantity, val + p.total_value()) + print(f" {'Category':<20} {'Items':>6} {'Value':>12}") + print(" " + "·" * 40) + for cat, (qty, val) in sorted(cats.items()): + print(f" {cat:<20} {qty:>6} ${val:>11,.2f}") diff --git a/submissions/Ali-Salad/final_project/utils/storage.py b/submissions/Ali-Salad/final_project/utils/storage.py new file mode 100644 index 00000000..8c2057c4 --- /dev/null +++ b/submissions/Ali-Salad/final_project/utils/storage.py @@ -0,0 +1,95 @@ +""" +utils/storage.py — Read / write products.txt and staff.txt (UTF-8 pipe-delimited). + +Products format: + # comment lines ignored + id|name|category|price|quantity|supplier + 1|USB Cable|Electronics|4.99|30|TechSupply Co. + +Staff format: + # comment lines ignored + id|name|role|salary|phone + 1|Ali Salad|manager|850.00|+252612345678 +""" +from __future__ import annotations +from pathlib import Path +from models.inventory import Product, Store +from models.staff import Employee, Roster + +PRODUCTS_FILE = Path("data/products.txt") +STAFF_FILE = Path("data/staff.txt") + + +# ── Products ────────────────────────────────────────────────────────────────── + +def load_products(store: Store) -> None: + if not PRODUCTS_FILE.exists(): + return + store.products.clear() + with PRODUCTS_FILE.open(encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or line.startswith("id|"): + continue + parts = line.split("|") + if len(parts) < 5: + continue + try: + store.products.append(Product( + id=int(parts[0]), + name=parts[1], + category=parts[2], + price=float(parts[3]), + quantity=int(parts[4]), + supplier=parts[5] if len(parts) > 5 and parts[5] else None, + )) + except ValueError: + pass + store.sync_ids() + + +def save_products(store: Store) -> None: + PRODUCTS_FILE.parent.mkdir(parents=True, exist_ok=True) + with PRODUCTS_FILE.open("w", encoding="utf-8") as f: + f.write("# Store Manager — products data\n") + f.write("id|name|category|price|quantity|supplier\n") + for p in store.products: + f.write(f"{p.id}|{p.name}|{p.category}|{p.price}|" + f"{p.quantity}|{p.supplier or ''}\n") + + +# ── Staff ───────────────────────────────────────────────────────────────────── + +def load_staff(roster: Roster) -> None: + if not STAFF_FILE.exists(): + return + roster.employees.clear() + with STAFF_FILE.open(encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or line.startswith("id|"): + continue + parts = line.split("|") + if len(parts) < 4: + continue + try: + roster.employees.append(Employee( + id=int(parts[0]), + name=parts[1], + role=parts[2], + salary=float(parts[3]), + phone=parts[4] if len(parts) > 4 and parts[4] else None, + )) + except ValueError: + pass + roster.sync_ids() + + +def save_staff(roster: Roster) -> None: + STAFF_FILE.parent.mkdir(parents=True, exist_ok=True) + with STAFF_FILE.open("w", encoding="utf-8") as f: + f.write("# Store Manager — staff data\n") + f.write("id|name|role|salary|phone\n") + for e in roster.employees: + f.write(f"{e.id}|{e.name}|{e.role}|{e.salary}|" + f"{e.phone or ''}\n") From 0052e7089907cad38c11418765ea80f14ab2f532 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 11:11:43 +0300 Subject: [PATCH 02/12] Delete submissions/Ali-Salad/final_project/README.md --- submissions/Ali-Salad/final_project/README.md | 75 ------------------- 1 file changed, 75 deletions(-) delete mode 100644 submissions/Ali-Salad/final_project/README.md diff --git a/submissions/Ali-Salad/final_project/README.md b/submissions/Ali-Salad/final_project/README.md deleted file mode 100644 index 2f441d63..00000000 --- a/submissions/Ali-Salad/final_project/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Store Manager — final project - -A professional **Inventory & Staff Management System** CLI built in Python 3.10+. -Covers all concepts from Sections 1–6 of the Python bootcamp: comments, `print`/`input`, -conditions, lists and loops, functions and `main()`, files (UTF-8), `try`/`except` on bad -input, `__str__` on a class, `@dataclass`, and composition. - ---- - -## Run - -```bash -cd store_manager -python main.py -``` - ---- - -## What's where - -| Path | Purpose | -|---|---| -| `main.py` | Main menu + sub-menu functions | -| `models/inventory.py` | `Product` (one item) + `Store` (all items in memory) | -| `models/staff.py` | `Employee` (one person) + `Roster` (all staff in memory) | -| `utils/storage.py` | Load / save `data/products.txt` and `data/staff.txt` | -| `utils/reports.py` | Formatted terminal reports | -| `utils/helpers.py` | Safe `ask()`, `ask_float()`, `ask_int()`, `confirm()` | -| `data/products.txt` | Saved product roster | -| `data/staff.txt` | Saved staff roster | - ---- - -## Menus - -### Main -``` -[1] Inventory -[2] Staff -[3] Reports -[0] Save & Quit -``` - -### Inventory -Add, list, search by name, browse by category, update, restock, remove. - -### Staff -Hire, list, search by name, browse by role, update, fire. -Valid roles: `manager`, `cashier`, `stock_clerk`, `supervisor`, `intern` - -### Reports -- Inventory summary (with low-stock ⚠ flag) -- Low stock alert (configurable threshold, default ≤ 5 units) -- Category breakdown (units + value per category) -- Staff summary (roles + monthly payroll total) - ---- - -## Data files - -### `data/products.txt` -UTF-8 text. Lines starting with `#` are comments. -Header row: `id|name|category|price|quantity|supplier` -Supplier is optional (empty string if absent). - -### `data/staff.txt` -UTF-8 text. Lines starting with `#` are comments. -Header row: `id|name|role|salary|phone` -Phone is optional (empty string if absent). - ---- - -## Requirements -- Python 3.10+ (uses `str | None` union syntax) -- No third-party libraries — standard library only From f199d5b5dce8a0d816995a7c10e51e1e5f2a72d5 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 11:16:57 +0300 Subject: [PATCH 03/12] Delete submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md --- .../submissions/README.md | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md deleted file mode 100644 index e206a55d..00000000 --- a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# 📥 Assignment Submissions - -Welcome! This folder is dedicated to **assignment** submissions on GitHub. - -**Homework** (each section’s **`homework.md`**) is **not** submitted here—only **assignments** from **`assignments/*.md`**. See **`assignments/README.md`** for the list. - -Please follow the rules below carefully so your work is accepted. - ---- - -## 🚀 How to Submit - -1. **Fork this repository** to your GitHub account. -2. Navigate to the `submissions/` folder. -3. **Create a folder with your exact GitHub username** (case-sensitive). - - Example: If your username is `sharafdin`, your folder must be named `sharafdin/`. -4. Place all your assignment files inside your folder: - - `main.py` - - `answers.md` - - any additional files required (datasets, images, etc.) -5. Commit and push your changes. -6. Open a **Pull Request (PR)** to this repository. - - PR title format: `Assignment Submission - ` (`Assignment Submission - sharafdin`) - ---- - -## ✅ Rules - -- Your folder **must exactly match your GitHub username** (uppercase/lowercase matters). -- Only edit your own folder. Do **not** modify or delete others’ work. -- Submissions outside the correct folder will be **rejected**. -- Each assignment should be kept inside your folder. For multiple assignments, you can structure like: - -``` - -submissions/ -├── sharafdin/ -│ ├── assignment1/ -│ │ ├── main.py -│ │ └── answers.md -│ └── assignment2/ -│ ├── main. -│ └── report.pdf -└── omartood/ -├── assignment1/ -└── assignment2/ - -``` - ---- - -Happy coding! 🚀 From 2cd12cb52fcbab034df9661c1f35bb533d3b8948 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 11:23:39 +0300 Subject: [PATCH 04/12] Delete submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt --- .../final_project/python-for-everyone-bootcamp/music.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt deleted file mode 100644 index b097bbd5..00000000 --- a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt +++ /dev/null @@ -1 +0,0 @@ -degmo | m.bakaal ciro | 2013 From 22266b47d5af887989a87cdf695d2a7fba133c10 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 11:31:07 +0300 Subject: [PATCH 05/12] Delete submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md --- .../python-for-everyone-bootcamp/README.md | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md deleted file mode 100644 index 7deba036..00000000 --- a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Python for Everyone Bootcamp - -Welcome to the Python for Everyone Bootcamp repository. - -This is the main starting point for the bootcamp. All lessons, practice files, and project work will be added here as we move through the program. - -**Lesson sections (in order):** - -| Folder | Focus | -|--------|--------| -| `01_python_foundations` | Setup, variables, types, I/O, debugging basics | -| `02_operators_and_conditions` | Operators, comparisons, logical `and`/`or`, `if` / `elif` / `else`, nested conditions | -| `03_collections_and_loops` | Collections, `for` / `while` / `break`–`continue`, patterns (`enumerate`, `.items()`), methods | -| `04_functions` | Functions, parameters, return values, defaults, `*args` / `**kwargs`, scope | -| `05_file_handling_and_error_handling` | File I/O (read / write / append), `try` / `except` / `finally` | -| `06_object_oriented_programming` | Classes, methods, inheritance, dataclasses, composition | - -If you're joining the bootcamp, start by cloning this repository and opening it in VS Code or Cursor. - -More content will be organized into sections as the bootcamp progresses. - -Happy learning. From 78a39cebe2c791eeb2d3f63fe476db474c5eef61 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 11:31:41 +0300 Subject: [PATCH 06/12] Delete submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt --- .../final_project/python-for-everyone-bootcamp/music.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt deleted file mode 100644 index b097bbd5..00000000 --- a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/music.txt +++ /dev/null @@ -1 +0,0 @@ -degmo | m.bakaal ciro | 2013 From 8b82b3c02f8d8f005b1cc812121208d42c31db41 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 11:32:41 +0300 Subject: [PATCH 07/12] Delete submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md --- .../submissions/README.md | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md deleted file mode 100644 index e206a55d..00000000 --- a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# 📥 Assignment Submissions - -Welcome! This folder is dedicated to **assignment** submissions on GitHub. - -**Homework** (each section’s **`homework.md`**) is **not** submitted here—only **assignments** from **`assignments/*.md`**. See **`assignments/README.md`** for the list. - -Please follow the rules below carefully so your work is accepted. - ---- - -## 🚀 How to Submit - -1. **Fork this repository** to your GitHub account. -2. Navigate to the `submissions/` folder. -3. **Create a folder with your exact GitHub username** (case-sensitive). - - Example: If your username is `sharafdin`, your folder must be named `sharafdin/`. -4. Place all your assignment files inside your folder: - - `main.py` - - `answers.md` - - any additional files required (datasets, images, etc.) -5. Commit and push your changes. -6. Open a **Pull Request (PR)** to this repository. - - PR title format: `Assignment Submission - ` (`Assignment Submission - sharafdin`) - ---- - -## ✅ Rules - -- Your folder **must exactly match your GitHub username** (uppercase/lowercase matters). -- Only edit your own folder. Do **not** modify or delete others’ work. -- Submissions outside the correct folder will be **rejected**. -- Each assignment should be kept inside your folder. For multiple assignments, you can structure like: - -``` - -submissions/ -├── sharafdin/ -│ ├── assignment1/ -│ │ ├── main.py -│ │ └── answers.md -│ └── assignment2/ -│ ├── main. -│ └── report.pdf -└── omartood/ -├── assignment1/ -└── assignment2/ - -``` - ---- - -Happy coding! 🚀 From 78f57372fb78ed13941dd6b8bf464f0221f7b2a6 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Thu, 25 Jun 2026 11:33:14 +0300 Subject: [PATCH 08/12] Delete submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md --- .../python-for-everyone-bootcamp/README.md | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md diff --git a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md b/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md deleted file mode 100644 index 7deba036..00000000 --- a/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/submissions/Ali-Salad/final_project/python-for-everyone-bootcamp/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# Python for Everyone Bootcamp - -Welcome to the Python for Everyone Bootcamp repository. - -This is the main starting point for the bootcamp. All lessons, practice files, and project work will be added here as we move through the program. - -**Lesson sections (in order):** - -| Folder | Focus | -|--------|--------| -| `01_python_foundations` | Setup, variables, types, I/O, debugging basics | -| `02_operators_and_conditions` | Operators, comparisons, logical `and`/`or`, `if` / `elif` / `else`, nested conditions | -| `03_collections_and_loops` | Collections, `for` / `while` / `break`–`continue`, patterns (`enumerate`, `.items()`), methods | -| `04_functions` | Functions, parameters, return values, defaults, `*args` / `**kwargs`, scope | -| `05_file_handling_and_error_handling` | File I/O (read / write / append), `try` / `except` / `finally` | -| `06_object_oriented_programming` | Classes, methods, inheritance, dataclasses, composition | - -If you're joining the bootcamp, start by cloning this repository and opening it in VS Code or Cursor. - -More content will be organized into sections as the bootcamp progresses. - -Happy learning. From 2ed7e9ec8c23226e76dfeb80af39a1241e862d2f Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Fri, 26 Jun 2026 04:57:04 +0100 Subject: [PATCH 09/12] Add final_project --- .../07_final_project_store_manager/README.md | 76 ++++ .../07_final_project_store_manager/main.py | 333 ++++++++++++++++++ .../models/inventory.py | 93 +++++ .../models/staff.py | 68 ++++ .../products.txt | 12 + .../07_final_project_store_manager/staff.txt | 6 + .../utils/helpers.py | 42 +++ .../utils/reports.py | 70 ++++ .../utils/storage.py | 95 +++++ 9 files changed, 795 insertions(+) create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/README.md create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/main.py create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/models/inventory.py create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/models/staff.py create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/products.txt create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/staff.txt create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/utils/helpers.py create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/utils/reports.py create mode 100644 submissions/Ali-Salad/07_final_project_store_manager/utils/storage.py diff --git a/submissions/Ali-Salad/07_final_project_store_manager/README.md b/submissions/Ali-Salad/07_final_project_store_manager/README.md new file mode 100644 index 00000000..808d697a --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/README.md @@ -0,0 +1,76 @@ +# Store Manager — final project +Name: Alisalad + +A professional **Inventory & Staff Management System** CLI built in Python 3.10+. +Covers all concepts from Sections 1–6 of the Python bootcamp: comments, `print`/`input`, +conditions, lists and loops, functions and `main()`, files (UTF-8), `try`/`except` on bad +input, `__str__` on a class, `@dataclass`, and composition. + +--- + +## Run + +```bash +cd store_manager +python main.py +``` + +--- + +## What's where + +| Path | Purpose | +|---|---| +| `main.py` | Main menu + sub-menu functions | +| `models/inventory.py` | `Product` (one item) + `Store` (all items in memory) | +| `models/staff.py` | `Employee` (one person) + `Roster` (all staff in memory) | +| `utils/storage.py` | Load / save `data/products.txt` and `data/staff.txt` | +| `utils/reports.py` | Formatted terminal reports | +| `utils/helpers.py` | Safe `ask()`, `ask_float()`, `ask_int()`, `confirm()` | +| `data/products.txt` | Saved product roster | +| `data/staff.txt` | Saved staff roster | + +--- + +## Menus + +### Main +``` +[1] Inventory +[2] Staff +[3] Reports +[0] Save & Quit +``` + +### Inventory +Add, list, search by name, browse by category, update, restock, remove. + +### Staff +Hire, list, search by name, browse by role, update, fire. +Valid roles: `manager`, `cashier`, `stock_clerk`, `supervisor`, `intern` + +### Reports +- Inventory summary (with low-stock ⚠ flag) +- Low stock alert (configurable threshold, default ≤ 5 units) +- Category breakdown (units + value per category) +- Staff summary (roles + monthly payroll total) + +--- + +## Data files + +### `data/products.txt` +UTF-8 text. Lines starting with `#` are comments. +Header row: `id|name|category|price|quantity|supplier` +Supplier is optional (empty string if absent). + +### `data/staff.txt` +UTF-8 text. Lines starting with `#` are comments. +Header row: `id|name|role|salary|phone` +Phone is optional (empty string if absent). + +--- + +## Requirements +- Python 3.10+ (uses `str | None` union syntax) +- No third-party libraries — standard library only diff --git a/submissions/Ali-Salad/07_final_project_store_manager/main.py b/submissions/Ali-Salad/07_final_project_store_manager/main.py new file mode 100644 index 00000000..938f49b5 --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/main.py @@ -0,0 +1,333 @@ +""" +main.py — Store Manager CLI +============================= +A professional inventory and staff management system. +Covers Python concepts from Sections 1–6: + comments, print/input, conditions, loops, functions, + main(), files (UTF-8), try/except, __str__, @dataclass, composition. + +Python 3.10+ (uses X | Y union type hints) + +Run: + cd store_manager + python main.py +""" +from __future__ import annotations +import sys +from models.inventory import Store +from models.staff import Roster, ROLES +from utils.storage import load_products, save_products, load_staff, save_staff +from utils.reports import ( + inventory_summary, low_stock_report, + staff_summary, category_breakdown, +) +from utils.helpers import ask, ask_float, ask_int, confirm + +_BANNER = r""" + ╔══════════════════════════════════════════╗ + ║ STORE MANAGER v1.0 ║ + ║ Inventory & Staff Management System ║ + ╚══════════════════════════════════════════╝ +""" + +_MAIN_MENU = """ + [1] Inventory + [2] Staff + [3] Reports + [0] Save & Quit +""" + +_INV_MENU = """ + ── Inventory ────────────────────── + [1] Add product + [2] List all products + [3] Search by name + [4] Browse by category + [5] Update product + [6] Restock product + [7] Remove product + [0] Back +""" + +_STAFF_MENU = """ + ── Staff ─────────────────────────── + [1] Hire employee + [2] List all staff + [3] Search by name + [4] Browse by role + [5] Update employee + [6] Fire employee + [0] Back +""" + +_REPORTS_MENU = """ + ── Reports ───────────────────────── + [1] Inventory summary + [2] Low stock alert + [3] Category breakdown + [4] Staff summary + [0] Back +""" + + +# ── Inventory sub-menu ───────────────────────────────────────────────────── + +def menu_inventory(store: Store) -> None: + while True: + print(_INV_MENU) + choice = input(" > ").strip() + + if choice == "1": + print("\n ── Add product ──") + name = ask(" Name : ") + category = ask(" Category : ") + price = ask_float(" Price $ : ", min_val=0.01) + quantity = ask_int(" Qty : ", min_val=0) + supplier = ask(" Supplier (optional, press Enter to skip): ", required=False) or None + p = store.add_product(name, category, price, quantity, supplier) + print(f"\n ✓ Added [{p.id}] {p.name}") + + elif choice == "2": + inventory_summary(store) + + elif choice == "3": + query = ask(" Name query: ") + results = store.find_by_name(query) + if not results: + print(" No products found.") + else: + for p in results: + print(f"\n{p}") + + elif choice == "4": + cats = store.categories() + if not cats: + print(" No categories yet.") + continue + print(" Categories:", ", ".join(cats)) + cat = ask(" Category: ") + results = store.find_by_category(cat) + if not results: + print(" No products in that category.") + else: + for p in results: + print(f"\n{p}") + + elif choice == "5": + pid = ask_int(" Product ID: ", min_val=1) + p = store.find_by_id(pid) + if not p: + print(" ✗ Product not found.") + continue + print(f"\n{p}\n") + new_name = ask(f" New name [{p.name}] (Enter to keep): ", required=False) + new_cat = ask(f" New category [{p.category}] (Enter to keep): ", required=False) + new_price_raw = input(f" New price [${p.price:.2f}] (Enter to keep): ").strip() + new_qty_raw = input(f" New qty [{p.quantity}] (Enter to keep): ").strip() + new_supplier = input(f" New supplier [{p.supplier or 'none'}] (Enter to keep, 'none' to clear): ").strip() + + if new_name: + p.name = new_name + if new_cat: + p.category = new_cat + if new_price_raw: + try: + p.price = float(new_price_raw) + except ValueError: + print(" ✗ Invalid price — skipped.") + if new_qty_raw: + try: + p.quantity = int(new_qty_raw) + except ValueError: + print(" ✗ Invalid quantity — skipped.") + if new_supplier.lower() == "none": + p.supplier = None + elif new_supplier: + p.supplier = new_supplier + print(" ✓ Product updated.") + + elif choice == "6": + pid = ask_int(" Product ID: ", min_val=1) + amount = ask_int(" Add how many units: ", min_val=1) + if store.restock(pid, amount): + p = store.find_by_id(pid) + print(f" ✓ {p.name} now has {p.quantity} units.") # type: ignore[union-attr] + else: + print(" ✗ Product not found.") + + elif choice == "7": + pid = ask_int(" Product ID: ", min_val=1) + p = store.find_by_id(pid) + if not p: + print(" ✗ Product not found.") + continue + print(f"\n{p}\n") + if confirm(" Remove this product? (y/n): "): + store.remove_product(pid) + print(" ✓ Product removed.") + else: + print(" Cancelled.") + + elif choice == "0": + break + else: + print(" ✗ Invalid option.") + + +# ── Staff sub-menu ───────────────────────────────────────────────────────── + +def menu_staff(roster: Roster) -> None: + while True: + print(_STAFF_MENU) + choice = input(" > ").strip() + + if choice == "1": + print("\n ── Hire employee ──") + name = ask(" Name : ") + print(f" Roles : {', '.join(sorted(ROLES))}") + role = ask(" Role : ").lower() + if role not in ROLES: + print(f" ✗ '{role}' is not a valid role. Please choose from the list.") + continue + salary = ask_float(" Salary (monthly $): ", min_val=0) + phone = ask(" Phone (optional, Enter to skip): ", required=False) or None + e = roster.hire(name, role, salary, phone) + print(f"\n ✓ Hired [{e.id}] {e.name} as {e.role}") + + elif choice == "2": + staff_summary(roster) + + elif choice == "3": + query = ask(" Name query: ") + results = roster.find_by_name(query) + if not results: + print(" No employees found.") + else: + for e in results: + print(f"\n{e}") + + elif choice == "4": + print(f" Roles: {', '.join(sorted(ROLES))}") + role = ask(" Role: ").lower() + results = roster.by_role(role) + if not results: + print(" No employees with that role.") + else: + for e in results: + print(f"\n{e}") + + elif choice == "5": + eid = ask_int(" Employee ID: ", min_val=1) + e = roster.find_by_id(eid) + if not e: + print(" ✗ Employee not found.") + continue + print(f"\n{e}\n") + new_name = ask(f" New name [{e.name}] (Enter to keep): ", required=False) + print(f" Roles: {', '.join(sorted(ROLES))}") + new_role = ask(f" New role [{e.role}] (Enter to keep): ", required=False).lower() + new_salary_raw = input(f" New salary [${e.salary:,.2f}] (Enter to keep): ").strip() + new_phone = input(f" New phone [{e.phone or 'none'}] (Enter to keep, 'none' to clear): ").strip() + + if new_name: + e.name = new_name + if new_role: + if new_role not in ROLES: + print(f" ✗ '{new_role}' is not valid — role unchanged.") + else: + e.role = new_role + if new_salary_raw: + try: + e.salary = float(new_salary_raw) + except ValueError: + print(" ✗ Invalid salary — skipped.") + if new_phone.lower() == "none": + e.phone = None + elif new_phone: + e.phone = new_phone + print(" ✓ Employee updated.") + + elif choice == "6": + eid = ask_int(" Employee ID: ", min_val=1) + e = roster.find_by_id(eid) + if not e: + print(" ✗ Employee not found.") + continue + print(f"\n{e}\n") + if confirm(" Remove this employee? (y/n): "): + roster.fire(eid) + print(" ✓ Employee removed.") + else: + print(" Cancelled.") + + elif choice == "0": + break + else: + print(" ✗ Invalid option.") + + +# ── Reports sub-menu ─────────────────────────────────────────────────────── + +def menu_reports(store: Store, roster: Roster) -> None: + while True: + print(_REPORTS_MENU) + choice = input(" > ").strip() + + if choice == "1": + inventory_summary(store) + elif choice == "2": + threshold = ask_int(" Low-stock threshold (default 5): ", min_val=1) if \ + input(" Custom threshold? (y/n): ").strip().lower() == "y" else 5 + low_stock_report(store, threshold) + elif choice == "3": + category_breakdown(store) + elif choice == "4": + staff_summary(roster) + elif choice == "0": + break + else: + print(" ✗ Invalid option.") + + +# ── Entry point ──────────────────────────────────────────────────────────── + +def main() -> None: + store = Store() + roster = Roster() + + print(_BANNER) + print(" Loading data...") + try: + load_products(store) + load_staff(roster) + print(f" ✓ {len(store.products)} product(s) and " + f"{len(roster.employees)} employee(s) loaded.\n") + except Exception as e: + print(f" ⚠ Could not load data: {e}") + + while True: + print(_MAIN_MENU) + choice = input(" > ").strip() + + if choice == "1": + menu_inventory(store) + elif choice == "2": + menu_staff(roster) + elif choice == "3": + menu_reports(store, roster) + elif choice == "0": + print("\n Saving data...", end=" ") + try: + save_products(store) + save_staff(roster) + print("✓") + except Exception as e: + print(f"\n ✗ Save failed: {e}") + print(" Goodbye!\n") + sys.exit(0) + else: + print(" ✗ Choose 0–3.") + + +if __name__ == "__main__": + main() diff --git a/submissions/Ali-Salad/07_final_project_store_manager/models/inventory.py b/submissions/Ali-Salad/07_final_project_store_manager/models/inventory.py new file mode 100644 index 00000000..838598c2 --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/models/inventory.py @@ -0,0 +1,93 @@ +""" +models/inventory.py — Product and Store dataclasses. +""" +from __future__ import annotations +from dataclasses import dataclass, field + + +@dataclass +class Product: + id: int + name: str + category: str + price: float + quantity: int + supplier: str | None = None + + def __str__(self) -> str: + supplier_info = f" Supplier : {self.supplier}" if self.supplier else "" + return ( + f" ID : {self.id}\n" + f" Name : {self.name}\n" + f" Category : {self.category}\n" + f" Price : ${self.price:.2f}\n" + f" Qty : {self.quantity}" + + (f"\n{supplier_info}" if supplier_info else "") + ) + + def total_value(self) -> float: + return self.price * self.quantity + + def is_low_stock(self, threshold: int = 5) -> bool: + return self.quantity <= threshold + + +@dataclass +class Store: + products: list[Product] = field(default_factory=list) + _next_id: int = 1 + + def add_product(self, name: str, category: str, price: float, + quantity: int, supplier: str | None = None) -> Product: + p = Product( + id=self._next_id, + name=name, + category=category, + price=price, + quantity=quantity, + supplier=supplier, + ) + self.products.append(p) + self._next_id += 1 + return p + + def find_by_id(self, pid: int) -> Product | None: + for p in self.products: + if p.id == pid: + return p + return None + + def find_by_name(self, query: str) -> list[Product]: + q = query.lower() + return [p for p in self.products if q in p.name.lower()] + + def find_by_category(self, category: str) -> list[Product]: + c = category.lower() + return [p for p in self.products if c in p.category.lower()] + + def remove_product(self, pid: int) -> bool: + for i, p in enumerate(self.products): + if p.id == pid: + self.products.pop(i) + return True + return False + + def restock(self, pid: int, amount: int) -> bool: + p = self.find_by_id(pid) + if p: + p.quantity += amount + return True + return False + + def low_stock_report(self, threshold: int = 5) -> list[Product]: + return [p for p in self.products if p.is_low_stock(threshold)] + + def inventory_value(self) -> float: + return sum(p.total_value() for p in self.products) + + def categories(self) -> list[str]: + return sorted(set(p.category for p in self.products)) + + def sync_ids(self) -> None: + if self.products: + self._next_id = max(p.id for p in self.products) + 1 diff --git a/submissions/Ali-Salad/07_final_project_store_manager/models/staff.py b/submissions/Ali-Salad/07_final_project_store_manager/models/staff.py new file mode 100644 index 00000000..cd366861 --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/models/staff.py @@ -0,0 +1,68 @@ +""" +models/staff.py — Employee and Roster dataclasses. +""" +from __future__ import annotations +from dataclasses import dataclass, field + + +ROLES = {"manager", "cashier", "stock_clerk", "supervisor", "intern"} + + +@dataclass +class Employee: + id: int + name: str + role: str + salary: float + phone: str | None = None + + def __str__(self) -> str: + phone_line = f"\n Phone : {self.phone}" if self.phone else "" + return ( + f" ID : {self.id}\n" + f" Name : {self.name}\n" + f" Role : {self.role}\n" + f" Salary : ${self.salary:,.2f}/mo" + + phone_line + ) + + +@dataclass +class Roster: + employees: list[Employee] = field(default_factory=list) + _next_id: int = 1 + + def hire(self, name: str, role: str, salary: float, + phone: str | None = None) -> Employee: + e = Employee(id=self._next_id, name=name, role=role, + salary=salary, phone=phone) + self.employees.append(e) + self._next_id += 1 + return e + + def find_by_id(self, eid: int) -> Employee | None: + for e in self.employees: + if e.id == eid: + return e + return None + + def find_by_name(self, query: str) -> list[Employee]: + q = query.lower() + return [e for e in self.employees if q in e.name.lower()] + + def fire(self, eid: int) -> bool: + for i, e in enumerate(self.employees): + if e.id == eid: + self.employees.pop(i) + return True + return False + + def total_payroll(self) -> float: + return sum(e.salary for e in self.employees) + + def by_role(self, role: str) -> list[Employee]: + return [e for e in self.employees if e.role == role] + + def sync_ids(self) -> None: + if self.employees: + self._next_id = max(e.id for e in self.employees) + 1 diff --git a/submissions/Ali-Salad/07_final_project_store_manager/products.txt b/submissions/Ali-Salad/07_final_project_store_manager/products.txt new file mode 100644 index 00000000..16775b5e --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/products.txt @@ -0,0 +1,12 @@ +# Store Manager — products data +id|name|category|price|quantity|supplier +# Store Manager — products data +id|name|category|price|quantity|supplier +1|USB-C Cable 2m|Electronics|4.99|30|TechSupply Co. +2|Screen Protector|Electronics|2.50|4|ShieldGlass Ltd. +3|Phone Case iPhone 14|Accessories|8.00|15|CaseWorld +4|Wireless Earbuds|Electronics|35.00|7|SoundPro +5|Power Bank 10000mAh|Electronics|22.99|3|PowerMax +6|Tempered Glass S23|Electronics|3.00|20|ShieldGlass Ltd. +7|Sim Card Tray Tool|Tools|1.25|50|FixIt Store +8|Charging Adapter EU|Accessories|6.50|12|TechSupply Co. \ No newline at end of file diff --git a/submissions/Ali-Salad/07_final_project_store_manager/staff.txt b/submissions/Ali-Salad/07_final_project_store_manager/staff.txt new file mode 100644 index 00000000..b9c75a2d --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/staff.txt @@ -0,0 +1,6 @@ +# Store Manager — staff data +id|name|role|salary|phone +1|Ali Salad|manager|900.00|+252612000001 +2|Faadumo Hassan|cashier|500.00|+252612000002 +3|Cabdi Warsame|stock_clerk|450.00| +4|Hodan Yusuf|supervisor|700.00|+252612000004 \ No newline at end of file diff --git a/submissions/Ali-Salad/07_final_project_store_manager/utils/helpers.py b/submissions/Ali-Salad/07_final_project_store_manager/utils/helpers.py new file mode 100644 index 00000000..07d77f98 --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/utils/helpers.py @@ -0,0 +1,42 @@ +""" +utils/helpers.py — Safe input helpers shared across menus. +""" +from __future__ import annotations + + +def ask(prompt: str, *, required: bool = True) -> str: + while True: + val = input(prompt).strip() + if val or not required: + return val + print(" ✗ This field cannot be empty.") + + +def ask_float(prompt: str, *, min_val: float = 0.0) -> float: + while True: + raw = input(prompt).strip() + try: + val = float(raw) + if val < min_val: + print(f" ✗ Must be ≥ {min_val}.") + continue + return val + except ValueError: + print(" ✗ Enter a valid number (e.g. 9.99).") + + +def ask_int(prompt: str, *, min_val: int = 0) -> int: + while True: + raw = input(prompt).strip() + try: + val = int(raw) + if val < min_val: + print(f" ✗ Must be ≥ {min_val}.") + continue + return val + except ValueError: + print(" ✗ Enter a whole number.") + + +def confirm(prompt: str = "Are you sure? (y/n): ") -> bool: + return input(prompt).strip().lower() in {"y", "yes"} diff --git a/submissions/Ali-Salad/07_final_project_store_manager/utils/reports.py b/submissions/Ali-Salad/07_final_project_store_manager/utils/reports.py new file mode 100644 index 00000000..5862c30c --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/utils/reports.py @@ -0,0 +1,70 @@ +""" +utils/reports.py — Print formatted reports to the terminal. +""" +from __future__ import annotations +from models.inventory import Store +from models.staff import Roster + +_SEP = "─" * 54 + + +def _header(title: str) -> None: + print(f"\n{_SEP}") + print(f" {title.upper()}") + print(_SEP) + + +def inventory_summary(store: Store) -> None: + _header("inventory summary") + if not store.products: + print(" No products on file.") + return + print(f" {'ID':<5} {'Name':<22} {'Category':<14} {'Qty':>5} {'Price':>8}") + print(" " + "·" * 52) + for p in store.products: + flag = " ⚠ LOW" if p.is_low_stock() else "" + print(f" {p.id:<5} {p.name:<22} {p.category:<14} " + f"{p.quantity:>5} ${p.price:>7.2f}{flag}") + print(" " + "·" * 52) + print(f" Total products : {len(store.products)}") + print(f" Inventory value : ${store.inventory_value():,.2f}") + print(f" Categories : {', '.join(store.categories()) or 'none'}") + + +def low_stock_report(store: Store, threshold: int = 5) -> None: + _header(f"low stock report (≤ {threshold} units)") + items = store.low_stock_report(threshold) + if not items: + print(" All products are sufficiently stocked.") + return + for p in items: + print(f" [{p.id}] {p.name} — {p.quantity} left") + + +def staff_summary(roster: Roster) -> None: + _header("staff summary") + if not roster.employees: + print(" No employees on file.") + return + print(f" {'ID':<5} {'Name':<22} {'Role':<14} {'Salary':>10}") + print(" " + "·" * 52) + for e in roster.employees: + print(f" {e.id:<5} {e.name:<22} {e.role:<14} ${e.salary:>9,.2f}") + print(" " + "·" * 52) + print(f" Total staff : {len(roster.employees)}") + print(f" Monthly payroll: ${roster.total_payroll():,.2f}") + + +def category_breakdown(store: Store) -> None: + _header("category breakdown") + if not store.products: + print(" No products on file.") + return + cats: dict[str, tuple[int, float]] = {} + for p in store.products: + qty, val = cats.get(p.category, (0, 0.0)) + cats[p.category] = (qty + p.quantity, val + p.total_value()) + print(f" {'Category':<20} {'Items':>6} {'Value':>12}") + print(" " + "·" * 40) + for cat, (qty, val) in sorted(cats.items()): + print(f" {cat:<20} {qty:>6} ${val:>11,.2f}") diff --git a/submissions/Ali-Salad/07_final_project_store_manager/utils/storage.py b/submissions/Ali-Salad/07_final_project_store_manager/utils/storage.py new file mode 100644 index 00000000..8c2057c4 --- /dev/null +++ b/submissions/Ali-Salad/07_final_project_store_manager/utils/storage.py @@ -0,0 +1,95 @@ +""" +utils/storage.py — Read / write products.txt and staff.txt (UTF-8 pipe-delimited). + +Products format: + # comment lines ignored + id|name|category|price|quantity|supplier + 1|USB Cable|Electronics|4.99|30|TechSupply Co. + +Staff format: + # comment lines ignored + id|name|role|salary|phone + 1|Ali Salad|manager|850.00|+252612345678 +""" +from __future__ import annotations +from pathlib import Path +from models.inventory import Product, Store +from models.staff import Employee, Roster + +PRODUCTS_FILE = Path("data/products.txt") +STAFF_FILE = Path("data/staff.txt") + + +# ── Products ────────────────────────────────────────────────────────────────── + +def load_products(store: Store) -> None: + if not PRODUCTS_FILE.exists(): + return + store.products.clear() + with PRODUCTS_FILE.open(encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or line.startswith("id|"): + continue + parts = line.split("|") + if len(parts) < 5: + continue + try: + store.products.append(Product( + id=int(parts[0]), + name=parts[1], + category=parts[2], + price=float(parts[3]), + quantity=int(parts[4]), + supplier=parts[5] if len(parts) > 5 and parts[5] else None, + )) + except ValueError: + pass + store.sync_ids() + + +def save_products(store: Store) -> None: + PRODUCTS_FILE.parent.mkdir(parents=True, exist_ok=True) + with PRODUCTS_FILE.open("w", encoding="utf-8") as f: + f.write("# Store Manager — products data\n") + f.write("id|name|category|price|quantity|supplier\n") + for p in store.products: + f.write(f"{p.id}|{p.name}|{p.category}|{p.price}|" + f"{p.quantity}|{p.supplier or ''}\n") + + +# ── Staff ───────────────────────────────────────────────────────────────────── + +def load_staff(roster: Roster) -> None: + if not STAFF_FILE.exists(): + return + roster.employees.clear() + with STAFF_FILE.open(encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or line.startswith("id|"): + continue + parts = line.split("|") + if len(parts) < 4: + continue + try: + roster.employees.append(Employee( + id=int(parts[0]), + name=parts[1], + role=parts[2], + salary=float(parts[3]), + phone=parts[4] if len(parts) > 4 and parts[4] else None, + )) + except ValueError: + pass + roster.sync_ids() + + +def save_staff(roster: Roster) -> None: + STAFF_FILE.parent.mkdir(parents=True, exist_ok=True) + with STAFF_FILE.open("w", encoding="utf-8") as f: + f.write("# Store Manager — staff data\n") + f.write("id|name|role|salary|phone\n") + for e in roster.employees: + f.write(f"{e.id}|{e.name}|{e.role}|{e.salary}|" + f"{e.phone or ''}\n") From e9c33bc8af547323665c04af2349e944448de89b Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Fri, 26 Jun 2026 12:02:44 +0100 Subject: [PATCH 10/12] Fix: move data files into data/ subfolder --- .../07_final_project_store_manager/{ => data}/products.txt | 0 .../Ali-Salad/07_final_project_store_manager/{ => data}/staff.txt | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename submissions/Ali-Salad/07_final_project_store_manager/{ => data}/products.txt (100%) rename submissions/Ali-Salad/07_final_project_store_manager/{ => data}/staff.txt (100%) diff --git a/submissions/Ali-Salad/07_final_project_store_manager/products.txt b/submissions/Ali-Salad/07_final_project_store_manager/data/products.txt similarity index 100% rename from submissions/Ali-Salad/07_final_project_store_manager/products.txt rename to submissions/Ali-Salad/07_final_project_store_manager/data/products.txt diff --git a/submissions/Ali-Salad/07_final_project_store_manager/staff.txt b/submissions/Ali-Salad/07_final_project_store_manager/data/staff.txt similarity index 100% rename from submissions/Ali-Salad/07_final_project_store_manager/staff.txt rename to submissions/Ali-Salad/07_final_project_store_manager/data/staff.txt From 043e1b4025e1b27fa6192a632be1c3475dcc49ab Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Fri, 26 Jun 2026 12:29:31 +0100 Subject: [PATCH 11/12] Remove old final_project folder --- .../Ali-Salad/final_project/data/products.txt | 2 - .../Ali-Salad/final_project/data/staff.txt | 2 - submissions/Ali-Salad/final_project/main.py | 333 ------------------ .../__pycache__/inventory.cpython-314.pyc | Bin 8184 -> 0 bytes .../models/__pycache__/staff.cpython-314.pyc | Bin 5703 -> 0 bytes .../final_project/models/inventory.py | 93 ----- .../Ali-Salad/final_project/models/staff.py | 68 ---- .../utils/__pycache__/helpers.cpython-314.pyc | Bin 2654 -> 0 bytes .../utils/__pycache__/reports.cpython-314.pyc | Bin 5971 -> 0 bytes .../utils/__pycache__/storage.cpython-314.pyc | Bin 6329 -> 0 bytes .../Ali-Salad/final_project/utils/helpers.py | 42 --- .../Ali-Salad/final_project/utils/reports.py | 70 ---- .../Ali-Salad/final_project/utils/storage.py | 95 ----- 13 files changed, 705 deletions(-) delete mode 100644 submissions/Ali-Salad/final_project/data/products.txt delete mode 100644 submissions/Ali-Salad/final_project/data/staff.txt delete mode 100644 submissions/Ali-Salad/final_project/main.py delete mode 100644 submissions/Ali-Salad/final_project/models/__pycache__/inventory.cpython-314.pyc delete mode 100644 submissions/Ali-Salad/final_project/models/__pycache__/staff.cpython-314.pyc delete mode 100644 submissions/Ali-Salad/final_project/models/inventory.py delete mode 100644 submissions/Ali-Salad/final_project/models/staff.py delete mode 100644 submissions/Ali-Salad/final_project/utils/__pycache__/helpers.cpython-314.pyc delete mode 100644 submissions/Ali-Salad/final_project/utils/__pycache__/reports.cpython-314.pyc delete mode 100644 submissions/Ali-Salad/final_project/utils/__pycache__/storage.cpython-314.pyc delete mode 100644 submissions/Ali-Salad/final_project/utils/helpers.py delete mode 100644 submissions/Ali-Salad/final_project/utils/reports.py delete mode 100644 submissions/Ali-Salad/final_project/utils/storage.py diff --git a/submissions/Ali-Salad/final_project/data/products.txt b/submissions/Ali-Salad/final_project/data/products.txt deleted file mode 100644 index 397c3d2f..00000000 --- a/submissions/Ali-Salad/final_project/data/products.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Store Manager — products data -id|name|category|price|quantity|supplier diff --git a/submissions/Ali-Salad/final_project/data/staff.txt b/submissions/Ali-Salad/final_project/data/staff.txt deleted file mode 100644 index faa83433..00000000 --- a/submissions/Ali-Salad/final_project/data/staff.txt +++ /dev/null @@ -1,2 +0,0 @@ -# Store Manager — staff data -id|name|role|salary|phone diff --git a/submissions/Ali-Salad/final_project/main.py b/submissions/Ali-Salad/final_project/main.py deleted file mode 100644 index 938f49b5..00000000 --- a/submissions/Ali-Salad/final_project/main.py +++ /dev/null @@ -1,333 +0,0 @@ -""" -main.py — Store Manager CLI -============================= -A professional inventory and staff management system. -Covers Python concepts from Sections 1–6: - comments, print/input, conditions, loops, functions, - main(), files (UTF-8), try/except, __str__, @dataclass, composition. - -Python 3.10+ (uses X | Y union type hints) - -Run: - cd store_manager - python main.py -""" -from __future__ import annotations -import sys -from models.inventory import Store -from models.staff import Roster, ROLES -from utils.storage import load_products, save_products, load_staff, save_staff -from utils.reports import ( - inventory_summary, low_stock_report, - staff_summary, category_breakdown, -) -from utils.helpers import ask, ask_float, ask_int, confirm - -_BANNER = r""" - ╔══════════════════════════════════════════╗ - ║ STORE MANAGER v1.0 ║ - ║ Inventory & Staff Management System ║ - ╚══════════════════════════════════════════╝ -""" - -_MAIN_MENU = """ - [1] Inventory - [2] Staff - [3] Reports - [0] Save & Quit -""" - -_INV_MENU = """ - ── Inventory ────────────────────── - [1] Add product - [2] List all products - [3] Search by name - [4] Browse by category - [5] Update product - [6] Restock product - [7] Remove product - [0] Back -""" - -_STAFF_MENU = """ - ── Staff ─────────────────────────── - [1] Hire employee - [2] List all staff - [3] Search by name - [4] Browse by role - [5] Update employee - [6] Fire employee - [0] Back -""" - -_REPORTS_MENU = """ - ── Reports ───────────────────────── - [1] Inventory summary - [2] Low stock alert - [3] Category breakdown - [4] Staff summary - [0] Back -""" - - -# ── Inventory sub-menu ───────────────────────────────────────────────────── - -def menu_inventory(store: Store) -> None: - while True: - print(_INV_MENU) - choice = input(" > ").strip() - - if choice == "1": - print("\n ── Add product ──") - name = ask(" Name : ") - category = ask(" Category : ") - price = ask_float(" Price $ : ", min_val=0.01) - quantity = ask_int(" Qty : ", min_val=0) - supplier = ask(" Supplier (optional, press Enter to skip): ", required=False) or None - p = store.add_product(name, category, price, quantity, supplier) - print(f"\n ✓ Added [{p.id}] {p.name}") - - elif choice == "2": - inventory_summary(store) - - elif choice == "3": - query = ask(" Name query: ") - results = store.find_by_name(query) - if not results: - print(" No products found.") - else: - for p in results: - print(f"\n{p}") - - elif choice == "4": - cats = store.categories() - if not cats: - print(" No categories yet.") - continue - print(" Categories:", ", ".join(cats)) - cat = ask(" Category: ") - results = store.find_by_category(cat) - if not results: - print(" No products in that category.") - else: - for p in results: - print(f"\n{p}") - - elif choice == "5": - pid = ask_int(" Product ID: ", min_val=1) - p = store.find_by_id(pid) - if not p: - print(" ✗ Product not found.") - continue - print(f"\n{p}\n") - new_name = ask(f" New name [{p.name}] (Enter to keep): ", required=False) - new_cat = ask(f" New category [{p.category}] (Enter to keep): ", required=False) - new_price_raw = input(f" New price [${p.price:.2f}] (Enter to keep): ").strip() - new_qty_raw = input(f" New qty [{p.quantity}] (Enter to keep): ").strip() - new_supplier = input(f" New supplier [{p.supplier or 'none'}] (Enter to keep, 'none' to clear): ").strip() - - if new_name: - p.name = new_name - if new_cat: - p.category = new_cat - if new_price_raw: - try: - p.price = float(new_price_raw) - except ValueError: - print(" ✗ Invalid price — skipped.") - if new_qty_raw: - try: - p.quantity = int(new_qty_raw) - except ValueError: - print(" ✗ Invalid quantity — skipped.") - if new_supplier.lower() == "none": - p.supplier = None - elif new_supplier: - p.supplier = new_supplier - print(" ✓ Product updated.") - - elif choice == "6": - pid = ask_int(" Product ID: ", min_val=1) - amount = ask_int(" Add how many units: ", min_val=1) - if store.restock(pid, amount): - p = store.find_by_id(pid) - print(f" ✓ {p.name} now has {p.quantity} units.") # type: ignore[union-attr] - else: - print(" ✗ Product not found.") - - elif choice == "7": - pid = ask_int(" Product ID: ", min_val=1) - p = store.find_by_id(pid) - if not p: - print(" ✗ Product not found.") - continue - print(f"\n{p}\n") - if confirm(" Remove this product? (y/n): "): - store.remove_product(pid) - print(" ✓ Product removed.") - else: - print(" Cancelled.") - - elif choice == "0": - break - else: - print(" ✗ Invalid option.") - - -# ── Staff sub-menu ───────────────────────────────────────────────────────── - -def menu_staff(roster: Roster) -> None: - while True: - print(_STAFF_MENU) - choice = input(" > ").strip() - - if choice == "1": - print("\n ── Hire employee ──") - name = ask(" Name : ") - print(f" Roles : {', '.join(sorted(ROLES))}") - role = ask(" Role : ").lower() - if role not in ROLES: - print(f" ✗ '{role}' is not a valid role. Please choose from the list.") - continue - salary = ask_float(" Salary (monthly $): ", min_val=0) - phone = ask(" Phone (optional, Enter to skip): ", required=False) or None - e = roster.hire(name, role, salary, phone) - print(f"\n ✓ Hired [{e.id}] {e.name} as {e.role}") - - elif choice == "2": - staff_summary(roster) - - elif choice == "3": - query = ask(" Name query: ") - results = roster.find_by_name(query) - if not results: - print(" No employees found.") - else: - for e in results: - print(f"\n{e}") - - elif choice == "4": - print(f" Roles: {', '.join(sorted(ROLES))}") - role = ask(" Role: ").lower() - results = roster.by_role(role) - if not results: - print(" No employees with that role.") - else: - for e in results: - print(f"\n{e}") - - elif choice == "5": - eid = ask_int(" Employee ID: ", min_val=1) - e = roster.find_by_id(eid) - if not e: - print(" ✗ Employee not found.") - continue - print(f"\n{e}\n") - new_name = ask(f" New name [{e.name}] (Enter to keep): ", required=False) - print(f" Roles: {', '.join(sorted(ROLES))}") - new_role = ask(f" New role [{e.role}] (Enter to keep): ", required=False).lower() - new_salary_raw = input(f" New salary [${e.salary:,.2f}] (Enter to keep): ").strip() - new_phone = input(f" New phone [{e.phone or 'none'}] (Enter to keep, 'none' to clear): ").strip() - - if new_name: - e.name = new_name - if new_role: - if new_role not in ROLES: - print(f" ✗ '{new_role}' is not valid — role unchanged.") - else: - e.role = new_role - if new_salary_raw: - try: - e.salary = float(new_salary_raw) - except ValueError: - print(" ✗ Invalid salary — skipped.") - if new_phone.lower() == "none": - e.phone = None - elif new_phone: - e.phone = new_phone - print(" ✓ Employee updated.") - - elif choice == "6": - eid = ask_int(" Employee ID: ", min_val=1) - e = roster.find_by_id(eid) - if not e: - print(" ✗ Employee not found.") - continue - print(f"\n{e}\n") - if confirm(" Remove this employee? (y/n): "): - roster.fire(eid) - print(" ✓ Employee removed.") - else: - print(" Cancelled.") - - elif choice == "0": - break - else: - print(" ✗ Invalid option.") - - -# ── Reports sub-menu ─────────────────────────────────────────────────────── - -def menu_reports(store: Store, roster: Roster) -> None: - while True: - print(_REPORTS_MENU) - choice = input(" > ").strip() - - if choice == "1": - inventory_summary(store) - elif choice == "2": - threshold = ask_int(" Low-stock threshold (default 5): ", min_val=1) if \ - input(" Custom threshold? (y/n): ").strip().lower() == "y" else 5 - low_stock_report(store, threshold) - elif choice == "3": - category_breakdown(store) - elif choice == "4": - staff_summary(roster) - elif choice == "0": - break - else: - print(" ✗ Invalid option.") - - -# ── Entry point ──────────────────────────────────────────────────────────── - -def main() -> None: - store = Store() - roster = Roster() - - print(_BANNER) - print(" Loading data...") - try: - load_products(store) - load_staff(roster) - print(f" ✓ {len(store.products)} product(s) and " - f"{len(roster.employees)} employee(s) loaded.\n") - except Exception as e: - print(f" ⚠ Could not load data: {e}") - - while True: - print(_MAIN_MENU) - choice = input(" > ").strip() - - if choice == "1": - menu_inventory(store) - elif choice == "2": - menu_staff(roster) - elif choice == "3": - menu_reports(store, roster) - elif choice == "0": - print("\n Saving data...", end=" ") - try: - save_products(store) - save_staff(roster) - print("✓") - except Exception as e: - print(f"\n ✗ Save failed: {e}") - print(" Goodbye!\n") - sys.exit(0) - else: - print(" ✗ Choose 0–3.") - - -if __name__ == "__main__": - main() diff --git a/submissions/Ali-Salad/final_project/models/__pycache__/inventory.cpython-314.pyc b/submissions/Ali-Salad/final_project/models/__pycache__/inventory.cpython-314.pyc deleted file mode 100644 index b5021060f4fc79e0311b4df93a47fc70634a9b06..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8184 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 diff --git a/submissions/Ali-Salad/final_project/models/__pycache__/staff.cpython-314.pyc b/submissions/Ali-Salad/final_project/models/__pycache__/staff.cpython-314.pyc deleted file mode 100644 index 2e102395cdd61b4832a276403a4463477ab93fed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5703 zcmcIoU2t2)72dr+(v_?~I~ER!6DxLN5Cj{7DIu|4Oq~!n#DoioPO+`INY_LlOUm9W zz;1_XN@sA|DM>p_BlA$3mVvl^AWR=J(cJ zDV=s_boT7e-QDx|on74-3i=6@f4w#Q-0!`F{2d!^k!^<7p97{uRAQ1DqS6U^N-%{U zHfKVTF)AThN;cY4ix5NvA>mGkHt>R6|(txU5{zGbWF_~rp{t%&D2s^%`kLhz#pep zo0iMvP0h^Yb4FaSe2sBSp3UglbjmpmuSB~+>)!>YM5c*JK~iC+%@hxUw9}F*^uvfM z0w*;%nR6hk>gAkb%Bl}qe%@*W=>lCO8!P9vH8g_{4gQ*{n%ta2V;eEP^BoG27g#ky&wRscLP%*2{I!b z{ShH)=<6nr(Q{O#sxU}ld+}48Yk8P%7Fmw@ps`9oSDx8Co6}4KSM0SOOdXqg($JYP zHI~ib8PZda=Jauvc|o5V*Nx}Rd|_%flhd-v0?R+Ar_3qaN>i?ttUxm9XkSkzt*9&V zKy&vl*vmjtBAY~!{WT=zUA(G8H{miOg9VQY^7!h11O{b1i@i6L+njbff|9FWGAlri z2xmYi1<(qt<2YyYQUNeDj7ZIWHV-2bjCfj($S|Vp98o<%qI!?`67+D>AXr0L2>D~N zvuMv4lw&cAECge*iE(T}ITqs+Cc!b9+bXz-+YZA5k8>~K^A9dahX;<#E{KD3`MAu& zuph${F^q54h9n3C!a*|h?5ySI&Xmk%a=L+f=#Q}|RF*fH1eXKTMVd^_)fRlDv9vff*CcB((?h2F>k3j^ZI0815B zL6uZl^{9ZIs&7z+)A(6Cd}6yZLc*3$vj8@^$A^&XK*H_DJ%_SR=&%8f`#8G=+SV>x zwr#-FhZFAsg23dF+iOV5yQ)~Ne2Z6Cqr9qg?gtN*+bee*sbS4copLfJEI1q2icm)Ty}33EF4?+7*Jp7cmPacT^jtdbVSBL;P1u{}gHPo<;hJ61W(;5c){jx2EhXYab}F3iJej z94xngsO@3gd$wc`XFiUhb-7fR_SD1;AZ0nj*GU`%?suw^_ zay1@Al!vvh*;-q19@4`joJTqZBom%E8J8dr=ZbSW)8Ji%V9lf65)1i)>kz0ui)LCmgamh|Er`Yj zSDOvdA9wC6r0P-?Feuy1v9J5#olw zYY9xC+g>av9;lhtj{?&Rb}Er4$VqsaoNsT1@Gp@PJfBa4j4o>5CuXP}*p%PeR|QKL zxZ5@vF7D=3-|(|KT(b&n)Vj@qYZD&6@H*4x;W?Qd7|!NXTGkjHXiOY|&ka=Pr*Nw4 z9Yb%UWFfmSKU5q^E@x1R{XZF^Xqt#;;S=z{f1 ztOG@yXWBj_3-#on2|y^y*2zX&16Bu<-4jCm7a+{Nx;#O`+!tBI@T>WmB8!`GEz0PSG| zFM}652Bo(>gWD1NzlvN-!v8OBiqQ|jrUu^4@%Fuyw95yZleOzEFpW4aeqGz>*FOFUuT^#+myJxLuyT=yCUprG1Nnj5T zt~j-~;dXb=YEeW5Qb|&k?PJX_-k^2S`L^E~*$zw>KL^6%!AQH?K zd^h{qhfipR1TTihNg%J1zs3$-O@9zOUYb~Zd0jeQ>5P_6EvD9`=%xg(x{U~2-!}s2 ziyI2u6gEPTOg20K6q}wc2er4rg(pW5CoB*Ay{EIBIOsqwN)dlk3MqZ zar<$`McXc}r3~ADl8mwpe1magJPM>LQc7!rK&1^2p?!ZLw}0j#zLT^hS3N{%+Z4jo dUnM~5A~dVKd5{krq7Tt3LCS|d6OiS?{~HC+9<2ZX diff --git a/submissions/Ali-Salad/final_project/models/inventory.py b/submissions/Ali-Salad/final_project/models/inventory.py deleted file mode 100644 index 838598c2..00000000 --- a/submissions/Ali-Salad/final_project/models/inventory.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -models/inventory.py — Product and Store dataclasses. -""" -from __future__ import annotations -from dataclasses import dataclass, field - - -@dataclass -class Product: - id: int - name: str - category: str - price: float - quantity: int - supplier: str | None = None - - def __str__(self) -> str: - supplier_info = f" Supplier : {self.supplier}" if self.supplier else "" - return ( - f" ID : {self.id}\n" - f" Name : {self.name}\n" - f" Category : {self.category}\n" - f" Price : ${self.price:.2f}\n" - f" Qty : {self.quantity}" - + (f"\n{supplier_info}" if supplier_info else "") - ) - - def total_value(self) -> float: - return self.price * self.quantity - - def is_low_stock(self, threshold: int = 5) -> bool: - return self.quantity <= threshold - - -@dataclass -class Store: - products: list[Product] = field(default_factory=list) - _next_id: int = 1 - - def add_product(self, name: str, category: str, price: float, - quantity: int, supplier: str | None = None) -> Product: - p = Product( - id=self._next_id, - name=name, - category=category, - price=price, - quantity=quantity, - supplier=supplier, - ) - self.products.append(p) - self._next_id += 1 - return p - - def find_by_id(self, pid: int) -> Product | None: - for p in self.products: - if p.id == pid: - return p - return None - - def find_by_name(self, query: str) -> list[Product]: - q = query.lower() - return [p for p in self.products if q in p.name.lower()] - - def find_by_category(self, category: str) -> list[Product]: - c = category.lower() - return [p for p in self.products if c in p.category.lower()] - - def remove_product(self, pid: int) -> bool: - for i, p in enumerate(self.products): - if p.id == pid: - self.products.pop(i) - return True - return False - - def restock(self, pid: int, amount: int) -> bool: - p = self.find_by_id(pid) - if p: - p.quantity += amount - return True - return False - - def low_stock_report(self, threshold: int = 5) -> list[Product]: - return [p for p in self.products if p.is_low_stock(threshold)] - - def inventory_value(self) -> float: - return sum(p.total_value() for p in self.products) - - def categories(self) -> list[str]: - return sorted(set(p.category for p in self.products)) - - def sync_ids(self) -> None: - if self.products: - self._next_id = max(p.id for p in self.products) + 1 diff --git a/submissions/Ali-Salad/final_project/models/staff.py b/submissions/Ali-Salad/final_project/models/staff.py deleted file mode 100644 index cd366861..00000000 --- a/submissions/Ali-Salad/final_project/models/staff.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -models/staff.py — Employee and Roster dataclasses. -""" -from __future__ import annotations -from dataclasses import dataclass, field - - -ROLES = {"manager", "cashier", "stock_clerk", "supervisor", "intern"} - - -@dataclass -class Employee: - id: int - name: str - role: str - salary: float - phone: str | None = None - - def __str__(self) -> str: - phone_line = f"\n Phone : {self.phone}" if self.phone else "" - return ( - f" ID : {self.id}\n" - f" Name : {self.name}\n" - f" Role : {self.role}\n" - f" Salary : ${self.salary:,.2f}/mo" - + phone_line - ) - - -@dataclass -class Roster: - employees: list[Employee] = field(default_factory=list) - _next_id: int = 1 - - def hire(self, name: str, role: str, salary: float, - phone: str | None = None) -> Employee: - e = Employee(id=self._next_id, name=name, role=role, - salary=salary, phone=phone) - self.employees.append(e) - self._next_id += 1 - return e - - def find_by_id(self, eid: int) -> Employee | None: - for e in self.employees: - if e.id == eid: - return e - return None - - def find_by_name(self, query: str) -> list[Employee]: - q = query.lower() - return [e for e in self.employees if q in e.name.lower()] - - def fire(self, eid: int) -> bool: - for i, e in enumerate(self.employees): - if e.id == eid: - self.employees.pop(i) - return True - return False - - def total_payroll(self) -> float: - return sum(e.salary for e in self.employees) - - def by_role(self, role: str) -> list[Employee]: - return [e for e in self.employees if e.role == role] - - def sync_ids(self) -> None: - if self.employees: - self._next_id = max(e.id for e in self.employees) + 1 diff --git a/submissions/Ali-Salad/final_project/utils/__pycache__/helpers.cpython-314.pyc b/submissions/Ali-Salad/final_project/utils/__pycache__/helpers.cpython-314.pyc deleted file mode 100644 index be44151c32128ce2fa9f465dc7cba13adbd3ecd8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2654 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 str: - while True: - val = input(prompt).strip() - if val or not required: - return val - print(" ✗ This field cannot be empty.") - - -def ask_float(prompt: str, *, min_val: float = 0.0) -> float: - while True: - raw = input(prompt).strip() - try: - val = float(raw) - if val < min_val: - print(f" ✗ Must be ≥ {min_val}.") - continue - return val - except ValueError: - print(" ✗ Enter a valid number (e.g. 9.99).") - - -def ask_int(prompt: str, *, min_val: int = 0) -> int: - while True: - raw = input(prompt).strip() - try: - val = int(raw) - if val < min_val: - print(f" ✗ Must be ≥ {min_val}.") - continue - return val - except ValueError: - print(" ✗ Enter a whole number.") - - -def confirm(prompt: str = "Are you sure? (y/n): ") -> bool: - return input(prompt).strip().lower() in {"y", "yes"} diff --git a/submissions/Ali-Salad/final_project/utils/reports.py b/submissions/Ali-Salad/final_project/utils/reports.py deleted file mode 100644 index 5862c30c..00000000 --- a/submissions/Ali-Salad/final_project/utils/reports.py +++ /dev/null @@ -1,70 +0,0 @@ -""" -utils/reports.py — Print formatted reports to the terminal. -""" -from __future__ import annotations -from models.inventory import Store -from models.staff import Roster - -_SEP = "─" * 54 - - -def _header(title: str) -> None: - print(f"\n{_SEP}") - print(f" {title.upper()}") - print(_SEP) - - -def inventory_summary(store: Store) -> None: - _header("inventory summary") - if not store.products: - print(" No products on file.") - return - print(f" {'ID':<5} {'Name':<22} {'Category':<14} {'Qty':>5} {'Price':>8}") - print(" " + "·" * 52) - for p in store.products: - flag = " ⚠ LOW" if p.is_low_stock() else "" - print(f" {p.id:<5} {p.name:<22} {p.category:<14} " - f"{p.quantity:>5} ${p.price:>7.2f}{flag}") - print(" " + "·" * 52) - print(f" Total products : {len(store.products)}") - print(f" Inventory value : ${store.inventory_value():,.2f}") - print(f" Categories : {', '.join(store.categories()) or 'none'}") - - -def low_stock_report(store: Store, threshold: int = 5) -> None: - _header(f"low stock report (≤ {threshold} units)") - items = store.low_stock_report(threshold) - if not items: - print(" All products are sufficiently stocked.") - return - for p in items: - print(f" [{p.id}] {p.name} — {p.quantity} left") - - -def staff_summary(roster: Roster) -> None: - _header("staff summary") - if not roster.employees: - print(" No employees on file.") - return - print(f" {'ID':<5} {'Name':<22} {'Role':<14} {'Salary':>10}") - print(" " + "·" * 52) - for e in roster.employees: - print(f" {e.id:<5} {e.name:<22} {e.role:<14} ${e.salary:>9,.2f}") - print(" " + "·" * 52) - print(f" Total staff : {len(roster.employees)}") - print(f" Monthly payroll: ${roster.total_payroll():,.2f}") - - -def category_breakdown(store: Store) -> None: - _header("category breakdown") - if not store.products: - print(" No products on file.") - return - cats: dict[str, tuple[int, float]] = {} - for p in store.products: - qty, val = cats.get(p.category, (0, 0.0)) - cats[p.category] = (qty + p.quantity, val + p.total_value()) - print(f" {'Category':<20} {'Items':>6} {'Value':>12}") - print(" " + "·" * 40) - for cat, (qty, val) in sorted(cats.items()): - print(f" {cat:<20} {qty:>6} ${val:>11,.2f}") diff --git a/submissions/Ali-Salad/final_project/utils/storage.py b/submissions/Ali-Salad/final_project/utils/storage.py deleted file mode 100644 index 8c2057c4..00000000 --- a/submissions/Ali-Salad/final_project/utils/storage.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -utils/storage.py — Read / write products.txt and staff.txt (UTF-8 pipe-delimited). - -Products format: - # comment lines ignored - id|name|category|price|quantity|supplier - 1|USB Cable|Electronics|4.99|30|TechSupply Co. - -Staff format: - # comment lines ignored - id|name|role|salary|phone - 1|Ali Salad|manager|850.00|+252612345678 -""" -from __future__ import annotations -from pathlib import Path -from models.inventory import Product, Store -from models.staff import Employee, Roster - -PRODUCTS_FILE = Path("data/products.txt") -STAFF_FILE = Path("data/staff.txt") - - -# ── Products ────────────────────────────────────────────────────────────────── - -def load_products(store: Store) -> None: - if not PRODUCTS_FILE.exists(): - return - store.products.clear() - with PRODUCTS_FILE.open(encoding="utf-8") as f: - for line in f: - line = line.strip() - if not line or line.startswith("#") or line.startswith("id|"): - continue - parts = line.split("|") - if len(parts) < 5: - continue - try: - store.products.append(Product( - id=int(parts[0]), - name=parts[1], - category=parts[2], - price=float(parts[3]), - quantity=int(parts[4]), - supplier=parts[5] if len(parts) > 5 and parts[5] else None, - )) - except ValueError: - pass - store.sync_ids() - - -def save_products(store: Store) -> None: - PRODUCTS_FILE.parent.mkdir(parents=True, exist_ok=True) - with PRODUCTS_FILE.open("w", encoding="utf-8") as f: - f.write("# Store Manager — products data\n") - f.write("id|name|category|price|quantity|supplier\n") - for p in store.products: - f.write(f"{p.id}|{p.name}|{p.category}|{p.price}|" - f"{p.quantity}|{p.supplier or ''}\n") - - -# ── Staff ───────────────────────────────────────────────────────────────────── - -def load_staff(roster: Roster) -> None: - if not STAFF_FILE.exists(): - return - roster.employees.clear() - with STAFF_FILE.open(encoding="utf-8") as f: - for line in f: - line = line.strip() - if not line or line.startswith("#") or line.startswith("id|"): - continue - parts = line.split("|") - if len(parts) < 4: - continue - try: - roster.employees.append(Employee( - id=int(parts[0]), - name=parts[1], - role=parts[2], - salary=float(parts[3]), - phone=parts[4] if len(parts) > 4 and parts[4] else None, - )) - except ValueError: - pass - roster.sync_ids() - - -def save_staff(roster: Roster) -> None: - STAFF_FILE.parent.mkdir(parents=True, exist_ok=True) - with STAFF_FILE.open("w", encoding="utf-8") as f: - f.write("# Store Manager — staff data\n") - f.write("id|name|role|salary|phone\n") - for e in roster.employees: - f.write(f"{e.id}|{e.name}|{e.role}|{e.salary}|" - f"{e.phone or ''}\n") From d5165043f495af74aa8d4bbfbfa94f3954af1a65 Mon Sep 17 00:00:00 2001 From: Ali-Salad Date: Fri, 26 Jun 2026 12:48:54 +0100 Subject: [PATCH 12/12] Update README with English description --- .../07_final_project_store_manager/README.md | 83 +++---------------- 1 file changed, 10 insertions(+), 73 deletions(-) diff --git a/submissions/Ali-Salad/07_final_project_store_manager/README.md b/submissions/Ali-Salad/07_final_project_store_manager/README.md index 808d697a..68908c8d 100644 --- a/submissions/Ali-Salad/07_final_project_store_manager/README.md +++ b/submissions/Ali-Salad/07_final_project_store_manager/README.md @@ -1,76 +1,13 @@ -# Store Manager — final project -Name: Alisalad +# Store Manager — Final Project -A professional **Inventory & Staff Management System** CLI built in Python 3.10+. -Covers all concepts from Sections 1–6 of the Python bootcamp: comments, `print`/`input`, -conditions, lists and loops, functions and `main()`, files (UTF-8), `try`/`except` on bad -input, `__str__` on a class, `@dataclass`, and composition. +**Student:** Ali Salad +**Bootcamp:** Python for Everyone +**Assignment:** 07 — Final Project ---- +## Description -## Run - -```bash -cd store_manager -python main.py -``` - ---- - -## What's where - -| Path | Purpose | -|---|---| -| `main.py` | Main menu + sub-menu functions | -| `models/inventory.py` | `Product` (one item) + `Store` (all items in memory) | -| `models/staff.py` | `Employee` (one person) + `Roster` (all staff in memory) | -| `utils/storage.py` | Load / save `data/products.txt` and `data/staff.txt` | -| `utils/reports.py` | Formatted terminal reports | -| `utils/helpers.py` | Safe `ask()`, `ask_float()`, `ask_int()`, `confirm()` | -| `data/products.txt` | Saved product roster | -| `data/staff.txt` | Saved staff roster | - ---- - -## Menus - -### Main -``` -[1] Inventory -[2] Staff -[3] Reports -[0] Save & Quit -``` - -### Inventory -Add, list, search by name, browse by category, update, restock, remove. - -### Staff -Hire, list, search by name, browse by role, update, fire. -Valid roles: `manager`, `cashier`, `stock_clerk`, `supervisor`, `intern` - -### Reports -- Inventory summary (with low-stock ⚠ flag) -- Low stock alert (configurable threshold, default ≤ 5 units) -- Category breakdown (units + value per category) -- Staff summary (roles + monthly payroll total) - ---- - -## Data files - -### `data/products.txt` -UTF-8 text. Lines starting with `#` are comments. -Header row: `id|name|category|price|quantity|supplier` -Supplier is optional (empty string if absent). - -### `data/staff.txt` -UTF-8 text. Lines starting with `#` are comments. -Header row: `id|name|role|salary|phone` -Phone is optional (empty string if absent). - ---- - -## Requirements -- Python 3.10+ (uses `str | None` union syntax) -- No third-party libraries — standard library only +This project is a command-line Inventory and Staff Management System built in Python 3.10+. +It allows a store owner to manage products and employees directly from the terminal. +The system supports adding, updating, searching, restocking, and removing products, +as well as hiring, updating, and firing staff members. All data is saved to text files +so it persists between sessions. \ No newline at end of file