Files
hossein-por-shop/backend/order/invoice_generator.py
T
2026-01-04 12:45:44 +03:30

168 lines
6.6 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 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(),
})
# Use stored model fields for accuracy and consistency
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': jdatetime.datetime.fromgregorian(datetime=order.created_at) if order.created_at else None,
'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(),
}
# Render HTML template
html_string = render_to_string('order/invoice_order.html', context)
# Generate PDF
html = HTML(string=html_string, base_url=settings.STATIC_URL)
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")
# Calculate total before any discount from shop order items
# shop_order.subtotal stores price AFTER product discount, so we need to recalculate
items = shop_order.items.select_related('order_item').all()
total_before_any_discount = 0
total_product_discount = 0
for shop_item in items:
order_item = shop_item.order_item
# order_item.price is unit price AFTER product discount
if order_item.discount_percent > 0:
discount_multiplier = (100 - order_item.discount_percent) / 100
if discount_multiplier > 0:
price_before_discount_unit = int(order_item.price / discount_multiplier)
else:
price_before_discount_unit = order_item.price
item_discount = (price_before_discount_unit - order_item.price) * shop_item.quantity
total_product_discount += item_discount
else:
price_before_discount_unit = order_item.price
total_before_any_discount += price_before_discount_unit * shop_item.quantity
# Prepare context for the template
subtotal_after_discount = total_before_any_discount - (total_product_discount + shop_order.special_discount_amount) + shop_order.tax_amount
context = {
'shop_order': shop_order,
'order_number': shop_order.order.pk + 1000,
'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,
'created_at_jalali': jdatetime.datetime.fromgregorian(datetime=shop_order.order_created_at) if shop_order.order_created_at else None,
'total_items': shop_order.items_count,
'subtotal': total_before_any_discount, # Total BEFORE any discount
'discount_amount': total_product_discount, # Product discount amount
'special_discount_amount': shop_order.special_discount_amount,
'subtotal_after_discount': subtotal_after_discount,
'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,
}
# Render HTML template
html_string = render_to_string('order/invoice_shop_order.html', context)
# Generate PDF
html = HTML(string=html_string, base_url=settings.STATIC_URL)
pdf_file = BytesIO()
html.write_pdf(pdf_file)
pdf_file.seek(0)
return pdf_file