Ver 14.06 Overtime working
This commit is contained in:
parent
bb49956693
commit
f3ada22ab3
Binary file not shown.
Binary file not shown.
19
core/migrations/0015_payrolladjustment_work_log.py
Normal file
19
core/migrations/0015_payrolladjustment_work_log.py
Normal 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'),
|
||||
),
|
||||
]
|
||||
Binary file not shown.
@ -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)
|
||||
|
||||
126
core/views.py
126
core/views.py
@ -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
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user