commit
37e1995db1
|
|
@ -104,3 +104,10 @@ class AttendanceData(models.Model):
|
||||||
# extra_hours = fields.Float()
|
# extra_hours = fields.Float()
|
||||||
status = fields.Selection([('leave','On Leave'),('present','Present'),('no_info','No Information')])
|
status = fields.Selection([('leave','On Leave'),('present','Present'),('no_info','No Information')])
|
||||||
attendance_id = fields.Many2one('attendance.attendance')
|
attendance_id = fields.Many2one('attendance.attendance')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class HRAttendnace(models.Model):
|
||||||
|
_inherit = 'hr.attendance'
|
||||||
|
|
||||||
|
employee_id = fields.Many2one(group_expand='')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from odoo import models, fields, api
|
from odoo import models, fields, api,_
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import xlwt
|
import xlwt
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
@ -13,15 +13,19 @@ class AttendanceReport(models.Model):
|
||||||
_name = 'attendance.report'
|
_name = 'attendance.report'
|
||||||
_description = 'Attendance Report'
|
_description = 'Attendance Report'
|
||||||
|
|
||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def get_attendance_report(self, employee_id, start_date, end_date):
|
def get_attendance_report(self, department_id, employee_id, start_date, end_date):
|
||||||
# Ensure start_date and end_date are in the correct format (datetime)
|
# Ensure start_date and end_date are in the correct format (datetime)
|
||||||
|
|
||||||
if employee_id == '-':
|
if employee_id == '-':
|
||||||
employee_id = False
|
employee_id = False
|
||||||
else:
|
else:
|
||||||
employee_id = int(employee_id)
|
employee_id = int(employee_id)
|
||||||
|
|
||||||
|
if department_id == '-':
|
||||||
|
department_id = False
|
||||||
|
else:
|
||||||
|
department_id = int(department_id)
|
||||||
|
|
||||||
if isinstance(start_date, str):
|
if isinstance(start_date, str):
|
||||||
start_date = datetime.strptime(start_date, '%d/%m/%Y')
|
start_date = datetime.strptime(start_date, '%d/%m/%Y')
|
||||||
if isinstance(end_date, str):
|
if isinstance(end_date, str):
|
||||||
|
|
@ -31,17 +35,59 @@ class AttendanceReport(models.Model):
|
||||||
start_date_str = start_date.strftime('%Y-%m-%d')
|
start_date_str = start_date.strftime('%Y-%m-%d')
|
||||||
end_date_str = end_date.strftime('%Y-%m-%d')
|
end_date_str = end_date.strftime('%Y-%m-%d')
|
||||||
|
|
||||||
# Define the base where condition
|
# Initialize parameters list with date range for date_range CTE
|
||||||
if employee_id:
|
params = [start_date_str, end_date_str]
|
||||||
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
|
# Build conditions for employee_dates CTE
|
||||||
|
emp_date_conditions = []
|
||||||
|
emp_date_params = []
|
||||||
|
if employee_id:
|
||||||
|
emp_date_conditions.append("emp.id = %s")
|
||||||
|
emp_date_params.append(employee_id)
|
||||||
|
if department_id:
|
||||||
|
emp_date_conditions.append("emp.department_id = %s")
|
||||||
|
emp_date_params.append(department_id)
|
||||||
|
|
||||||
|
# Build conditions for daily_checkins CTE
|
||||||
|
checkin_conditions = ["at.check_in >= %s", "at.check_out <= %s"]
|
||||||
|
checkin_params = [start_date_str, end_date_str]
|
||||||
|
if employee_id:
|
||||||
|
checkin_conditions.append("emp.id = %s")
|
||||||
|
checkin_params.append(employee_id)
|
||||||
|
if department_id:
|
||||||
|
checkin_conditions.append("emp.department_id = %s")
|
||||||
|
checkin_params.append(department_id)
|
||||||
|
|
||||||
|
# Define the query
|
||||||
query = """
|
query = """
|
||||||
WITH daily_checkins AS (
|
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
|
SELECT
|
||||||
emp.id,
|
emp.id,
|
||||||
emp.name,
|
emp.name,
|
||||||
|
|
@ -50,97 +96,276 @@ class AttendanceReport(models.Model):
|
||||||
at.check_out AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_out,
|
at.check_out AT TIME ZONE 'UTC' AT TIME ZONE 'Asia/Kolkata' AS check_out,
|
||||||
at.worked_hours,
|
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) 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
|
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
|
FROM
|
||||||
hr_attendance at
|
hr_attendance at
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
hr_employee emp ON at.employee_id = emp.id
|
hr_employee emp ON at.employee_id = emp.id
|
||||||
""" + case + """
|
LEFT JOIN
|
||||||
)
|
hr_department dep ON emp.department_id = dep.id
|
||||||
|
WHERE
|
||||||
|
""" + " AND ".join(checkin_conditions) + """
|
||||||
|
),
|
||||||
|
attendance_summary AS (
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
date,
|
date,
|
||||||
MAX(CASE WHEN first_checkin_row = 1 THEN check_in END) AS first_check_in,
|
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,
|
MAX(CASE WHEN last_checkout_row = 1 THEN check_out END) AS last_check_out,
|
||||||
SUM(worked_hours) AS total_worked_hours
|
SUM(worked_hours) AS total_worked_hours,
|
||||||
|
department
|
||||||
FROM
|
FROM
|
||||||
daily_checkins
|
daily_checkins
|
||||||
GROUP BY
|
GROUP BY
|
||||||
id, name, date
|
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
|
ORDER BY
|
||||||
id, date;
|
ed.employee_id, ed.date;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Execute the query with parameters
|
# Combine all parameters in the correct order:
|
||||||
self.env.cr.execute(query, params)
|
# 1. date_range params (start_date_str, end_date_str)
|
||||||
rows = self.env.cr.dictfetchall()
|
# 2. employee_dates params (emp_date_params)
|
||||||
data = []
|
# 3. daily_checkins params (checkin_params)
|
||||||
a = 0
|
# 4. leave_data params (start_date_str, end_date_str)
|
||||||
for r in rows:
|
all_params = [
|
||||||
a += 1
|
start_date_str, end_date_str, # date_range
|
||||||
# Calculate worked hours in Python, but here it's better done in the query itself.
|
*emp_date_params, # employee_dates
|
||||||
worked_hours = r['last_check_out'] - r['first_check_in'] if r['first_check_in'] and r[
|
*checkin_params, # daily_checkins
|
||||||
'last_check_out'] else 0
|
start_date_str, end_date_str # leave_data
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Execute the query with parameters
|
||||||
|
self.env.cr.execute(query, all_params)
|
||||||
|
rows = self.env.cr.dictfetchall()
|
||||||
|
|
||||||
|
data = []
|
||||||
|
for idx, r in enumerate(rows, 1):
|
||||||
data.append({
|
data.append({
|
||||||
'id': a,
|
'id': idx,
|
||||||
'employee_id': r['id'],
|
'employee_id': r['id'],
|
||||||
'employee_name': r['name'],
|
'employee_name': r['name'],
|
||||||
|
'employee_department': r['department'],
|
||||||
'date': r['date'],
|
'date': r['date'],
|
||||||
|
'week_info': r['week_info'],
|
||||||
|
'day_name': r['day_name'],
|
||||||
'check_in': r['first_check_in'],
|
'check_in': r['first_check_in'],
|
||||||
'check_out': r['last_check_out'],
|
'check_out': r['last_check_out'],
|
||||||
'worked_hours': worked_hours,
|
'worked_hours': float(r['total_worked_hours']) if r['total_worked_hours'] is not None else 0.0,
|
||||||
|
'status': r['status']
|
||||||
})
|
})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@api.model
|
except Exception as e:
|
||||||
def export_to_excel(self, employee_id, start_date, end_date):
|
error_msg = f"Error executing attendance report query: {str(e)}"
|
||||||
# Fetch the attendance data (replace with your logic to fetch attendance data)
|
print(error_msg)
|
||||||
attendance_data = self.get_attendance_report(employee_id, start_date, end_date)
|
raise UserError(
|
||||||
|
_("An error occurred while generating the attendance report. Please check the logs for details."))
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def export_to_excel(self, department_id, employee_id, start_date, end_date):
|
||||||
|
attendance_data = self.get_attendance_report(department_id, employee_id, start_date, end_date)
|
||||||
if not attendance_data:
|
if not attendance_data:
|
||||||
raise UserError("No data to export!")
|
raise UserError("No data to export!")
|
||||||
|
|
||||||
# Create an Excel workbook and a sheet
|
# Create workbook and sheet
|
||||||
workbook = xlwt.Workbook()
|
workbook = xlwt.Workbook(encoding='utf-8')
|
||||||
sheet = workbook.add_sheet('Attendance Report')
|
sheet = workbook.add_sheet('Attendance Report')
|
||||||
|
|
||||||
# Define the column headers
|
# Define styles - using only xlwt supported color names
|
||||||
headers = ['Employee Name', 'Check-in', 'Check-out', 'Worked Hours']
|
title_style = xlwt.easyxf(
|
||||||
|
'font: bold on, height 300, color white;'
|
||||||
|
'pattern: pattern solid, fore_color dark_blue;'
|
||||||
|
'align: vert centre, horiz center;'
|
||||||
|
)
|
||||||
|
|
||||||
|
header_style = xlwt.easyxf(
|
||||||
|
'font: bold on, height 240, color white;'
|
||||||
|
'pattern: pattern solid, fore_color dark_blue;' # Changed from navy to dark_blue
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz center'
|
||||||
|
)
|
||||||
|
|
||||||
|
data_style = xlwt.easyxf(
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz left'
|
||||||
|
)
|
||||||
|
|
||||||
|
time_style = xlwt.easyxf(
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz left',
|
||||||
|
num_format_str='YYYY-MM-DD HH:MM:SS'
|
||||||
|
)
|
||||||
|
|
||||||
|
date_style = xlwt.easyxf(
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz left',
|
||||||
|
num_format_str='YYYY-MM-DD'
|
||||||
|
)
|
||||||
|
|
||||||
|
hours_style = xlwt.easyxf(
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz right',
|
||||||
|
num_format_str='0.00'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Status color styles using supported colors
|
||||||
|
status_present = xlwt.easyxf(
|
||||||
|
'font: color green;'
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz left'
|
||||||
|
)
|
||||||
|
|
||||||
|
status_leave = xlwt.easyxf(
|
||||||
|
'font: color red;'
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz left'
|
||||||
|
)
|
||||||
|
|
||||||
|
status_halfday = xlwt.easyxf(
|
||||||
|
'font: color orange;'
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz left'
|
||||||
|
)
|
||||||
|
|
||||||
|
status_na = xlwt.easyxf(
|
||||||
|
'font: color blue;'
|
||||||
|
'borders: left thin, right thin, top thin, bottom thin;'
|
||||||
|
'align: vert centre, horiz left'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set column widths (in units of 1/256 of a character width)
|
||||||
|
col_widths = [6000, 8000, 7000, 3000, 4000, 5000, 5000, 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)
|
||||||
|
|
||||||
|
# Write date range
|
||||||
|
date_range = f"From: {start_date} To: {end_date}"
|
||||||
|
sheet.write_merge(1, 1, 0, 8, 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'
|
||||||
|
]
|
||||||
|
|
||||||
# Write headers to the first row
|
|
||||||
for col_num, header in enumerate(headers):
|
for col_num, header in enumerate(headers):
|
||||||
sheet.write(0, col_num, header)
|
sheet.write(2, col_num, header, header_style)
|
||||||
|
|
||||||
# Write the attendance data to the sheet
|
# Write data rows
|
||||||
for row_num, record in enumerate(attendance_data, start=1):
|
current_employee = None
|
||||||
sheet.write(row_num, 0, record['employee_name'])
|
for row_num, record in enumerate(attendance_data, start=3):
|
||||||
sheet.write(row_num, 1, record['check_in'].strftime("%Y-%m-%d %H:%M:%S"))
|
# Highlight employee changes with a subtle border
|
||||||
sheet.write(row_num, 2, record['check_out'].strftime("%Y-%m-%d %H:%M:%S"))
|
if current_employee != record['employee_name']:
|
||||||
if isinstance(record['worked_hours'], timedelta):
|
current_employee = record['employee_name']
|
||||||
hours = record['worked_hours'].seconds // 3600
|
sheet.row(row_num).height = 400 # Slightly taller row for new employee
|
||||||
minutes = (record['worked_hours'].seconds % 3600) // 60
|
|
||||||
# Format as "X hours Y minutes"
|
# Apply appropriate status style
|
||||||
worked_hours_str = f"{record['worked_hours'].days * 24 + hours} hours {minutes} minutes"
|
if 'Present' in record['status']:
|
||||||
sheet.write(row_num, 3, worked_hours_str)
|
status_style = status_present
|
||||||
|
elif 'Half day' in record['status']:
|
||||||
|
status_style = status_halfday
|
||||||
|
elif 'on ' in record['status']:
|
||||||
|
status_style = status_leave
|
||||||
else:
|
else:
|
||||||
sheet.write(row_num, 3, record['worked_hours'])
|
status_style = status_na
|
||||||
# Save the workbook to a BytesIO buffer
|
|
||||||
|
# Write data
|
||||||
|
sheet.write(row_num, 0, record['employee_department'], data_style)
|
||||||
|
sheet.write(row_num, 1, record['employee_name'], data_style)
|
||||||
|
sheet.write(row_num, 2, record['week_info'], data_style)
|
||||||
|
sheet.write(row_num, 3, record['date'], date_style)
|
||||||
|
sheet.write(row_num, 4, record['day_name'], data_style)
|
||||||
|
|
||||||
|
# Check-in/Check-out times
|
||||||
|
if record['check_in']:
|
||||||
|
sheet.write(row_num, 5, record['check_in'].strftime("%Y-%m-%d %H:%M:%S"), time_style)
|
||||||
|
else:
|
||||||
|
sheet.write(row_num, 5, '', data_style)
|
||||||
|
|
||||||
|
if record['check_out']:
|
||||||
|
sheet.write(row_num, 6, record['check_out'].strftime("%Y-%m-%d %H:%M:%S"), time_style)
|
||||||
|
else:
|
||||||
|
sheet.write(row_num, 6, '', data_style)
|
||||||
|
|
||||||
|
# Worked hours formatting
|
||||||
|
if isinstance(record['worked_hours'], (float, int)):
|
||||||
|
sheet.write(row_num, 7, float(record['worked_hours']), hours_style)
|
||||||
|
else:
|
||||||
|
sheet.write(row_num, 7, str(record['worked_hours']), data_style)
|
||||||
|
|
||||||
|
sheet.write(row_num, 8, 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_vert_split_pos(0) # No vertical split
|
||||||
|
|
||||||
|
# Save to buffer
|
||||||
output = BytesIO()
|
output = BytesIO()
|
||||||
workbook.save(output)
|
workbook.save(output)
|
||||||
|
|
||||||
# Convert the output to base64 for saving in Odoo
|
|
||||||
file_data = base64.b64encode(output.getvalue())
|
file_data = base64.b64encode(output.getvalue())
|
||||||
|
|
||||||
# Create an attachment record to save the Excel file in Odoo
|
# Create attachment with timestamp
|
||||||
|
report_date = datetime.now().strftime("%Y-%m-%d_%H-%M")
|
||||||
|
filename = f"Attendance_Report_{report_date}.xls"
|
||||||
|
|
||||||
attachment = self.env['ir.attachment'].create({
|
attachment = self.env['ir.attachment'].create({
|
||||||
'name': 'attendance_report.xls',
|
'name': filename,
|
||||||
'type': 'binary',
|
'type': 'binary',
|
||||||
'datas': file_data,
|
'datas': file_data,
|
||||||
'mimetype': 'application/vnd.ms-excel',
|
'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),
|
return '/web/content/%d/%s' % (attachment.id, attachment.name),
|
||||||
|
|
@ -16,7 +16,8 @@ export default class AttendanceReport extends Component {
|
||||||
endDate: "",
|
endDate: "",
|
||||||
attendanceData: [], // Initialized as an empty array
|
attendanceData: [], // Initialized as an empty array
|
||||||
groupedData: [], // To store the grouped attendance data by employee_id
|
groupedData: [], // To store the grouped attendance data by employee_id
|
||||||
employeeIDS: [] // List of employee IDs to bind with select dropdown
|
employeeIDS: [], // List of employee IDs to bind with select dropdown
|
||||||
|
departmentIDS: []
|
||||||
});
|
});
|
||||||
onWillStart(async () => {
|
onWillStart(async () => {
|
||||||
|
|
||||||
|
|
@ -34,6 +35,7 @@ export default class AttendanceReport extends Component {
|
||||||
});
|
});
|
||||||
onMounted( () => {
|
onMounted( () => {
|
||||||
this.loademployeeIDS();
|
this.loademployeeIDS();
|
||||||
|
this.loaddepartmentIDS();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,11 +55,55 @@ export default class AttendanceReport extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loaddepartmentIDS() {
|
||||||
|
try {
|
||||||
|
const department = await this.orm.searchRead('hr.department', [], ['id', 'display_name']);
|
||||||
|
this.state.departmentIDS = department;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
this.initializeSelect2();
|
||||||
|
this.render();// Initialize Select2 after data is loaded
|
||||||
|
this.reload();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading departmentIDS:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
annotateWeekRowspan(records) {
|
||||||
|
let result = [];
|
||||||
|
let weekMap = {};
|
||||||
|
|
||||||
|
// Count occurrences of each week_info
|
||||||
|
for (let row of records) {
|
||||||
|
if (!weekMap[row.week_info]) {
|
||||||
|
weekMap[row.week_info] = [];
|
||||||
|
}
|
||||||
|
weekMap[row.week_info].push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let week in weekMap) {
|
||||||
|
const rows = weekMap[week];
|
||||||
|
rows[0]._render_week = true;
|
||||||
|
rows[0]._rowspan = rows.length;
|
||||||
|
for (let i = 1; i < rows.length; i++) {
|
||||||
|
rows[i]._render_week = false;
|
||||||
|
}
|
||||||
|
result = result.concat(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Initialize Select2 with error handling and ensuring it's initialized only once
|
// Initialize Select2 with error handling and ensuring it's initialized only once
|
||||||
initializeSelect2() {
|
initializeSelect2() {
|
||||||
|
const departmentIDS = this.state.departmentIDS;
|
||||||
const employeeIDS = this.state.employeeIDS;
|
const employeeIDS = this.state.employeeIDS;
|
||||||
|
|
||||||
// Ensure the <select> element is initialized only once
|
// Ensure the <select> element is initialized only once
|
||||||
|
const $deptSelect = $('#dept');
|
||||||
const $empSelect = $('#emp');
|
const $empSelect = $('#emp');
|
||||||
const from_date = $("#from_date").datepicker({
|
const from_date = $("#from_date").datepicker({
|
||||||
dateFormat: "dd/mm/yy", // Date format
|
dateFormat: "dd/mm/yy", // Date format
|
||||||
|
|
@ -78,21 +124,45 @@ export default class AttendanceReport extends Component {
|
||||||
});
|
});
|
||||||
// Debugging the employeeIDS array to verify its structure
|
// Debugging the employeeIDS array to verify its structure
|
||||||
console.log("employeeIDS:", employeeIDS);
|
console.log("employeeIDS:", employeeIDS);
|
||||||
|
console.log("departmentIDS:", departmentIDS);
|
||||||
|
|
||||||
|
if (Array.isArray(departmentIDS) && departmentIDS.length > 0){
|
||||||
|
$deptSelect.empty();
|
||||||
|
|
||||||
|
$deptSelect.append(
|
||||||
|
`<option value="-">All</option>`
|
||||||
|
);
|
||||||
|
departmentIDS.forEach(dept => {
|
||||||
|
$deptSelect.append(
|
||||||
|
`<option value="${dept.id}">${dept.display_name}</option>`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$deptSelect.on('change', (ev) => {
|
||||||
|
const selectedDepartmentIds = $(ev.target).val();
|
||||||
|
console.log('Selected Department IDs: ', selectedDepartmentIds);
|
||||||
|
|
||||||
|
const selectedDepartments = departmentIDS.filter(dept => selectedDepartmentIds.includes(dept.id.toString()));
|
||||||
|
console.log('Selected Department: ', selectedDepartments);
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error("Invalid department data format:", departmentIDS);
|
||||||
|
}
|
||||||
// Check if employeeIDS is an array and has the necessary properties
|
// Check if employeeIDS is an array and has the necessary properties
|
||||||
if (Array.isArray(employeeIDS) && employeeIDS.length > 0) {
|
if (Array.isArray(employeeIDS) && employeeIDS.length > 0) {
|
||||||
// Clear the current options (if any)
|
// Clear the current options (if any)
|
||||||
$empSelect.empty();
|
$empSelect.empty();
|
||||||
|
|
||||||
|
|
||||||
|
$empSelect.append(
|
||||||
|
`<option value="-">All</option>`
|
||||||
|
);
|
||||||
// Add options for each employee
|
// Add options for each employee
|
||||||
employeeIDS.forEach(emp => {
|
employeeIDS.forEach(emp => {
|
||||||
$empSelect.append(
|
$empSelect.append(
|
||||||
`<option value="${emp.id}">${emp.display_name}</option>`
|
`<option value="${emp.id}">${emp.display_name}</option>`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
$empSelect.append(
|
|
||||||
`<option value="-">All</option>`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initialize the select with the 'multiple' attribute for multi-select
|
// Initialize the select with the 'multiple' attribute for multi-select
|
||||||
// $empSelect.attr('multiple', 'multiple');
|
// $empSelect.attr('multiple', 'multiple');
|
||||||
|
|
@ -126,9 +196,8 @@ export default class AttendanceReport extends Component {
|
||||||
domain.push(['employee_id', '=', parseInt($('#emp').val())]);
|
domain.push(['employee_id', '=', parseInt($('#emp').val())]);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
debugger;
|
|
||||||
// Fetch the attendance data based on the date range and selected employees
|
// Fetch the attendance data based on the date range and selected employees
|
||||||
const URL = await this.orm.call('attendance.report', 'export_to_excel', [$('#emp').val(), startdate, enddate]);
|
const URL = await this.orm.call('attendance.report', 'export_to_excel', [$('#dept').val(), $('#emp').val(), startdate, enddate]);
|
||||||
window.open(getOrigin()+URL, '_blank');
|
window.open(getOrigin()+URL, '_blank');
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -161,10 +230,13 @@ export default class AttendanceReport extends Component {
|
||||||
try {
|
try {
|
||||||
// Fetch the attendance data based on the date range and selected employees
|
// 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.searchRead('hr.attendance', domain, ['employee_id', 'check_in', 'check_out', 'worked_hours']);
|
||||||
const attendanceData = await this.orm.call('attendance.report','get_attendance_report',[$('#emp').val(),startDate,endDate]);
|
const attendanceData = await this.orm.call('attendance.report','get_attendance_report',[$('#dept').val(),$('#emp').val(),startDate,endDate]);
|
||||||
|
|
||||||
// Group data by employee_id
|
// Group data by employee_id
|
||||||
const groupedData = this.groupDataByEmployee(attendanceData);
|
const rawGroups = this.groupDataByEmployee(attendanceData);
|
||||||
|
|
||||||
|
// Annotate week rows inside each group
|
||||||
|
const groupedData = rawGroups.map(group => this.annotateWeekRowspan(group));
|
||||||
|
|
||||||
// Update state with the fetched and grouped data
|
// Update state with the fetched and grouped data
|
||||||
this.state.attendanceData = attendanceData;
|
this.state.attendanceData = attendanceData;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,10 @@
|
||||||
<div class="header pt-5" style="text-align:center">
|
<div class="header pt-5" style="text-align:center">
|
||||||
<h1>Attendance Report</h1>
|
<h1>Attendance Report</h1>
|
||||||
<div class="navbar navbar-expand-lg container">
|
<div class="navbar navbar-expand-lg container">
|
||||||
|
<h4 class="p-3 text-nowrap">Department</h4>
|
||||||
|
<div class="input-group input-group-lg">
|
||||||
|
<select type="text" id="dept" class="form-control" />
|
||||||
|
</div>
|
||||||
<h4 class="p-3 text-nowrap">Employee </h4>
|
<h4 class="p-3 text-nowrap">Employee </h4>
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
<select type="text" id="emp" class="form-control" />
|
<select type="text" id="emp" class="form-control" />
|
||||||
|
|
@ -42,6 +46,12 @@
|
||||||
<t t-else="">
|
<t t-else="">
|
||||||
<span>Unknown Employee</span>
|
<span>Unknown Employee</span>
|
||||||
</t>
|
</t>
|
||||||
|
<t t-if="group[0].employee_department">
|
||||||
|
(<t t-esc="group[0].employee_department"/>)
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
|
<span>(Unknown Department)</span>
|
||||||
|
</t>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<!-- Scrollable Container for the Table -->
|
<!-- Scrollable Container for the Table -->
|
||||||
|
|
@ -49,27 +59,27 @@
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Week</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Employee</th>
|
<th>Day</th>
|
||||||
<th>Check In</th>
|
<th>Check In</th>
|
||||||
<th>Check Out</th>
|
<th>Check Out</th>
|
||||||
<th>Worked Hours</th>
|
<th>Worked Hours</th>
|
||||||
|
<th>Status</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr t-foreach="group" t-as="data" t-key="data.id">
|
<tr t-foreach="group" t-as="data" t-key="data.id">
|
||||||
|
<t t-if="data._render_week">
|
||||||
|
<td t-att-rowspan="data._rowspan"><t t-esc="data.week_info"/></td>
|
||||||
|
</t>
|
||||||
|
<t t-else=""></t>
|
||||||
<td><t t-esc="data.date"/></td>
|
<td><t t-esc="data.date"/></td>
|
||||||
<td>
|
<td><t t-esc="data.day_name"/></td>
|
||||||
<t t-if="data.employee_id">
|
|
||||||
<t t-esc="data.employee_name"/>
|
|
||||||
</t>
|
|
||||||
<t t-else="">
|
|
||||||
<span>Unknown Employee</span>
|
|
||||||
</t>
|
|
||||||
</td>
|
|
||||||
<td><t t-esc="data.check_in"/></td>
|
<td><t t-esc="data.check_in"/></td>
|
||||||
<td><t t-esc="data.check_out"/></td>
|
<td><t t-esc="data.check_out"/></td>
|
||||||
<td><t t-esc="data.worked_hours"/></td>
|
<td><t t-esc="data.worked_hours"/></td>
|
||||||
|
<td><t t-esc="data.status"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
||||||
|
|
@ -107,31 +107,6 @@
|
||||||
</field>
|
</field>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<h5>Education Details</h5>
|
|
||||||
<field name="education_history">
|
|
||||||
<list string="Education Details">
|
|
||||||
<field name="education_type"/>
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="university"/>
|
|
||||||
<field name="start_year"/>
|
|
||||||
<field name="end_year"/>
|
|
||||||
<field name="marks_or_grade"/>
|
|
||||||
<field name="employee_id" column_invisible="1"/>
|
|
||||||
</list>
|
|
||||||
</field>
|
|
||||||
<br/>
|
|
||||||
<br/>
|
|
||||||
<h5>Employer History</h5>
|
|
||||||
<field name="employer_history">
|
|
||||||
<list string="Employer Details">
|
|
||||||
<field name="company_name"/>
|
|
||||||
<field name="designation"/>
|
|
||||||
<field name="date_of_joining"/>
|
|
||||||
<field name="last_working_day"/>
|
|
||||||
<field name="ctc"/>
|
|
||||||
<field name="employee_id" column_invisible="1"/>
|
|
||||||
</list>
|
|
||||||
</field>
|
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='employee_type']" position="attributes">
|
<xpath expr="//field[@name='employee_type']" position="attributes">
|
||||||
<attribute name="invisible">1</attribute>
|
<attribute name="invisible">1</attribute>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue