Ver 10.3 fixing Spark

This commit is contained in:
Flatlogic Bot 2026-02-04 16:31:58 +00:00
parent 8feedf5963
commit 60ef14cac4
14 changed files with 150 additions and 19 deletions

View File

@ -23,14 +23,16 @@ DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true"
ALLOWED_HOSTS = [
"127.0.0.1",
"localhost",
"foxlog.flatlogic.app",
os.getenv("HOST_FQDN", ""),
]
CSRF_TRUSTED_ORIGINS = [
origin for origin in [
"https://foxlog.flatlogic.app",
*[origin for origin in [
os.getenv("HOST_FQDN", ""),
os.getenv("CSRF_TRUSTED_ORIGIN", "")
] if origin
] if origin]
]
CSRF_TRUSTED_ORIGINS = [
f"https://{host}" if not host.startswith(("http://", "https://")) else host
@ -156,6 +158,10 @@ STATICFILES_DIRS = [
BASE_DIR / 'node_modules',
]
# Media files (Uploads)
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Email
EMAIL_BACKEND = os.getenv(
"EMAIL_BACKEND",

View File

@ -28,3 +28,4 @@ urlpatterns = [
if settings.DEBUG:
urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets")
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -7,8 +7,9 @@ class UserProfileAdmin(admin.ModelAdmin):
@admin.register(Worker)
class WorkerAdmin(admin.ModelAdmin):
list_display = ('name', 'id_no', 'phone_no', 'monthly_salary')
list_display = ('name', 'id_no', 'phone_no', 'monthly_salary', 'date_of_employment', 'projects_worked_on_count')
search_fields = ('name', 'id_no')
readonly_fields = ('projects_worked_on_count',) # Calculated field should be readonly in edit form
@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):

View File

@ -0,0 +1,34 @@
# Generated by Django 5.2.7 on 2026-02-04 14:11
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_alter_expensereceipt_payment_method_and_more'),
]
operations = [
migrations.AddField(
model_name='worker',
name='date_of_employment',
field=models.DateField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='worker',
name='id_photo',
field=models.ImageField(blank=True, null=True, upload_to='workers/ids/', verbose_name='ID Document'),
),
migrations.AddField(
model_name='worker',
name='notes',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='worker',
name='photo',
field=models.ImageField(blank=True, null=True, upload_to='workers/photos/'),
),
]

View File

@ -27,6 +27,13 @@ class Worker(models.Model):
id_no = models.CharField(max_length=50, unique=True, verbose_name="ID Number")
phone_no = models.CharField(max_length=20, verbose_name="Phone Number")
monthly_salary = models.DecimalField(max_digits=10, decimal_places=2, validators=[MinValueValidator(Decimal('0.00'))])
# New fields
photo = models.ImageField(upload_to='workers/photos/', blank=True, null=True)
id_photo = models.ImageField(upload_to='workers/ids/', blank=True, null=True, verbose_name="ID Document")
date_of_employment = models.DateField(default=timezone.now)
notes = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
@ -34,6 +41,11 @@ class Worker(models.Model):
def day_rate(self):
return self.monthly_salary / Decimal('20.0')
@property
def projects_worked_on_count(self):
"""Returns the number of distinct projects this worker has worked on."""
return self.work_logs.values('project').distinct().count()
def __str__(self):
return self.name

View File

@ -0,0 +1,70 @@
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; border: 1px solid #ddd; padding: 20px; }
.header { text-align: center; border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 20px; }
.title { font-size: 24px; font-weight: bold; text-transform: uppercase; color: #000; }
.subtitle { font-size: 14px; color: #666; margin-top: 5px; }
.meta { margin-bottom: 20px; background-color: #f8f9fa; padding: 10px; border-radius: 4px; }
.items-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
.items-table th, .items-table td { border-bottom: 1px solid #eee; padding: 8px; text-align: left; }
.items-table th { background-color: #f8f9fa; }
.totals { text-align: right; margin-top: 20px; border-top: 2px solid #333; padding-top: 10px; }
.total-row { font-size: 20px; font-weight: bold; color: #000; }
.footer { margin-top: 30px; font-size: 12px; color: #777; text-align: center; border-top: 1px solid #eee; padding-top: 10px; }
.positive { color: green; }
.negative { color: red; }
</style>
</head>
<body>
<div class="container">
<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 style="text-align: right;">Amount</th>
</tr>
</thead>
<tbody>
<!-- Base Pay -->
<tr>
<td>Base Pay ({{ logs_count }} days worked)</td>
<td style="text-align: right;">R {{ logs_amount }}</td>
</tr>
<!-- Adjustments -->
{% for adj in adjustments %}
<tr>
<td>{{ adj.get_type_display }}: {{ adj.description }}</td>
<td style="text-align: right;" class="{% if adj.amount < 0 %}negative{% else %}positive{% endif %}">
R {{ adj.amount }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="totals">
<p class="total-row">Net Pay: R {{ record.amount }}</p>
</div>
<div class="footer">
<p>Generated by Fox Fitt App for {{ record.worker.name }}</p>
<p>Date Generated: {% now "Y-m-d H:i" %}</p>
</div>
</div>
</body>
</html>

View File

@ -590,21 +590,27 @@ def process_payment(request, worker_id):
# Email Notification
subject = f"Payslip for {worker.name} - {payroll_record.date}"
message = (
f"Payslip Generated\n\n"
f"Record ID: #{payroll_record.id}\n"
f"Worker: {worker.name}\n"
f"Date: {payroll_record.date}\n"
f"Total Paid: R {payroll_record.amount}\n\n"
f"Breakdown:\n"
f"Base Pay ({log_count} days): R {logs_amount}\n"
f"Adjustments: R {adj_amount}\n\n"
f"This is an automated notification."
)
# Prepare HTML content
context = {
'record': payroll_record,
'logs_count': log_count,
'logs_amount': logs_amount,
'adjustments': payroll_record.adjustments.all(),
}
html_message = render_to_string('core/email/payslip_email.html', context)
plain_message = strip_tags(html_message)
recipient_list = ['foxfitt-ed9wc+expense@to.sparkreceipt.com']
try:
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, recipient_list)
send_mail(
subject,
plain_message,
settings.DEFAULT_FROM_EMAIL,
recipient_list,
html_message=html_message
)
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)}")

View File

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