I lost track of all these changes. Congratulations me.

This commit is contained in:
Yaro Kasear 2025-10-31 16:31:23 -05:00
parent 8481a40553
commit 4ef4d5e23f
6 changed files with 170 additions and 68 deletions

View file

@ -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}",

View file

@ -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():

View file

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

View file

@ -11,14 +11,38 @@
</div>
<div class="col">
<p class="display-6 text-center">Supply Status</p>
{% for d in device_types %}
<p>
{{ d['description'] }}: {{ d['target'] }} needed
</p>
{% endfor %}
<pre class="border border-black bg-warning-subtle p-2">
{{ needed_inventory }}
</pre>
<div class="table-responsive">
<table class="table table-sm table-bordered table-striped table-hover">
{% if not needed_inventory.empty %}
<thead>
<tr>
<th>Device</th>
<th>Target</th>
<th>On Hand</th>
<th>Needed</th>
</tr>
</thead>
<tbody>
{% for row in needed_inventory.itertuples() %}
<tr class="{{ 'table-warning' if row.needed else '' }}"
onclick="location.href='{{ url_for('listing.show_list', model='inventory', device_type_id__eq=row.id) }}&condition.category=Available'"
style="cursor: pointer;">
<td>{{ row.description }}</td>
<td>{{ row.target }}</td>
<td>{{ row.actual }}</td>
<td class="{{ 'fw-bold' if row.needed else '' }}">{{ row.needed }}</td>
</tr>
{% endfor %}
</tbody>
{% else %}
<thead>
<tr>
<th colspan="4" class="text-center">No data.</th>
</tr>
</thead>
{% endif %}
</table>
</div>
</div>
</div>
{% endblock %}
{% endblock %}

View file

@ -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 %}
<h1 class="display-4 text-center mb-3">Inventory Summary</h1>
<div class="table-responsive mx-5 overflow-y-auto border" style="max-height: 70vh;">
<table class="table table-sm table-striped table-hover table-bordered align-middle mb-0">
<thead>
<tr class="position-sticky top-0 bg-body border">
<th class="text-nowrap position-sticky top-0 bg-body border">Device Type</th>
{% for col in col_headers %}
{% if col.href %}
<th class="text-end position-sticky top-0 bg-body border"><a class="link-dark link-underline link-underline-opacity-0" href="{{ col.href }}">{{ col.label }}</a></th>
{% else %}
<th class="text-end position-sticky top-0 bg-body border">{{ col.label }}</th>
{% endif %}
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in table_rows %}
{% set need_more = (row['cells'][-2]['value'] | int > 0) %}
<tr class="{% if need_more %}table-warning{% endif %}{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}">
<th class="text-nowrap{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}">
{% if row.href %}
<a class="link-dark link-underline link-underline-opacity-0" href="{{ row.href }}">{{ row.label }}</a>
{% else %}
{{ row.label }}
{% endif %}
</th>
{% for cell in row.cells %}
{% if cell.href %}
<td class="text-end{% if need_more and loop.index == (row.cells|length - 1) %} fw-bold{% endif %}{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}"><a class="link-dark link-underline link-underline-opacity-0" href="{{ cell.href }}">{{ cell.value }}</a></td>
{% else %}
<td class="text-end{% if need_more and loop.index == (row.cells|length - 1) %} fw-bold{% endif %}{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}">{{ cell.value }}</td>
{% endif %}
{% endfor %}
</tr>
<h1 class="display-4 text-center mb-3">Inventory Summary</h1>
<div class="table-responsive mx-5 overflow-y-auto border" style="max-height: 70vh;">
<table class="table table-sm table-striped table-hover table-bordered align-middle mb-0">
<thead>
<tr class="position-sticky top-0 bg-body border">
<th class="text-nowrap position-sticky top-0 bg-body border">Device Type</th>
{% for col in col_headers %}
{% if col.href %}
<th class="text-end position-sticky top-0 bg-body border"><a
class="link-dark link-underline link-underline-opacity-0" href="{{ col.href }}">{{ col.label }}</a></th>
{% else %}
<th class="text-end position-sticky top-0 bg-body border">{{ col.label }}</th>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
</tr>
</thead>
<tbody>
{% for row in table_rows %}
{% set need_more = (row['cells'][-2]['value'] | int > 0) %}
<tr
class="{% if need_more %}table-warning{% endif %}{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}">
<th class="text-nowrap{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}">
{% if row.href %}
<a class="link-dark link-underline link-underline-opacity-0" href="{{ row.href }}">{{ row.label }}</a>
{% else %}
{{ row.label }}
{% endif %}
</th>
{% for cell in row.cells %}
{% if cell.href %}
<td
class="text-end{% if need_more and loop.index == (row.cells|length - 1) %} fw-bold{% endif %}{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}">
<a class="link-dark link-underline link-underline-opacity-0" href="{{ cell.href }}">{{ cell.value }}</a></td>
{% else %}
<td
class="text-end{% if need_more and loop.index == (row.cells|length - 1) %} fw-bold{% endif %}{% if loop.index == table_rows|length %} position-sticky bottom-0 border{% endif %}">
{{ cell.value }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View file

@ -0,0 +1,33 @@
<label class="form-label mt-2">Assigned Inventory</label>
{% set inv = field['template_ctx']['values']['inventory'] %}
<div class="table-responsive">
<table class="table table-sm table-bordered table-striped table-hover">
{% if inv %}
<thead>
<tr>
<th>Device</th>
<th>Brand</th>
<th>Model</th>
<th>Type</th>
</tr>
</thead>
<tbody>
{% for i in inv %}
<tr style="cursor: pointer;" onclick="location.href='{{ url_for('entry.entry', model='inventory', id=i.id) }}'">
<td>{{ i.label }}</td>
<td>{{ i['brand.name'] }}</td>
<td>{{ i.model }}</td>
<td>{{ i['device_type.description'] }}</td>
</tr>
{% endfor %}
</tbody>
{% else %}
<thead>
<tr>
<th colspan="4" class="text-center">No data.</th>
</tr>
</thead>
{% endif %}
</table>
</div>