From c0bb40cafc897c4abc2e008c2cf8b40424b2a128 Mon Sep 17 00:00:00 2001 From: raman Date: Tue, 21 Jan 2025 18:19:35 +0530 Subject: [PATCH] employee dashboard --- .../flutter_odoo/__manifest__.py | 1 + .../hr_attendance_extended/__manifest__.py | 1 + .../hr_emp_dashboard/__init__.py | 2 + .../hr_emp_dashboard/__manifest__.py | 22 ++ .../hr_emp_dashboard/models/__init__.py | 1 + .../hr_emp_dashboard/models/emp_dashboard.py | 170 ++++++++++ .../static/description/icon.png | Bin 0 -> 1327 bytes .../static/src/css/employee_dashboard.css | 321 ++++++++++++++++++ .../static/src/js/profile_component.js | 195 +++++++++++ .../src/xml/employee_profile_template.xml | 262 ++++++++++++++ .../views/employee_dashboard_views.xml | 15 + .../hr_employee_extended/__manifest__.py | 4 +- .../hr_timeoff_extended/__manifest__.py | 1 + .../muk_web_theme/__manifest__.py | 4 +- 14 files changed, 996 insertions(+), 3 deletions(-) create mode 100644 addons_extensions/hr_emp_dashboard/__init__.py create mode 100644 addons_extensions/hr_emp_dashboard/__manifest__.py create mode 100644 addons_extensions/hr_emp_dashboard/models/__init__.py create mode 100644 addons_extensions/hr_emp_dashboard/models/emp_dashboard.py create mode 100644 addons_extensions/hr_emp_dashboard/static/description/icon.png create mode 100644 addons_extensions/hr_emp_dashboard/static/src/css/employee_dashboard.css create mode 100644 addons_extensions/hr_emp_dashboard/static/src/js/profile_component.js create mode 100644 addons_extensions/hr_emp_dashboard/static/src/xml/employee_profile_template.xml create mode 100644 addons_extensions/hr_emp_dashboard/views/employee_dashboard_views.xml diff --git a/addons_extensions/flutter_odoo/__manifest__.py b/addons_extensions/flutter_odoo/__manifest__.py index 4283a00ce..d384b974e 100644 --- a/addons_extensions/flutter_odoo/__manifest__.py +++ b/addons_extensions/flutter_odoo/__manifest__.py @@ -15,6 +15,7 @@ # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # for the full list 'category': 'Flutter', + 'license': 'LGPL-3', 'version': '0.1', # any module necessary for this one to work correctly diff --git a/addons_extensions/hr_attendance_extended/__manifest__.py b/addons_extensions/hr_attendance_extended/__manifest__.py index c779deeee..b51c3a80d 100644 --- a/addons_extensions/hr_attendance_extended/__manifest__.py +++ b/addons_extensions/hr_attendance_extended/__manifest__.py @@ -16,6 +16,7 @@ # for the full list 'category': 'Human Resources/Attendances', 'version': '0.1', + 'license': 'LGPL-3', # any module necessary for this one to work correctly 'depends': ['base','hr','hr_attendance','hr_holidays','hr_employee_extended'], diff --git a/addons_extensions/hr_emp_dashboard/__init__.py b/addons_extensions/hr_emp_dashboard/__init__.py new file mode 100644 index 000000000..24c19d687 --- /dev/null +++ b/addons_extensions/hr_emp_dashboard/__init__.py @@ -0,0 +1,2 @@ + +from . import models \ No newline at end of file diff --git a/addons_extensions/hr_emp_dashboard/__manifest__.py b/addons_extensions/hr_emp_dashboard/__manifest__.py new file mode 100644 index 000000000..49ac99da1 --- /dev/null +++ b/addons_extensions/hr_emp_dashboard/__manifest__.py @@ -0,0 +1,22 @@ +# my_employee_profile/__manifest__.py + +{ + 'name': 'Employee Profile', + 'version': '1.0', + 'category': 'Human Resources', + 'summary': 'Display employee profile using Owl.js', + 'depends': ['base', 'hr', 'web','muk_web_theme'], # Depends on base, hr, and web for Owl.js and hr.employee model + 'data': [ + 'views/employee_dashboard_views.xml', # Your template + ], + 'assets': { + 'web.assets_backend': [ + 'hr_emp_dashboard/static/src/js/profile_component.js', + 'hr_emp_dashboard/static/src/xml/employee_profile_template.xml', + 'hr_emp_dashboard/static/src/css/employee_dashboard.css' + + ], + }, + 'installable': True, + 'application': False, +} diff --git a/addons_extensions/hr_emp_dashboard/models/__init__.py b/addons_extensions/hr_emp_dashboard/models/__init__.py new file mode 100644 index 000000000..0ea1fb51d --- /dev/null +++ b/addons_extensions/hr_emp_dashboard/models/__init__.py @@ -0,0 +1 @@ +from . import emp_dashboard \ No newline at end of file diff --git a/addons_extensions/hr_emp_dashboard/models/emp_dashboard.py b/addons_extensions/hr_emp_dashboard/models/emp_dashboard.py new file mode 100644 index 000000000..09bd26f2b --- /dev/null +++ b/addons_extensions/hr_emp_dashboard/models/emp_dashboard.py @@ -0,0 +1,170 @@ +import pandas as pd +from collections import defaultdict +from datetime import timedelta, datetime, date +from dateutil.relativedelta import relativedelta +from odoo import api, fields, models, _ +from odoo.http import request +from odoo.tools import float_utils +from odoo.tools import format_duration +from pytz import utc + + +class HrEmployee(models.Model): + """ Inherit hr_employee to add birthday field and custom methods. """ + _inherit = 'hr.employee' + + def attendance_manual(self): + """Create and update an attendance for the user employee""" + employee = request.env['hr.employee'].sudo().browse( + self.env.user.employee_id.id) + employee.sudo()._attendance_action_change({ + 'city': request.geoip.city.name or _('Unknown'), + 'country_name': request.geoip.country.name or + request.geoip.continent.name or _('Unknown'), + 'latitude': request.geoip.location.latitude or False, + 'longitude': request.geoip.location.longitude or False, + 'ip_address': request.geoip.ip, + 'browser': request.httprequest.user_agent.browser, + 'mode': 'kiosk' + }) + return employee + + + @api.model + def get_user_employee_details(self): + """To fetch the details of employee""" + uid = request.session.uid + employee = self.env['hr.employee'].sudo().search_read( + [('user_id', '=', uid)], limit=1) + attendance = self.env['hr.attendance'].sudo().search_read( + [('employee_id', '=', employee[0]['id'])], + fields=['id', 'check_in', 'check_out', 'worked_hours']) + attendance_line = [] + for line in attendance: + if line['check_in'] and line['check_out']: + val = { + 'id':line['id'], + 'date': line['check_in'].date(), + 'sign_in': line['check_in'].time().strftime('%H:%M'), + 'sign_out': line['check_out'].time().strftime('%H:%M'), + 'worked_hours': format_duration(line['worked_hours']) + } + attendance_line.append(val) + leaves = self.env['hr.leave'].sudo().search_read( + [('employee_id', '=', employee[0]['id'])], + fields=['request_date_from', 'request_date_to', 'state', + 'holiday_status_id']) + for line in leaves: + line['type'] = line.pop('holiday_status_id')[1] + if line['state'] == 'confirm': + line['state'] = 'To Approve' + line['color'] = 'orange' + elif line['state'] == 'validate1': + line['state'] = 'Second Approval' + line['color'] = '#7CFC00' + elif line['state'] == 'validate': + line['state'] = 'Approved' + line['color'] = 'green' + elif line['state'] == 'cancel': + line['state'] = 'Cancelled' + line['color'] = 'red' + else: + line['state'] = 'Refused' + line['color'] = 'red' + expense =[] + # self.env['hr.expense'].sudo().search_read( + # [('employee_id', '=', employee[0]['id'])], + # fields=['name', 'date', 'state', 'total_amount']) + # for line in expense: + # if line['state'] == 'draft': + # line['state'] = 'To Report' + # line['color'] = '#17A2B8' + # elif line['state'] == 'reported': + # line['state'] = 'To Submit' + # line['color'] = '#17A2B8' + # elif line['state'] == 'submitted': + # line['state'] = 'Submitted' + # line['color'] = '#FFAC00' + # elif line['state'] == 'approved': + # line['state'] = 'Approved' + # line['color'] = '#28A745' + # elif line['state'] == 'done': + # line['state'] = 'Done' + # line['color'] = '#28A745' + # else: + # line['state'] = 'Refused' + # line['color'] = 'red' + leaves_to_approve = self.env['hr.leave'].sudo().search_count( + [('state', 'in', ['confirm', 'validate1'])]) + today = datetime.strftime(datetime.today(), '%Y-%m-%d') + query = """ + select count(id) + from hr_leave + WHERE (hr_leave.date_from::DATE,hr_leave.date_to::DATE) + OVERLAPS ('%s', '%s') and + state='validate'""" % (today, today) + cr = self._cr + cr.execute(query) + leaves_today = cr.fetchall() + first_day = date.today().replace(day=1) + last_day = (date.today() + relativedelta(months=1, day=1)) - timedelta( + 1) + query = """ + select count(id) + from hr_leave + WHERE (hr_leave.date_from::DATE,hr_leave.date_to::DATE) + OVERLAPS ('%s', '%s') + and state='validate'""" % (first_day, last_day) + cr = self._cr + cr.execute(query) + leaves_this_month = cr.fetchall() + leaves_alloc_req = self.env['hr.leave.allocation'].sudo().search_count( + [('state', 'in', ['confirm', 'validate1'])]) + timesheet_count = self.env['account.analytic.line'].sudo().search_count( + [('project_id', '!=', False), ('user_id', '=', uid)]) + timesheet_view_id = self.env.ref( + 'hr_timesheet.hr_timesheet_line_search') + job_applications = self.env['hr.applicant'].sudo().search_count([]) + if employee: + # sql = """select broad_factor from hr_employee_broad_factor + # where id =%s""" + # self.env.cr.execute(sql, (employee[0]['id'],)) + # result = self.env.cr.dictfetchall() + # broad_factor = result[0]['broad_factor'] if result[0][ + # 'broad_factor'] else False + broad_factor = False + if employee[0]['birthday']: + diff = relativedelta(datetime.today(), employee[0]['birthday']) + age = diff.years + else: + age = False + if employee[0]['joining_date']: + diff = relativedelta(datetime.today(), + employee[0]['joining_date']) + years = diff.years + months = diff.months + days = diff.days + experience = '{} years {} months {} days'.format(years, months, + days) + else: + experience = False + if employee: + data = { + 'broad_factor': broad_factor if broad_factor else 0, + 'leaves_to_approve': leaves_to_approve, + 'leaves_today': leaves_today, + 'leaves_this_month': leaves_this_month, + 'leaves_alloc_req': leaves_alloc_req, + 'emp_timesheets': timesheet_count, + 'job_applications': job_applications, + 'timesheet_view_id': timesheet_view_id, + 'experience': experience, + 'age': age, + 'attendance_lines': attendance_line, + 'leave_lines': leaves, + 'expense_lines': expense + } + employee[0].update(data) + return employee + else: + return False \ No newline at end of file diff --git a/addons_extensions/hr_emp_dashboard/static/description/icon.png b/addons_extensions/hr_emp_dashboard/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..88ab8963d627978cf79c8e9184586e525a1d3655 GIT binary patch literal 1327 zcma)4Z8#GM9G}V^lBdMjX5N}D(Q2l%6S)m-lb4ygD6<@x=-{J;FNNZ$BuI$#|D0I-cna3iZd zbc0&osWodU$pmxASrVd-ev0hjJpdoLoDp*q; zJBe=Cqlp>|W$oUO;|A%2EvECBrh>+~k;}~8Z?~0JyWa&HlBmoN#fJ3+8!lWoYYfTq z@DB@6XrDPkiT4d|)8JvlkA-Da?J-_V^d8i#sWFs`QE_EaD#lFjCd%sM$8)EYimWxJe6{k-Q%7Y=_;Egugu2XG1#^h+>@f26U7MfW7Mvq4KI~@-h3c@T1T4>N2*{PNTi~2j zQ&*Z~$;1DOYV2l3`0s#|KC8bc(27THN0VGKUP_u$J7X-Cm7KP_Yc{+ zOH9fOxZocA2*c&1YX1AkN2S1*0p^`(91nHBwfV0-nm9P~w4kHjkvh=pPSPv3o=eL! zYhIqk_DE^7_Y2wCw_rDHw(A2|j4)UNO{`@>{u&pXq~O2|%pGuAjRyLb9SO9G(tR*U zv{5mmwV=?vbE{>NkMJB>id4Kw8NPqJ+@rmhZ`a_?1LuT*(}er=p(2=ni@#nm=orx~ zc`COI>9D>}93?8Lln>Pg>LUEA@2QZb5wCca?+6Qsg>dJma1L3ShR+*Ups2n;{^9qg zs9Nymf3~7lEmnuue)@unXnU5?gFyJa>yD96&L_yY`TBSzv2fgHs=`HTcEQEkwC-6I zyZ)p>n4l>5PR@@fR%RIzjFA!K8Qz`{1&$jVp>mM;-&DThC%;ESGMpXJ)_#ZguJt|4 z9kl*-QJ5nQTFI4sM23j#M5fROb`p$sgI(c4$MQMJgX}j_V~CCGO(E24dhgC*;nmTC zXLo{n%^?}~dPN6N64jT$oTzkr?@@Vcb;?j!q)lbaLFTpY<&~=Inex>`;I!Fl*@JA# zu|*2e1@nGn!Q-j>p|JQZ z?KNY=j5*DSw&H8X5L#dO4Y6*%walY8{SOzPr0TjCQKe$t;l?@H*W>_|nFKjF!Ru-l zPCsX|{x7u1nV#zq9k{+vW}IY;b?6Kg)b5m@=s6XyQ!!>B7sPtOAbM05Bh;+`g1}hV zF3Uq3%ZEbiDL;IkwoNW(QLmtAE?nra1 { + this.fetchEmployeeData(); + }); + } + attendance_sign_in_out() { + if (this.state.login_employee.attendance_state == 'checked_out') { + this.state.login_employee.attendance_state = 'checked_in' + } + else{ + if (this.state.login_employee.attendance_state == 'checked_in') { + this.state.login_employee.attendance_state = 'checked_out' + } + } + this.update_attendance() + } + async update_attendance() { + var self = this; + var result = await this.orm.call('hr.employee', 'attendance_manual',[[this.props.action.context.user_id]]) + if (result) { + var attendance_state = this.state.login_employee.attendance_state; + var message = '' + if (attendance_state == 'checked_in'){ + message = 'Checked In' + this.env.bus.trigger('signin_signout', { + mode: "checked_in", + }); + } + else if (attendance_state == 'checked_out'){ + message = 'Checked Out' + this.env.bus.trigger('signin_signout', { + mode: false, + }); + } + this.effect.add({ + message: ("Successfully " + message), + type: 'rainbow_man', + fadeout: "fast", + }) + } + } + add_attendance() { + this.action.doAction({ + name: ("Attendances"), + type: 'ir.actions.act_window', + res_model: 'hr.attendance', + view_mode: 'form', + views: [[false, 'form']], + target: 'new' + }); + } + add_leave() { + this.action.doAction({ + name: ("Leave Request"), + type: 'ir.actions.act_window', + res_model: 'hr.leave', + view_mode: 'form', + views: [[false, 'form']], + target: 'new' + }); + } + + + // Method to fetch employee data from hr.employee model + async fetchEmployeeData() { + console.log(this.props.action.context.user_id) + + try { + + const employeeData = await this.orm.searchRead("hr.employee", [["user_id", "=", this.props.action.context.user_id]], ['name', 'image_1920','job_id','employee_id','current_company_exp','doj','birthday','mobile_phone','work_email','private_street','attendance_state' ,'id' + ] ); + const attendanceLines = await this.orm.searchRead( + 'hr.attendance', + [['employee_id', '=', employeeData[0].id]], + ['create_date', 'check_in', 'check_out', 'worked_hours'] + ); + const Leaves = await this.orm.searchRead('hr.leave',[['employee_id', '=', employeeData[0].id]], + ['request_date_from', 'request_date_to', 'state','holiday_status_id']); + + Leaves.forEach(line => { + // Extract the 'type' from 'holiday_status_id' and assign it + line['type'] = line['holiday_status_id'][1]; + + // Change state and set color based on the state + if (line['state'] === 'confirm') { + line['state'] = 'To Approve'; + line['color'] = 'orange'; + } else if (line['state'] === 'validate1') { + line['state'] = 'Second Approval'; + line['color'] = '#7CFC00'; + } else if (line['state'] === 'validate') { + line['state'] = 'Approved'; + line['color'] = 'green'; + } else if (line['state'] === 'cancel') { + line['state'] = 'Cancelled'; + line['color'] = 'red'; + } else { + line['state'] = 'Refused'; + line['color'] = 'red'; + } + }); + + + if (employeeData.length > 0) { + const employee = employeeData[0]; + attendanceLines.forEach(line => { + let createDate = new Date(line.create_date); + line.create_date = createDate.toISOString().split('T')[0]; // Format as 'YYYY-MM-DD' + let checkIn = new Date(line.check_in); + line.check_in = checkIn.toTimeString().slice(0, 5); // Format as 'HH:MM' + let checkOut = new Date(line.check_out); + line.check_out = checkOut.toTimeString().slice(0, 5); // Format as 'HH:MM' + line.worked_hours = line.worked_hours.toFixed(2); + }); + this.state.attendance_lines = attendanceLines, + this.state.leaves = Leaves + + this.state.login_employee = { + name: employee.name, + image_1920: employee.image_1920, + doj:employee.doj, + job_id: employee.job_id, + employee_id:employee.employee_id, + current_company_exp: employee.current_company_exp, + attendance_state:employee.attendance_state, + birthday: employee.birthday, + mobile_phone: employee.mobile_phone, + work_email: employee.work_email, + private_street: employee.private_street, + }; + } + } catch (error) { + console.error('Error fetching employee data:', error); + } + } +} +registry.category("actions").add("NetflixProfileContainer", NetflixProfileContainer) +patch(ActivityMenu.prototype, { + setup() { + super.setup(); + var self = this + onMounted(() => { + this.env.bus.addEventListener('signin_signout', ({ + detail + }) => { + if (detail.mode == 'checked_in') { + self.state.checkedIn = detail.mode + } else { + self.state.checkedIn = false + } + }) + }) + }, +}) diff --git a/addons_extensions/hr_emp_dashboard/static/src/xml/employee_profile_template.xml b/addons_extensions/hr_emp_dashboard/static/src/xml/employee_profile_template.xml new file mode 100644 index 000000000..8dfeec8d4 --- /dev/null +++ b/addons_extensions/hr_emp_dashboard/static/src/xml/employee_profile_template.xml @@ -0,0 +1,262 @@ + + + + +
+
+
+ Employee Image +
+
+
+

