""" Invoice generation utilities for OrderModel and ShopOrderModel. These functions generate PDF invoices from HTML templates. """ from io import BytesIO from datetime import datetime from django.template.loader import render_to_string from utils.jalali_formatter import to_jalali from weasyprint import HTML from django.conf import settings import jdatetime from django.conf import settings def generate_order_invoice(order_id): """ Generate a PDF invoice for an OrderModel instance. Args: order_id: The ID of the OrderModel instance Returns: BytesIO: A BytesIO object containing the PDF data """ from .models import OrderModel try: order = OrderModel.objects.select_related( 'user', 'address', 'discount_code' ).prefetch_related('items__product__product').get(pk=order_id) except OrderModel.DoesNotExist: raise ValueError(f"Order with ID {order_id} does not exist") # Prepare items with calculated discount amounts using stored values items = order.items.all() items_with_discount = [] total_items_discount = 0 for item in items: total_items_discount += item.total_product_discount_amount() or 0 items_with_discount.append({ 'item': item, 'discount_amount': item.total_product_discount_amount() or 0, 'price_before_discount': item.total_price_before_discount(), 'unit_price': item.unit_price(), '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 # Format the Jalali date for display jalali_date = to_jalali(order.created_at) created_at_jalali_str = jalali_date.strftime('%Y-%m-%d %H:%M') if jalali_date else '---' context = { 'order': order, 'order_number': order.pk, 'items': items, 'items_with_discount': items_with_discount, 'user': order.user, 'address': order.address, 'discount_code': order.discount_code, 'created_at_jalali': created_at_jalali_str, 'total_items': sum(item.quantity for item in items), 'subtotal': order.cart_total or 0, # Stored field: total before any discounts 'items_discount_amount': total_items_discount, 'discount_amount': order.discount_amount or 0, # Stored field: discount code amount 'special_discount_total': order.special_discount_total or 0, # Stored field: special discount total 'tax_rate': settings.DEFAULT_TAX_RATE, 'tax': order.tax or 0, # Stored field: tax amount '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_order2.html', context) # 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) return pdf_file def generate_shop_order_invoice(shop_order_id): """ Generate a PDF invoice for a ShopOrderModel instance. Args: shop_order_id: The ID of the ShopOrderModel instance Returns: BytesIO: A BytesIO object containing the PDF data """ from .models import ShopOrderModel try: shop_order = ShopOrderModel.objects.select_related( 'order', 'shop', 'shop__user', 'customer', 'address' ).prefetch_related('items__order_item__product__product').get(pk=shop_order_id) except ShopOrderModel.DoesNotExist: raise ValueError(f"ShopOrder with ID {shop_order_id} does not exist") # Prepare items with calculated discount amounts using stored values items = shop_order.items.select_related('order_item').all() items_with_discount = [] total_items_discount = 0 total_special_discount = 0 for shop_item in items: order_item = shop_item.order_item # Calculate discount amount based on stored values # discount_amount = shop_item.discount_amount or 0 # special_discount_amount = shop_item.special_discount_amount or 0 total_items_discount += shop_item.discount_amount or 0 total_special_discount += shop_item.special_discount_amount or 0 # # Calculate unit price (after product discount, before special discount) # unit_price = shop_item.unit_price or 0 # # Calculate price before discount (original unit price) # if order_item.discount_percent > 0: # discount_multiplier = (100 - order_item.discount_percent) / 100 # if discount_multiplier > 0: # price_before_discount_unit = int(unit_price / discount_multiplier) # else: # price_before_discount_unit = unit_price # else: # price_before_discount_unit = unit_price # price_before_discount = price_before_discount_unit * shop_item.quantity # # Final price after all discounts # final_price = shop_item.total_price or 0 items_with_discount.append({ 'item': shop_item, 'order_item': order_item, 'discount_amount': order_item.total_product_discount_amount() or 0, 'special_discount_amount': order_item.special_discount_amount or 0, 'price_before_discount': order_item.total_price_before_discount(), 'unit_price': order_item.unit_price or 0, 'final_price': order_item.price_after_special_discount() or 0, }) # Calculate subtotal (cart total before discounts) subtotal = shop_order.subtotal or 0 # Format the Jalali date for display jalali_date = jdatetime.datetime.fromgregorian(datetime=shop_order.order_created_at) if shop_order.order_created_at else None created_at_jalali_str = jalali_date.strftime('%Y-%m-%d %H:%M') if jalali_date else '---' # 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, 'order_number': shop_order.order.pk, 'shop_order_id': shop_order.pk, 'shop': shop_order.shop, 'customer': shop_order.customer, 'customer_name': shop_order.customer_name, 'customer_phone': shop_order.customer_phone, 'address_text': shop_order.address_text, 'address_postal_code': shop_order.address_postal_code, 'address_phone': shop_order.address_phone, 'address_city': shop_order.address_city, 'address_province': shop_order.address_province, 'address_recipient_name': shop_order.address_recipient_name, 'items': items, 'items_with_discount': items_with_discount, 'created_at_jalali': created_at_jalali_str, 'total_items': shop_order.items_count, 'subtotal': subtotal, # Total after product discount 'items_discount_amount': total_items_discount, # Product discount amount 'special_discount_total': total_special_discount, # Special discount total 'tax_amount': shop_order.tax_amount, 'commission_percent': shop_order.commission_percent, 'commission_amount': shop_order.commission_amount, 'payable_amount': shop_order.payable_amount, '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_order2.html', context) # 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) return pdf_file