From f3ada22ab3899843163c5ec29e084ef2bd322dcb Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 10 Feb 2026 11:49:00 +0000 Subject: [PATCH] Ver 14.06 Overtime working --- core/__pycache__/models.cpython-311.pyc | Bin 14943 -> 15043 bytes core/__pycache__/views.cpython-311.pyc | Bin 55374 -> 56030 bytes .../0015_payrolladjustment_work_log.py | 19 +++ ...payrolladjustment_work_log.cpython-311.pyc | Bin 0 -> 1072 bytes core/models.py | 5 +- core/views.py | 130 ++++++++++-------- 6 files changed, 96 insertions(+), 58 deletions(-) create mode 100644 core/migrations/0015_payrolladjustment_work_log.py create mode 100644 core/migrations/__pycache__/0015_payrolladjustment_work_log.cpython-311.pyc diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 0b0b86d3615b0d31bdd70390f10a6dbb0d899b6b..e842ed41988f2fcce8d981fb8dd6b4bf07f73bd9 100644 GIT binary patch delta 436 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
v?ote0b*{eRbcFZ`j z6`6&V;s|(T%DT!d5Q`)6Jj)6UeVE;cphNHgNGcMHz$Vy>1w9C-5uQf)6aW<+gPq{c zorF~JoQWgVFyFFmo51iZ(Ds zV0!rqQq>6TUVwGvhnW2kAq>r9C>@NDEtp5ED4QgvJ676q|BgP?Rw*wAwu_jN{wn5m z&HGzJUGes4gnSS4N;stawyYzuA6buseDVOo_Yl~6-GnK&UKoP2en^^F&rZCmB8O0x zad*5k97x(`iPjwx3}=D033MxVTuFV0YDUf+i)i|qp|G8zkktrT&J|k}Cl)d@m?zH< zJHT;0BqLm32_c~eb~X$kvwF<>YRO)xDH-MC7i?}I0_Qgl2e+|gz&iv~N&5JSd|a~^ zPt+_}j^xY|J&@u~+F8u9y_<0)m|nza@gtt~nUz6H;5YmevnXld^?o4S4ztdtl1-|f zr~QxkRc}M@zcq65k-zc$TeSOuditi;OS@h$Q`NUW;LGXcxBsFP%!_>#nL-1ShOz493QsBsN;w9y* znKQ|Hj(+xdWr>>fV|hj?+3d0D|A?crPA-x48l_4aFmyN$N!i=pmF4d-_mA0Err^wi zqGgnCx|mussYkF^%!2kn3jg*XNXAvsP^={yZ{Cj+^eS?sH5aZQMUMXVWPN3NJmf#H zrUus=><;U@l*j(eaV1^yM4kS>f&61sJ_qf7;@(1+%Ki!5Y1qfK6$9!)-Qfk3;BS)_Am=TEO(w;Zc7!R)MBfm!AWGj)V9a(=C>ejgx-9V3kCXDIDM1l*!vux>kD zt@;S^e;j%8beONg3lky$WJLp#4lRqUrN0AG!N`hd9#za?%G8fi*j|LowqOi)A)5u9 zkH_+4^;`*u=*G{(?(-YD_Lun=E@ryPvl;W-MV^4VaaKcn3W>`IeD2 z_X)Pa+U%##JzuK&6!MOd|9rk%-O9wXg^8v5E{6ojE@{|M!9T+hv=FrQNe^!Wa{nH? zebKgHJA$=#2HU-Yo_g8EchJ{fE^EYX=XZbcIN!kvml|vzJIv&h0eP6&VZKIY7K*S- z+mV`?547TblRO{q0ZFzn>Ax^#hDts|swvjf+6vw?b53LM&yYRsT`X;i$X>E3_sWm7Jf$D}WK#`Od7kM)%Dc;B*TraCQl1*9_6@IB z@i)BDf99l*JxZ@roCS3?>35lVa=f$iJZF!f`EpCG(}@Fq2#EwLz(8(}xDlwV23 zmfDe>XJ;!d%(>f&oS$M>sz)9=H^^_q0F3=phu|eQ2_l3LF#I9x)WAcV;AuqAf)cbA z1nmI9T}4nUScC8ar+?hV7mwUK{(IiNjpyf2a)_TOIfdn5>7;@c?WGUBRlDGNCBJNv tLrhayk*EgVNkuQu7fy1BX)-I?PxH>->+tjbNeF5DX&M$>Lr2aZ`5&t(P>TQn delta 10393 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 J3wCo1{{U!qA|wC+ literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 96b9e07..7c3d0ee 100644 --- a/core/models.py +++ b/core/models.py @@ -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) @@ -201,4 +204,4 @@ class ExpenseLineItem(models.Model): verbose_name_plural = "Expense Line Items" def __str__(self): - return f"{self.product} - {self.amount}" \ No newline at end of file + return f"{self.product} - {self.amount}" diff --git a/core/views.py b/core/views.py index b7b3dd9..cb7ca5d 100644 --- a/core/views.py +++ b/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 project_id: + # Include only adjustments linked to this project (via work_log) + adjustments = adjustments.filter(work_log__project_id=project_id) - if payment_status == 'paid': - adjustments = adjustments.filter(payroll_record__isnull=False) - elif payment_status == 'unpaid': - adjustments = adjustments.filter(payroll_record__isnull=True) + 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 + } + + if user_is_admin: + total_amount += 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 - } - - 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