Files
hossein-por-shop/backend/order/invoice_generator.py
T
2026-05-12 09:48:34 +03:30

219 lines
8.7 KiB
Python

"""
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