Add public refund creation endpoint and PDF generation
Introduces RefundPublicView for public refund creation via email and invoice/order ID, returning refund info and a base64-encoded PDF slip. Adds RefundCreatePublicSerializer for validation and creation, implements PDF generation in Refund model, and provides a customer-facing HTML template for the return slip. Updates URLs to expose the new endpoint.
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Return/Refund Slip – Order {{ order.number|default:order.code|default:order.id }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
:root { --fg:#111; --muted:#666; --border:#ddd; --accent:#0f172a; --bg:#fff; }
|
||||
* { box-sizing: border-box; }
|
||||
html, body { margin:0; padding:0; background:var(--bg); color:var(--fg); font:14px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial; }
|
||||
.sheet { max-width: 800px; margin: 24px auto; padding: 24px; border:1px solid var(--border); border-radius: 8px; }
|
||||
header { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:16px; }
|
||||
.title { font-size:20px; font-weight:700; letter-spacing:.2px; }
|
||||
.sub { color:var(--muted); font-size:12px; }
|
||||
.meta { display:grid; grid-template-columns: 1fr 1fr; gap: 8px 16px; padding:12px; border:1px solid var(--border); border-radius:8px; margin-bottom:16px; }
|
||||
.meta div { display:flex; gap:8px; }
|
||||
.label { width:140px; color:var(--muted); }
|
||||
table { width:100%; border-collapse: collapse; margin: 12px 0 4px; }
|
||||
th, td { border:1px solid var(--border); padding:8px; vertical-align: top; }
|
||||
th { text-align:left; background:#f8fafc; font-weight:600; }
|
||||
.muted { color:var(--muted); }
|
||||
.section { margin-top:18px; }
|
||||
.section h3 { margin:0 0 8px; font-size:14px; text-transform:uppercase; letter-spacing:.4px; color:var(--accent); }
|
||||
.textarea { border:1px solid var(--border); border-radius:8px; min-height:90px; padding:10px; white-space:pre-wrap; }
|
||||
.grid-2 { display:grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
||||
.row { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
|
||||
.line { height:1px; background:var(--border); margin: 8px 0; }
|
||||
.sign { height:48px; border-bottom:1px solid var(--border); }
|
||||
.print-tip { color:var(--muted); font-size:12px; margin-top:8px; }
|
||||
.print-btn { display:inline-block; padding:8px 12px; border:1px solid var(--border); border-radius:6px; background:#f8fafc; cursor:pointer; font-size:13px; }
|
||||
@media print {
|
||||
.sheet { border:none; border-radius:0; margin:0; padding:0; }
|
||||
.print-btn, .print-tip { display:none !important; }
|
||||
body { font-size:12px; }
|
||||
th, td { padding:6px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sheet">
|
||||
<header>
|
||||
<div>
|
||||
<div class="title">Return / Refund Slip</div>
|
||||
<div class="sub">Include this page inside the package for the shopkeeper to examine the return.</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="print-btn" onclick="window.print()">Print</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="meta">
|
||||
<div><div class="label">Order number</div><div><strong>{{ order.number|default:order.code|default:order.id }}</strong></div></div>
|
||||
<div><div class="label">Order date</div><div>{% if order.created_at %}{{ order.created_at|date:"Y-m-d H:i" }}{% else %}{% now "Y-m-d" %}{% endif %}</div></div>
|
||||
<div><div class="label">Customer name</div><div>{{ order.customer_name|default:order.user.get_full_name|default:order.user.username|default:"" }}</div></div>
|
||||
<div><div class="label">Customer email</div><div>{{ order.customer_email|default:order.user.email|default:"" }}</div></div>
|
||||
<div><div class="label">Phone</div><div>{{ order.customer_phone|default:"" }}</div></div>
|
||||
<div><div class="label">Return created</div><div>{% now "Y-m-d H:i" %}</div></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Returned items</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:44%">Item</th>
|
||||
<th style="width:16%">SKU</th>
|
||||
<th style="width:10%">Qty</th>
|
||||
<th style="width:30%">Reason (per item)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr>
|
||||
<td>
|
||||
<div><strong>{{ it.product_name|default:it.product.title|default:it.name|default:"Item" }}</strong></div>
|
||||
{% if it.variant or it.options %}
|
||||
<div class="muted" style="font-size:12px;">
|
||||
{% if it.variant %}Variant: {{ it.variant }}{% endif %}
|
||||
{% if it.options %}{% if it.variant %} • {% endif %}Options: {{ it.options }}{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ it.sku|default:"—" }}</td>
|
||||
<td>{{ it.quantity|default:1 }}</td>
|
||||
<td>{% if it.reason %}{{ it.reason }}{% else %} {% endif %}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="muted">No items listed.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="print-tip">Tip: If the reason differs per item, write it in the last column above.</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Return reason (customer)</h3>
|
||||
<div class="textarea">
|
||||
{% if return_reason %}{{ return_reason }}{% else %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h3>Shopkeeper inspection</h3>
|
||||
<div class="grid-2">
|
||||
<div>
|
||||
<div class="row">
|
||||
<strong>Package condition:</strong>
|
||||
[ ] Intact
|
||||
[ ] Opened
|
||||
[ ] Damaged
|
||||
</div>
|
||||
<div class="row" style="margin-top:6px;">
|
||||
<strong>Items condition:</strong>
|
||||
[ ] New
|
||||
[ ] Light wear
|
||||
[ ] Used
|
||||
[ ] Damaged
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="row">
|
||||
<strong>Resolution:</strong>
|
||||
[ ] Accept refund
|
||||
[ ] Deny
|
||||
[ ] Exchange
|
||||
</div>
|
||||
<div class="row" style="margin-top:6px;">
|
||||
<strong>Restocking fee:</strong> ________ %
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section" style="margin-top:12px;">
|
||||
<div class="row"><strong>Notes:</strong></div>
|
||||
<div class="textarea" style="min-height:70px;"></div>
|
||||
</div>
|
||||
|
||||
<div class="grid-2" style="margin-top:16px;">
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;">Processed by (name/signature)</div>
|
||||
<div class="sign"></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted" style="font-size:12px;">Date</div>
|
||||
<div class="sign"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="line"></div>
|
||||
<div class="muted" style="font-size:12px; margin-top:8px;">
|
||||
Attach this slip inside the package. Keep a copy for your records.
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user