Operations can now create and edit a specification and its parameters together, over the existing model, mirroring the admin's inline. The approval half is deliberately held: the form does not expose status, is_current, approved_by or approved_at, so a portal-created specification is a draft and cannot be approved here. The edit screen warns when the record is not a draft, because its parameters are the limits QC results are judged against. Nothing is removed from the admin. The parameter formset refuses an empty specification (with no parameters every QC result would pass), a numeric parameter with no limits, and a visual parameter with no allowed values.
100 lines
3.9 KiB
HTML
100 lines
3.9 KiB
HTML
{% extends 'ops/base_ops.html' %}
|
|
{% block title %}Specifications - IXG Operations{% endblock %}
|
|
{% block content %}
|
|
|
|
{% if messages %}
|
|
{% for m in messages %}<div class="alert alert-{{ m.tags }} py-2">{{ m }}</div>{% endfor %}
|
|
{% endif %}
|
|
|
|
<div class="page-header d-flex justify-content-between align-items-center">
|
|
<h2>Product Specifications</h2>
|
|
<div>
|
|
<span class="text-secondary me-2">{{ page_obj.paginator.count }} total</span>
|
|
<a href="{% url 'ops:specification_create' %}" class="btn btn-sm btn-primary"><i class="bi bi-plus-lg"></i> New specification</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="alert alert-light border py-2 small">
|
|
A specification is the set of limits every QC result is judged against. The portal can
|
|
create and edit one, but <strong>approval is held</strong>: a specification created here
|
|
is a draft and cannot be approved from the portal yet.
|
|
</div>
|
|
|
|
<div class="filter-bar">
|
|
<form method="get" class="row g-2 align-items-end">
|
|
<div class="col-auto">
|
|
<input type="text" name="product" class="form-control form-control-sm" placeholder="Search product..." value="{{ current_product }}">
|
|
</div>
|
|
<div class="col-auto">
|
|
<select name="status" class="form-select form-select-sm">
|
|
<option value="">All Statuses</option>
|
|
{% for val, label in status_choices %}
|
|
<option value="{{ val }}" {% if current_status == val %}selected{% endif %}>{{ label }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div class="col-auto">
|
|
<button type="submit" class="btn btn-sm btn-primary">Filter</button>
|
|
<a href="{% url 'ops:specification_list' %}" class="btn btn-sm btn-outline-secondary">Clear</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="stat-card p-0">
|
|
<table class="table table-hover mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Product</th>
|
|
<th>Version</th>
|
|
<th>Status</th>
|
|
<th>Current</th>
|
|
<th>Effective from</th>
|
|
<th>Effective to</th>
|
|
<th>Parameters</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for s in specs %}
|
|
<tr>
|
|
<td><strong>{{ s.product }}</strong></td>
|
|
<td>{{ s.version }}</td>
|
|
<td>
|
|
{% if s.status == 'approved' %}
|
|
<span class="badge bg-success">{{ s.get_status_display }}</span>
|
|
{% elif s.status == 'superseded' %}
|
|
<span class="badge bg-warning text-dark">{{ s.get_status_display }}</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">{{ s.get_status_display }}</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{% if s.is_current %}<i class="bi bi-check-circle-fill text-success"></i>{% else %}-{% endif %}</td>
|
|
<td>{{ s.effective_from|date:'d/m/Y'|default:'-' }}</td>
|
|
<td>{{ s.effective_to|date:'d/m/Y'|default:'-' }}</td>
|
|
<td>{{ s.parameters.all|length }}</td>
|
|
<td><a href="{% url 'ops:specification_update' s.pk %}" class="btn btn-sm btn-outline-primary">Edit</a></td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr><td colspan="8" class="text-muted text-center py-3">No specifications yet</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{% if page_obj.paginator.num_pages > 1 %}
|
|
<nav class="mt-3">
|
|
<ul class="pagination pagination-sm justify-content-center">
|
|
{% if page_obj.has_previous %}
|
|
<li class="page-item"><a class="page-link" href="?page=1">«</a></li>
|
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">{{ page_obj.previous_page_number }}</a></li>
|
|
{% endif %}
|
|
<li class="page-item active"><span class="page-link">{{ page_obj.number }}</span></li>
|
|
{% if page_obj.has_next %}
|
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">{{ page_obj.next_page_number }}</a></li>
|
|
<li class="page-item"><a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">»</a></li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
{% endblock %}
|