diff --git a/inventory/routes/entry.py b/inventory/routes/entry.py index 951d56e..2f8ab7d 100644 --- a/inventory/routes/entry.py +++ b/inventory/routes/entry.py @@ -81,7 +81,11 @@ def _fields_for_model(model: str): "title", "active", "staff", - "supervisor.id" + "supervisor.id", + "inventory.label", + "inventory.brand.name", + "inventory.model", + "inventory.device_type.description" ] fields_spec = [ {"name": "label", "row": "label", "label": "", "type": "display", @@ -106,6 +110,7 @@ def _fields_for_model(model: str): "row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}}, {"name": "staff", "label": "Staff Member", "label_attrs": {"class": "form-check-label"}, "row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}}, + {"name": "inventory", "label": "Inventory", "type": "template", "row": "inventory", "template": "user_inventory.html"}, ] layout = [ {"name": "label", "order": 0, "attrs": {"class": "row align-items-center"}}, @@ -113,6 +118,7 @@ def _fields_for_model(model: str): {"name": "details", "order": 20, "attrs": {"class": "row mt-2"}}, {"name": "checkboxes", "order": 30, "parent": "details", "attrs": {"class": "col d-flex flex-column justify-content-end"}}, + {"name": "inventory", "order": 40}, ] elif model == "worklog": @@ -127,7 +133,7 @@ def _fields_for_model(model: str): "updates.id", "updates.content", "updates.timestamp", - "updates.is_deleted", + "updates.is_deleted" ] fields_spec = [ {"name": "id", "label": "", "type": "display", "label_spec": "Work Item #{id}", diff --git a/inventory/routes/index.py b/inventory/routes/index.py index d0ee22b..240089b 100644 --- a/inventory/routes/index.py +++ b/inventory/routes/index.py @@ -16,6 +16,7 @@ bp_index = Blueprint("index", __name__) def init_index_routes(app): @bp_index.get("/") def index(): + # 1. work log stuff (leave it) work_log_service = crudkit.crud.get_service(WorkLog) work_logs = work_log_service.list({ "complete__ne": 1, @@ -33,36 +34,69 @@ def init_index_routes(app): {"field": "work_item.label", "label": "Work Item", "link": {"endpoint": "entry.entry", "params": {"id": "{work_item.id}", "model": "inventory"}}} ] - logs = render_table(work_logs, columns=columns, opts={"object_class": "worklog"}) + # 2. get device types with targets device_type_service = crudkit.crud.get_service(DeviceType) - device_types = device_type_service.list({ + dt_rows = device_type_service.list({ 'limit': 0, 'target__gt': 0, 'fields': [ 'description', - 'target', - 'condition.category' + 'target' ], + "sort": "description", }) - device_types = [d.as_dict() for d in device_types] - dt_ids = [d['id'] for d in device_types] - dt_filter = {'$or': [ - {'device_type_id': d} for d in dt_ids - ], - 'condition.category': 'Available'} + # turn into df + device_types = pd.DataFrame([d.as_dict() for d in dt_rows]) + + # if nobody has targets, just show empty table + if device_types.empty: + empty_df = pd.DataFrame(columns=['id', 'description', 'target', 'actual', 'needed']) + return render_template("index.html", logs=logs, needed_inventory=empty_df) + + # 3. now we can safely collect ids from the DF + dt_ids = device_types['id'].tolist() + + # 4. build inventory filter + dt_filter = { + '$or': [{'device_type_id': d} for d in dt_ids], + # drop this if you decided to ignore condition + 'condition.category': 'Available' + } + + # 5. fetch inventory inventory_service = crudkit.crud.get_service(Inventory) - needed_inventory = inventory_service.list({ + inv_rows = inventory_service.list({ 'limit': 0, **dt_filter, - 'fields': ['device_type.description'] + 'fields': ['device_type.description'], + 'sort': 'device_type.description', }) - needed_inventory = pd.DataFrame([i.as_dict() for i in needed_inventory]) - needed_inventory = pd.pivot_table(needed_inventory, columns='device_type.description', aggfunc='size') + inventory_df = pd.DataFrame([i.as_dict() for i in inv_rows]) - return render_template("index.html", logs=logs, device_types=device_types, needed_inventory=needed_inventory) + # if there is no inventory for these device types, actual = 0 + if inventory_df.empty: + device_types['actual'] = 0 + device_types['needed'] = device_types['target'] + return render_template("index.html", logs=logs, needed_inventory=device_types) + + # 6. aggregate counts + inv_counts = ( + inventory_df['device_type.description'] + .value_counts() + .rename('actual') + .reset_index() + .rename(columns={'device_type.description': 'description'}) + ) + + # 7. merge + merged = device_types.merge(inv_counts, on='description', how='left') + merged['actual'] = merged['actual'].fillna(0).astype(int) + merged['needed'] = (merged['target'] - merged['actual']).clip(lower=0) + + return render_template("index.html", logs=logs, needed_inventory=merged) @bp_index.get("/LICENSE") def license(): diff --git a/inventory/routes/settings.py b/inventory/routes/settings.py index 1128422..4885d1b 100644 --- a/inventory/routes/settings.py +++ b/inventory/routes/settings.py @@ -53,7 +53,6 @@ def init_settings_routes(app): ], }) statuses = render_table(statuses, opts={"object_class": 'status'}) - print([t.as_dict() for t in device_types]) return render_template("settings.html", brands=brands, device_types=device_types, areas=areas, functions=functions, rooms=rooms, statuses=statuses) diff --git a/inventory/templates/index.html b/inventory/templates/index.html index 7ef4a05..eaaf426 100644 --- a/inventory/templates/index.html +++ b/inventory/templates/index.html @@ -11,14 +11,38 @@

Supply Status

- {% for d in device_types %} -

- {{ d['description'] }}: {{ d['target'] }} needed -

- {% endfor %} -
-      {{ needed_inventory }}
-    
+
+ + {% if not needed_inventory.empty %} + + + + + + + + + + {% for row in needed_inventory.itertuples() %} + + + + + + + {% endfor %} + + {% else %} + + + + + + {% endif %} +
DeviceTargetOn HandNeeded
{{ row.description }}{{ row.target }}{{ row.actual }}{{ row.needed }}
No data.
+
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/inventory/templates/summary.html b/inventory/templates/summary.html index f407356..e7833ab 100644 --- a/inventory/templates/summary.html +++ b/inventory/templates/summary.html @@ -1,48 +1,54 @@ {% extends "base.html" %} {% block style %} - thead.sticky-top th { - z-index: 2; - } +thead.sticky-top th { +z-index: 2; +} {% endblock %} {% block main %} -

Inventory Summary

-
- - - - - {% for col in col_headers %} - {% if col.href %} - - {% else %} - - {% endif %} - {% endfor %} - - - - {% for row in table_rows %} - {% set need_more = (row['cells'][-2]['value'] | int > 0) %} - - - {% for cell in row.cells %} - {% if cell.href %} - - {% else %} - - {% endif %} - {% endfor %} - +

Inventory Summary

+
+
Device Type{{ col.label }}{{ col.label }}
- {% if row.href %} - {{ row.label }} - {% else %} - {{ row.label }} - {% endif %} - {{ cell.value }}{{ cell.value }}
+ + + + {% for col in col_headers %} + {% if col.href %} + + {% else %} + + {% endif %} {% endfor %} - -
Device Type{{ col.label }}{{ col.label }}
-
-{% endblock %} + + + + {% for row in table_rows %} + {% set need_more = (row['cells'][-2]['value'] | int > 0) %} + + + {% if row.href %} + {{ row.label }} + {% else %} + {{ row.label }} + {% endif %} + + {% for cell in row.cells %} + {% if cell.href %} + + {{ cell.value }} + {% else %} + + {{ cell.value }} + {% endif %} + {% endfor %} + + {% endfor %} + + + +{% endblock %} \ No newline at end of file diff --git a/inventory/templates/user_inventory.html b/inventory/templates/user_inventory.html new file mode 100644 index 0000000..b2d3c40 --- /dev/null +++ b/inventory/templates/user_inventory.html @@ -0,0 +1,33 @@ + +{% set inv = field['template_ctx']['values']['inventory'] %} + +
+ + {% if inv %} + + + + + + + + + + {% for i in inv %} + + + + + + + {% endfor %} + + {% else %} + + + + + + {% endif %} +
DeviceBrandModelType
{{ i.label }}{{ i['brand.name'] }}{{ i.model }}{{ i['device_type.description'] }}
No data.
+
\ No newline at end of file