diff --git a/inventory/models/users.py b/inventory/models/users.py index a2748c2..c34bce4 100644 --- a/inventory/models/users.py +++ b/inventory/models/users.py @@ -19,6 +19,7 @@ class User(db.Model, ImageAttachable): active: Mapped[Optional[bool]] = mapped_column(Boolean, server_default=text('((0))')) last_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) first_name: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True) + title: Mapped[Optional[str]] = mapped_column(Unicode(255), nullable=True, default=None) location_id: Mapped[Optional[int]] = mapped_column(ForeignKey("rooms.id"), nullable=True, index=True) supervisor_id: Mapped[Optional[int]] = mapped_column(Integer, ForeignKey("users.id"), nullable=True, index=True) image_id: Mapped[Optional[int]] = mapped_column(ForeignKey('images.id', ondelete='SET NULL'), nullable=True, index=True) @@ -35,10 +36,12 @@ class User(db.Model, ImageAttachable): return f"{self.first_name or ''} {self.last_name or ''}".strip() def __init__(self, first_name: Optional[str] = None, last_name: Optional[str] = None, - location_id: Optional[int] = None, supervisor_id: Optional[int] = None, - staff: Optional[bool] = False, active: Optional[bool] = False): + title: Optional[str] = None,location_id: Optional[int] = None, + supervisor_id: Optional[int] = None, staff: Optional[bool] = False, + active: Optional[bool] = False): self.first_name = first_name self.last_name = last_name + self.title = title self.location_id = location_id self.supervisor_id = supervisor_id self.staff = staff @@ -53,6 +56,7 @@ class User(db.Model, ImageAttachable): 'id': self.id, 'first_name': self.first_name, 'last_name': self.last_name, + 'title': self.title, 'location_id': self.location_id, 'supervisor_id': self.supervisor_id, 'staff': self.staff, @@ -66,6 +70,7 @@ class User(db.Model, ImageAttachable): active=bool(data.get("active", False)), last_name=data.get("last_name"), first_name=data.get("first_name"), + title=data.get("title"), location_id=data.get("location_id"), supervisor_id=data.get("supervisor_id") ) \ No newline at end of file diff --git a/inventory/routes/helpers.py b/inventory/routes/helpers.py index cdf2e14..c954a19 100644 --- a/inventory/routes/helpers.py +++ b/inventory/routes/helpers.py @@ -52,6 +52,7 @@ FILTER_MAP = { user_headers = { "Last Name": lambda i: {"text": i.last_name}, "First Name": lambda i: {"text": i.first_name}, + "Title": lambda i: {"text": i.title}, "Supervisor": lambda i: {"text": i.supervisor.identifier, "url": url_for("main.user", id=i.supervisor.id)} if i.supervisor else {"text": None}, "Location": lambda i: {"text": i.location.identifier} if i.location else {"text": None}, "Staff?": lambda i: {"text": i.staff, "type": "bool", "html": checked_box if i.staff else unchecked_box}, diff --git a/inventory/routes/search.py b/inventory/routes/search.py index 243abba..4823074 100644 --- a/inventory/routes/search.py +++ b/inventory/routes/search.py @@ -14,7 +14,7 @@ def search(): if not query: return redirect(url_for('main.index')) - + InventoryAlias = aliased(Inventory) UserAlias = aliased(User) @@ -25,15 +25,18 @@ def search(): Inventory.barcode.ilike(f"%{query}%"), Inventory.notes.ilike(f"%{query}%"), UserAlias.first_name.ilike(f"%{query}%"), - UserAlias.last_name.ilike(f"%{query}%") + UserAlias.last_name.ilike(f"%{query}%"), + UserAlias.title.ilike(f"%{query}%") )) inventory_results = inventory_query.all() user_query = eager_load_user_relationships(db.session.query(User).join(UserAlias, User.supervisor)).filter( or_( User.first_name.ilike(f"%{query}%"), User.last_name.ilike(f"%{query}%"), + User.title.ilike(f"%{query}%"), UserAlias.first_name.ilike(f"%{query}%"), - UserAlias.last_name.ilike(f"%{query}%") + UserAlias.last_name.ilike(f"%{query}%"), + UserAlias.title.ilike(f"%{query}%") )) user_results = user_query.all() worklog_query = eager_load_worklog_relationships(db.session.query(WorkLog).join(UserAlias, WorkLog.contact).join(InventoryAlias, WorkLog.work_item)).filter( @@ -41,26 +44,27 @@ def search(): WorkLog.notes.ilike(f"%{query}%"), UserAlias.first_name.ilike(f"%{query}%"), UserAlias.last_name.ilike(f"%{query}%"), + UserAlias.title.ilike(f"%{query}%"), InventoryAlias.name.ilike(f"%{query}%"), InventoryAlias.serial.ilike(f"%{query}%"), InventoryAlias.barcode.ilike(f"%{query}%") )) worklog_results = worklog_query.all() - + results = { 'inventory': { - 'results': inventory_query, - 'headers': inventory_headers, + 'results': inventory_query, + 'headers': inventory_headers, 'rows': [{"id": item.id, "cells": [fn(item) for fn in inventory_headers.values()]} for item in inventory_results] }, 'users': { - 'results': user_query, - 'headers': user_headers, + 'results': user_query, + 'headers': user_headers, 'rows': [{"id": user.id, "cells": [fn(user) for fn in user_headers.values()]} for user in user_results] }, 'worklog': { - 'results': worklog_query, - 'headers': worklog_headers, + 'results': worklog_query, + 'headers': worklog_headers, 'rows': [{"id": log.id, "cells": [fn(log) for fn in worklog_headers.values()]} for log in worklog_results] } } diff --git a/inventory/routes/user.py b/inventory/routes/user.py index 6ffff6e..c1305fc 100644 --- a/inventory/routes/user.py +++ b/inventory/routes/user.py @@ -70,6 +70,32 @@ def user(id): worklog_rows=[{"id": log.id, "cells": [fn(log) for fn in filtered_worklog_headers.values()]} for log in worklog] ) +@main.route("/user//org") +def user_org(id): + user = eager_load_user_relationships(db.session.query(User).filter(User.id == id)).first() + if not user: + return render_template('error.html', title='User Not Found', message=f'User with ID {id} not found.') + + current_user = user + org_chart = [] + while current_user: + subordinates = ( + eager_load_user_relationships( + db.session.query(User).filter(User.supervisor_id == current_user.id) + ).all() + ) + org_chart.insert(0, { + "user": current_user, + "subordinates": [subordinate for subordinate in subordinates if subordinate.active and subordinate.staff] + }) + current_user = current_user.supervisor + + return render_template( + "user_org.html", + user=user, + org_chart=org_chart + ) + @main.route("/user/new", methods=["GET"]) def new_user(): rooms = eager_load_room_relationships(db.session.query(Room)).all() @@ -116,6 +142,7 @@ def update_user(id): user.active = bool(data.get("active", user.active)) user.last_name = data.get("last_name", user.last_name) user.first_name = data.get("first_name", user.first_name) + user.title = data.get("title", user.title) user.location_id = data.get("location_id", user.location_id) user.supervisor_id = data.get("supervisor_id", user.supervisor_id) @@ -155,6 +182,7 @@ def get_user_csv(): "active", "last_name", "first_name", + "title", "location", "supervisor" ] diff --git a/inventory/templates/user.html b/inventory/templates/user.html index 96a0a83..b8b0a96 100644 --- a/inventory/templates/user.html +++ b/inventory/templates/user.html @@ -12,6 +12,7 @@ active: document.querySelector("input[name='activeCheck']").checked, last_name: document.querySelector("input[name='lastName']").value, first_name: document.querySelector("input[name='firstName']").value, + title: document.querySelector("input[name='title']").value, supervisor_id: parseInt(document.querySelector("input[name='supervisor']").value) || null, location_id: parseInt(document.querySelector("input[name='location']").value) || null }; @@ -84,15 +85,20 @@
-
+
-
+
+ +
+ + +
diff --git a/inventory/templates/user_org.html b/inventory/templates/user_org.html new file mode 100644 index 0000000..95c7244 --- /dev/null +++ b/inventory/templates/user_org.html @@ -0,0 +1,36 @@ +{% extends 'layout.html' %} + +{% block content %} +{% for layer in org_chart %} + {# Only show the top-level user on the first iteration #} + {% if loop.first %} +
+
+ {{ layer.user.identifier }} +
+
+ {% endif %} + + {# Render this layer's subordinates #} + {% if layer['subordinates'] %} +
+ {% set current_index = loop.index0 %} + {% set next_user = org_chart[current_index + 1].user if current_index + 1 < org_chart|length else None %} + + {% for subordinate in layer.subordinates %} +
+ {% if subordinate == user %} + {{ subordinate.identifier }} + {% else %} + + {{ subordinate.identifier }} + + {% endif %} +
+ {% endfor %} +
+ {% endif %} +{% endfor %} + +{% endblock %} \ No newline at end of file