ixg_platform/templates/ops/coa_review_detail.html
ixgadmin cdefbd010c Serve document and certificate files through a permission-checked download
Media was unreachable from either portal, which is why the certificate button on
the buyer compliance page returned page-not-found and no document row had
anything to click. The cause was structural: the portal and ops hosts proxy
location / to the app with a path prefix, so a stored /media/... URL arrives as
/portal/media/... and matches no route.

Add a download view per portal behind the same role gate as the page that links
to it — buyer-visible documents and published certificates for buyers, anything
the ops list shows for operations — resolving the stored path safely under
MEDIA_ROOT so a traversal or an absolute path is refused. Point the seven
templates that list files at the new routes.

Also add a portal-wide render sweep: nothing in the suite was fetching the ops
or portal pages, so a view could lose its template_name and 500 in front of a
user with every test still green.
2026-09-17 12:44:52 +00:00

65 lines
3.9 KiB
HTML

{% extends 'ops/base_ops.html' %}
{% block title %}CoA Review - IXG Operations{% endblock %}
{% block content %}
<div class=page-header d-flex justify-content-between align-items-center>
<h2>CoA Review: {{ coa.batch.batch_reference }}</h2>
<a href={% url 'ops:coa_review_list' %} class=btn btn-sm btn-outline-secondary><i class=bi bi-arrow-left></i> Back</a>
</div>
{% if messages %}
{% for m in messages %}<div class="alert alert-{{ m.tags }} py-2">{{ m }}</div>{% endfor %}
{% endif %}
<div class=row g-3>
<div class=col-md-7>
<div class=stat-card>
<h6 class=mb-3>Certificate Details</h6>
<table class=table table-sm table-borderless mb-0>
<tr><td class=text-secondary>Batch</td><td><a href={% url 'ops:batch_detail' coa.batch.pk %}>{{ coa.batch.batch_reference }}</a></td></tr>
<tr><td class=text-secondary>Status</td><td><span class=badge {% if coa.status == 'published' %}bg-success{% elif coa.status == 'superseded' %}bg-secondary{% else %}bg-warning text-dark{% endif %}>{{ coa.get_status_display }}</span></td></tr>
<tr><td class=text-secondary>Version</td><td>v{{ coa.version }}</td></tr>
<tr><td class=text-secondary>Specification</td><td>{{ coa.spec_version|default:'-' }}</td></tr>
<tr><td class=text-secondary>Issuer</td><td>{{ coa.issuer|default:'-' }}</td></tr>
<tr><td class=text-secondary>Issue Date</td><td>{{ coa.issue_date|date:'d/m/Y'|default:'-' }}</td></tr>
<tr><td class=text-secondary>Quality Approved By</td><td>{% if coa.quality_approved_by %}{{ coa.quality_approved_by.full_name|default:coa.quality_approved_by.email }}{% else %}-{% endif %}</td></tr>
<tr><td class=text-secondary>Quality Approved At</td><td>{{ coa.quality_approved_at|date:'d/m/Y H:i'|default:'-' }}</td></tr>
<tr><td class=text-secondary>Released At</td><td>{{ coa.released_at|date:'d/m/Y H:i'|default:'-' }}</td></tr>
<tr><td class=text-secondary>Published At</td><td>{{ coa.published_at|date:'d/m/Y H:i'|default:'-' }}</td></tr>
<tr><td class=text-secondary>SHA-256</td><td><code>{{ coa.sha256_hash|default:'-' }}</code></td></tr>
<tr><td class=text-secondary>Summary PDF</td><td><a href="{% url 'ops:coa_download' coa.id %}" target=_blank class=text-decoration-none><i class=bi bi-file-earmark-pdf></i> {{ coa.file_name }}</a></td></tr>
</table>
</div>
</div>
<div class=col-md-5>
<div class=stat-card mb-3>
<h6 class=mb-3>Source Documents</h6>
<p class=small text-muted>Independent laboratory report (authoritative):</p>
{% if coa.source_document %}
<a href="{% url 'ops:document_download' coa.source_document.id %}" target=_blank class=text-decoration-none><i class=bi bi-file-earmark-pdf></i> {{ coa.source_document.file_name }}</a>
{% else %}
<p class=text-muted mb-0>No lab report linked. <a href={% url 'ops:document_list' %}>Upload</a> the original laboratory PDF for the batch.</p>
{% endif %}
{% if lab_documents %}
<hr>
<p class=small text-muted mb-1>All lab documents for batch:</p>
{% for d in lab_documents %}<div><a href="{% url 'ops:document_download' d.id %}" target=_blank class=text-decoration-none><i class=bi bi-file-earmark-pdf></i> {{ d.file_name }}</a></div>{% endfor %}
{% endif %}
</div>
<div class=stat-card>
<h6 class=mb-3>Commercial Release</h6>
{% if coa.status == 'pending_approval' %}
<form method=post>
{% csrf_token %}
<input type=hidden name=action value=approve>
<p class=text-muted>Publishing exposes the summary PDF to authorised buyers and triggers buyer notification.</p>
<button type=submit class="btn btn-success w-100"><i class=bi bi-send></i> Approve &amp; Publish</button>
</form>
{% else %}
<p class=text-muted mb-0">Commercial release approved. Buyer document and notification created.</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}