diff --git a/core/__pycache__/utils.cpython-311.pyc b/core/__pycache__/utils.cpython-311.pyc new file mode 100644 index 0000000..30fc6a9 Binary files /dev/null and b/core/__pycache__/utils.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index e3d2735..af46955 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/templates/core/pdf/payslip_pdf.html b/core/templates/core/pdf/payslip_pdf.html new file mode 100644 index 0000000..2b3bb02 --- /dev/null +++ b/core/templates/core/pdf/payslip_pdf.html @@ -0,0 +1,79 @@ + + + + + + + +
+
Payslip
+
Reference: #{{ record.id }}
+
+ +
+ Worker: {{ record.worker.name }}
+ ID Number: {{ record.worker.id_no }}
+ Date: {{ record.date }} +
+ + + + + + + + + + + + + + + + + {% for adj in adjustments %} + + + + + {% endfor %} + +
DescriptionAmount
Base Pay ({{ logs_count }} days worked)R {{ logs_amount|floatformat:2 }}
{{ adj.get_type_display }}: {{ adj.description }} + R {{ adj.amount|floatformat:2 }} +
+ +
+

Net Pay: R {{ record.amount|floatformat:2 }}

+
+ + + + diff --git a/core/templates/core/pdf/receipt_pdf.html b/core/templates/core/pdf/receipt_pdf.html new file mode 100644 index 0000000..c81d724 --- /dev/null +++ b/core/templates/core/pdf/receipt_pdf.html @@ -0,0 +1,70 @@ + + + + + + + +
+
RECEIPT FROM
+
{{ receipt.vendor }}
+
+ +
+ Date: {{ receipt.date }}
+ Payment Method: {{ receipt.get_payment_method_display }}
+ Description: {{ receipt.description|default:"-" }} +
+ + + + + + + + + + {% for item in items %} + + + + + {% endfor %} + +
ItemAmount
{{ item.product }}R {{ item.amount|floatformat:2 }}
+ +
+

Subtotal: R {{ receipt.subtotal|floatformat:2 }}

+

VAT ({{ receipt.get_vat_type_display }}): R {{ receipt.vat_amount|floatformat:2 }}

+

Total: R {{ receipt.total_amount|floatformat:2 }}

+
+ + + + diff --git a/core/utils.py b/core/utils.py new file mode 100644 index 0000000..e84415b --- /dev/null +++ b/core/utils.py @@ -0,0 +1,20 @@ +from io import BytesIO +from django.template.loader import get_template +from xhtml2pdf import pisa +from django.conf import settings +import os + +def render_to_pdf(template_src, context_dict={}): + template = get_template(template_src) + html = template.render(context_dict) + result = BytesIO() + + # Enable logging for debugging + # pisa.showLogging() + + # Create the PDF + pdf = pisa.pisaDocument(BytesIO(html.encode("UTF-8")), result) + + if not pdf.err: + return result.getvalue() + return None diff --git a/core/views.py b/core/views.py index 90f1af0..db1a3f8 100644 --- a/core/views.py +++ b/core/views.py @@ -8,7 +8,7 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.utils import timezone from django.contrib.auth.decorators import login_required, user_passes_test from django.db.models import Sum, Q, Prefetch -from django.core.mail import send_mail +from django.core.mail import send_mail, EmailMultiAlternatives from django.conf import settings from django.contrib import messages from django.http import JsonResponse, HttpResponse @@ -18,6 +18,7 @@ from .models import Worker, Project, Team, WorkLog, PayrollRecord, Loan, Payroll from .forms import WorkLogForm, ExpenseReceiptForm, ExpenseLineItemFormSet from datetime import timedelta from decimal import Decimal +from core.utils import render_to_pdf def is_staff_or_supervisor(user): """Check if user is staff or manages at least one team/project.""" @@ -591,26 +592,37 @@ def process_payment(request, worker_id): # Email Notification subject = f"Payslip for {worker.name} - {payroll_record.date}" - # Prepare HTML content + # Prepare Context context = { 'record': payroll_record, 'logs_count': log_count, 'logs_amount': logs_amount, 'adjustments': payroll_record.adjustments.all(), } + + # 1. Render HTML Body html_message = render_to_string('core/email/payslip_email.html', context) plain_message = strip_tags(html_message) + # 2. Render PDF Attachment + pdf_content = render_to_pdf('core/pdf/payslip_pdf.html', context) + recipient_list = ['foxfitt-ed9wc+expense@to.sparkreceipt.com'] try: - send_mail( - subject, - plain_message, - settings.DEFAULT_FROM_EMAIL, - recipient_list, - html_message=html_message + # Construct Email with Attachment + email = EmailMultiAlternatives( + subject, + plain_message, + settings.DEFAULT_FROM_EMAIL, + recipient_list, ) + email.attach_alternative(html_message, "text/html") + + if pdf_content: + email.attach(f"Payslip_{worker.id}_{payroll_record.date}.pdf", pdf_content, 'application/pdf') + + email.send() messages.success(request, f"Payment processed for {worker.name}. Net Pay: R {payroll_record.amount}") except Exception as e: messages.warning(request, f"Payment processed, but email delivery failed: {str(e)}") @@ -780,21 +792,33 @@ def create_receipt(request): subject = f"Receipt from {receipt.vendor} - {receipt.date}" recipient_list = ['foxfitt-ed9wc+expense@to.sparkreceipt.com'] - # Prepare HTML content - html_message = render_to_string('core/email/receipt_email.html', { + # Prepare Context + context = { 'receipt': receipt, 'items': line_items, - }) + } + + # 1. Render HTML Body + html_message = render_to_string('core/email/receipt_email.html', context) plain_message = strip_tags(html_message) + # 2. Render PDF Attachment + pdf_content = render_to_pdf('core/pdf/receipt_pdf.html', context) + try: - send_mail( - subject, - plain_message, - settings.DEFAULT_FROM_EMAIL, - recipient_list, - html_message=html_message + # Construct Email with Attachment + email = EmailMultiAlternatives( + subject, + plain_message, + settings.DEFAULT_FROM_EMAIL, + recipient_list, ) + email.attach_alternative(html_message, "text/html") + + if pdf_content: + email.attach(f"Receipt_{receipt.id}.pdf", pdf_content, 'application/pdf') + + email.send() messages.success(request, "Receipt created and sent to SparkReceipt.") return redirect('create_receipt') except Exception as e: @@ -807,4 +831,4 @@ def create_receipt(request): return render(request, 'core/create_receipt.html', { 'form': form, 'items': items - }) + }) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 65b2871..19a9652 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ Django==5.2.7 mysqlclient==2.2.7 python-dotenv==1.1.1 Pillow +xhtml2pdf