34715994ce
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
147 lines
5.3 KiB
Python
147 lines
5.3 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
|
|
|
|
|
|
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
|
|
items = order.items.all()
|
|
items_with_discount = []
|
|
for item in items:
|
|
# Calculate the discount amount from the discount_percent
|
|
# price is after discount, so we need to calculate original price
|
|
# original_price = price / (1 - discount_percent/100)
|
|
# discount_amount = original_price - price
|
|
if item.discount_percent > 0:
|
|
discount_multiplier = (100 - item.discount_percent) / 100
|
|
price_after_discount = item.price
|
|
price_before_discount = int(price_after_discount / discount_multiplier) if discount_multiplier > 0 else price_after_discount
|
|
item_discount_amount = (price_before_discount - price_after_discount)
|
|
else:
|
|
item_discount_amount = 0
|
|
price_before_discount = item.price
|
|
|
|
items_with_discount.append({
|
|
'item': item,
|
|
'discount_amount': item_discount_amount,
|
|
'price_before_discount': price_before_discount
|
|
})
|
|
|
|
# Prepare context for the template
|
|
context = {
|
|
'order': order,
|
|
'order_number': order.pk + 1000,
|
|
'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,
|
|
'discount_amount': order.discount_amount or 0,
|
|
'special_discount_total': order.special_discount_total or 0,
|
|
'tax': order.tax or 0,
|
|
'final_price': order.final_price,
|
|
'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")
|
|
|
|
# Prepare context for the template
|
|
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': shop_order.items.all(),
|
|
'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': shop_order.subtotal,
|
|
'discount_amount': shop_order.discount_amount,
|
|
'special_discount_amount': shop_order.special_discount_amount,
|
|
'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
|