+ + + - + + +

+

+ + + + + Add Job Title + +

+

+ + Joined ago + + + Joined Date: + +

+

+ Birthday : + + + + + --/--/---- + +

+
+
+

+ + + + + + --- + +

+

+ + + + + + --- + +

+

+ Address: + + + + + --- + +

+
+
+
+ +
+ +
+
+

+ Check In +

+
+
+ +
+ + +
+
+

+ Check Out +

+
+
+
+
+
+
+
+ + +
+
+

+ Timesheets +

+

+ +

+
+
+
+
+ +
+
+

+ Attendance +

+ +
+
+ + + + + + + + + + + + + + + + + +
+ Date + + Sign In + + Sign Out + + Worked Hours +
+ + + +
+
+
+ + +
+
+

+ Leaves +

+ +
+
+ + + + + + + + + + + + + + + + + +
+ From Date + + To Date + + Type + + Status +
+ + + + + +
+
+
+ +
+
+

+ Expenses +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ Date + + Subject + + Amount + + Status +
+
+
+
+
+
+
\ No newline at end of file diff --git a/addons_extensions/hr_emp_dashboard/views/employee_dashboard_views.xml b/addons_extensions/hr_emp_dashboard/views/employee_dashboard_views.xml new file mode 100644 index 000000000..65c202cea --- /dev/null +++ b/addons_extensions/hr_emp_dashboard/views/employee_dashboard_views.xml @@ -0,0 +1,15 @@ + + + + + Dashboard + NetflixProfileContainer + {'user_id':uid} + + + + \ No newline at end of file diff --git a/addons_extensions/hr_employee_extended/__manifest__.py b/addons_extensions/hr_employee_extended/__manifest__.py index aa543e864..98fe4e581 100644 --- a/addons_extensions/hr_employee_extended/__manifest__.py +++ b/addons_extensions/hr_employee_extended/__manifest__.py @@ -16,13 +16,13 @@ # for the full list 'category': 'Human Resources/Employees', 'version': '0.1', + 'license': 'LGPL-3', # any module necessary for this one to work correctly - 'depends': ['base','hr','mail'], + 'depends': ['base','hr'], # always loaded 'data': [ - 'security/security.xml', 'security/ir.model.access.csv', 'views/hr_employee.xml', 'wizards/work_location_wizard.xml' diff --git a/addons_extensions/hr_timeoff_extended/__manifest__.py b/addons_extensions/hr_timeoff_extended/__manifest__.py index f61987969..6dd333fca 100644 --- a/addons_extensions/hr_timeoff_extended/__manifest__.py +++ b/addons_extensions/hr_timeoff_extended/__manifest__.py @@ -16,6 +16,7 @@ # for the full list 'category': 'Human Resources/Time Off', 'version': '0.1', + 'license': 'LGPL-3', # any module necessary for this one to work correctly 'depends': ['base','hr','hr_holidays','hr_employee_extended'], diff --git a/third_party_addons/muk_web_theme/__manifest__.py b/third_party_addons/muk_web_theme/__manifest__.py index b03c99e9d..4c163b584 100644 --- a/third_party_addons/muk_web_theme/__manifest__.py +++ b/third_party_addons/muk_web_theme/__manifest__.py @@ -8,7 +8,7 @@ 'version': '18.0.1.2.3', 'category': 'Themes/Backend', 'license': 'LGPL-3', - 'author': 'MuK IT', + 'author': 'Raman Marikanti', 'website': 'http://www.mukit.at', 'live_test_url': 'https://my.mukit.at/r/f6m', 'contributors': [ @@ -19,6 +19,7 @@ 'muk_web_dialog', 'muk_web_appsbar', 'muk_web_colors', + 'hr', 'web' ], 'excludes': [ 'web_enterprise', @@ -43,6 +44,7 @@ 'web.assets_backend': [ 'muk_web_theme/static/src/webclient/**/*.xml', 'muk_web_theme/static/src/webclient/**/*.scss', + # 'muk_web_theme/static/src/employeeDashboard/*', 'muk_web_theme/static/src/webclient/**/*.js', 'muk_web_theme/static/src/views/**/*.scss', ],