diff --git a/README.md b/README.md index b880960..4a75b92 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,16 @@ A simple SQLite-based system for managing tablet lending and returns. - **Return Management**: Record when tablets are returned - **History Tracking**: Complete history of all loans and returns - **User Loans View**: See all tablets loaned to each user (demonstrates one-to-many relationship) +- **Non-Loanable Devices**: Track inventory devices that cannot be loaned (e.g., projectors, monitors) - **Project Notes**: Markdown editor for development documentation ## Files -- `minimal_app.py` - Interactive command-line application +- `minimal_app.py` - Interactive command-line application (includes non-loanable device management) - `test_app.py` - Test script that demonstrates functionality -- `tablets.db` - SQLite database (created automatically) -- `simple_app.py` - Web-based version (requires Flask) -- `app.py` - Alternative web version (requires Flask) +- `tablets.db` - SQLite database (created automatically, includes non_loanable_devices table) +- `simple_app.py` - Web-based version (requires Flask, includes non-loanable device management) +- `app.py` - Alternative web version (requires Flask, includes non-loanable device management) ## Quick Start @@ -144,6 +145,7 @@ python3 app.py - **Loan Tablet**: Loan a tablet to a user (with search for large datasets) - **User Loans**: View all tablets loaned to each user - demonstrates the **one-to-many relationship** (one user, multiple tablets) - **Loan History**: Complete history of all loan and return transactions +- **Non-Loanable Devices**: Manage inventory of devices that cannot be loaned (add, edit, delete, view) - **Project Management**: Markdown editor for development notes (saved to `notes_development/project_notes.md`) ## Database Backup diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index ad421ad..f0c6929 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -5,6 +5,17 @@ El sistema debe permitir la gestión del préstamo y devolución de dispositivos tablets entre un inventario y un conjunto de usuarios. El enfoque principal es el seguimiento del estado de cada dispositivo y su relación con los usuarios que los han tomado prestados. --- +## Requisitos Faltantes + +### Gestión de dispositivos no prestables + +✅ **IMPLEMENTADO** - El sistema ahora permite: +- Registrar nuevos dispositivos no prestables en un inventario separado (tabla `non_loanable_devices`) +- Gestionar dispositivos no prestables a través de: + - Interfaz web (app.py): /non_loanable_devices, /add_non_loanable_device, /edit_non_loanable_device, /delete_non_loanable_device + - Interfaz CLI (minimal_app.py): Opciones 8, 9, 10 del menú +- La implementación utiliza una tabla separada en la misma base de datos con los siguientes campos: + - brand, model, serial_number (único), device_type, location, status, notes, purchase_date, purchase_cost ## Requisitos Funcionales diff --git a/app.py b/app.py index a5332b7..7dfba4d 100644 --- a/app.py +++ b/app.py @@ -63,6 +63,22 @@ def init_db(): ) ''') + # Create non_loanable_devices table for devices that cannot be loaned + cursor.execute(''' + CREATE TABLE IF NOT EXISTS non_loanable_devices ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + brand TEXT NOT NULL, + model TEXT NOT NULL, + serial_number TEXT UNIQUE NOT NULL, + device_type TEXT NOT NULL, + location TEXT, + status TEXT DEFAULT 'available', + notes TEXT, + purchase_date TEXT, + purchase_cost REAL + ) + ''') + conn.commit() @app.route('/') @@ -319,6 +335,99 @@ def user_loans(): return render_template('user_loans.html', users_with_loans=user_loans_list) +@app.route('/non_loanable_devices') +def non_loanable_devices(): + """Show all non-loanable devices""" + with get_db() as conn: + cursor = conn.cursor() + cursor.execute("SELECT * FROM non_loanable_devices ORDER BY device_type, brand, model") + devices = cursor.fetchall() + + return render_template('non_loanable_devices.html', devices=devices) + + +@app.route('/add_non_loanable_device', methods=['GET', 'POST']) +def add_non_loanable_device(): + """Add a new non-loanable device to inventory""" + if request.method == 'POST': + brand = request.form['brand'] + model = request.form['model'] + serial_number = request.form['serial_number'] + device_type = request.form['device_type'] + location = request.form['location'] + notes = request.form['notes'] + purchase_date = request.form.get('purchase_date', '') + purchase_cost = request.form.get('purchase_cost', '') + + try: + with get_db() as conn: + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO non_loanable_devices + (brand, model, serial_number, device_type, location, status, notes, purchase_date, purchase_cost) + VALUES (?, ?, ?, ?, ?, 'available', ?, ?, ?) + ''', (brand, model, serial_number, device_type, location, notes, purchase_date, purchase_cost)) + conn.commit() + flash('Non-loanable device added successfully!', 'success') + except sqlite3.IntegrityError: + flash('Error: Serial number already exists!', 'error') + + return redirect(url_for('non_loanable_devices')) + + return render_template('add_non_loanable_device.html') + + +@app.route('/edit_non_loanable_device/', methods=['GET', 'POST']) +def edit_non_loanable_device(device_id): + """Edit a non-loanable device""" + with get_db() as conn: + cursor = conn.cursor() + cursor.execute("SELECT * FROM non_loanable_devices WHERE id = ?", (device_id,)) + device = cursor.fetchone() + + if not device: + flash('Error: Device not found!', 'error') + return redirect(url_for('non_loanable_devices')) + + if request.method == 'POST': + brand = request.form['brand'] + model = request.form['model'] + serial_number = request.form['serial_number'] + device_type = request.form['device_type'] + location = request.form['location'] + status = request.form['status'] + notes = request.form['notes'] + purchase_date = request.form.get('purchase_date', '') + purchase_cost = request.form.get('purchase_cost', '') + + try: + cursor.execute(''' + UPDATE non_loanable_devices SET + brand = ?, model = ?, serial_number = ?, device_type = ?, + location = ?, status = ?, notes = ?, purchase_date = ?, purchase_cost = ? + WHERE id = ? + ''', (brand, model, serial_number, device_type, location, status, notes, purchase_date, purchase_cost, device_id)) + conn.commit() + flash('Device updated successfully!', 'success') + return redirect(url_for('non_loanable_devices')) + except sqlite3.IntegrityError: + flash('Error: Serial number already exists!', 'error') + + return render_template('edit_non_loanable_device.html', device=device) + + +@app.route('/delete_non_loanable_device/') +def delete_non_loanable_device(device_id): + """Delete a non-loanable device""" + with get_db() as conn: + cursor = conn.cursor() + cursor.execute("DELETE FROM non_loanable_devices WHERE id = ?", (device_id,)) + conn.commit() + flash('Device deleted successfully!', 'success') + + return redirect(url_for('non_loanable_devices')) + + if __name__ == '__main__': # Initialize database init_db() diff --git a/gestiontablets.sh b/gestiontablets.sh new file mode 100644 index 0000000..1d4b29d --- /dev/null +++ b/gestiontablets.sh @@ -0,0 +1 @@ +vibe --resume fa1bb230 diff --git a/minimal_app.py b/minimal_app.py index ff0295a..381a26d 100644 --- a/minimal_app.py +++ b/minimal_app.py @@ -44,6 +44,22 @@ def init_db(): ) ''') + # Create non_loanable_devices table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS non_loanable_devices ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + brand TEXT NOT NULL, + model TEXT NOT NULL, + serial_number TEXT UNIQUE NOT NULL, + device_type TEXT NOT NULL, + location TEXT, + status TEXT DEFAULT 'available', + notes TEXT, + purchase_date TEXT, + purchase_cost REAL + ) + ''') + conn.commit() conn.close() @@ -202,6 +218,59 @@ def show_loan_history(): conn.close() + +def add_non_loanable_device(brand, model, serial_number, device_type, location='', notes='', purchase_date='', purchase_cost=None): + """Add a new non-loanable device to inventory""" + conn = sqlite3.connect('tablets.db') + cursor = conn.cursor() + + try: + cursor.execute(''' + INSERT INTO non_loanable_devices + (brand, model, serial_number, device_type, location, status, notes, purchase_date, purchase_cost) + VALUES (?, ?, ?, ?, ?, 'available', ?, ?, ?) + ''', (brand, model, serial_number, device_type, location, notes, purchase_date, purchase_cost)) + conn.commit() + print(f"✓ Added non-loanable device: {brand} {model} ({serial_number}) - Type: {device_type}") + except sqlite3.IntegrityError: + print(f"✗ Error: Serial number {serial_number} already exists") + finally: + conn.close() + + +def show_non_loanable_devices(): + """Show all non-loanable devices""" + conn = sqlite3.connect('tablets.db') + cursor = conn.cursor() + + cursor.execute("SELECT id, brand, model, serial_number, device_type, location, status FROM non_loanable_devices") + devices = cursor.fetchall() + + print("\n=== Non-Loanable Devices ===") + if devices: + for device in devices: + print(f"ID: {device[0]}, Type: {device[4]}, {device[1]} {device[2]} ({device[3]}), Location: {device[5] or 'N/A'}, Status: {device[6]}") + else: + print("No non-loanable devices") + + conn.close() + + +def delete_non_loanable_device(device_id): + """Delete a non-loanable device""" + conn = sqlite3.connect('tablets.db') + cursor = conn.cursor() + + cursor.execute("DELETE FROM non_loanable_devices WHERE id = ?", (device_id,)) + conn.commit() + + if cursor.rowcount > 0: + print(f"✓ Non-loanable device {device_id} deleted") + else: + print(f"✗ Error: Device {device_id} not found") + + conn.close() + def main(): """Main menu""" init_db() @@ -218,9 +287,12 @@ def main(): print("5. Show Available Tablets") print("6. Show Active Loans") print("7. Show Loan History") - print("8. Exit") + print("8. Add Non-Loanable Device") + print("9. Show Non-Loanable Devices") + print("10. Delete Non-Loanable Device") + print("11. Exit") - choice = input("Enter your choice (1-8): ") + choice = input("Enter your choice (1-11): ") if choice == '1': print("\n=== Add Tablet ===") @@ -269,6 +341,28 @@ def main(): show_loan_history() elif choice == '8': + print("\n=== Add Non-Loanable Device ===") + brand = input("Brand: ") + model = input("Model: ") + serial_number = input("Serial Number: ") + device_type = input("Device Type (e.g., projector, monitor): ") + location = input("Location (optional): ") + notes = input("Notes (optional): ") + add_non_loanable_device(brand, model, serial_number, device_type, location, notes) + + elif choice == '9': + show_non_loanable_devices() + + elif choice == '10': + print("\n=== Delete Non-Loanable Device ===") + show_non_loanable_devices() + device_id = input("Enter Device ID to delete: ") + try: + delete_non_loanable_device(int(device_id)) + except ValueError: + print("✗ Error: Invalid ID format") + + elif choice == '11': print("Goodbye!") break diff --git a/simple_app.py b/simple_app.py index f50478d..0674035 100644 --- a/simple_app.py +++ b/simple_app.py @@ -62,6 +62,22 @@ def init_db(): ) ''') + # Create non_loanable_devices table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS non_loanable_devices ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + brand TEXT NOT NULL, + model TEXT NOT NULL, + serial_number TEXT UNIQUE NOT NULL, + device_type TEXT NOT NULL, + location TEXT, + status TEXT DEFAULT 'available', + notes TEXT, + purchase_date TEXT, + purchase_cost REAL + ) + ''') + conn.commit() class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): diff --git a/templates/add_non_loanable_device.html b/templates/add_non_loanable_device.html new file mode 100644 index 0000000..ae0489e --- /dev/null +++ b/templates/add_non_loanable_device.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} + +{% block content %} +

