Ver 14.06 Overtime working

This commit is contained in:
Flatlogic Bot 2026-02-10 11:49:00 +00:00
parent bb49956693
commit f3ada22ab3
6 changed files with 96 additions and 58 deletions

View File

@ -0,0 +1,19 @@
# Generated by Django 5.2.7 on 2026-02-10 11:11
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0014_remove_worklog_overtime_priced_and_more'),
]
operations = [
migrations.AddField(
model_name='payrolladjustment',
name='work_log',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='adjustments', to='core.worklog'),
),
]

View File

@ -145,6 +145,9 @@ class PayrollAdjustment(models.Model):
worker = models.ForeignKey(Worker, on_delete=models.CASCADE, related_name='adjustments') worker = models.ForeignKey(Worker, on_delete=models.CASCADE, related_name='adjustments')
payroll_record = models.ForeignKey(PayrollRecord, on_delete=models.SET_NULL, null=True, blank=True, related_name='adjustments') payroll_record = models.ForeignKey(PayrollRecord, on_delete=models.SET_NULL, null=True, blank=True, related_name='adjustments')
loan = models.ForeignKey(Loan, on_delete=models.SET_NULL, null=True, blank=True, related_name='repayments') loan = models.ForeignKey(Loan, on_delete=models.SET_NULL, null=True, blank=True, related_name='repayments')
# Link back to WorkLog to track Project/Team context for Overtime
work_log = models.ForeignKey(WorkLog, on_delete=models.SET_NULL, null=True, blank=True, related_name='adjustments')
amount = models.DecimalField(max_digits=10, decimal_places=2, help_text="Positive adds to pay, negative subtracts (except for Loan Repayment which is auto-handled)") amount = models.DecimalField(max_digits=10, decimal_places=2, help_text="Positive adds to pay, negative subtracts (except for Loan Repayment which is auto-handled)")
date = models.DateField(default=timezone.now) date = models.DateField(default=timezone.now)
description = models.CharField(max_length=255) description = models.CharField(max_length=255)

View File

@ -63,10 +63,23 @@ def home(request):
if user_is_admin: if user_is_admin:
# 1. Outstanding Payments # 1. Outstanding Payments
active_workers = Worker.objects.filter(is_active=True) active_workers = Worker.objects.filter(is_active=True).prefetch_related('work_logs', 'adjustments')
for worker in active_workers: for worker in active_workers:
# Unpaid logs
unpaid_logs_count = worker.work_logs.exclude(paid_in__worker=worker).count() unpaid_logs_count = worker.work_logs.exclude(paid_in__worker=worker).count()
outstanding_total += unpaid_logs_count * worker.day_rate log_amount = unpaid_logs_count * worker.day_rate
# Pending Adjustments
pending_adjustments = worker.adjustments.filter(payroll_record__isnull=True)
adj_total = Decimal('0.00')
for adj in pending_adjustments:
if adj.type in ['BONUS', 'OVERTIME', 'LOAN']:
adj_total += adj.amount
elif adj.type in ['DEDUCTION', 'LOAN_REPAYMENT']:
adj_total -= adj.amount
total_payable = log_amount + adj_total
outstanding_total += max(total_payable, Decimal('0.00'))
# 2. Paid This Month # 2. Paid This Month
recent_payments_total = PayrollRecord.objects.filter( recent_payments_total = PayrollRecord.objects.filter(
@ -298,18 +311,18 @@ def work_log_list(request):
logs = logs.filter(paid_in__isnull=True) logs = logs.filter(paid_in__isnull=True)
# --- 2. Fetch Adjustments --- # --- 2. Fetch Adjustments ---
# Adjustments are shown unless a Project/Team filter is active (as they don't belong to projects/teams), adjustments = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record', 'work_log')
# OR if a specific worker is selected (then we always show their adjustments).
show_adjustments = True
if (project_id or team_id) and not worker_id:
show_adjustments = False
adjustments = PayrollAdjustment.objects.none()
if show_adjustments:
adjustments = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record')
if worker_id: if worker_id:
adjustments = adjustments.filter(worker_id=worker_id) adjustments = adjustments.filter(worker_id=worker_id)
if project_id:
# Include only adjustments linked to this project (via work_log)
adjustments = adjustments.filter(work_log__project_id=project_id)
if team_id:
adjustments = adjustments.filter(work_log__team_id=team_id)
if payment_status == 'paid': if payment_status == 'paid':
adjustments = adjustments.filter(payroll_record__isnull=False) adjustments = adjustments.filter(payroll_record__isnull=False)
elif payment_status == 'unpaid': elif payment_status == 'unpaid':
@ -338,7 +351,7 @@ def work_log_list(request):
end_date = datetime.date(curr_year, curr_month, num_days) end_date = datetime.date(curr_year, curr_month, num_days)
logs = logs.filter(date__range=(start_date, end_date)) logs = logs.filter(date__range=(start_date, end_date))
if show_adjustments: # No 'show_adjustments' check needed as query is already filtered
adjustments = adjustments.filter(date__range=(start_date, end_date)) adjustments = adjustments.filter(date__range=(start_date, end_date))
# --- 4. Combine and Sort --- # --- 4. Combine and Sort ---
@ -376,7 +389,6 @@ def work_log_list(request):
combined_records.append(record) combined_records.append(record)
# Process Adjustments # Process Adjustments
if show_adjustments:
for adj in adjustments: for adj in adjustments:
# Determine signed amount for display/total # Determine signed amount for display/total
amt = adj.amount amt = adj.amount
@ -522,20 +534,23 @@ def export_work_log_csv(request):
logs = logs.filter(paid_in__isnull=True) logs = logs.filter(paid_in__isnull=True)
# --- 2. Fetch Adjustments --- # --- 2. Fetch Adjustments ---
show_adjustments = True adjustments = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record', 'work_log')
if (project_id or team_id) and not worker_id:
show_adjustments = False
adjustments = []
if show_adjustments:
qs = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record')
if worker_id: if worker_id:
qs = qs.filter(worker_id=worker_id) adjustments = adjustments.filter(worker_id=worker_id)
if project_id:
adjustments = adjustments.filter(work_log__project_id=project_id)
if team_id:
adjustments = adjustments.filter(work_log__team_id=team_id)
if payment_status == 'paid': if payment_status == 'paid':
qs = qs.filter(payroll_record__isnull=False) adjustments = adjustments.filter(payroll_record__isnull=False)
elif payment_status == 'unpaid': elif payment_status == 'unpaid':
qs = qs.filter(payroll_record__isnull=True) adjustments = adjustments.filter(payroll_record__isnull=True)
adjustments = list(qs)
adjustments = list(adjustments)
user_is_admin = is_admin(request.user) user_is_admin = is_admin(request.user)
@ -982,7 +997,8 @@ def price_overtime(request):
type='OVERTIME', type='OVERTIME',
amount=amount, amount=amount,
date=worklog.date, date=worklog.date,
description=f"Overtime: {worklog.get_overtime_display()} @ {rate_pct}% - {worklog.date.strftime('%d %b %Y')}" description=f"Overtime: {worklog.get_overtime_display()} @ {rate_pct}% - {worklog.date.strftime('%d %b %Y')}",
work_log=worklog
) )
created += 1 created += 1