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')
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')
# 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)")
date = models.DateField(default=timezone.now)
description = models.CharField(max_length=255)

View File

@ -63,10 +63,23 @@ def home(request):
if user_is_admin:
# 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:
# Unpaid logs
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
recent_payments_total = PayrollRecord.objects.filter(
@ -298,22 +311,22 @@ def work_log_list(request):
logs = logs.filter(paid_in__isnull=True)
# --- 2. Fetch Adjustments ---
# Adjustments are shown unless a Project/Team filter is active (as they don't belong to projects/teams),
# 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.all().select_related('worker', 'payroll_record', 'work_log')
adjustments = PayrollAdjustment.objects.none()
if show_adjustments:
adjustments = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record')
if worker_id:
adjustments = adjustments.filter(worker_id=worker_id)
if worker_id:
adjustments = adjustments.filter(worker_id=worker_id)
if payment_status == 'paid':
adjustments = adjustments.filter(payroll_record__isnull=False)
elif payment_status == 'unpaid':
adjustments = adjustments.filter(payroll_record__isnull=True)
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':
adjustments = adjustments.filter(payroll_record__isnull=False)
elif payment_status == 'unpaid':
adjustments = adjustments.filter(payroll_record__isnull=True)
# --- 3. Date Filtering for Calendar View (Applied to both) ---
start_date = None
@ -338,8 +351,8 @@ def work_log_list(request):
end_date = datetime.date(curr_year, curr_month, num_days)
logs = logs.filter(date__range=(start_date, end_date))
if show_adjustments:
adjustments = adjustments.filter(date__range=(start_date, end_date))
# No 'show_adjustments' check needed as query is already filtered
adjustments = adjustments.filter(date__range=(start_date, end_date))
# --- 4. Combine and Sort ---
user_is_admin = is_admin(request.user)
@ -376,32 +389,31 @@ def work_log_list(request):
combined_records.append(record)
# Process Adjustments
if show_adjustments:
for adj in adjustments:
# Determine signed amount for display/total
amt = adj.amount
if adj.type in ['DEDUCTION', 'LOAN_REPAYMENT']:
amt = -amt
for adj in adjustments:
# Determine signed amount for display/total
amt = adj.amount
if adj.type in ['DEDUCTION', 'LOAN_REPAYMENT']:
amt = -amt
record = {
'type': 'ADJ',
'date': adj.date,
'obj': adj,
'project_name': f"{adj.get_type_display()}", # Use project column for Type
'team_name': None,
'workers': [adj.worker],
'supervisor': "System",
'is_paid': adj.payroll_record is not None,
'paid_record': adj.payroll_record,
'notes': adj.description,
'amount': amt if user_is_admin else None,
'sort_id': adj.id
}
record = {
'type': 'ADJ',
'date': adj.date,
'obj': adj,
'project_name': f"{adj.get_type_display()}", # Use project column for Type
'team_name': None,
'workers': [adj.worker],
'supervisor': "System",
'is_paid': adj.payroll_record is not None,
'paid_record': adj.payroll_record,
'notes': adj.description,
'amount': amt if user_is_admin else None,
'sort_id': adj.id
}
if user_is_admin:
total_amount += amt
if user_is_admin:
total_amount += amt
combined_records.append(record)
combined_records.append(record)
# Sort combined list by Date Descending, then ID Descending
combined_records.sort(key=lambda x: (x['date'], x['sort_id']), reverse=True)
@ -522,20 +534,23 @@ def export_work_log_csv(request):
logs = logs.filter(paid_in__isnull=True)
# --- 2. Fetch Adjustments ---
show_adjustments = True
if (project_id or team_id) and not worker_id:
show_adjustments = False
adjustments = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record', 'work_log')
adjustments = []
if show_adjustments:
qs = PayrollAdjustment.objects.all().select_related('worker', 'payroll_record')
if worker_id:
qs = qs.filter(worker_id=worker_id)
if payment_status == 'paid':
qs = qs.filter(payroll_record__isnull=False)
elif payment_status == 'unpaid':
qs = qs.filter(payroll_record__isnull=True)
adjustments = list(qs)
if 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':
adjustments = adjustments.filter(payroll_record__isnull=False)
elif payment_status == 'unpaid':
adjustments = adjustments.filter(payroll_record__isnull=True)
adjustments = list(adjustments)
user_is_admin = is_admin(request.user)
@ -982,7 +997,8 @@ def price_overtime(request):
type='OVERTIME',
amount=amount,
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