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 @@
+
+
+
+
+
+
+
+
+
+
+ Worker: {{ record.worker.name }}
+ ID Number: {{ record.worker.id_no }}
+ Date: {{ record.date }}
+
+
+
+
+
+ | Description |
+ Amount |
+
+
+
+
+
+ | Base Pay ({{ logs_count }} days worked) |
+ R {{ logs_amount|floatformat:2 }} |
+
+
+
+ {% for adj in adjustments %}
+
+ | {{ adj.get_type_display }}: {{ adj.description }} |
+
+ R {{ adj.amount|floatformat:2 }}
+ |
+
+ {% endfor %}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ Date: {{ receipt.date }}
+ Payment Method: {{ receipt.get_payment_method_display }}
+ Description: {{ receipt.description|default:"-" }}
+
+
+
+
+
+ | Item |
+ Amount |
+
+
+
+ {% for item in items %}
+
+ | {{ item.product }} |
+ R {{ item.amount|floatformat:2 }} |
+
+ {% endfor %}
+
+
+
+
+
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