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.)

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + 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 %} 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 %} -
+

{{ user.name }} ({{ user.identification }})

- {{ loans|length }} tablet{{ 's' if loans|length != 1 else '' }} loaned + + {{ active_loans|length }} active, {{ returned_loans|length }} past +
- {% if loans %} - - - - - - - - - - - - {% for loan in loans %} - - - - - - + {% if active_loans %} +
+

Current Loans

+
TabletSerial NumberLoan DateReturn DateStatus
{{ loan.brand }} {{ loan.model }}{{ loan.serial_number }}{{ loan.loan_date }}{{ loan.return_date or '-' }} - - {{ loan.status }} - -
+ + + + + + - {% endfor %} - -
TabletSerial NumberLoan DateStatus
- {% else %} + + + {% for loan in active_loans %} + + {{ loan.brand }} {{ loan.model }} + {{ loan.serial_number }} + {{ loan.loan_date }} + + + {{ loan.status }} + + + + {% endfor %} + + +
+ {% endif %} + + {% if returned_loans %} +
+

+ +

+ +
+ {% endif %} + + {% if not active_loans and not returned_loans %}

No loans recorded for this user.

{% endif %}
@@ -56,6 +109,32 @@ {% endif %}
+ +