I lost track of all these changes. Congratulations me.
This commit is contained in:
parent
8481a40553
commit
4ef4d5e23f
6 changed files with 170 additions and 68 deletions
|
|
@ -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}",
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
<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 %}
|
||||
<pre class="border border-black bg-warning-subtle p-2">
|
||||
{{ needed_inventory }}
|
||||
</pre>
|
||||
</tbody>
|
||||
{% else %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" class="text-center">No data.</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
{% 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;">
|
||||
<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>
|
||||
<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 %}
|
||||
|
|
@ -25,7 +26,8 @@
|
|||
<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 %}">
|
||||
<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>
|
||||
|
|
@ -35,14 +37,18 @@
|
|||
</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>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
{% endblock %}
|
||||
33
inventory/templates/user_inventory.html
Normal file
33
inventory/templates/user_inventory.html
Normal 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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue