ixg_platform/templates/portal/compliance.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

62 lines
2.7 KiB
HTML

{% extends 'portal/base_portal.html' %}
{% block title %}Compliance - IXG Buyer Portal{% endblock %}
{% block content %}
<div class="page-header d-flex justify-content-between align-items-center">
<h2>Compliance</h2>
<span class="text-secondary">Approved evidence for your batches</span>
</div>
<div class="alert alert-light border py-2 small">
<i class="bi bi-info-circle"></i> Shea butter is not listed in EUDR Annex I; the due-diligence status shown here is
evidence and buyer assurance provided by IXG, not an EU-mandated filing.
</div>
<div class="row g-3">
<div class="col-md-7">
<div class="stat-card">
<h6 class="mb-3"><i class="bi bi-file-earmark-medical"></i> Certificates of Analysis (approved)</h6>
<table class="table table-sm mb-0">
<thead><tr><th>Batch</th><th>Product</th><th>Issued</th><th>Download</th></tr></thead>
<tbody>
{% for c in approved_coas %}
<tr>
<td><strong>{{ c.batch.batch_reference }}</strong></td>
<td>Unrefined Shea Butter</td>
<td>{{ c.issue_date|date:'d/m/Y'|default:'-' }}</td>
<td><a href="{% url 'portal:coa_download' c.id %}" target="_blank" class="btn btn-sm btn-outline-success"><i class="bi bi-download"></i> PDF</a></td>
</tr>
{% empty %}
<tr><td colspan="4" class="text-muted text-center py-3">No approved certificates available</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="col-md-5">
<div class="stat-card">
<h6 class="mb-3"><i class="bi bi-shield-check"></i> EUDR Due Diligence Status</h6>
<table class="table table-sm mb-0">
<thead><tr><th>Batch</th><th>Status</th><th>Risk</th></tr></thead>
<tbody>
{% for c in eudr_cases %}
<tr>
<td><strong>{{ c.batch.batch_reference }}</strong></td>
<td><span class="badge
{% if c.status == 'approved' %}bg-success
{% elif c.status == 'rejected' %}bg-danger
{% elif c.status == 'complete_for_review' %}bg-primary
{% elif c.status == 'mitigation_required' or c.status == 'risk_identified' %}bg-warning text-dark
{% else %}bg-secondary{% endif %}">{{ c.get_status_display }}</span></td>
<td><span class="badge {% if c.risk_level == 'high' %}bg-danger{% elif c.risk_level == 'low' %}bg-success{% else %}bg-info text-dark{% endif %}">{{ c.get_risk_level_display }}</span></td>
</tr>
{% empty %}
<tr><td colspan="3" class="text-muted text-center py-3">No due-diligence cases recorded</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}