|
9 | 9 | from frappe import _, msgprint
|
10 | 10 | from frappe.model.naming import make_autoname
|
11 | 11 | from frappe.query_builder import Order
|
12 |
| -from frappe.query_builder.functions import Sum |
| 12 | +from frappe.query_builder.functions import Count, Sum |
13 | 13 | from frappe.utils import (
|
14 | 14 | add_days,
|
15 | 15 | ceil,
|
@@ -479,63 +479,83 @@ def get_working_days_details(self, lwp=None, for_preview=0):
|
479 | 479 | payroll_settings.payroll_based_on == "Attendance"
|
480 | 480 | and consider_unmarked_attendance_as == "Absent"
|
481 | 481 | ):
|
482 |
| - unmarked_days = self.get_unmarked_days(payroll_settings.include_holidays_in_total_working_days) |
| 482 | + unmarked_days = self.get_unmarked_days( |
| 483 | + payroll_settings.include_holidays_in_total_working_days, holidays |
| 484 | + ) |
483 | 485 | self.absent_days += unmarked_days # will be treated as absent
|
484 | 486 | self.payment_days -= unmarked_days
|
485 | 487 | else:
|
486 | 488 | self.payment_days = 0
|
487 | 489 |
|
488 |
| - def get_unmarked_days(self, include_holidays_in_total_working_days): |
489 |
| - unmarked_days = self.total_working_days |
| 490 | + def get_unmarked_days( |
| 491 | + self, include_holidays_in_total_working_days: bool, holidays: list | None = None |
| 492 | + ) -> float: |
| 493 | + """Calculates the number of unmarked days for an employee within a date range""" |
| 494 | + unmarked_days = ( |
| 495 | + self.total_working_days |
| 496 | + - self._get_days_outside_period(include_holidays_in_total_working_days, holidays) |
| 497 | + - self._get_marked_attendance_days(holidays) |
| 498 | + ) |
| 499 | + |
| 500 | + if include_holidays_in_total_working_days and holidays: |
| 501 | + unmarked_days -= self._get_number_of_holidays(holidays) |
| 502 | + |
| 503 | + return unmarked_days |
| 504 | + |
| 505 | + def _get_days_outside_period( |
| 506 | + self, include_holidays_in_total_working_days: bool, holidays: list | None = None |
| 507 | + ): |
| 508 | + """Returns days before DOJ or after relieving date""" |
| 509 | + |
| 510 | + def _get_days(start_date, end_date): |
| 511 | + no_of_days = date_diff(end_date, start_date) + 1 |
490 | 512 |
|
| 513 | + if include_holidays_in_total_working_days: |
| 514 | + return no_of_days |
| 515 | + else: |
| 516 | + days = 0 |
| 517 | + end_date = getdate(end_date) |
| 518 | + for day in range(no_of_days): |
| 519 | + date = add_days(end_date, -day) |
| 520 | + if date not in holidays: |
| 521 | + days += 1 |
| 522 | + return days |
| 523 | + |
| 524 | + days = 0 |
491 | 525 | if self.actual_start_date != self.start_date:
|
492 |
| - unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving( |
493 |
| - unmarked_days, |
494 |
| - include_holidays_in_total_working_days, |
495 |
| - self.start_date, |
496 |
| - add_days(self.joining_date, -1), |
497 |
| - ) |
| 526 | + days += _get_days(self.start_date, add_days(self.joining_date, -1)) |
498 | 527 |
|
499 | 528 | if self.actual_end_date != self.end_date:
|
500 |
| - unmarked_days = self.get_unmarked_days_based_on_doj_or_relieving( |
501 |
| - unmarked_days, |
502 |
| - include_holidays_in_total_working_days, |
503 |
| - add_days(self.relieving_date, 1), |
504 |
| - self.end_date, |
505 |
| - ) |
| 529 | + days += _get_days(add_days(self.relieving_date, 1), self.end_date) |
506 | 530 |
|
507 |
| - # exclude days for which attendance has been marked |
508 |
| - marked_days = frappe.db.count( |
509 |
| - "Attendance", |
510 |
| - filters={ |
511 |
| - "attendance_date": ["between", [self.actual_start_date, self.actual_end_date]], |
512 |
| - "employee": self.employee, |
513 |
| - "docstatus": 1, |
514 |
| - }, |
515 |
| - ) |
516 |
| - unmarked_days -= marked_days |
| 531 | + return days |
517 | 532 |
|
518 |
| - return unmarked_days |
| 533 | + def _get_number_of_holidays(self, holidays: list | None = None) -> float: |
| 534 | + no_of_holidays = 0 |
| 535 | + actual_end_date = getdate(self.actual_end_date) |
519 | 536 |
|
520 |
| - def get_unmarked_days_based_on_doj_or_relieving( |
521 |
| - self, unmarked_days, include_holidays_in_total_working_days, start_date, end_date |
522 |
| - ): |
523 |
| - """ |
524 |
| - Exclude days before DOJ or after |
525 |
| - Relieving Date from unmarked days |
526 |
| - """ |
527 |
| - from erpnext.setup.doctype.employee.employee import is_holiday |
| 537 | + for days in range(date_diff(self.actual_end_date, self.actual_start_date) + 1): |
| 538 | + date = add_days(actual_end_date, -days) |
| 539 | + if date in holidays: |
| 540 | + no_of_holidays += 1 |
528 | 541 |
|
529 |
| - if include_holidays_in_total_working_days: |
530 |
| - unmarked_days -= date_diff(end_date, start_date) + 1 |
531 |
| - else: |
532 |
| - # exclude only if not holidays |
533 |
| - for days in range(date_diff(end_date, start_date) + 1): |
534 |
| - date = add_days(end_date, -days) |
535 |
| - if not is_holiday(self.employee, date): |
536 |
| - unmarked_days -= 1 |
| 542 | + return no_of_holidays |
537 | 543 |
|
538 |
| - return unmarked_days |
| 544 | + def _get_marked_attendance_days(self, holidays: list | None = None) -> float: |
| 545 | + Attendance = frappe.qb.DocType("Attendance") |
| 546 | + query = ( |
| 547 | + frappe.qb.from_(Attendance) |
| 548 | + .select(Count("*")) |
| 549 | + .where( |
| 550 | + (Attendance.attendance_date.between(self.actual_start_date, self.actual_end_date)) |
| 551 | + & (Attendance.employee == self.employee) |
| 552 | + & (Attendance.docstatus == 1) |
| 553 | + ) |
| 554 | + ) |
| 555 | + if holidays: |
| 556 | + query = query.where(Attendance.attendance_date.notin(holidays)) |
| 557 | + |
| 558 | + return query.run()[0][0] |
539 | 559 |
|
540 | 560 | def get_payment_days(self, include_holidays_in_total_working_days):
|
541 | 561 | if self.joining_date and self.joining_date > getdate(self.end_date):
|
|
0 commit comments