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",
|
"title",
|
||||||
"active",
|
"active",
|
||||||
"staff",
|
"staff",
|
||||||
"supervisor.id"
|
"supervisor.id",
|
||||||
|
"inventory.label",
|
||||||
|
"inventory.brand.name",
|
||||||
|
"inventory.model",
|
||||||
|
"inventory.device_type.description"
|
||||||
]
|
]
|
||||||
fields_spec = [
|
fields_spec = [
|
||||||
{"name": "label", "row": "label", "label": "", "type": "display",
|
{"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"}},
|
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
|
||||||
{"name": "staff", "label": "Staff Member", "label_attrs": {"class": "form-check-label"},
|
{"name": "staff", "label": "Staff Member", "label_attrs": {"class": "form-check-label"},
|
||||||
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
|
"row": "checkboxes", "attrs": {"class": "form-check-input"}, "wrap": {"class": "form-check"}},
|
||||||
|
{"name": "inventory", "label": "Inventory", "type": "template", "row": "inventory", "template": "user_inventory.html"},
|
||||||
]
|
]
|
||||||
layout = [
|
layout = [
|
||||||
{"name": "label", "order": 0, "attrs": {"class": "row align-items-center"}},
|
{"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": "details", "order": 20, "attrs": {"class": "row mt-2"}},
|
||||||
{"name": "checkboxes", "order": 30, "parent": "details",
|
{"name": "checkboxes", "order": 30, "parent": "details",
|
||||||
"attrs": {"class": "col d-flex flex-column justify-content-end"}},
|
"attrs": {"class": "col d-flex flex-column justify-content-end"}},
|
||||||
|
{"name": "inventory", "order": 40},
|
||||||
]
|
]
|
||||||
|
|
||||||
elif model == "worklog":
|
elif model == "worklog":
|
||||||
|
|
@ -127,7 +133,7 @@ def _fields_for_model(model: str):
|
||||||
"updates.id",
|
"updates.id",
|
||||||
"updates.content",
|
"updates.content",
|
||||||
"updates.timestamp",
|
"updates.timestamp",
|
||||||
"updates.is_deleted",
|
"updates.is_deleted"
|
||||||
]
|
]
|
||||||
fields_spec = [
|
fields_spec = [
|
||||||
{"name": "id", "label": "", "type": "display", "label_spec": "Work Item #{id}",
|
{"name": "id", "label": "", "type": "display", "label_spec": "Work Item #{id}",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ bp_index = Blueprint("index", __name__)
|
||||||
def init_index_routes(app):
|
def init_index_routes(app):
|
||||||
@bp_index.get("/")
|
@bp_index.get("/")
|
||||||
def index():
|
def index():
|
||||||
|
# 1. work log stuff (leave it)
|
||||||
work_log_service = crudkit.crud.get_service(WorkLog)
|
work_log_service = crudkit.crud.get_service(WorkLog)
|
||||||
work_logs = work_log_service.list({
|
work_logs = work_log_service.list({
|
||||||
"complete__ne": 1,
|
"complete__ne": 1,
|
||||||
|
|
@ -33,36 +34,69 @@ def init_index_routes(app):
|
||||||
{"field": "work_item.label", "label": "Work Item",
|
{"field": "work_item.label", "label": "Work Item",
|
||||||
"link": {"endpoint": "entry.entry", "params": {"id": "{work_item.id}", "model": "inventory"}}}
|
"link": {"endpoint": "entry.entry", "params": {"id": "{work_item.id}", "model": "inventory"}}}
|
||||||
]
|
]
|
||||||
|
|
||||||
logs = render_table(work_logs, columns=columns, opts={"object_class": "worklog"})
|
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_type_service = crudkit.crud.get_service(DeviceType)
|
||||||
device_types = device_type_service.list({
|
dt_rows = device_type_service.list({
|
||||||
'limit': 0,
|
'limit': 0,
|
||||||
'target__gt': 0,
|
'target__gt': 0,
|
||||||
'fields': [
|
'fields': [
|
||||||
'description',
|
'description',
|
||||||
'target',
|
'target'
|
||||||
'condition.category'
|
|
||||||
],
|
],
|
||||||
|
"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)
|
inventory_service = crudkit.crud.get_service(Inventory)
|
||||||
needed_inventory = inventory_service.list({
|
inv_rows = inventory_service.list({
|
||||||
'limit': 0,
|
'limit': 0,
|
||||||
**dt_filter,
|
**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])
|
inventory_df = pd.DataFrame([i.as_dict() for i in inv_rows])
|
||||||
needed_inventory = pd.pivot_table(needed_inventory, columns='device_type.description', aggfunc='size')
|
|
||||||
|
|
||||||
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")
|
@bp_index.get("/LICENSE")
|
||||||
def license():
|
def license():
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ def init_settings_routes(app):
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
statuses = render_table(statuses, opts={"object_class": 'status'})
|
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)
|
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>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="display-6 text-center">Supply Status</p>
|
<p class="display-6 text-center">Supply Status</p>
|
||||||
{% for d in device_types %}
|
<div class="table-responsive">
|
||||||
<p>
|
<table class="table table-sm table-bordered table-striped table-hover">
|
||||||
{{ d['description'] }}: {{ d['target'] }} needed
|
{% if not needed_inventory.empty %}
|
||||||
</p>
|
<thead>
|
||||||
{% endfor %}
|
<tr>
|
||||||
<pre class="border border-black bg-warning-subtle p-2">
|
<th>Device</th>
|
||||||
{{ needed_inventory }}
|
<th>Target</th>
|
||||||
</pre>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
@ -1,48 +1,54 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block style %}
|
{% block style %}
|
||||||
thead.sticky-top th {
|
thead.sticky-top th {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
<h1 class="display-4 text-center mb-3">Inventory Summary</h1>
|
<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;">
|
<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">
|
<table class="table table-sm table-striped table-hover table-bordered align-middle mb-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="position-sticky top-0 bg-body border">
|
<tr class="position-sticky top-0 bg-body border">
|
||||||
<th class="text-nowrap position-sticky top-0 bg-body border">Device Type</th>
|
<th class="text-nowrap position-sticky top-0 bg-body border">Device Type</th>
|
||||||
{% for col in col_headers %}
|
{% for col in col_headers %}
|
||||||
{% if col.href %}
|
{% 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
|
||||||
{% else %}
|
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">{{ col.label }}</th>
|
{% else %}
|
||||||
{% endif %}
|
<th class="text-end position-sticky top-0 bg-body border">{{ col.label }}</th>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
</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 %}
|
{% endfor %}
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
</thead>
|
||||||
</div>
|
<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 %}
|
{% 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