From ad80907aa4bcb6f4161884722cea75dfb59690fe Mon Sep 17 00:00:00 2001 From: Parsa Nazer Date: Tue, 24 Feb 2026 16:51:15 +0330 Subject: [PATCH] new invoice design --- backend/order/invoice_generator.py | 32 +- backend/order/urls.py | 5 +- backend/order/views.py | 49 +- backend/templates/order/invoice_order2.html | 412 ++++++++----- .../templates/order/invoice_shop_order2.html | 555 ++++++++++++++++++ 5 files changed, 892 insertions(+), 161 deletions(-) create mode 100644 backend/templates/order/invoice_shop_order2.html diff --git a/backend/order/invoice_generator.py b/backend/order/invoice_generator.py index 26496c1..55b67bd 100644 --- a/backend/order/invoice_generator.py +++ b/backend/order/invoice_generator.py @@ -46,6 +46,13 @@ def generate_order_invoice(order_id): 'final_price': item.price_after_special_discount(), }) + # Resolve image paths for the template (absolute file paths for WeasyPrint) + import os + template_dir = os.path.join(settings.BASE_DIR, 'templates', 'order') + logo_path = os.path.join(template_dir, 'logo2.png') + logo_pattern_path = os.path.join(template_dir, 'logo-pattern.png') + qr_code_path = os.path.join(template_dir, 'qr-code.png') + # Use stored model fields for accuracy and consistency context = { 'order': order, @@ -66,13 +73,16 @@ def generate_order_invoice(order_id): 'final_price': order.final_price or 0, # Stored field: final price after all calculations 'is_paid': order.is_paid, 'status': order.get_status_display(), + 'logo_path': logo_path, + 'logo_pattern_path': logo_pattern_path, + 'qr_code_path': qr_code_path, } # Render HTML template - html_string = render_to_string('order/invoice_order.html', context) + html_string = render_to_string('order/invoice_order2.html', context) - # Generate PDF - html = HTML(string=html_string, base_url=settings.STATIC_URL) + # Generate PDF - use template directory as base_url so images resolve correctly + html = HTML(string=html_string, base_url=template_dir) pdf_file = BytesIO() html.write_pdf(pdf_file) pdf_file.seek(0) @@ -146,6 +156,13 @@ def generate_shop_order_invoice(shop_order_id): # Calculate subtotal (cart total before discounts) subtotal = shop_order.subtotal or 0 + # Resolve image paths for the template (absolute file paths for WeasyPrint) + import os + template_dir = os.path.join(settings.BASE_DIR, 'templates', 'order') + logo_path = os.path.join(template_dir, 'logo2.png') + logo_pattern_path = os.path.join(template_dir, 'logo-pattern.png') + qr_code_path = os.path.join(template_dir, 'qr-code.png') + # Prepare context for the template context = { 'shop_order': shop_order, @@ -175,13 +192,16 @@ def generate_shop_order_invoice(shop_order_id): 'is_paid': shop_order.is_paid, 'status': shop_order.get_status_display(), 'is_settled': shop_order.is_settled, + 'logo_path': logo_path, + 'logo_pattern_path': logo_pattern_path, + 'qr_code_path': qr_code_path, } # Render HTML template - html_string = render_to_string('order/invoice_shop_order.html', context) + html_string = render_to_string('order/invoice_shop_order2.html', context) - # Generate PDF - html = HTML(string=html_string, base_url=settings.STATIC_URL) + # Generate PDF - use template directory as base_url so images resolve correctly + html = HTML(string=html_string, base_url=template_dir) pdf_file = BytesIO() html.write_pdf(pdf_file) pdf_file.seek(0) diff --git a/backend/order/urls.py b/backend/order/urls.py index 552bfad..56573c8 100644 --- a/backend/order/urls.py +++ b/backend/order/urls.py @@ -16,7 +16,10 @@ urlpatterns = [ CallbackView.as_view(), name='callback-gateway'), path('', OrderGetView.as_view(), name='order-get'), - # Invoice download endpoints + # User invoice download endpoint (DRF APIView) + path('invoice/', UserOrderInvoiceView.as_view(), name='user-order-invoice'), + + # Admin invoice download endpoints path('invoice/order//download', download_order_invoice, name='download-order-invoice'), path('invoice/shop-order//download', download_shop_order_invoice, name='download-shop-order-invoice'), ] diff --git a/backend/order/views.py b/backend/order/views.py index 69eb586..f33681c 100644 --- a/backend/order/views.py +++ b/backend/order/views.py @@ -517,6 +517,53 @@ from django.contrib.auth.decorators import login_required from .invoice_generator import generate_order_invoice, generate_shop_order_invoice +class UserOrderInvoiceView(APIView): + """ + API endpoint for authenticated users to download their own order invoice PDF. + Users can only download invoices for orders that belong to them. + """ + permission_classes = [IsAuthenticated] + + @extend_schema( + tags=["order invoice"], + description="Download PDF invoice for the authenticated user's order.", + responses={200: OpenApiTypes.BINARY}, + ) + def get(self, request, order_id): + from .models import OrderModel + + try: + order = OrderModel.objects.get(pk=order_id) + except OrderModel.DoesNotExist: + return Response( + {'detail': 'سفارش مورد نظر یافت نشد'}, + status=status.HTTP_404_NOT_FOUND, + ) + + if order.user != request.user: + return Response( + {'detail': 'شما اجازه دسترسی به این فاکتور را ندارید'}, + status=status.HTTP_403_FORBIDDEN, + ) + + if not order.is_paid: + return Response( + {'detail': 'فاکتور فقط برای سفارش‌های پرداخت شده قابل دانلود است'}, + status=status.HTTP_400_BAD_REQUEST, + ) + + try: + pdf_file = generate_order_invoice(order_id) + response = HttpResponse(pdf_file.read(), content_type='application/pdf') + response['Content-Disposition'] = f'attachment; filename="invoice_{order_id}.pdf"' + return response + except Exception as e: + return Response( + {'detail': f'خطا در ایجاد فاکتور: {str(e)}'}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + @login_required def download_order_invoice(request, order_id): """ @@ -530,7 +577,7 @@ def download_order_invoice(request, order_id): pdf_file = generate_order_invoice(order_id) response = HttpResponse(pdf_file.read(), content_type='application/pdf') - response['Content-Disposition'] = f'attachment; filename="order_invoice_{order_id + 1000}.pdf"' + response['Content-Disposition'] = f'attachment; filename="order_invoice_{order_id}.pdf"' return response except ValueError as e: diff --git a/backend/templates/order/invoice_order2.html b/backend/templates/order/invoice_order2.html index e389370..974817d 100644 --- a/backend/templates/order/invoice_order2.html +++ b/backend/templates/order/invoice_order2.html @@ -1,11 +1,18 @@ + +{% load price_filters %} - صورت‌حساب الکترونیکی - فروشگاه هیملز + صورت‌حساب الکترونیکی - شماره {{ order_number }} @@ -242,80 +263,114 @@
-
- QR -
+ {% comment %}
+ QR +
{% endcomment %}
صورت حساب الکترونیکی
- Logo + Logo
فروشگاه هیملز
-
فروشگاه
هی ملز
- -
-
-
- فرستنده :مقدار -
-
- شناسه ملی :مقدار -
-
- شماره ثبت :مقدار -
-
- شماره مجوز :مقدار -
-
- نشانی شرکت :مقدار -
-
- کد پستی :مقدار -
-
- تلفن و فکس :مقدار -
-
-
- +
- شماره فاکتور :مقدار + شماره فاکتور :{{ order_number }}
-
تاریخ :مقدار
- شماره پیگیری :مقدار + تاریخ :{{ created_at_jalali|default:"---" }} +
+
+ وضعیت پرداخت : + + {% if is_paid %} + پرداخت شده + {% else %} + پرداخت نشده + {% endif %} + +
+
+ وضعیت سفارش : + {{ status }}
+ + +
+
+
+ فرستنده :فروشگاه هیملز +
+
+ شناسه ملی :--- +
+
+ شماره ثبت :--- +
+
+ شماره مجوز :--- +
+
+ نشانی شرکت :--- +
+
+ کد پستی :--- +
+
+ تلفن و فکس :--- +
+
+
+ + +
هیملز
-
خریدار
+
-
خریدار :مقدار
+ {% if user %}
- شماره ملی :مقدار + خریدار :{{ user.first_name }} {{ user.last_name }}
- شماره تماس :مقدار + ایمیل :{{ user.email|default:"---" }}
- کد پستی :مقدار + شماره تماس :{{ user.phone }}
-
نشانی :مقدار
+ {% endif %} + {% if address %} +
+ نام گیرنده :{{ address.name }} +
+
+ کد پستی :{{ address.postal_code }} +
+
+ شهر/استان :{{ address.city }}، {{ address.province }} +
+
+ تلفن :{{ address.phone }} +
+
+ نشانی :{{ address.address }} +
+ {% endif %}
+
خریدار
@@ -324,70 +379,121 @@ ردیف - شناسه کالا یا خدمات - شرح کالا یا خدمات + نام محصول + تنوع تعداد - مبلغ واحد (تومان) - مبلغ کل (تومان) - تخفیف (تومان) - مبلغ کل پس از تخفیف (تومان) - جمع مالیات و عوارض ارزش افزوده (تومان) - جمع کل پس از تخفیف و مالیات و عوارض (تومان) + قیمت واحد (تومان) + جمع (تومان) + تخفیف محصول + تخفیف ویژه (تومان) + قیمت نهایی (تومان) - + {% for item_data in items_with_discount %} - 1 - --- - --- - --- - --- - --- - --- - --- - --- - --- + {{ forloop.counter }} + {{ item_data.item.product.product.name }} + {{ item_data.item.product.title }} + {{ item_data.item.quantity }} + {{ item_data.unit_price|price_format }} + {{ item_data.price_before_discount|price_format }} + + {% if item_data.item.discount_percent > 0 %} + {{ item_data.item.discount_percent }}% ({{ item_data.discount_amount|price_format }}) + {% else %} + --- + {% endif %} + + + {% if item_data.item.special_discount_amount > 0 %} + {{ item_data.item.special_discount_amount|price_format }} + {% else %} + --- + {% endif %} + + {{ item_data.final_price|price_format }} + {% endfor %} جمع کل + {{ total_items }} --- - --- - --- - --- - --- - --- + {{ subtotal|price_format }} + + {% if items_discount_amount > 0 %} + -{{ items_discount_amount|price_format }} + {% else %} + 0 + {% endif %} + + + {% if special_discount_total > 0 %} + -{{ special_discount_total|price_format }} + {% else %} + 0 + {% endif %} + --- + {% if discount_amount > 0 %} + + + تخفیف کد تخفیف + {% if discount_code %}({{ discount_code.code }} - {{ discount_code.percent }}%){% endif %} + + -{{ discount_amount|price_format }} + + {% endif %} + + + + مالیات ({{ tax_rate }}%) + + {{ tax|price_format }} + + - + جمع کل پس از کسر تخفیف با احتساب مالیات و عوارض (تومان) - --- + {{ final_price|price_format }}
- مهر و امضای فروشگاه :مقدار + مهر و امضای فروشگاه :
- تاریخ تحویل :مقدار + تاریخ تحویل :
- ساعت تحویل :مقدار + ساعت تحویل :
- روش های پرداخت :مقدار + روش های پرداخت : + + {% if is_paid %} + پرداخت شده + {% else %} + پرداخت نشده + {% endif %} +
- مهر و امضای خریدار :مقدار + مهر و امضای خریدار :
+ + diff --git a/backend/templates/order/invoice_shop_order2.html b/backend/templates/order/invoice_shop_order2.html new file mode 100644 index 0000000..acbe388 --- /dev/null +++ b/backend/templates/order/invoice_shop_order2.html @@ -0,0 +1,555 @@ +{% load price_filters %} + + + + + + فاکتور فروشگاه - {{ shop_order_id }} + + + + +
+ +
+ + {% comment %}
+ QR +
{% endcomment %} + + +
+
فاکتور فروشگاه
+
+ Logo +
+
+ سفارش: {{ order_number }} | فاکتور: {{ shop_order_id }} +
+
+ + +
+
+

