diff --git a/addons_extensions/hr_attendance_extended/models/hr_attendance_report.py b/addons_extensions/hr_attendance_extended/models/hr_attendance_report.py index 7b5ae2ab6..c7eaf2a68 100644 --- a/addons_extensions/hr_attendance_extended/models/hr_attendance_report.py +++ b/addons_extensions/hr_attendance_extended/models/hr_attendance_report.py @@ -61,111 +61,116 @@ class AttendanceReport(models.Model): # Define the query query = """ WITH date_range AS ( - SELECT generate_series( - %s::date, - %s::date, - interval '1 day' - )::date AS date - ), - employee_dates AS ( - SELECT - emp.id AS employee_id, - emp.name AS employee_name, - dr.date, - TO_CHAR(dr.date, 'Day') AS day_name, - EXTRACT(WEEK FROM dr.date) AS week_number, - TO_CHAR(date_trunc('week', dr.date), 'MON DD') || ' - ' || - TO_CHAR(date_trunc('week', dr.date) + interval '6 days', 'MON DD') AS week_range, - dep.name->>'en_US' AS department - FROM - hr_employee emp - CROSS JOIN - date_range dr - LEFT JOIN - hr_department dep ON emp.department_id = dep.id - WHERE - emp.active = true - """ + (" AND " + " AND ".join(emp_date_conditions) if emp_date_conditions else "") + """ - ), - daily_checkins AS ( - SELECT - emp.id, - emp.name, - DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') AS date, - at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_in, - at.check_out AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_out, - at.worked_hours, - ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in) AS first_checkin_row, - ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in DESC) AS last_checkout_row, - dep.name->>'en_US' AS department - FROM - hr_attendance at - LEFT JOIN - hr_employee emp ON at.employee_id = emp.id - LEFT JOIN - hr_department dep ON emp.department_id = dep.id - WHERE - """ + " AND ".join(checkin_conditions) + """ - ), - attendance_summary AS ( - SELECT - id, - name, - date, - MAX(CASE WHEN first_checkin_row = 1 THEN check_in END) AS first_check_in, - MAX(CASE WHEN last_checkout_row = 1 THEN check_out END) AS last_check_out, - SUM(worked_hours) AS total_worked_hours, - department - FROM - daily_checkins - GROUP BY - id, name, date, department - ), - leave_data AS ( - SELECT - hl.employee_id, - hl.date_from AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_start, - hl.date_to AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_end, - hlt.name->>'en_US' AS leave_type, - hl.request_unit_half AS is_half_day, - hl.request_date_from, - hl.request_date_to - FROM - hr_leave hl - JOIN - hr_leave_type hlt ON hl.holiday_status_id = hlt.id - WHERE - hl.state IN ('validate', 'confirm', 'validate1') - AND (hl.date_from, hl.date_to) OVERLAPS (%s::timestamp, %s::timestamp) - ) - SELECT - ed.employee_id AS id, - ed.employee_name AS name, - ed.date, - 'Week ' || ed.week_number || ' (' || ed.week_range || ')' AS week_info, - TRIM(ed.day_name) AS day_name, - COALESCE(ats.first_check_in, NULL) AS first_check_in, - COALESCE(ats.last_check_out, NULL) AS last_check_out, - COALESCE(ats.total_worked_hours, 0) AS total_worked_hours, - ed.department, - CASE - WHEN ld.leave_type IS NOT NULL AND ld.is_half_day THEN 'on Half day ' || ld.leave_type - WHEN ld.leave_type IS NOT NULL THEN 'on ' || ld.leave_type - WHEN ats.first_check_in IS NOT NULL THEN 'Present' - ELSE 'NA' - END AS status - FROM - employee_dates ed - LEFT JOIN - attendance_summary ats ON ed.employee_id = ats.id AND ed.date = ats.date - LEFT JOIN - leave_data ld ON ed.employee_id = ld.employee_id - AND ed.date >= DATE(ld.leave_start) - AND ed.date <= DATE(ld.leave_end) - ORDER BY - ed.employee_id, ed.date; + SELECT generate_series( + %s::date, + %s::date, + interval '1 day' + )::date AS date +), +employee_dates AS ( + SELECT + emp.id AS employee_id, + emp.name AS employee_name, + dr.date, + TO_CHAR(dr.date, 'Day') AS day_name, + EXTRACT(WEEK FROM dr.date) AS week_number, + TO_CHAR(date_trunc('week', dr.date), 'MON DD') || ' - ' || + TO_CHAR(date_trunc('week', dr.date) + interval '6 days', 'MON DD') AS week_range, + dep.name->>'en_US' AS department + FROM + hr_employee emp + CROSS JOIN + date_range dr + LEFT JOIN + hr_department dep ON emp.department_id = dep.id + WHERE + emp.active = true + """ + (" AND " + " AND ".join(emp_date_conditions) if emp_date_conditions else "") + """ +), +daily_checkins AS ( + SELECT + emp.id, + emp.name, + DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') AS date, + at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_in, + at.check_out AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_out, + at.worked_hours, + ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in) AS first_checkin_row, + ROW_NUMBER() OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in DESC) AS last_checkout_row, + dep.name->>'en_US' AS department, + LEAD(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') OVER (PARTITION BY emp.id, DATE(at.check_in AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata') ORDER BY at.check_in) AS next_check_in + FROM + hr_attendance at + LEFT JOIN + hr_employee emp ON at.employee_id = emp.id + LEFT JOIN + hr_department dep ON emp.department_id = dep.id + WHERE + """ + " AND ".join(checkin_conditions) + """ +), +attendance_summary AS ( + SELECT + id, + name, + date, + MAX(CASE WHEN first_checkin_row = 1 THEN check_in END) AS first_check_in, + MAX(CASE WHEN last_checkout_row = 1 THEN check_out END) AS last_check_out, + SUM(worked_hours) AS total_worked_hours, + -- 👇 Calculate total break time (sum of gaps between check_out and next check_in) + SUM( + EXTRACT(EPOCH FROM (next_check_in - check_out)) / 3600 + ) FILTER (WHERE next_check_in IS NOT NULL AND check_out IS NOT NULL) AS break_hours, + department + FROM + daily_checkins + GROUP BY + id, name, date, department +), +leave_data AS ( + SELECT + hl.employee_id, + hl.date_from AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_start, + hl.date_to AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS leave_end, + hlt.name->>'en_US' AS leave_type, + hl.request_unit_half AS is_half_day, + hl.request_date_from, + hl.request_date_to + FROM + hr_leave hl + JOIN + hr_leave_type hlt ON hl.holiday_status_id = hlt.id + WHERE + hl.state IN ('validate', 'confirm', 'validate1') + AND (hl.date_from, hl.date_to) OVERLAPS (%s::timestamp, %s::timestamp) +) +SELECT + ed.employee_id AS id, + ed.employee_name AS name, + ed.date, + 'Week ' || ed.week_number || ' (' || ed.week_range || ')' AS week_info, + TRIM(ed.day_name) AS day_name, + COALESCE(ats.first_check_in, NULL) AS first_check_in, + COALESCE(ats.last_check_out, NULL) AS last_check_out, + COALESCE(ats.total_worked_hours, 0) AS total_worked_hours, + COALESCE(ats.break_hours, 0) AS total_break_hours, + ed.department, + CASE + WHEN ld.leave_type IS NOT NULL AND ld.is_half_day THEN 'on Half day ' || ld.leave_type + WHEN ld.leave_type IS NOT NULL THEN 'on ' || ld.leave_type + WHEN ats.first_check_in IS NOT NULL THEN 'Present' + ELSE 'NA' + END AS status +FROM + employee_dates ed +LEFT JOIN + attendance_summary ats ON ed.employee_id = ats.id AND ed.date = ats.date +LEFT JOIN + leave_data ld ON ed.employee_id = ld.employee_id + AND ed.date >= DATE(ld.leave_start) + AND ed.date <= DATE(ld.leave_end) +ORDER BY + ed.employee_id, ed.date; """ - # Combine all parameters in the correct order: # 1. date_range params (start_date_str, end_date_str) # 2. employee_dates params (emp_date_params) @@ -196,6 +201,7 @@ class AttendanceReport(models.Model): 'check_in': r['first_check_in'], 'check_out': r['last_check_out'], 'worked_hours': float(r['total_worked_hours']) if r['total_worked_hours'] is not None else 0.0, + 'break_hours': float(r['total_break_hours']) if r['total_break_hours'] is not None else 0.0, 'status': r['status'] }) @@ -213,7 +219,6 @@ class AttendanceReport(models.Model): attendance_data = self.get_attendance_report(department_id, employee_id, start_date, end_date) if not attendance_data: raise UserError("No data to export!") - # Create workbook and sheet workbook = xlwt.Workbook(encoding='utf-8') sheet = workbook.add_sheet('Attendance Report') @@ -281,28 +286,26 @@ class AttendanceReport(models.Model): ) # Set column widths (in units of 1/256 of a character width) - col_widths = [6000, 8000, 7000, 3000, 4000, 5000, 5000, 4000, 5000] + col_widths = [6000, 8000, 7000, 3000, 4000, 5000, 5000, 4000, 4000, 5000] for i, width in enumerate(col_widths): sheet.col(i).width = width # Write title - sheet.write_merge(0, 0, 0, 8, 'ATTENDANCE REPORT', title_style) - + sheet.write_merge(0, 0, 0, 9, 'ATTENDANCE REPORT', title_style) # Write date range date_range = f"From: {start_date} To: {end_date}" - sheet.write_merge(1, 1, 0, 8, date_range, xlwt.easyxf( + sheet.write_merge(1, 1, 0, 9, date_range, xlwt.easyxf( 'font: italic on; align: horiz center' )) # Write headers headers = [ 'Department', 'Employee Name','Week', 'Date', 'Day', - 'Check-in', 'Check-out', 'Worked Hours', 'Status' + 'Check-in', 'Check-out', 'Worked Hours', 'Break Hours', 'Status' ] for col_num, header in enumerate(headers): sheet.write(2, col_num, header, header_style) - # Write data rows current_employee = None for row_num, record in enumerate(attendance_data, start=3): @@ -345,11 +348,16 @@ class AttendanceReport(models.Model): else: sheet.write(row_num, 7, str(record['worked_hours']), data_style) - sheet.write(row_num, 8, record['status'], status_style) + # Break hours formatting + if isinstance(record['break_hours'], (float, int)): + sheet.write(row_num, 8, float(record['break_hours']), hours_style) + else: + sheet.write(row_num, 8, str(record['break_hours']), data_style) + sheet.write(row_num, 9, record['status'], status_style) # Add freeze panes (headers will stay visible when scrolling) sheet.set_panes_frozen(True) - sheet.set_horz_split_pos(4) # After row 3 (headers) + sheet.set_horz_split_pos(3) # After row 3 (headers) sheet.set_vert_split_pos(0) # No vertical split # Save to buffer diff --git a/addons_extensions/hr_attendance_extended/static/src/js/attendance_report.js b/addons_extensions/hr_attendance_extended/static/src/js/attendance_report.js index 92faba293..9c495baee 100644 --- a/addons_extensions/hr_attendance_extended/static/src/js/attendance_report.js +++ b/addons_extensions/hr_attendance_extended/static/src/js/attendance_report.js @@ -206,7 +206,7 @@ export default class AttendanceReport extends Component { } async generateReport() { - + debugger; let { startDate, endDate, selectedEmployeeIds } = this.state; startDate = $('#from_date').val() endDate = $('#to_date').val() @@ -231,7 +231,7 @@ export default class AttendanceReport extends Component { // Fetch the attendance data based on the date range and selected employees // const attendanceData = await this.orm.searchRead('hr.attendance', domain, ['employee_id', 'check_in', 'check_out', 'worked_hours']); const attendanceData = await this.orm.call('attendance.report','get_attendance_report',[$('#dept').val(),$('#emp').val(),startDate,endDate]); - + debugger; // Group data by employee_id const rawGroups = this.groupDataByEmployee(attendanceData); diff --git a/addons_extensions/hr_attendance_extended/static/src/xml/attendance_report.xml b/addons_extensions/hr_attendance_extended/static/src/xml/attendance_report.xml index f19d0884a..f22ace0f5 100644 --- a/addons_extensions/hr_attendance_extended/static/src/xml/attendance_report.xml +++ b/addons_extensions/hr_attendance_extended/static/src/xml/attendance_report.xml @@ -65,6 +65,7 @@