Spaces:
Sleeping
Sleeping
| from reportlab.lib import colors | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle | |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
| from reportlab.lib.units import inch | |
| from io import BytesIO | |
| from datetime import datetime | |
| from typing import List | |
| def generate_bill_pdf(order, settings): | |
| """ | |
| Generate a PDF bill for a single order | |
| Args: | |
| order: The order object with all details | |
| settings: The hotel settings object | |
| Returns: | |
| BytesIO: A buffer containing the PDF data | |
| """ | |
| # Convert single order to list and use the multi-order function | |
| return generate_multi_order_bill_pdf([order], settings) | |
| def generate_multi_order_bill_pdf(orders: List, settings): | |
| """ | |
| Generate a PDF bill for multiple orders in a receipt-like format | |
| Args: | |
| orders: List of order objects with all details | |
| settings: The hotel settings object | |
| Returns: | |
| BytesIO: A buffer containing the PDF data | |
| """ | |
| buffer = BytesIO() | |
| # Use a narrower page size to mimic a receipt | |
| doc = SimpleDocTemplate( | |
| buffer, | |
| pagesize=(4*inch, 11*inch), # Typical receipt width | |
| rightMargin=10, | |
| leftMargin=10, | |
| topMargin=10, | |
| bottomMargin=10 | |
| ) | |
| # Create styles | |
| styles = getSampleStyleSheet() | |
| styles.add(ParagraphStyle( | |
| name='HotelName', | |
| fontName='Helvetica-Bold', | |
| fontSize=14, | |
| alignment=1, # Center alignment | |
| spaceAfter=2 | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='HotelTagline', | |
| fontName='Helvetica', | |
| fontSize=9, | |
| alignment=1, # Center alignment | |
| spaceAfter=2 | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='HotelAddress', | |
| fontName='Helvetica', | |
| fontSize=8, | |
| alignment=1, # Center alignment | |
| spaceAfter=1 | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='BillInfo', | |
| fontName='Helvetica', | |
| fontSize=8, | |
| alignment=0, # Left alignment | |
| spaceAfter=1 | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='BillInfoRight', | |
| fontName='Helvetica', | |
| fontSize=8, | |
| alignment=2, # Right alignment | |
| spaceAfter=1 | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='TableHeader', | |
| fontName='Helvetica-Bold', | |
| fontSize=8, | |
| alignment=0 | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='ItemName', | |
| fontName='Helvetica', | |
| fontSize=8, | |
| alignment=0 | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='ItemValue', | |
| fontName='Helvetica', | |
| fontSize=8, | |
| alignment=2 # Right alignment | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='Total', | |
| fontName='Helvetica-Bold', | |
| fontSize=9, | |
| alignment=1 # Center alignment | |
| )) | |
| styles.add(ParagraphStyle( | |
| name='Footer', | |
| fontName='Helvetica', | |
| fontSize=7, | |
| alignment=1, # Center alignment | |
| textColor=colors.black | |
| )) | |
| # Create content elements | |
| elements = [] | |
| # We're not using the logo in this receipt-style bill | |
| # Add hotel name and info | |
| elements.append(Paragraph(settings.hotel_name.upper(), styles['HotelName'])) | |
| # Add tagline (if any, otherwise use a default) | |
| tagline = "AN AUTHENTIC CUISINE SINCE 2000" | |
| elements.append(Paragraph(tagline, styles['HotelTagline'])) | |
| # Add address with formatting similar to the image | |
| if settings.address: | |
| elements.append(Paragraph(settings.address, styles['HotelAddress'])) | |
| # Add contact info | |
| if settings.contact_number: | |
| elements.append(Paragraph(f"Contact: {settings.contact_number}", styles['HotelAddress'])) | |
| # Add tax ID (GSTIN) | |
| if settings.tax_id: | |
| elements.append(Paragraph(f"GSTIN: {settings.tax_id}", styles['HotelAddress'])) | |
| # Add a separator line | |
| elements.append(Paragraph("_" * 50, styles['HotelAddress'])) | |
| # Add bill details in a more receipt-like format | |
| # Use the first order for common details | |
| first_order = orders[0] | |
| # Create a table for the bill header info | |
| # Get customer name if available | |
| customer_name = "" | |
| if hasattr(first_order, 'person_name') and first_order.person_name: | |
| customer_name = first_order.person_name | |
| bill_info_data = [ | |
| ["Name:", customer_name], | |
| [f"Date: {datetime.now().strftime('%d/%m/%y')}", f"Dine In: {first_order.table_number}"], | |
| [f"{datetime.now().strftime('%H:%M')}", f"Bill No.: {first_order.id}"] | |
| ] | |
| bill_info_table = Table(bill_info_data, colWidths=[doc.width/2-20, doc.width/2-20]) | |
| bill_info_table.setStyle(TableStyle([ | |
| ('FONT', (0, 0), (-1, -1), 'Helvetica', 8), | |
| ('ALIGN', (0, 0), (0, -1), 'LEFT'), | |
| ('ALIGN', (1, 0), (1, -1), 'RIGHT'), | |
| ('LINEBELOW', (0, 0), (1, 0), 0.5, colors.black), | |
| ])) | |
| elements.append(bill_info_table) | |
| elements.append(Paragraph("_" * 50, styles['HotelAddress'])) | |
| # Create header for items table | |
| items_header = [["Item", "Qty.", "Price", "Amount"]] | |
| items_header_table = Table(items_header, colWidths=[doc.width*0.4, doc.width*0.15, doc.width*0.2, doc.width*0.25]) | |
| items_header_table.setStyle(TableStyle([ | |
| ('FONT', (0, 0), (-1, -1), 'Helvetica-Bold', 8), | |
| ('ALIGN', (0, 0), (0, -1), 'LEFT'), | |
| ('ALIGN', (1, 0), (-1, -1), 'RIGHT'), | |
| ('LINEBELOW', (0, 0), (-1, 0), 0.5, colors.black), | |
| ])) | |
| elements.append(items_header_table) | |
| # Add all order items | |
| total_items = 0 | |
| subtotal_amount = 0 | |
| total_loyalty_discount = 0 | |
| total_selection_discount = 0 | |
| grand_total = 0 | |
| for order in orders: | |
| order_data = [] | |
| for item in order.items: | |
| dish_name = item.dish.name if item.dish else "Unknown Dish" | |
| price = item.dish.price if item.dish else 0 | |
| quantity = item.quantity | |
| total = price * quantity | |
| subtotal_amount += total | |
| total_items += quantity | |
| order_data.append([ | |
| dish_name, | |
| str(quantity), | |
| f"{price:.2f}", | |
| f"{total:.2f}" | |
| ]) | |
| # Accumulate discount amounts from order records | |
| if hasattr(order, 'loyalty_discount_amount') and order.loyalty_discount_amount: | |
| total_loyalty_discount += order.loyalty_discount_amount | |
| if hasattr(order, 'selection_offer_discount_amount') and order.selection_offer_discount_amount: | |
| total_selection_discount += order.selection_offer_discount_amount | |
| # Use stored total_amount if available, otherwise calculate from subtotal | |
| if hasattr(order, 'total_amount') and order.total_amount is not None: | |
| grand_total += order.total_amount | |
| else: | |
| # Fallback to original calculation if no stored total | |
| grand_total += subtotal_amount | |
| # Create the table for this order's items | |
| if order_data: | |
| items_table = Table(order_data, colWidths=[doc.width*0.4, doc.width*0.15, doc.width*0.2, doc.width*0.25]) | |
| items_table.setStyle(TableStyle([ | |
| ('FONT', (0, 0), (-1, -1), 'Helvetica', 8), | |
| ('ALIGN', (0, 0), (0, -1), 'LEFT'), | |
| ('ALIGN', (1, 0), (-1, -1), 'RIGHT'), | |
| ])) | |
| elements.append(items_table) | |
| # Add a separator line | |
| elements.append(Paragraph("_" * 50, styles['HotelAddress'])) | |
| # Add totals section with discounts | |
| totals_data = [ | |
| [f"Total Qty: {total_items}", f"Sub Total", f"${subtotal_amount:.2f}"], | |
| ] | |
| # Add loyalty discount if applicable | |
| if total_loyalty_discount > 0: | |
| totals_data.append(["", f"Loyalty Discount", f"-${total_loyalty_discount:.2f}"]) | |
| # Add selection offer discount if applicable | |
| if total_selection_discount > 0: | |
| totals_data.append(["", f"Offer Discount", f"-${total_selection_discount:.2f}"]) | |
| # Calculate amount after discounts | |
| amount_after_discounts = subtotal_amount - total_loyalty_discount - total_selection_discount | |
| # Calculate tax on discounted amount (assuming 5% CGST and 5% SGST) | |
| tax_rate = 0.05 # 5% | |
| cgst = amount_after_discounts * tax_rate | |
| sgst = amount_after_discounts * tax_rate | |
| # Add tax lines | |
| totals_data.extend([ | |
| ["", f"CGST (5%)", f"${cgst:.2f}"], | |
| ["", f"SGST (5%)", f"${sgst:.2f}"], | |
| ]) | |
| # Calculate final total including tax | |
| final_total = amount_after_discounts + cgst + sgst | |
| totals_table = Table(totals_data, colWidths=[doc.width*0.4, doc.width*0.35, doc.width*0.25]) | |
| totals_table.setStyle(TableStyle([ | |
| ('FONT', (0, 0), (-1, -1), 'Helvetica', 8), | |
| ('ALIGN', (0, 0), (0, -1), 'LEFT'), | |
| ('ALIGN', (1, 0), (1, -1), 'RIGHT'), | |
| ('ALIGN', (2, 0), (2, -1), 'RIGHT'), | |
| ])) | |
| elements.append(totals_table) | |
| # Add grand total with emphasis | |
| elements.append(Paragraph("_" * 50, styles['HotelAddress'])) | |
| elements.append(Paragraph(f"Grand Total ${final_total:.2f}", styles['Total'])) | |
| elements.append(Paragraph("_" * 50, styles['HotelAddress'])) | |
| # Add license info and thank you message | |
| elements.append(Spacer(1, 5)) | |
| elements.append(Paragraph("FSSAI Lic No: 12018033000205", styles['Footer'])) | |
| elements.append(Paragraph("!!! Thank You !!! Visit Again !!!", styles['Footer'])) | |
| # Build the PDF | |
| doc.build(elements) | |
| buffer.seek(0) | |
| return buffer | |