feat: Add Telegram chat ID to ShopModel for automatic invoice sending

chore: Update Dockerfile to install WeasyPrint dependencies

feat: Enhance ShopOrderModelAdmin with invoice download buttons

feat: Implement invoice generation for OrderModel and ShopOrderModel

feat: Send invoice to shop's Telegram chat upon ShopOrderModel creation

feat: Create Celery task to send shop order invoice via Telegram

feat: Add invoice download endpoints for OrderModel and ShopOrderModel

feat: Implement views for downloading order and shop order invoices

chore: Update requirements.txt to include necessary packages for PDF generation

feat: Create HTML templates for order and shop order invoices
This commit is contained in:
Parsa Nazer
2025-12-28 11:43:33 +03:30
parent 6a7e526f23
commit 34715994ce
12 changed files with 1168 additions and 5 deletions
+357
View File
@@ -0,0 +1,357 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>فاکتور سفارش - {{ order_number }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Vazir', 'Tahoma', Arial, sans-serif;
direction: rtl;
padding: 10px;
background-color: #fff;
}
.invoice-container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 15px;
border-bottom: 2px solid #333;
padding-bottom: 10px;
}
.header h1 {
color: #333;
font-size: 22px;
margin-bottom: 5px;
}
.header h2 {
color: #666;
font-size: 14px;
}
.order-info {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
background-color: #f9f9f9;
padding: 12px;
border-radius: 3px;
}
.info-section {
flex: 1;
}
.info-section h3 {
color: #333;
margin-bottom: 6px;
font-size: 13px;
border-bottom: 1px solid #ddd;
padding-bottom: 3px;
}
.info-row {
margin: 4px 0;
font-size: 11px;
}
.info-label {
font-weight: bold;
color: #555;
}
.info-value {
color: #333;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
.items-table th {
background-color: #333;
color: white;
padding: 6px;
text-align: center;
font-size: 11px;
}
.items-table td {
padding: 6px;
text-align: center;
border-bottom: 1px solid #ddd;
font-size: 10px;
}
.items-table tr:hover {
background-color: #f5f5f5;
}
.summary {
margin-top: 15px;
float: left;
width: 300px;
}
.summary-row {
display: flex;
justify-content: space-between;
padding: 6px;
border-bottom: 1px solid #ddd;
font-size: 11px;
}
.summary-row.total {
background-color: #333;
color: white;
font-weight: bold;
font-size: 14px;
margin-top: 6px;
}
.summary-label {
font-weight: bold;
}
.summary-value {
text-align: left;
}
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 9px;
font-weight: bold;
}
.status-paid {
background-color: #4CAF50;
color: white;
}
.status-unpaid {
background-color: #f44336;
color: white;
}
.status-pending {
background-color: #ff9800;
color: white;
}
.footer {
margin-top: 30px;
text-align: center;
padding-top: 10px;
border-top: 1px solid #ddd;
color: #777;
font-size: 9px;
}
.clearfix::after {
content: "";
display: table;
clear: both;
}
.discount-highlight {
color: #d32f2f;
font-weight: bold;
}
</style>
</head>
<body>
<div class="invoice-container">
<div class="header">
<h1>فاکتور فروش</h1>
<h2>شماره سفارش: {{ order_number }}</h2>
</div>
<div class="order-info">
<div class="info-section">
<h3>اطلاعات مشتری</h3>
{% if user %}
<div class="info-row">
<span class="info-label">نام:</span>
<span class="info-value">{{ user.first_name }} {{ user.last_name }}</span>
</div>
<div class="info-row">
<span class="info-label">ایمیل:</span>
<span class="info-value">{{ user.email|default:"---" }}</span>
</div>
<div class="info-row">
<span class="info-label">تلفن:</span>
<span class="info-value">{{ user.phone }}</span>
</div>
{% endif %}
</div>
<div class="info-section">
<h3>اطلاعات سفارش</h3>
<div class="info-row">
<span class="info-label">تاریخ:</span>
<span class="info-value">{{ created_at_jalali|default:"---" }}</span>
</div>
<div class="info-row">
<span class="info-label">وضعیت پرداخت:</span>
<span class="info-value">
{% if is_paid %}
<span class="status-badge status-paid">پرداخت شده</span>
{% else %}
<span class="status-badge status-unpaid">پرداخت نشده</span>
{% endif %}
</span>
</div>
<div class="info-row">
<span class="info-label">وضعیت سفارش:</span>
<span class="info-value">
<span class="status-badge status-pending">{{ status }}</span>
</span>
</div>
</div>
</div>
{% if address %}
<div class="order-info">
<div class="info-section">
<h3>آدرس تحویل</h3>
<div class="info-row">
<span class="info-label">نام گیرنده:</span>
<span class="info-value">{{ address.name }}</span>
</div>
<div class="info-row">
<span class="info-label">آدرس:</span>
<span class="info-value">{{ address.address }}</span>
</div>
<div class="info-row">
<span class="info-label">شهر/استان:</span>
<span class="info-value">{{ address.city }}, {{ address.province }}</span>
</div>
<div class="info-row">
<span class="info-label">کد پستی:</span>
<span class="info-value">{{ address.postal_code }}</span>
</div>
<div class="info-row">
<span class="info-label">تلفن:</span>
<span class="info-value">{{ address.phone }}</span>
</div>
</div>
</div>
{% endif %}
<table class="items-table">
<thead>
<tr>
<th>ردیف</th>
<th>نام محصول</th>
<th>تنوع</th>
<th>تعداد</th>
<th>قیمت اصلی</th>
<th>تخفیف محصول</th>
<th>تخفیف ویژه</th>
<th>قیمت نهایی</th>
</tr>
</thead>
<tbody>
{% for item_data in items_with_discount %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ item_data.item.product.product.name }}</td>
<td>{{ item_data.item.product.title }}</td>
<td>{{ item_data.item.quantity }}</td>
<td>{{ item_data.price_before_discount|floatformat:0 }} تومان</td>
<td class="discount-highlight">
{% if item_data.item.discount_percent > 0 %}
{{ item_data.item.discount_percent }}% ({{ item_data.discount_amount|floatformat:0 }} تومان)
{% else %}
---
{% endif %}
</td>
<td class="discount-highlight">
{% if item_data.item.special_discount_amount > 0 %}
{{ item_data.item.special_discount_amount|floatformat:0 }} تومان
{% else %}
---
{% endif %}
</td>
<td>{{ item_data.item.price|floatformat:0 }} تومان</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="clearfix">
<div class="summary">
<div class="summary-row">
<span class="summary-label">تعداد کل اقلام:</span>
<span class="summary-value">{{ total_items }}</span>
</div>
<div class="summary-row">
<span class="summary-label">جمع کل:</span>
<span class="summary-value">{{ subtotal|floatformat:0 }} تومان</span>
</div>
{% if discount_amount > 0 %}
<div class="summary-row">
<span class="summary-label discount-highlight">تخفیف کد:</span>
<span class="summary-value discount-highlight">{{ discount_amount|floatformat:0 }} تومان</span>
</div>
{% endif %}
{% if special_discount_total > 0 %}
<div class="summary-row">
<span class="summary-label discount-highlight">تخفیف ویژه:</span>
<span class="summary-value discount-highlight">{{ special_discount_total|floatformat:0 }} تومان</span>
</div>
{% endif %}
<div class="summary-row">
<span class="summary-label">مالیات:</span>
<span class="summary-value">{{ tax|floatformat:0 }} تومان</span>
</div>
<div class="summary-row total">
<span class="summary-label">مبلغ قابل پرداخت:</span>
<span class="summary-value">{{ final_price|floatformat:0 }} تومان</span>
</div>
</div>
</div>
{% if discount_code %}
<div class="clearfix" style="margin-top: 100px;">
<div class="order-info">
<div class="info-section">
<h3>کد تخفیف استفاده شده</h3>
<div class="info-row">
<span class="info-label">کد:</span>
<span class="info-value">{{ discount_code.code }}</span>
</div>
<div class="info-row">
<span class="info-label">درصد:</span>
<span class="info-value">{{ discount_code.percent }}%</span>
</div>
</div>
</div>
</div>
{% endif %}
<div class="footer">
<p>با تشکر از خرید شما</p>
<p>این فاکتور به صورت الکترونیکی صادر شده و نیازی به امضا و مهر ندارد</p>
</div>
</div>
</body>
</html>
@@ -0,0 +1,434 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>فاکتور فروشگاه - {{ shop_order_id }}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Vazir', 'Tahoma', Arial, sans-serif;
direction: rtl;
padding: 10px;
background-color: #fff;
}
.invoice-container {
max-width: 850px;
margin: 0 auto;
background: white;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 15px;
border-bottom: 2px solid #333;
padding-bottom: 10px;
}
.header h1 {
color: #333;
font-size: 22px;
margin-bottom: 5px;
}
.header h2 {
color: #666;
font-size: 14px;
}
.shop-info {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 12px;
border-radius: 5px;
margin-bottom: 15px;
}
.shop-info h3 {
font-size: 15px;
margin-bottom: 5px;
}
.shop-info p {
font-size: 11px;
margin: 3px 0;
}
.order-info {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
background-color: #f9f9f9;
padding: 12px;
border-radius: 3px;
}
.info-section {
flex: 1;
margin: 0 5px;
}
.info-section h3 {
color: #333;
margin-bottom: 6px;
font-size: 13px;
border-bottom: 1px solid #ddd;
padding-bottom: 3px;
}
.info-row {
margin: 4px 0;
font-size: 11px;
}
.info-label {
font-weight: bold;
color: #555;
}
.info-value {
color: #333;
}
.items-table {
width: 100%;
border-collapse: collapse;
margin: 15px 0;
}
.items-table th {
background-color: #333;
color: white;
padding: 6px;
text-align: center;
font-size: 11px;
}
.items-table td {
padding: 6px;
text-align: center;
border-bottom: 1px solid #ddd;
font-size: 10px;
}
.items-table tr:hover {
background-color: #f5f5f5;
}
.financial-summary {
margin-top: 15px;
background-color: #f9f9f9;
padding: 12px;
border-radius: 5px;
}
.summary-section {
margin-bottom: 10px;
}
.summary-section h3 {
color: #333;
margin-bottom: 8px;
font-size: 14px;
border-bottom: 1px solid #667eea;
padding-bottom: 5px;
}
.summary-row {
display: flex;
justify-content: space-between;
padding: 6px;
border-bottom: 1px solid #ddd;
font-size: 11px;
}
.summary-row.highlight {
background-color: #e3f2fd;
font-weight: bold;
}
.summary-row.total {
background-color: #333;
color: white;
font-weight: bold;
font-size: 14px;
margin-top: 6px;
}
.summary-row.payable {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-weight: bold;
font-size: 15px;
margin-top: 6px;
}
.summary-label {
font-weight: bold;
}
.summary-value {
text-align: left;
}
.status-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 10px;
font-size: 9px;
font-weight: bold;
}
.status-paid {
background-color: #4CAF50;
color: white;
}
.status-unpaid {
background-color: #f44336;
color: white;
}
.status-pending {
background-color: #ff9800;
color: white;
}
.status-settled {
background-color: #2196F3;
color: white;
}
.footer {
margin-top: 30px;
text-align: center;
padding-top: 10px;
border-top: 1px solid #ddd;
color: #777;
font-size: 9px;
}
.clearfix::after {
content: "";
display: table;
clear: both;
}
.discount-highlight {
color: #d32f2f;
font-weight: bold;
}
.commission-highlight {
color: #1976d2;
font-weight: bold;
}
.address-box {
background-color: #fff3cd;
border: 1px solid #ffc107;
padding: 10px;
border-radius: 3px;
margin: 12px 0;
}
.address-box h3 {
color: #856404;
margin-bottom: 6px;
font-size: 13px;
}
</style>
</head>
<body>
<div class="invoice-container">
<div class="header">
<h1>فاکتور فروشگاه</h1>
<h2>شماره سفارش اصلی: {{ order_number }} | شماره فاکتور فروشگاه: {{ shop_order_id }}</h2>
</div>
<div class="shop-info">
<h3>{{ shop.shop_name }}</h3>
<p>{{ shop.shop_description }}</p>
<p>کمیسیون: {{ commission_percent }}%</p>
</div>
<div class="order-info">
<div class="info-section">
<h3>اطلاعات مشتری</h3>
<div class="info-row">
<span class="info-label">نام:</span>
<span class="info-value">{{ customer_name|default:"---" }}</span>
</div>
<div class="info-row">
<span class="info-label">تلفن:</span>
<span class="info-value">{{ customer_phone|default:"---" }}</span>
</div>
</div>
<div class="info-section">
<h3>اطلاعات سفارش</h3>
<div class="info-row">
<span class="info-label">تاریخ:</span>
<span class="info-value">{{ created_at_jalali|default:"---" }}</span>
</div>
<div class="info-row">
<span class="info-label">وضعیت پرداخت:</span>
<span class="info-value">
{% if is_paid %}
<span class="status-badge status-paid">پرداخت شده</span>
{% else %}
<span class="status-badge status-unpaid">پرداخت نشده</span>
{% endif %}
</span>
</div>
<div class="info-row">
<span class="info-label">وضعیت سفارش:</span>
<span class="info-value">
<span class="status-badge status-pending">{{ status }}</span>
</span>
</div>
<div class="info-row">
<span class="info-label">وضعیت تسویه:</span>
<span class="info-value">
{% if is_settled %}
<span class="status-badge status-settled">تسویه شده</span>
{% else %}
<span class="status-badge status-unpaid">تسویه نشده</span>
{% endif %}
</span>
</div>
</div>
</div>
{% if address_text %}
<div class="address-box">
<h3>آدرس تحویل</h3>
<div class="info-row">
<span class="info-label">گیرنده:</span>
<span class="info-value">{{ address_recipient_name }}</span>
</div>
<div class="info-row">
<span class="info-label">آدرس:</span>
<span class="info-value">{{ address_text }}</span>
</div>
<div class="info-row">
<span class="info-label">شهر/استان:</span>
<span class="info-value">{{ address_city }}, {{ address_province }}</span>
</div>
<div class="info-row">
<span class="info-label">کد پستی:</span>
<span class="info-value">{{ address_postal_code }}</span>
</div>
<div class="info-row">
<span class="info-label">تلفن:</span>
<span class="info-value">{{ address_phone }}</span>
</div>
</div>
{% endif %}
<table class="items-table">
<thead>
<tr>
<th>ردیف</th>
<th>نام محصول</th>
<th>تنوع</th>
<th>تعداد</th>
<th>قیمت واحد</th>
<th>تخفیف محصول</th>
<th>تخفیف ویژه</th>
<th>قیمت نهایی</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ item.order_item.product.product.name }}</td>
<td>{{ item.order_item.product.title }}</td>
<td>{{ item.quantity }}</td>
<td>{{ item.unit_price|floatformat:0 }} تومان</td>
<td class="discount-highlight">
{% if item.discount_amount > 0 %}
{{ item.order_item.discount_percent }}% ({{ item.discount_amount|floatformat:0 }} تومان)
{% else %}
---
{% endif %}
</td>
<td class="discount-highlight">
{% if item.special_discount_amount > 0 %}
{{ item.special_discount_amount|floatformat:0 }} تومان
{% else %}
---
{% endif %}
</td>
<td>{{ item.total_price|floatformat:0 }} تومان</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="financial-summary">
<div class="summary-section">
<h3>خلاصه مالی</h3>
<div class="summary-row">
<span class="summary-label">تعداد کل اقلام:</span>
<span class="summary-value">{{ total_items }}</span>
</div>
<div class="summary-row">
<span class="summary-label">جمع کل (Subtotal):</span>
<span class="summary-value">{{ subtotal|floatformat:0 }} تومان</span>
</div>
{% if discount_amount > 0 %}
<div class="summary-row">
<span class="summary-label discount-highlight">تخفیف معمولی:</span>
<span class="summary-value discount-highlight">{{ discount_amount|floatformat:0 }} تومان</span>
</div>
{% endif %}
{% if special_discount_amount > 0 %}
<div class="summary-row">
<span class="summary-label discount-highlight">تخفیف ویژه:</span>
<span class="summary-value discount-highlight">{{ special_discount_amount|floatformat:0 }} تومان</span>
</div>
{% endif %}
<div class="summary-row">
<span class="summary-label">مالیات:</span>
<span class="summary-value">{{ tax_amount|floatformat:0 }} تومان</span>
</div>
</div>
<div class="summary-section">
<h3>محاسبات کمیسیون</h3>
<div class="summary-row highlight">
<span class="summary-label commission-highlight">درصد کمیسیون:</span>
<span class="summary-value commission-highlight">{{ commission_percent }}%</span>
</div>
<div class="summary-row highlight">
<span class="summary-label commission-highlight">مبلغ کمیسیون:</span>
<span class="summary-value commission-highlight">{{ commission_amount|floatformat:0 }} تومان</span>
</div>
</div>
<div class="summary-section">
<h3>مبلغ نهایی</h3>
<div class="summary-row payable">
<span class="summary-label">مبلغ قابل پرداخت به فروشگاه:</span>
<span class="summary-value">{{ payable_amount|floatformat:0 }} تومان</span>
</div>
</div>
</div>
<div class="footer">
<p>با تشکر از همکاری شما</p>
<p>این فاکتور به صورت الکترونیکی صادر شده و نیازی به امضا و مهر ندارد</p>
<p>در صورت هرگونه سوال یا مشکل با پشتیبانی تماس بگیرید</p>
</div>
</div>
</body>
</html>