Add Non-Loanable Device

+

Add a device that will be tracked in inventory but cannot be loaned to users (e.g., projectors, monitors, etc.)

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + Cancel +
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html index 629882b..e93f5f7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -144,6 +144,7 @@ Loan Tablet Loan History User Loans + Non-Loanable Devices Project Management diff --git a/templates/edit_non_loanable_device.html b/templates/edit_non_loanable_device.html new file mode 100644 index 0000000..80c90b4 --- /dev/null +++ b/templates/edit_non_loanable_device.html @@ -0,0 +1,71 @@ +{% extends "base.html" %} + +{% block content %} +

Edit Non-Loanable Device

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + Cancel +
+
+{% endblock %} diff --git a/templates/non_loanable_devices.html b/templates/non_loanable_devices.html new file mode 100644 index 0000000..a27e6ac --- /dev/null +++ b/templates/non_loanable_devices.html @@ -0,0 +1,43 @@ +{% extends "base.html" %} + +{% block content %} +
+

Non-Loanable Devices Inventory

+

These devices are tracked in inventory but cannot be loaned to users.

+ Add Non-Loanable Device + + {% if devices %} + + + + + + + + + + + + + + {% for device in devices %} + + + + + + + + + + {% endfor %} + +
TypeBrandModelSerial NumberLocationStatusActions
{{ device.device_type }}{{ device.brand }}{{ device.model }}{{ device.serial_number }}{{ device.location or '-' }}{{ device.status }} + Edit + Delete +
+ {% else %} +

No non-loanable devices registered.

+ {% endif %} +
+{% endblock %}