diff --git a/addons_extensions/hr_attendance_extended/__manifest__.py b/addons_extensions/hr_attendance_extended/__manifest__.py index 9aa4d8b5b..8691fd812 100644 --- a/addons_extensions/hr_attendance_extended/__manifest__.py +++ b/addons_extensions/hr_attendance_extended/__manifest__.py @@ -12,7 +12,6 @@ 'website': "https://www.ftprotech.com", # Categories can be used to filter modules in modules listing - # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # for the full list 'category': 'Human Resources/Attendances', 'version': '0.1', @@ -26,8 +25,16 @@ 'security/security.xml', 'data/cron.xml', 'data/sequence.xml', - 'views/hr_attendance.xml', 'views/on_duty_form.xml', + 'views/hr_attendance.xml', + 'views/day_attendance_report.xml', ], + 'assets': { + 'web.assets_backend': [ + 'hr_attendance_extended/static/src/xml/attendance_report.xml', + 'hr_attendance_extended/static/src/js/attendance_report.js', + ] + } } + diff --git a/addons_extensions/hr_attendance_extended/models/__init__.py b/addons_extensions/hr_attendance_extended/models/__init__.py index 92e7e4a4a..39bd653ef 100644 --- a/addons_extensions/hr_attendance_extended/models/__init__.py +++ b/addons_extensions/hr_attendance_extended/models/__init__.py @@ -1,2 +1,4 @@ from . import hr_attendance +from . import hr_attendance +from . import hr_attendance_report from . import on_duty_form \ No newline at end of file diff --git a/addons_extensions/hr_attendance_extended/models/hr_attendance_report.py b/addons_extensions/hr_attendance_extended/models/hr_attendance_report.py new file mode 100644 index 000000000..90e9f98dc --- /dev/null +++ b/addons_extensions/hr_attendance_extended/models/hr_attendance_report.py @@ -0,0 +1,146 @@ +from odoo import models, fields, api +from datetime import datetime, timedelta +import xlwt +from io import BytesIO +import base64 +from odoo.exceptions import UserError + + +def convert_to_date(date_string): + # Use strptime to parse the date string in 'dd/mm/yyyy' format + return datetime.strptime(date_string, '%Y/%m/%d') +class AttendanceReport(models.Model): + _name = 'attendance.report' + _description = 'Attendance Report' + + + @api.model + def get_attendance_report(self, employee_id, start_date, end_date): + # Ensure start_date and end_date are in the correct format (datetime) + + if employee_id == '-': + employee_id = False + else: + employee_id = int(employee_id) + if isinstance(start_date, str): + start_date = datetime.strptime(start_date, '%d/%m/%Y') + if isinstance(end_date, str): + end_date = datetime.strptime(end_date, '%d/%m/%Y') + + # Convert the dates to 'YYYY-MM-DD' format for PostgreSQL + start_date_str = start_date.strftime('%Y-%m-%d') + end_date_str = end_date.strftime('%Y-%m-%d') + + # Define the base where condition + if employee_id: + case = """WHERE emp.id = %s AND at.check_in >= %s AND at.check_out <= %s""" + params = (employee_id, start_date_str, end_date_str) + else: + case = """WHERE at.check_in >= %s AND at.check_out <= %s""" + params = (start_date_str, end_date_str) + + # Define the query with improved date handling + query = """ + WITH 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 + FROM + hr_attendance at + LEFT JOIN + hr_employee emp ON at.employee_id = emp.id + """ + case + """ + ) + 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 + FROM + daily_checkins + GROUP BY + id, name, date + ORDER BY + id, date; + """ + + # Execute the query with parameters + self.env.cr.execute(query, params) + rows = self.env.cr.dictfetchall() + data = [] + a = 0 + for r in rows: + a += 1 + # Calculate worked hours in Python, but here it's better done in the query itself. + worked_hours = r['last_check_out'] - r['first_check_in'] if r['first_check_in'] and r[ + 'last_check_out'] else 0 + + data.append({ + 'id': a, + 'employee_id': r['id'], + 'employee_name': r['name'], + 'date': r['date'], + 'check_in': r['first_check_in'], + 'check_out': r['last_check_out'], + 'worked_hours': worked_hours, + }) + + return data + + @api.model + def export_to_excel(self, employee_id, start_date, end_date): + # Fetch the attendance data (replace with your logic to fetch attendance data) + attendance_data = self.get_attendance_report(employee_id, start_date, end_date) + + if not attendance_data: + raise UserError("No data to export!") + + # Create an Excel workbook and a sheet + workbook = xlwt.Workbook() + sheet = workbook.add_sheet('Attendance Report') + + # Define the column headers + headers = ['Employee Name', 'Check-in', 'Check-out', 'Worked Hours'] + + # Write headers to the first row + for col_num, header in enumerate(headers): + sheet.write(0, col_num, header) + + # Write the attendance data to the sheet + for row_num, record in enumerate(attendance_data, start=1): + sheet.write(row_num, 0, record['employee_name']) + sheet.write(row_num, 1, record['check_in'].strftime("%Y-%m-%d %H:%M:%S")) + sheet.write(row_num, 2, record['check_out'].strftime("%Y-%m-%d %H:%M:%S")) + if isinstance(record['worked_hours'], timedelta): + hours = record['worked_hours'].seconds // 3600 + minutes = (record['worked_hours'].seconds % 3600) // 60 + # Format as "X hours Y minutes" + worked_hours_str = f"{record['worked_hours'].days * 24 + hours} hours {minutes} minutes" + sheet.write(row_num, 3, worked_hours_str) + else: + sheet.write(row_num, 3, record['worked_hours']) + # Save the workbook to a BytesIO buffer + output = BytesIO() + workbook.save(output) + + # Convert the output to base64 for saving in Odoo + file_data = base64.b64encode(output.getvalue()) + + # Create an attachment record to save the Excel file in Odoo + attachment = self.env['ir.attachment'].create({ + 'name': 'attendance_report.xls', + 'type': 'binary', + 'datas': file_data, + 'mimetype': 'application/vnd.ms-excel', + }) + + # Return the attachment's URL to allow downloading in the Odoo UI + return '/web/content/%d/%s' % (attachment.id, attachment.name), 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 new file mode 100644 index 000000000..be4eeb448 --- /dev/null +++ b/addons_extensions/hr_attendance_extended/static/src/js/attendance_report.js @@ -0,0 +1,200 @@ +import { useService } from "@web/core/utils/hooks"; +import { loadJS, loadCSS } from "@web/core/assets"; +import { Component, xml, useState, onMounted,onWillStart } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { getOrigin } from "@web/core/utils/urls"; +export default class AttendanceReport extends Component { + static props = ['*']; + static template = 'attendance_report_template'; + + setup() { + + super.setup(...arguments); + this.orm = useService("orm"); + this.state = useState({ + startDate: "", // To store the start date parameter + endDate: "", + attendanceData: [], // Initialized as an empty array + groupedData: [], // To store the grouped attendance data by employee_id + employeeIDS: [] // List of employee IDs to bind with select dropdown + }); + onWillStart(async () => { + + try { + await Promise.all([ + loadJS('https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.js'), + loadJS('https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.14.1/jquery-ui.min.js'), + loadCSS('https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.14.1/themes/base/jquery-ui.min.css') + ]) + } catch (error) { + + throw error; + + } + }); + onMounted( () => { + this.loademployeeIDS(); + }); + } + + async loademployeeIDS() { + try { + const employee = await this.orm.searchRead('hr.employee', [], ['id', 'display_name']); + this.state.employeeIDS = employee; + + + + + this.initializeSelect2(); + this.render();// Initialize Select2 after data is loaded + this.reload(); + } catch (error) { + console.error("Error loading employeeIDS:", error); + } + } + + // Initialize Select2 with error handling and ensuring it's initialized only once + initializeSelect2() { + const employeeIDS = this.state.employeeIDS; + + // Ensure the + +

From Date

+
+ +
+

To Date

+
+ +
+ + + + +
+
+
+

+ Employee: + + + + + Unknown Employee + +

+ + +
+ + + + + + + + + + + + + + + + + + + +
DateEmployeeCheck InCheck OutWorked Hours
+ + + + + Unknown Employee + +
+
+ +
+
+
+ +
+

No data available for the selected date range.

+
+ + + diff --git a/addons_extensions/hr_attendance_extended/views/day_attendance_report.xml b/addons_extensions/hr_attendance_extended/views/day_attendance_report.xml new file mode 100644 index 000000000..646aa0a15 --- /dev/null +++ b/addons_extensions/hr_attendance_extended/views/day_attendance_report.xml @@ -0,0 +1,7 @@ + + + Attendance Report + AttendanceReport + + +