Compare commits
50 Commits
af05151f6e
...
2cabd2df96
| Author | SHA1 | Date |
|---|---|---|
|
|
2cabd2df96 | |
|
|
dc817254cc | |
|
|
d61ee6455e | |
|
|
db3083e9b9 | |
|
|
733decd14f | |
|
|
f41bf0e0bc | |
|
|
398f27188a | |
|
|
d1e8cec626 | |
|
|
b7c06d2062 | |
|
|
a03a472c78 | |
|
|
66ac001dce | |
|
|
3c80eae8f0 | |
|
|
e1d6f6b669 | |
|
|
a126df1274 | |
|
|
0a440b9c87 | |
|
|
d11c74ecee | |
|
|
cf6af5239f | |
|
|
e3947a927e | |
|
|
716be65c65 | |
|
|
e3edf042bd | |
|
|
6b5975d2f5 | |
|
|
a43d6c2e96 | |
|
|
70ea396840 | |
|
|
4062fed0de | |
|
|
55daddcb33 | |
|
|
d3356bde83 | |
|
|
9cabc99092 | |
|
|
5e279ca14e | |
|
|
82d43000f5 | |
|
|
15141c3a70 | |
|
|
68063edf95 | |
|
|
bfea4e033a | |
|
|
0ac4ea2957 | |
|
|
0af6c6c0b8 | |
|
|
3388c6b091 | |
|
|
459b3a4be1 | |
|
|
9f46b4cf21 | |
|
|
d20318d8c3 | |
|
|
eb16684b6c | |
|
|
4fdbc336a7 | |
|
|
bb46aafa95 | |
|
|
0019297afc | |
|
|
0d649dd8bf | |
|
|
ac53c7ca8d | |
|
|
fccfe56b41 | |
|
|
eca1a38294 | |
|
|
f2897086a2 | |
|
|
555da86614 | |
|
|
ab3163978d | |
|
|
08e93e44c5 |
|
|
@ -104,3 +104,10 @@ class AttendanceData(models.Model):
|
|||
# extra_hours = fields.Float()
|
||||
status = fields.Selection([('leave','On Leave'),('present','Present'),('no_info','No Information')])
|
||||
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
|
||||
import xlwt
|
||||
from io import BytesIO
|
||||
|
|
@ -13,15 +13,19 @@ class AttendanceReport(models.Model):
|
|||
_name = 'attendance.report'
|
||||
_description = 'Attendance Report'
|
||||
|
||||
|
||||
@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)
|
||||
|
||||
if employee_id == '-':
|
||||
employee_id = False
|
||||
else:
|
||||
employee_id = int(employee_id)
|
||||
|
||||
if department_id == '-':
|
||||
department_id = False
|
||||
else:
|
||||
department_id = int(department_id)
|
||||
|
||||
if isinstance(start_date, str):
|
||||
start_date = datetime.strptime(start_date, '%d/%m/%Y')
|
||||
if isinstance(end_date, str):
|
||||
|
|
@ -31,17 +35,59 @@ class AttendanceReport(models.Model):
|
|||
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)
|
||||
# Initialize parameters list with date range for date_range CTE
|
||||
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 = """
|
||||
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
|
||||
emp.id,
|
||||
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.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
|
||||
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
|
||||
""" + case + """
|
||||
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
|
||||
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
|
||||
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
|
||||
daily_checkins
|
||||
GROUP BY
|
||||
id, name, date
|
||||
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
|
||||
id, date;
|
||||
ed.employee_id, ed.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
|
||||
# Combine all parameters in the correct order:
|
||||
# 1. date_range params (start_date_str, end_date_str)
|
||||
# 2. employee_dates params (emp_date_params)
|
||||
# 3. daily_checkins params (checkin_params)
|
||||
# 4. leave_data params (start_date_str, end_date_str)
|
||||
all_params = [
|
||||
start_date_str, end_date_str, # date_range
|
||||
*emp_date_params, # employee_dates
|
||||
*checkin_params, # daily_checkins
|
||||
start_date_str, end_date_str # leave_data
|
||||
]
|
||||
|
||||
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,
|
||||
})
|
||||
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({
|
||||
'id': idx,
|
||||
'employee_id': r['id'],
|
||||
'employee_name': r['name'],
|
||||
'employee_department': r['department'],
|
||||
'date': r['date'],
|
||||
'week_info': r['week_info'],
|
||||
'day_name': r['day_name'],
|
||||
'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,
|
||||
'status': r['status']
|
||||
})
|
||||
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error executing attendance report query: {str(e)}"
|
||||
print(error_msg)
|
||||
raise UserError(
|
||||
_("An error occurred while generating the attendance report. Please check the logs for details."))
|
||||
|
||||
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)
|
||||
|
||||
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:
|
||||
raise UserError("No data to export!")
|
||||
|
||||
# Create an Excel workbook and a sheet
|
||||
workbook = xlwt.Workbook()
|
||||
# Create workbook and sheet
|
||||
workbook = xlwt.Workbook(encoding='utf-8')
|
||||
sheet = workbook.add_sheet('Attendance Report')
|
||||
|
||||
# Define the column headers
|
||||
headers = ['Employee Name', 'Check-in', 'Check-out', 'Worked Hours']
|
||||
# Define styles - using only xlwt supported color names
|
||||
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):
|
||||
sheet.write(0, col_num, header)
|
||||
sheet.write(2, col_num, header, header_style)
|
||||
|
||||
# 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)
|
||||
# Write data rows
|
||||
current_employee = None
|
||||
for row_num, record in enumerate(attendance_data, start=3):
|
||||
# Highlight employee changes with a subtle border
|
||||
if current_employee != record['employee_name']:
|
||||
current_employee = record['employee_name']
|
||||
sheet.row(row_num).height = 400 # Slightly taller row for new employee
|
||||
|
||||
# Apply appropriate status style
|
||||
if 'Present' in record['status']:
|
||||
status_style = status_present
|
||||
elif 'Half day' in record['status']:
|
||||
status_style = status_halfday
|
||||
elif 'on ' in record['status']:
|
||||
status_style = status_leave
|
||||
else:
|
||||
sheet.write(row_num, 3, record['worked_hours'])
|
||||
# Save the workbook to a BytesIO buffer
|
||||
status_style = status_na
|
||||
|
||||
# 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()
|
||||
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
|
||||
# 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({
|
||||
'name': 'attendance_report.xls',
|
||||
'name': filename,
|
||||
'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),
|
||||
return '/web/content/%d/%s' % (attachment.id, attachment.name),
|
||||
|
|
@ -16,7 +16,8 @@ export default class AttendanceReport extends Component {
|
|||
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
|
||||
employeeIDS: [], // List of employee IDs to bind with select dropdown
|
||||
departmentIDS: []
|
||||
});
|
||||
onWillStart(async () => {
|
||||
|
||||
|
|
@ -34,6 +35,7 @@ export default class AttendanceReport extends Component {
|
|||
});
|
||||
onMounted( () => {
|
||||
this.loademployeeIDS();
|
||||
this.loaddepartmentIDS();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -53,19 +55,63 @@ export default class AttendanceReport extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
// Initialize Select2 with error handling and ensuring it's initialized only once
|
||||
initializeSelect2() {
|
||||
const employeeIDS = this.state.employeeIDS;
|
||||
async loaddepartmentIDS() {
|
||||
try {
|
||||
const department = await this.orm.searchRead('hr.department', [], ['id', 'display_name']);
|
||||
this.state.departmentIDS = department;
|
||||
|
||||
// Ensure the <select> element is initialized only once
|
||||
const $empSelect = $('#emp');
|
||||
const from_date = $("#from_date").datepicker({
|
||||
dateFormat: "dd/mm/yy", // Date format
|
||||
showAnim: "slideDown", // Animation
|
||||
changeMonth: true, // Allow month selection
|
||||
changeYear: true, // Allow year selection
|
||||
yearRange: "2010:2030", // Year range
|
||||
// ... other options
|
||||
|
||||
|
||||
|
||||
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
|
||||
initializeSelect2() {
|
||||
const departmentIDS = this.state.departmentIDS;
|
||||
const employeeIDS = this.state.employeeIDS;
|
||||
|
||||
// Ensure the <select> element is initialized only once
|
||||
const $deptSelect = $('#dept');
|
||||
const $empSelect = $('#emp');
|
||||
const from_date = $("#from_date").datepicker({
|
||||
dateFormat: "dd/mm/yy", // Date format
|
||||
showAnim: "slideDown", // Animation
|
||||
changeMonth: true, // Allow month selection
|
||||
changeYear: true, // Allow year selection
|
||||
yearRange: "2010:2030", // Year range
|
||||
// ... other options
|
||||
});
|
||||
|
||||
const to_date = $("#to_date").datepicker({
|
||||
|
|
@ -78,21 +124,45 @@ export default class AttendanceReport extends Component {
|
|||
});
|
||||
// Debugging the employeeIDS array to verify its structure
|
||||
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
|
||||
if (Array.isArray(employeeIDS) && employeeIDS.length > 0) {
|
||||
// Clear the current options (if any)
|
||||
$empSelect.empty();
|
||||
|
||||
|
||||
$empSelect.append(
|
||||
`<option value="-">All</option>`
|
||||
);
|
||||
// Add options for each employee
|
||||
employeeIDS.forEach(emp => {
|
||||
$empSelect.append(
|
||||
`<option value="${emp.id}">${emp.display_name}</option>`
|
||||
);
|
||||
});
|
||||
$empSelect.append(
|
||||
`<option value="-">All</option>`
|
||||
);
|
||||
|
||||
// Initialize the select with the 'multiple' attribute for multi-select
|
||||
// $empSelect.attr('multiple', 'multiple');
|
||||
|
|
@ -126,9 +196,8 @@ export default class AttendanceReport extends Component {
|
|||
domain.push(['employee_id', '=', parseInt($('#emp').val())]);
|
||||
}
|
||||
try {
|
||||
debugger;
|
||||
// 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');
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -161,10 +230,13 @@ export default class AttendanceReport extends Component {
|
|||
try {
|
||||
// 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',[$('#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
|
||||
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
|
||||
this.state.attendanceData = attendanceData;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
<div class="header pt-5" style="text-align:center">
|
||||
<h1>Attendance Report</h1>
|
||||
<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>
|
||||
<div class="input-group input-group-lg">
|
||||
<select type="text" id="emp" class="form-control" />
|
||||
|
|
@ -42,6 +46,12 @@
|
|||
<t t-else="">
|
||||
<span>Unknown Employee</span>
|
||||
</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>
|
||||
|
||||
<!-- Scrollable Container for the Table -->
|
||||
|
|
@ -49,27 +59,27 @@
|
|||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Week</th>
|
||||
<th>Date</th>
|
||||
<th>Employee</th>
|
||||
<th>Day</th>
|
||||
<th>Check In</th>
|
||||
<th>Check Out</th>
|
||||
<th>Worked Hours</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<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-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.day_name"/></td>
|
||||
<td><t t-esc="data.check_in"/></td>
|
||||
<td><t t-esc="data.check_out"/></td>
|
||||
<td><t t-esc="data.worked_hours"/></td>
|
||||
<td><t t-esc="data.status"/></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -18,8 +18,12 @@
|
|||
'version': '0.1',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
|
||||
'depends': ['base','hr','account','mail','hr_skills', 'hr_contract'],
|
||||
|
||||
|
||||
|
||||
|
||||
# always loaded
|
||||
'data': [
|
||||
'security/security.xml',
|
||||
|
|
|
|||
|
|
@ -107,31 +107,6 @@
|
|||
</field>
|
||||
<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 expr="//field[@name='employee_type']" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
|
|
|
|||
|
|
@ -256,6 +256,8 @@ class HRJobRecruitment(models.Model):
|
|||
rec.submission_status = 'zero'
|
||||
|
||||
|
||||
experience = fields.Many2one('candidate.experience', string="Experience")
|
||||
|
||||
@api.depends('application_ids.submitted_to_client')
|
||||
def _compute_no_of_submissions(self):
|
||||
counts = dict(self.env['hr.applicant']._read_group(
|
||||
|
|
|
|||
Loading…
Reference in New Issue