181 lines
7.7 KiB
HTML
181 lines
7.7 KiB
HTML
{% extends 'ops/base_ops.html' %}
|
|
{% block title %}{{ batch.batch_reference }} - IXG Operations{% endblock %}
|
|
{% block content %}
|
|
|
|
<div class="page-header d-flex justify-content-between align-items-center">
|
|
<h2>Batch: {{ batch.batch_reference }}</h2>
|
|
<div>
|
|
<a href="{% url 'ops:batch_lineage' batch.pk %}" class="btn btn-sm btn-primary"><i class="bi bi-basket"></i> Edit intake lineage</a>
|
|
<a href="{% url 'ops:batch_update' batch.pk %}" class="btn btn-sm btn-outline-primary"><i class="bi bi-pencil"></i> Edit batch</a>
|
|
<a href="{% url 'ops:batch_list' %}" class="btn btn-sm btn-outline-secondary"><i class="bi bi-arrow-left"></i> Back</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<div class="stat-card">
|
|
<table class="table table-sm table-borderless mb-0">
|
|
<tr><td class="text-secondary">Status</td><td><span class="badge bg-secondary badge-status">{{ batch.get_status_display }}</span></td></tr>
|
|
<tr><td class="text-secondary">Grade</td><td>{{ batch.grade|default:'Not graded' }}</td></tr>
|
|
<tr><td class="text-secondary">Weight</td><td>{{ batch.weight_kg|default:'-' }} kg</td></tr>
|
|
<tr><td class="text-secondary">Origin Facility</td><td>{{ batch.origin_facility }}</td></tr>
|
|
<tr><td class="text-secondary">Harvest Date</td><td>{{ batch.harvest_date|date:'d/m/Y'|default:'-' }}</td></tr>
|
|
<tr><td class="text-secondary">Created By</td><td>{% if batch.created_by %}{{ batch.created_by.full_name|default:batch.created_by.email }}{% else %}-{% endif %}</td></tr>
|
|
<tr><td class="text-secondary">Created At</td><td>{{ batch.created_at|date:'d/m/Y H:i' }}</td></tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="stat-card">
|
|
<h6 class="mb-3">Notes</h6>
|
|
<p class="mb-0">{{ batch.notes|linebreaks|default:'<span class=text-muted>No notes</span>' }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-12">
|
|
<div class="stat-card">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h6 class="mb-0">Intake Lineage ({{ lineage|length }})</h6>
|
|
<a href="{% url 'ops:batch_lineage' batch.pk %}" class="btn btn-sm btn-outline-primary"><i class="bi bi-basket"></i> Edit lineage</a>
|
|
</div>
|
|
{% if lineage %}
|
|
<table class="table table-sm mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Intake</th>
|
|
<th>Date</th>
|
|
<th>Received (kg)</th>
|
|
<th>Allocated (kg)</th>
|
|
<th>Origin Zone</th>
|
|
<th>Collector Group</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for row in lineage %}
|
|
<tr>
|
|
<td><strong>{{ row.harvest_intake.intake_reference }}</strong></td>
|
|
<td>{{ row.harvest_intake.intake_date|date:'d/m/Y'|default:'-' }}</td>
|
|
<td>{{ row.harvest_intake.weight_kg|default:'-' }}</td>
|
|
<td>{{ row.weight_allocated_kg|default:'-' }}</td>
|
|
<td>
|
|
{% if row.harvest_intake.origin_zone %}
|
|
{{ row.harvest_intake.origin_zone.zone_code }}
|
|
{% if row.harvest_intake.origin_zone.verification_status != 'verified' %}
|
|
<span class="badge bg-warning text-dark">{{ row.harvest_intake.origin_zone.get_verification_status_display }}</span>
|
|
{% endif %}
|
|
{% else %}-{% endif %}
|
|
</td>
|
|
<td>{{ row.harvest_intake.collector_group.name|default:'-' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
<tr class="table-light">
|
|
<td colspan="3" class="text-end text-secondary">Total allocated</td>
|
|
<td><strong>{{ lineage_allocated }}</strong></td>
|
|
<td colspan="2" class="text-secondary">batch weight {{ batch.weight_kg|default:'-' }} kg</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
{% if lineage_difference > 0 %}
|
|
<p class="small text-secondary mt-2 mb-0">
|
|
{{ lineage_difference }} kg of this batch is not traced back to a harvest intake.
|
|
The platform shows the two figures side by side; it does not reconcile them.
|
|
</p>
|
|
{% elif lineage_difference < 0 %}
|
|
<p class="small text-secondary mt-2 mb-0">
|
|
The allocated weights are above the batch weight — the usual direction,
|
|
since processing loses weight. The platform shows the two figures side by
|
|
side; it does not reconcile them.
|
|
</p>
|
|
{% endif %}
|
|
{% else %}
|
|
<p class="text-muted mb-0">
|
|
No harvest intakes are linked to this batch. Until at least one is, this
|
|
batch cannot be traced back to a collector group and an origin zone, and
|
|
the due-diligence case for it will fail its lineage check.
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3 mb-4">
|
|
<div class="col-md-6">
|
|
<div class="stat-card">
|
|
<h6 class="mb-3">QC Results ({{ qc_results|length }})</h6>
|
|
{% if qc_results %}
|
|
<table class="table table-sm mb-0">
|
|
<thead><tr><th>Result</th><th>FFA%</th><th>Moisture%</th><th>Peroxide</th><th>Date</th></tr></thead>
|
|
<tbody>
|
|
{% for r in qc_results %}
|
|
<tr>
|
|
<td>{% if r.overall_pass %}<span class="badge bg-success">Pass</span>{% else %}<span class="badge bg-danger">Fail</span>{% endif %}</td>
|
|
<td>{{ r.ffa_percentage }}</td>
|
|
<td>{{ r.moisture_percentage }}</td>
|
|
<td>{{ r.peroxide_value }}</td>
|
|
<td>{{ r.tested_at|date:'d/m/Y' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}<p class="text-muted mb-0">No QC results</p>{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="stat-card">
|
|
<h6 class="mb-3">Shipments ({{ shipments|length }})</h6>
|
|
{% if shipments %}
|
|
<table class="table table-sm mb-0">
|
|
<thead><tr><th>Status</th><th>Vessel</th><th>ETD</th><th>ETA</th></tr></thead>
|
|
<tbody>
|
|
{% for s in shipments %}
|
|
<tr>
|
|
<td><span class="badge bg-secondary badge-status">{{ s.get_status_display }}</span></td>
|
|
<td>{{ s.vessel_name|default:'-' }}</td>
|
|
<td>{{ s.etd_nigeria|date:'d/m/Y'|default:'-' }}</td>
|
|
<td>{{ s.eta_uk|date:'d/m/Y'|default:'-' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}<p class="text-muted mb-0">No shipments</p>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
<div class="col-md-6">
|
|
<div class="stat-card">
|
|
<h6 class="mb-3">CAPA Records ({{ capa_records|length }})</h6>
|
|
{% if capa_records %}
|
|
<table class="table table-sm mb-0">
|
|
<thead><tr><th>Issue</th><th>Status</th><th>Due Date</th></tr></thead>
|
|
<tbody>
|
|
{% for c in capa_records %}
|
|
<tr>
|
|
<td>{{ c.issue_description|truncatewords:10 }}</td>
|
|
<td><span class="badge bg-warning text-dark badge-status">{{ c.get_status_display }}</span></td>
|
|
<td>{{ c.due_date|date:'d/m/Y'|default:'-' }}</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}<p class="text-muted mb-0">No CAPA records</p>{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="stat-card">
|
|
<h6 class="mb-3">Documents ({{ documents|length }})</h6>
|
|
{% if documents %}
|
|
<ul class="list-unstyled mb-0">
|
|
{% for d in documents %}
|
|
<li class="border-bottom pb-1 mb-1"><a href="{% url 'ops:document_download' d.id %}" target="_blank" class="text-decoration-none"><i class="bi bi-file-earmark"></i> {{ d.file_name }}</a> <span class="badge bg-light text-dark">{{ d.get_doc_type_display }}</span></li>
|
|
{% endfor %}
|
|
</ul>
|
|
{% else %}<p class="text-muted mb-0">No documents</p>{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|