Ver 10.4 fix mail to spark
This commit is contained in:
parent
60ef14cac4
commit
0702dcda68
BIN
core/__pycache__/utils.cpython-311.pyc
Normal file
BIN
core/__pycache__/utils.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
79
core/templates/core/pdf/payslip_pdf.html
Normal file
79
core/templates/core/pdf/payslip_pdf.html
Normal 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>
|
||||
70
core/templates/core/pdf/receipt_pdf.html
Normal file
70
core/templates/core/pdf/receipt_pdf.html
Normal 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
20
core/utils.py
Normal 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
|
||||
@ -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(
|
||||
# Construct Email with Attachment
|
||||
email = EmailMultiAlternatives(
|
||||
subject,
|
||||
plain_message,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list,
|
||||
html_message=html_message
|
||||
)
|
||||
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(
|
||||
# Construct Email with Attachment
|
||||
email = EmailMultiAlternatives(
|
||||
subject,
|
||||
plain_message,
|
||||
settings.DEFAULT_FROM_EMAIL,
|
||||
recipient_list,
|
||||
html_message=html_message
|
||||
)
|
||||
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:
|
||||
|
||||
@ -2,3 +2,4 @@ Django==5.2.7
|
||||
mysqlclient==2.2.7
|
||||
python-dotenv==1.1.1
|
||||
Pillow
|
||||
xhtml2pdf
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user