{{ shop.shop_name }}

+ {% if shop.shop_description %} +

{{ shop.shop_description|truncatewords:10 }}

+ {% endif %} +
+

کمیسیون: {{ commission_percent }}%

+
+ + +
+ +
+
+
+ شماره سفارش اصلی :{{ order_number }} +
+
+ شماره فاکتور فروشگاه :{{ shop_order_id }} +
+
+ تاریخ :{{ created_at_jalali|default:"---" }} +
+
+ وضعیت پرداخت : + + {% if is_paid %} + پرداخت شده + {% else %} + پرداخت نشده + {% endif %} + +
+
+ وضعیت سفارش : + {{ status }} +
+
+ وضعیت تسویه : + + {% if is_settled %} + تسویه شده + {% else %} + تسویه نشده + {% endif %} + +
+
+
+ + +
+
+
+ فرستنده :{{ shop.shop_name }} +
+
+ شناسه ملی :--- +
+
+ شماره ثبت :--- +
+
+ شماره مجوز :--- +
+
+ نشانی شرکت :--- +
+
+ کد پستی :--- +
+
+ تلفن و فکس :--- +
+
+
+ + +
فروشگاه
+
+ + +
+
+
+
+ خریدار :{{ customer_name|default:"---" }} +
+
+ شماره تماس :{{ customer_phone|default:"---" }} +
+ {% if address_recipient_name %} +
+ نام گیرنده :{{ address_recipient_name }} +
+ {% endif %} + {% if address_postal_code %} +
+ کد پستی :{{ address_postal_code }} +
+ {% endif %} + {% if address_city %} +
+ شهر/استان :{{ address_city }}، {{ address_province }} +
+ {% endif %} + {% if address_phone %} +
+ تلفن :{{ address_phone }} +
+ {% endif %} + {% if address_text %} +
+ نشانی :{{ address_text }} +
+ {% endif %} +
+
+
خریدار
+
+ + +
+ + + + + + + + + + + + + + + + {% for item_data in items_with_discount %} + + + + + + + + + + + + {% endfor %} + + + + + + + + + + + + + + + + + + + + + +
ردیفنام محصولتنوعتعدادقیمت واحد (تومان)جمع (تومان)تخفیف محصولتخفیف ویژه (تومان)قیمت نهایی (تومان)
{{ forloop.counter }}{{ item_data.order_item.product.product.name }}{{ item_data.order_item.product.title }}{{ item_data.item.quantity }}{{ item_data.unit_price|price_format }}{{ item_data.price_before_discount|price_format }} + {% if item_data.order_item.discount_percent > 0 %} + {{ item_data.order_item.discount_percent }}% ({{ item_data.discount_amount|price_format }}) + {% else %} + --- + {% endif %} + + {% if item_data.special_discount_amount > 0 %} + {{ item_data.special_discount_amount|price_format }} + {% else %} + --- + {% endif %} + {{ item_data.final_price|price_format }}
جمع کل{{ total_items }}---{{ subtotal|price_format }} + {% if items_discount_amount > 0 %} + -{{ items_discount_amount|price_format }} + {% else %} + 0 + {% endif %} + + {% if special_discount_total > 0 %} + -{{ special_discount_total|price_format }} + {% else %} + 0 + {% endif %} + ---
+ کمیسیون ({{ commission_percent }}%) + -{{ commission_amount|price_format }}
+ قیمت نهایی (قابل پرداخت به فروشگاه) (تومان) + {{ payable_amount|price_format }}
+ +
+
+ مهر و امضای فروشگاه : +
+
+ تاریخ تحویل : +
+
+ ساعت تحویل : +
+
+ روش های پرداخت : + + {% if is_paid %} + پرداخت شده + {% else %} + پرداخت نشده + {% endif %} + +
+
+ مهر و امضای خریدار : +
+
+
+ + +
+ +