diff --git a/README.md b/README.md
index 58fab68..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
@@ -153,6 +155,17 @@ To backup your data:
cp tablets.db tablets_backup_$(date +%Y%m%d).db
```
+## Version Control
+
+This project uses Git for version control with the following structure:
+
+- `master` - Production-ready releases
+- `develop` - Integration branch for features
+- `feature/*` - Individual feature branches
+- `hotfix/*` - Urgent bug fixes
+
+See `CONTRIBUTING.md` for detailed workflow and commit conventions.
+
## License
This is a simple educational project. Feel free to use and modify it as needed.
\ No newline at end of file
diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md
new file mode 100644
index 0000000..f0c6929
--- /dev/null
+++ b/REQUIREMENTS.md
@@ -0,0 +1,221 @@
+# Requisitos del Sistema de Gestión de Préstamos de Tablets
+
+## Descripción General
+
+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
+
+### Gestión de Dispositivos
+
+El sistema debe permitir:
+- Registrar nuevos dispositivos en el inventario
+- Identificar cada dispositivo de manera única
+- Almacenar información descriptiva de cada dispositivo (marca, modelo, número de serie)
+- Consultar el estado actual de cada dispositivo (disponible, prestado)
+
+### Gestión de Usuarios
+
+El sistema debe permitir:
+- Registrar nuevos usuarios
+- Identificar cada usuario de manera única
+- Almacenar información de contacto básica (nombre, identificación)
+- Consultar la información de los usuarios registrados
+
+### Gestión de Préstamos
+
+El sistema debe permitir:
+- Asignar un dispositivo disponible a un usuario
+- Registrar la fecha y hora del préstamo
+- Registrar la fecha y hora de la devolución
+- Consultar el historial de préstamos de un dispositivo
+- Consultar el historial de préstamos de un usuario
+- Visualizar todos los dispositivos actualmente prestados
+
+### Consulta y Reportes
+
+El sistema debe permitir:
+- Buscar dispositivos por cualquier atributo (marca, modelo, número de serie)
+- Buscar usuarios por cualquier atributo (nombre, identificación)
+- Filtrar préstamos por estado (activos, finalizados)
+- Filtrar préstamos por dispositivo
+- Filtrar préstamos por usuario
+- Ver el historial completo de préstamos y devoluciones
+
+---
+
+## Modelo de Datos
+
+### Entidades Principales
+
+1. **Dispositivo**
+ - Identificador único
+ - Marca
+ - Modelo
+ - Número de serie (único)
+ - Estado (disponible, prestado)
+
+2. **Usuario**
+ - Identificador único
+ - Nombre
+ - Identificación (única)
+
+3. **Préstamo**
+ - Identificador único
+ - Dispositivo asociado
+ - Usuario asociado
+ - Fecha de préstamo
+ - Fecha de devolución (opcional)
+ - Estado (activo, finalizado)
+
+### Relaciones
+
+- Un **usuario** puede tener **múltiples préstamos** (relación uno a muchos)
+- Un **dispositivo** solo puede estar en **un préstamo activo** a la vez
+- Un **préstamo** relaciona exactamente un dispositivo con un usuario
+- El historial de préstamos permite rastrear todos los movimientos de cada dispositivo
+
+### Reglas de Negocio
+
+1. Un dispositivo en estado "prestado" no puede ser prestado a otro usuario
+2. Un préstamo finalizado debe registrar fecha de devolución
+3. La identificación de dispositivos y usuarios debe ser única
+4. Al devolver un dispositivo, este vuelve al estado "disponible"
+
+---
+
+## Flujos de Trabajo
+
+### Registro de un Nuevo Dispositivo
+
+1. El sistema solicita la información del dispositivo (marca, modelo, número de serie)
+2. El sistema valida que el número de serie no esté registrado
+3. El sistema registra el dispositivo con estado "disponible"
+4. El sistema confirma el registro
+
+### Registro de un Nuevo Usuario
+
+1. El sistema solicita la información del usuario (nombre, identificación)
+2. El sistema valida que la identificación no esté registrada
+3. El sistema registra el usuario
+4. El sistema confirma el registro
+
+### Préstamo de un Dispositivo
+
+1. El sistema muestra los dispositivos disponibles
+2. El usuario selecciona un dispositivo
+3. El sistema muestra los usuarios registrados
+4. El usuario selecciona un usuario
+5. El sistema registra el préstamo con:
+ - Dispositivo seleccionado
+ - Usuario seleccionado
+ - Fecha y hora actual
+ - Estado "activo"
+6. El sistema actualiza el estado del dispositivo a "prestado"
+7. El sistema confirma el préstamo
+
+### Devolución de un Dispositivo
+
+1. El sistema muestra los préstamos activos
+2. El usuario selecciona un préstamo
+3. El sistema registra la fecha y hora de devolución
+4. El sistema actualiza el estado del préstamo a "finalizado"
+5. El sistema actualiza el estado del dispositivo a "disponible"
+6. El sistema confirma la devolución
+
+### Consulta de Préstamos por Usuario
+
+1. El sistema muestra la lista de usuarios
+2. El usuario selecciona un usuario
+3. El sistema muestra:
+ - Dispositivos actualmente prestados (préstamos activos)
+ - Historial de préstamos pasados (opcional: expandible)
+4. El sistema permite buscar por cualquier campo
+
+---
+
+## Requisitos No Funcionales
+
+### Usabilidad
+
+- La interfaz debe permitir búsqueda eficiente en listas grandes (500+ elementos)
+- La visualización de información debe ser clara y organizada
+- Los estados deben ser fácilmente identificables
+- Las acciones principales deben ser accesibles
+
+### Escalabilidad
+
+- El diseño debe soportar un crecimiento en el número de dispositivos y usuarios
+- La búsqueda debe ser eficiente
+- El historial debe ser consultable sin afectar el rendimiento
+
+### Mantenibilidad
+
+- La estructura de datos debe ser clara
+- Las relaciones entre entidades deben estar bien definidas
+- Los flujos de trabajo deben estar documentados
+
+---
+
+## Diseño de Interfaz (Conceptual)
+
+### Vistas Principales
+
+1. **Vista de Inventario**
+ - Lista de dispositivos
+ - Filtros por estado
+ - Búsqueda
+
+2. **Vista de Usuarios**
+ - Lista de usuarios
+ - Búsqueda
+
+3. **Vista de Préstamos Activos**
+ - Lista de préstamos en curso
+ - Filtros por dispositivo/usuario
+ - Acciones: devolver dispositivo
+
+4. **Vista de Historial**
+ - Lista completa de préstamos
+ - Filtros por fecha, usuario, dispositivo, estado
+ - Detalles de cada préstamo
+
+5. **Vista de Préstamos por Usuario**
+ - Lista de usuarios
+ - Para cada usuario: dispositivos actualmente prestados
+ - Opcional: historial de préstamos pasados (expandible)
+
+### Elementos de Interacción
+
+- Campos de búsqueda en todas las listas
+- Botones de acción claros (Prestar, Devolver, Registrar)
+- Indicadores visuales de estado (colores, iconos)
+- Confirmación de acciones críticas
+
+---
+
+## Notas de Implementación
+
+Este documento describe los requisitos a nivel de diseño. La implementación específica (lenguaje de programación, base de datos, framework, arquitectura) queda a criterio del equipo de desarrollo y puede variar según las necesidades técnicas.
+
+Los detalles técnicos como:
+- Tecnologías específicas
+- Estructura de base de datos física
+- APIs
+- Autenticación
+- Despliegue
+
+Deberán ser definidos en la fase de diseño técnico y pueden evolucionar sin afectar los requisitos funcionales descritos aquí.
diff --git a/TECHNICAL_SPECIFICATIONS.md b/TECHNICAL_SPECIFICATIONS.md
new file mode 100644
index 0000000..8a17aef
--- /dev/null
+++ b/TECHNICAL_SPECIFICATIONS.md
@@ -0,0 +1,149 @@
+# Especificaciones Técnicas
+
+Documento complementario a `REQUIREMENTS.md` con propuestas concretas de implementación técnica.
+
+---
+
+## 1. Concurrencia en Base de Datos
+
+| Opción | Descripción | Ventajas | Desventajas |
+|--------|-------------|---------|-------------|
+| **SQLite + WAL** | Modo Write-Ahead Logging | Zero-config, embebido, buena para <100 conexiones | Limitado a 1 escritor simultáneo |
+| **PostgreSQL** | Base de datos relacional | Concurrencia completa, transacciones ACID | Requiere servidor |
+| **Transacciones** | Aislamiento READ COMMITTED | Previene lecturas sucias | Bloqueos temporales |
+| **Locking optimista** | Versión de fila en actualizaciones | Sin bloqueos | Conflictos requieren reintento |
+
+**Recomendación**: SQLite con WAL para prototipos, PostgreSQL para producción. Usar transacciones para operaciones críticas (préstamo/devolución).
+
+---
+
+## 2. Autenticación Básica
+
+| Opción | Descripción | Complejidad |
+|--------|-------------|-------------|
+| **Sesiones (Flask)** | Cookies firmadas | Baja |
+| **JWT** | Tokens sin estado | Media |
+| **OAuth 2.0** | Delegación a proveedores | Alta |
+| **Basic Auth** | HTTP Basic Authentication | Mínima |
+
+**Flujo mínimo**:
+```
+Login (user/pass) → Session/JWT → Middleware verifica rol → Acceso a endpoints
+```
+
+**Roles sugeridos**:
+- `admin`: CRUD completo, gestión de usuarios
+- `staff`: Préstamos/devoluciones
+- `viewer`: Solo consulta
+
+---
+
+## 3. Separación Frontend/Backend
+
+| Arquitectura | Descripción | Stack Ejemplo |
+|--------------|-------------|---------------|
+| **Monolítica + Templates** | Servidor renderiza HTML | Flask+Jinja2, Django |
+| **API + SPA** | Backend API, frontend dinámico | FastAPI + React/Vue |
+| **API + SSR** | Backend API + renderizado servidor | Next.js + API |
+| **Microservicios** | Servicios independientes | Backend API + Frontend estático |
+
+**Para este proyecto**:
+- **Opción A**: Flask + Jinja2 (simple, suficiente para requisitos)
+- **Opción B**: FastAPI + React (escalable, separable)
+
+---
+
+## 4. Búsqueda Eficiente (500+ elementos)
+
+| Opción | Descripción | Implementación |
+|--------|-------------|----------------|
+| **Índices DB** | B-tree en columnas buscadas | `CREATE INDEX idx_brand ON tablets(brand)` |
+| **Búsqueda full-text** | Búsqueda en texto | SQLite FTS5, PostgreSQL tsvector |
+| **Filtrado cliente** | JavaScript filtra datos | Ideal para <2000 elementos |
+| **Paginación** | Dividir resultados | `LIMIT 50 OFFSET 0` |
+
+**Recomendación**: Índices en `serial_number`, `brand`, `model`, `user.name`, `user.identification`.
+
+---
+
+## 5. Estructura de Proyecto Propuesta
+
+```
+project/
+├── backend/
+│ ├── models/ # Entidades (Device, User, Loan)
+│ ├── services/ # Lógica de negocio
+│ ├── repositories/ # Acceso a datos
+│ ├── routes/ # Endpoints/API
+│ └── app.py # Configuración
+├── frontend/ # Opcional para opción B
+│ ├── public/
+│ └── src/
+├── migrations/ # Control de cambios DB
+├── tests/
+└── docs/
+```
+
+---
+
+## 6. Decisiones Clave por Tomar
+
+| Decisión | Opciones | Impacto |
+|----------|----------|---------|
+| Base de datos | SQLite / PostgreSQL | Concurrencia, despliegue |
+| Autenticación | Sesiones / JWT / Ninguna | Seguridad, complejidad |
+| Frontend | Templates / SPA / Ninguno | Experiencia usuario |
+| Despliegue | Local / Docker / Cloud | Escalabilidad |
+
+---
+
+## 7. Ejemplo de Implementación Mínima (Pseudocódigo)
+
+```python
+# modelos.py
+class Device:
+ id: int (PK)
+ serial_number: str (unique)
+ status: str (available/loaned)
+ loans: List[Loan] (one-to-many)
+
+class User:
+ id: int (PK)
+ identification: str (unique)
+ loans: List[Loan] (one-to-many)
+
+class Loan:
+ id: int (PK)
+ device_id: int (FK)
+ user_id: int (FK)
+ loan_date: datetime
+ return_date: datetime (nullable)
+ status: str (active/returned)
+
+# servicios/loan_service.py
+def loan_device(device_id: int, user_id: int) -> Loan:
+ with transaction(): # Atomicidad
+ device = get_device_for_update(device_id)
+ if device.status != 'available':
+ raise DeviceNotAvailableError()
+
+ loan = Loan.create(
+ device_id=device_id,
+ user_id=user_id,
+ loan_date=now(),
+ status='active'
+ )
+ device.status = 'loaned'
+ return loan
+```
+
+---
+
+## 8. Consideraciones Adicionales
+
+- **Validaciones**: Únicas (serial_number, identification) a nivel de base de datos
+- **Auditoría**: Campos `created_at`, `updated_at` en todas las entidades
+- **Backups**: Script para exportar base de datos periódicamente
+- **Logging**: Registrar préstamos/devoluciones para auditoría
+- **Versionado API**: `/api/v1/...` para compatibilidad futura
+- **Pruebas**: Cubrir flujos críticos (préstamo con dispositivo no disponible, devolución de préstamo inexistente)
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.)
+{% endblock %}
diff --git a/templates/user_loans.html b/templates/user_loans.html
index 8c10045..07fac86 100644
--- a/templates/user_loans.html
+++ b/templates/user_loans.html
@@ -7,45 +7,98 @@
This page demonstrates the many-to-many relationship between users and tablets via the loans table.
-
+
+
+
+
+
+
+
{% for user_data in users_with_loans %}
{% set user = user_data.user %}
{% set loans = user_data.loans %}
+ {% set active_loans = loans|selectattr('status', 'equalto', 'active')|list %}
+ {% set returned_loans = loans|selectattr('status', 'equalto', 'returned')|list %}
-