Ver 10.4 fix mail to spark

This commit is contained in:
Flatlogic Bot 2026-02-04 17:36:56 +00:00
parent 60ef14cac4
commit 0702dcda68
7 changed files with 212 additions and 18 deletions

Binary file not shown.

View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
@page {
size: a4 portrait;
margin: 2cm;
@frame footer_frame { /* Another static Frame */
-pdf-frame-content: footerContent;
bottom: 1cm;
margin-left: 1cm;
margin-right: 1cm;
height: 1cm;
}
}
body { font-family: Helvetica, sans-serif; font-size: 12pt; line-height: 1.5; color: #333; }
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 20px; }
.title { font-size: 24pt; font-weight: bold; text-transform: uppercase; color: #000; }
.subtitle { font-size: 14pt; color: #666; margin-top: 5px; }
.meta { margin-bottom: 20px; padding: 10px; border: 1px solid #ddd; background-color: #f9f9f9; }
.items-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.items-table th { border-bottom: 1px solid #000; padding: 8px; text-align: left; background-color: #eee; font-weight: bold; }
.items-table td { border-bottom: 1px solid #eee; padding: 8px; text-align: left; }
.totals { text-align: right; margin-top: 20px; border-top: 2px solid #333; padding-top: 10px; }
.total-row { font-size: 16pt; font-weight: bold; color: #000; }
.footer { text-align: center; font-size: 10pt; color: #777; }
.positive { color: green; }
.negative { color: red; }
.text-right { text-align: right; }
</style>
</head>
<body>
<div class="header">
<div class="title">Payslip</div>
<div class="subtitle">Reference: #{{ record.id }}</div>
</div>
<div class="meta">
<strong>Worker:</strong> {{ record.worker.name }}<br>
<strong>ID Number:</strong> {{ record.worker.id_no }}<br>
<strong>Date:</strong> {{ record.date }}
</div>
<table class="items-table">
<thead>
<tr>
<th>Description</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
<!-- Base Pay -->
<tr>
<td>Base Pay ({{ logs_count }} days worked)</td>
<td class="text-right">R {{ logs_amount|floatformat:2 }}</td>
</tr>
<!-- Adjustments -->
{% for adj in adjustments %}
<tr>
<td>{{ adj.get_type_display }}: {{ adj.description }}</td>
<td class="text-right">
R {{ adj.amount|floatformat:2 }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="totals">
<p class="total-row">Net Pay: R {{ record.amount|floatformat:2 }}</p>
</div>
<div id="footerContent" class="footer">
Generated by Fox Fitt App for {{ record.worker.name }} | {% now "Y-m-d H:i" %}
</div>
</body>
</html>

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
@page {
size: a4 portrait;
margin: 2cm;
@frame footer_frame {
-pdf-frame-content: footerContent;
bottom: 1cm;
margin-left: 1cm;
margin-right: 1cm;
height: 1cm;
}
}
body { font-family: Helvetica, sans-serif; font-size: 12pt; line-height: 1.5; color: #333; }
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 20px; }
.vendor-name { font-size: 24pt; font-weight: bold; text-transform: uppercase; color: #000; }
.sub-header { font-size: 12pt; color: #666; }
.meta { margin-bottom: 20px; padding: 10px; border: 1px solid #ddd; background-color: #f9f9f9; }
.items-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.items-table th { border-bottom: 1px solid #000; padding: 8px; text-align: left; background-color: #eee; font-weight: bold; }
.items-table td { border-bottom: 1px solid #eee; padding: 8px; text-align: left; }
.totals { text-align: right; margin-top: 20px; }
.total-row { font-size: 16pt; font-weight: bold; border-top: 1px solid #000; padding-top: 5px; margin-top: 5px; }
.footer { text-align: center; font-size: 10pt; color: #777; }
.text-right { text-align: right; }
</style>
</head>
<body>
<div class="header">
<div class="sub-header">RECEIPT FROM</div>
<div class="vendor-name">{{ receipt.vendor }}</div>
</div>
<div class="meta">
<strong>Date:</strong> {{ receipt.date }}<br>
<strong>Payment Method:</strong> {{ receipt.get_payment_method_display }}<br>
<strong>Description:</strong> {{ receipt.description|default:"-" }}
</div>
<table class="items-table">
<thead>
<tr>
<th>Item</th>
<th class="text-right">Amount</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td>{{ item.product }}</td>
<td class="text-right">R {{ item.amount|floatformat:2 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="totals">
<p>Subtotal: R {{ receipt.subtotal|floatformat:2 }}</p>
<p>VAT ({{ receipt.get_vat_type_display }}): R {{ receipt.vat_amount|floatformat:2 }}</p>
<p class="total-row">Total: R {{ receipt.total_amount|floatformat:2 }}</p>
</div>
<div id="footerContent" class="footer">
Generated by {{ receipt.user.get_full_name|default:receipt.user.username }} via Fox Fitt App
</div>
</body>
</html>

20
core/utils.py Normal file
View File

@ -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

View File

@ -8,7 +8,7 @@ from django.shortcuts import render, redirect, get_object_or_404
from django.utils import timezone from django.utils import timezone
from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.decorators import login_required, user_passes_test
from django.db.models import Sum, Q, Prefetch 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.conf import settings
from django.contrib import messages from django.contrib import messages
from django.http import JsonResponse, HttpResponse 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 .forms import WorkLogForm, ExpenseReceiptForm, ExpenseLineItemFormSet
from datetime import timedelta from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from core.utils import render_to_pdf
def is_staff_or_supervisor(user): def is_staff_or_supervisor(user):
"""Check if user is staff or manages at least one team/project.""" """Check if user is staff or manages at least one team/project."""
@ -591,26 +592,37 @@ def process_payment(request, worker_id):
# Email Notification # Email Notification
subject = f"Payslip for {worker.name} - {payroll_record.date}" subject = f"Payslip for {worker.name} - {payroll_record.date}"
# Prepare HTML content # Prepare Context
context = { context = {
'record': payroll_record, 'record': payroll_record,
'logs_count': log_count, 'logs_count': log_count,
'logs_amount': logs_amount, 'logs_amount': logs_amount,
'adjustments': payroll_record.adjustments.all(), 'adjustments': payroll_record.adjustments.all(),
} }
# 1. Render HTML Body
html_message = render_to_string('core/email/payslip_email.html', context) html_message = render_to_string('core/email/payslip_email.html', context)
plain_message = strip_tags(html_message) 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'] recipient_list = ['foxfitt-ed9wc+expense@to.sparkreceipt.com']
try: try:
send_mail( # Construct Email with Attachment
subject, email = EmailMultiAlternatives(
plain_message, subject,
settings.DEFAULT_FROM_EMAIL, plain_message,
recipient_list, settings.DEFAULT_FROM_EMAIL,
html_message=html_message 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}") messages.success(request, f"Payment processed for {worker.name}. Net Pay: R {payroll_record.amount}")
except Exception as e: except Exception as e:
messages.warning(request, f"Payment processed, but email delivery failed: {str(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}" subject = f"Receipt from {receipt.vendor} - {receipt.date}"
recipient_list = ['foxfitt-ed9wc+expense@to.sparkreceipt.com'] recipient_list = ['foxfitt-ed9wc+expense@to.sparkreceipt.com']
# Prepare HTML content # Prepare Context
html_message = render_to_string('core/email/receipt_email.html', { context = {
'receipt': receipt, 'receipt': receipt,
'items': line_items, 'items': line_items,
}) }
# 1. Render HTML Body
html_message = render_to_string('core/email/receipt_email.html', context)
plain_message = strip_tags(html_message) plain_message = strip_tags(html_message)
# 2. Render PDF Attachment
pdf_content = render_to_pdf('core/pdf/receipt_pdf.html', context)
try: try:
send_mail( # Construct Email with Attachment
subject, email = EmailMultiAlternatives(
plain_message, subject,
settings.DEFAULT_FROM_EMAIL, plain_message,
recipient_list, settings.DEFAULT_FROM_EMAIL,
html_message=html_message 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.") messages.success(request, "Receipt created and sent to SparkReceipt.")
return redirect('create_receipt') return redirect('create_receipt')
except Exception as e: except Exception as e:
@ -807,4 +831,4 @@ def create_receipt(request):
return render(request, 'core/create_receipt.html', { return render(request, 'core/create_receipt.html', {
'form': form, 'form': form,
'items': items 'items': items
}) })

View File

@ -2,3 +2,4 @@ Django==5.2.7
mysqlclient==2.2.7 mysqlclient==2.2.7
python-dotenv==1.1.1 python-dotenv==1.1.1
Pillow Pillow
xhtml2pdf