@ -0,0 +1,42 @@ |
|||
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg |
|||
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html |
|||
:alt: License: AGPL-3 |
|||
|
|||
HR Payroll Dashboard |
|||
==================== |
|||
* Detailed Dashboard View for Payroll Module |
|||
|
|||
License |
|||
------- |
|||
General Public License, Version 3 (AGPL v3). |
|||
(https://www.gnu.org/licenses/agpl-3.0-standalone.html) |
|||
|
|||
Company |
|||
------- |
|||
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
|||
|
|||
Credits |
|||
------- |
|||
* Developer: (V16)Anfas Faisal K, Contact: odoo@cybrosys.com |
|||
|
|||
Contacts |
|||
-------- |
|||
* Mail Contact : odoo@cybrosys.com |
|||
* Website : https://cybrosys.com |
|||
|
|||
Bug Tracker |
|||
----------- |
|||
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
|||
|
|||
Maintainer |
|||
========== |
|||
.. image:: https://cybrosys.com/images/logo.png |
|||
:target: https://cybrosys.com |
|||
|
|||
This module is maintained by Cybrosys Technologies. |
|||
|
|||
For support and more information, please visit https://www.cybrosys.com |
|||
|
|||
Further information |
|||
=================== |
|||
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,22 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from . import models |
@ -0,0 +1,54 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
{ |
|||
'name': "HR Payroll Dashboard", |
|||
'version': '16.0.1.0.0', |
|||
'category': 'Human Resource', |
|||
'summary': """Detailed Dashboard View for Payroll Module""", |
|||
'description': "This module helps you to see the Overview of Payroll, " |
|||
"here You can see the details of Attendance, Leaves, " |
|||
"Payslip contracts, etc.", |
|||
'author': 'Cybrosys Techno Solutions', |
|||
'company': 'Cybrosys Techno Solutions', |
|||
'maintainer': 'Cybrosys Techno Solutions', |
|||
'website': "https://www.cybrosys.com", |
|||
'depends': ['hr_payroll_community', 'hr_attendance', 'hr_expense'], |
|||
'data': [ |
|||
'views/dashboard_views.xml', |
|||
], |
|||
'assets': { |
|||
'web.assets_backend': [ |
|||
'hr_payroll_dashboard/static/src/js/hr_payroll_dashboard.js', |
|||
'hr_payroll_dashboard/static/src/css/lib/nv.d3.css', |
|||
'hr_payroll_dashboard/static/src/css/dashboard.css', |
|||
'hr_payroll_dashboard/static/src/css/style.scss', |
|||
'hr_payroll_dashboard/static/src/js/lib/d3.min.js', |
|||
'hr_payroll_dashboard/static/src/xml/payroll_dashboard.xml' |
|||
], |
|||
}, |
|||
'images': ['static/description/banner.png'], |
|||
"external_dependencies": {"python": ["pandas"]}, |
|||
'license': "AGPL-3", |
|||
'installable': True, |
|||
'auto_install': False, |
|||
'application': True, |
|||
} |
@ -0,0 +1,7 @@ |
|||
## Module <hr_payroll_dashboard> |
|||
|
|||
#### 03.06.2024 |
|||
#### Version 16.0.1.0.0 |
|||
##### ADD |
|||
|
|||
- Initial commit for HR Payroll Dashboard |
@ -0,0 +1,27 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from . import hr_attendance |
|||
from . import hr_contract |
|||
from . import hr_employee |
|||
from . import hr_expense |
|||
from . import hr_leave |
|||
from . import hr_payslip |
@ -0,0 +1,42 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class HrAttendance(models.Model): |
|||
""" |
|||
This class extends the HR Attendance model to include additional fields |
|||
and functionalities. |
|||
""" |
|||
_inherit = 'hr.attendance' |
|||
|
|||
attendance_date = fields.Date(compute="_compute_attendance_date", |
|||
store=True, |
|||
help="The date of attendance based on the " |
|||
"check-in time.") |
|||
|
|||
@api.depends('check_in') |
|||
def _compute_attendance_date(self): |
|||
"""Compute function for the attendance date""" |
|||
for rec in self: |
|||
if rec.check_in: |
|||
rec.attendance_date = rec.check_in.date() |
@ -0,0 +1,52 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class Contract(models.Model): |
|||
""" |
|||
This class extends the HR Contract model to include additional fields |
|||
and functionalities. |
|||
""" |
|||
_inherit = 'hr.contract' |
|||
|
|||
state_label = fields.Char(compute="_compute_state_label", store=True, |
|||
help="A representation of the contract state.") |
|||
|
|||
@api.depends('state') |
|||
def _compute_state_label(self): |
|||
"""Compute to get the label value of the contract state""" |
|||
for record in self: |
|||
record.state_label = dict(self._fields['state'].selection).get( |
|||
record.state) |
|||
|
|||
@api.model |
|||
def get_employee_contract(self): |
|||
"""Return employees contract details""" |
|||
cr = self._cr |
|||
cr.execute("""SELECT hr_contract.state_label,count(*) |
|||
FROM hr_contract |
|||
JOIN hr_employee ON hr_employee.id=hr_contract.employee_id |
|||
GROUP BY hr_contract.state_label""") |
|||
dat = cr.fetchall() |
|||
data = [{'label': d[0], 'value': d[1]} for d in dat] |
|||
return data |
@ -0,0 +1,260 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
import pandas as pd |
|||
from datetime import timedelta, datetime, date |
|||
from collections import defaultdict |
|||
from dateutil.relativedelta import relativedelta |
|||
from pytz import utc |
|||
from odoo.tools import float_utils |
|||
from odoo import api, fields, models |
|||
from odoo.http import request |
|||
|
|||
ROUNDING_FACTOR = 16 |
|||
|
|||
|
|||
class Employee(models.Model): |
|||
""" |
|||
This class extends the HR Employee model to include additional fields |
|||
and functionalities |
|||
""" |
|||
_inherit = 'hr.employee' |
|||
|
|||
is_manager = fields.Boolean(compute='_compute_is_manager', |
|||
help="Flag indicating whether the employee is a" |
|||
"manager.") |
|||
|
|||
def _compute_is_manager(self): |
|||
"""Compute function for checking whether it is a manager or not""" |
|||
for rec in self: |
|||
if (rec.env.user.has_group |
|||
('hr_payroll_community.group_hr_payroll_community_manager')): |
|||
rec.is_manager = True |
|||
else: |
|||
rec.is_manager = False |
|||
|
|||
@api.model |
|||
def get_user_employee_info(self): |
|||
"""To get the employee information""" |
|||
uid = request.session.uid |
|||
employee_user_id = self.env['hr.employee'].sudo().search([ |
|||
('user_id', '=', uid) |
|||
], limit=1) |
|||
employee = self.env['hr.employee'].sudo().search_read([ |
|||
('user_id', '=', uid)], limit=1) |
|||
attendance_count = self.env['hr.attendance'].sudo().search( |
|||
[('employee_id', '=', employee_user_id.id), |
|||
('attendance_date', '=', date.today())]) |
|||
manager_attendance_count = self.env['hr.attendance'].sudo().search( |
|||
[('attendance_date', '=', date.today())]) |
|||
leave_request_count = self.env['hr.leave'].sudo().search( |
|||
[('employee_id', '=', employee_user_id.id), |
|||
('request_date_from', '=', date.today())]) |
|||
manager_leave_request = self.env['hr.leave'].sudo().search( |
|||
[('request_date_from', '=', date.today())]) |
|||
employee_contracts = self.env['hr.contract'].sudo().search([ |
|||
('employee_id', '=', employee_user_id.id)]) |
|||
payslips = self.env['hr.payslip'].sudo().search([ |
|||
('employee_id', '=', employee_user_id.id)]) |
|||
salary_rules = self.env['hr.salary.rule'].sudo().search([]) |
|||
salary_structures = self.env['hr.payroll.structure'].sudo().search([]) |
|||
salary_rule_count = len(salary_rules) |
|||
salary_structure_count = len(salary_structures) |
|||
emp_leave = len(manager_leave_request) if employee_user_id.is_manager \ |
|||
else len(leave_request_count) |
|||
payslip_count = len(payslips) if not employee_user_id.is_manager \ |
|||
else len(self.env['hr.payslip'].sudo().search([])) |
|||
emp_contracts_count = len(employee_contracts) \ |
|||
if not employee_user_id.is_manager else len( |
|||
self.env['hr.contract'].sudo().search([])) |
|||
attendance_today = len(manager_attendance_count) \ |
|||
if employee_user_id.is_manager else len(attendance_count) |
|||
if employee: |
|||
data = { |
|||
'emp_timesheets': attendance_today, |
|||
'emp_leave': emp_leave, |
|||
'emp_contracts_count': emp_contracts_count, |
|||
'payslip_count': payslip_count, |
|||
'leave_requests': leave_request_count, |
|||
'salary_rule_count': salary_rule_count, |
|||
'salary_structure_count': salary_structure_count, |
|||
'attendance_state': employee[0]['attendance_state'], |
|||
} |
|||
employee[0].update(data) |
|||
return employee |
|||
|
|||
def get_work_days_dashboard(self, from_datetime, to_datetime, |
|||
compute_leaves=False, calendar=None, |
|||
domain=None): |
|||
""" |
|||
Calculate the total work days between two datetimes. |
|||
""" |
|||
resource = self.resource_id |
|||
calendar = calendar or self.resource_calendar_id |
|||
if not from_datetime.tzinfo: |
|||
from_datetime = from_datetime.replace(tzinfo=utc) |
|||
if not to_datetime.tzinfo: |
|||
to_datetime = to_datetime.replace(tzinfo=utc) |
|||
from_full = from_datetime - timedelta(days=1) |
|||
to_full = to_datetime + timedelta(days=1) |
|||
intervals = calendar._attendance_intervals_batch(from_full, to_full, |
|||
resource) |
|||
day_total = defaultdict(float) |
|||
for start, stop, meta in intervals[resource.id]: |
|||
day_total[start.date()] += (stop - start).total_seconds() / 3600 |
|||
if compute_leaves: |
|||
intervals = calendar._work_intervals_batch(from_datetime, |
|||
to_datetime, resource, |
|||
domain) |
|||
else: |
|||
intervals = calendar._attendance_intervals_batch(from_datetime, |
|||
to_datetime, |
|||
resource) |
|||
day_hours = defaultdict(float) |
|||
for start, stop, meta in intervals[resource.id]: |
|||
day_hours[start.date()] += (stop - start).total_seconds() / 3600 |
|||
days = sum( |
|||
float_utils.round(ROUNDING_FACTOR * day_hours[day] / day_total[ |
|||
day]) / ROUNDING_FACTOR |
|||
for day in day_hours |
|||
) |
|||
return days |
|||
|
|||
@api.model |
|||
def get_department_leave(self): |
|||
""" Return department leave details.""" |
|||
month_list = [format(datetime.now() - relativedelta(months=i), '%B %Y') |
|||
for i in range(5, -1, -1)] |
|||
self.env.cr.execute( |
|||
"""select id, name from hr_department where active=True """) |
|||
departments = self.env.cr.dictfetchall() |
|||
department_list = [x['name'] for x in departments] |
|||
graph_result = [{ |
|||
'l_month': month, |
|||
'leave': {dept['name']: 0 for dept in departments} |
|||
} for month in month_list] |
|||
sql = """ |
|||
SELECT h.id, h.employee_id,h.department_id |
|||
, extract('month' FROM y)::int AS leave_month |
|||
, to_char(y, 'Month YYYY') as month_year |
|||
, GREATEST(y , h.date_from) AS date_from |
|||
, LEAST (y + interval '1 month', h.date_to) AS date_to |
|||
FROM (select * from hr_leave where state = 'validate') h |
|||
, generate_series(date_trunc('month', date_from::timestamp) |
|||
, date_trunc('month', date_to::timestamp) |
|||
, interval '1 month') y |
|||
where date_trunc('month', GREATEST(y , h.date_from)) >= date_trunc('month', now()) - interval '6 month' and |
|||
date_trunc('month', GREATEST(y , h.date_from)) <= date_trunc('month', now()) |
|||
and h.department_id is not null |
|||
""" |
|||
self.env.cr.execute(sql) |
|||
results = self.env.cr.dictfetchall() |
|||
leave_lines = [{ |
|||
'department': line['department_id'], |
|||
'l_month': line['month_year'], |
|||
'days': self.browse(line['employee_id']).get_work_days_dashboard( |
|||
fields.Datetime.from_string(line['date_from']), |
|||
fields.Datetime.from_string(line['date_to']) |
|||
) |
|||
} for line in results] |
|||
if leave_lines: |
|||
df = pd.DataFrame(leave_lines) |
|||
rf = df.groupby(['l_month', 'department']).sum() |
|||
result_lines = rf.to_dict('index') |
|||
for month in month_list: |
|||
for line in result_lines: |
|||
if month.replace(' ', '') == line[0].replace(' ', ''): |
|||
match = list(filter(lambda d: d['l_month'] in [month], |
|||
graph_result))[0]['leave'] |
|||
dept_name = self.env['hr.department'].browse( |
|||
line[1]).name |
|||
if match: |
|||
match[dept_name] = result_lines[line]['days'] |
|||
for result in graph_result: |
|||
result['l_month'] = result['l_month'].split(' ')[:1][0].strip()[ |
|||
:3] + " " + \ |
|||
result['l_month'].split(' ')[1:2][0] |
|||
return graph_result, department_list |
|||
|
|||
@api.model |
|||
def employee_leave_trend(self): |
|||
"""Return employee monthly leave details""" |
|||
month_list = [format(datetime.now() - relativedelta(months=i), '%B %Y') |
|||
for i in range(5, -1, -1)] |
|||
uid = request.session.uid |
|||
employee = False |
|||
employee_user = self.env['hr.employee'].sudo().search_read([ |
|||
('user_id', '=', uid) |
|||
], limit=1) |
|||
employees = self.env['hr.employee'].sudo().search_read([], limit=1) |
|||
if employee_user: |
|||
employee = self.env['hr.employee'].sudo().search_read([ |
|||
('user_id', '=', uid) |
|||
], limit=1) |
|||
elif employees: |
|||
employee = self.env['hr.employee'].sudo().search_read([], limit=1) |
|||
graph_result = [{ |
|||
'l_month': month, |
|||
'leave': 0 |
|||
} for month in month_list] |
|||
sql = """ |
|||
SELECT h.id, h.employee_id |
|||
, extract('month' FROM y)::int AS leave_month |
|||
, to_char(y, 'Month YYYY') as month_year |
|||
, GREATEST(y , h.date_from) AS date_from |
|||
, LEAST (y + interval '1 month', h.date_to) AS date_to |
|||
FROM (select * from hr_leave where state = 'validate') h |
|||
, generate_series(date_trunc('month', date_from::timestamp) |
|||
, date_trunc('month', date_to::timestamp) |
|||
, interval '1 month') y |
|||
where date_trunc('month', GREATEST(y , h.date_from)) >= |
|||
date_trunc('month', now()) - interval '6 month' and |
|||
date_trunc('month', GREATEST(y , h.date_from)) <= |
|||
date_trunc('month', now()) |
|||
and h.employee_id = %s |
|||
""" |
|||
if employee: |
|||
self.env.cr.execute(sql, (employee[0]['id'],)) |
|||
results = self.env.cr.dictfetchall() |
|||
leave_lines = [{ |
|||
'l_month': line['month_year'], |
|||
'days': self.browse( |
|||
line['employee_id']).get_work_days_dashboard( |
|||
fields.Datetime.from_string(line['date_from']), |
|||
fields.Datetime.from_string(line['date_to']) |
|||
) |
|||
} for line in results] |
|||
if leave_lines: |
|||
df = pd.DataFrame(leave_lines) |
|||
rf = df.groupby(['l_month']).sum() |
|||
result_lines = rf.to_dict('index') |
|||
for line in result_lines: |
|||
match = list(filter(lambda d: d['l_month'].replace( |
|||
' ', '') == line.replace(' ', ''), graph_result)) |
|||
if match: |
|||
match[0]['leave'] = result_lines[line]['days'] |
|||
for result in graph_result: |
|||
result['l_month'] = result['l_month'].split(' ')[:1][0].strip()[ |
|||
:3] \ |
|||
+ " " + result['l_month'].split(' ')[1:2][0] |
|||
return graph_result |
|||
else: |
|||
return False |
@ -0,0 +1,96 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from datetime import datetime |
|||
from dateutil.relativedelta import relativedelta |
|||
from odoo import api, fields, models |
|||
from odoo.http import request |
|||
|
|||
|
|||
class HrExpense(models.Model): |
|||
""" |
|||
This class extends the HR Expense model to include additional fields |
|||
and functionalities. |
|||
""" |
|||
_inherit = 'hr.expense' |
|||
|
|||
state_label = fields.Char(compute="_compute_state_label", store=True) |
|||
|
|||
@api.depends('state') |
|||
def _compute_state_label(self): |
|||
"""Compute function for the expense state label""" |
|||
for record in self: |
|||
record.state_label = dict(self._fields['state'].selection).get( |
|||
record.state) |
|||
|
|||
@api.model |
|||
def get_employee_expense(self): |
|||
"""Return employee expense details""" |
|||
cr = self._cr |
|||
month_list = [format(datetime.now() - relativedelta(months=i), '%B %Y') |
|||
for i in range(11, -1, -1)] |
|||
approved_trend = [{'l_month': month, 'count': 0} |
|||
for month in month_list] |
|||
uid = request.session.uid |
|||
employee = False |
|||
employee_user = self.env['hr.employee'].sudo().search_read([ |
|||
('user_id', '=', uid) |
|||
], limit=1) |
|||
employees = self.env['hr.employee'].sudo().search_read([], limit=1) |
|||
if employee_user: |
|||
employee = self.env['hr.employee'].sudo().search_read([ |
|||
('user_id', '=', uid) |
|||
], limit=1) |
|||
elif employees: |
|||
employee = self.env['hr.employee'].sudo().search_read([], limit=1) |
|||
if employee: |
|||
employee_id = self.env['hr.employee'].browse(employee[0]['id']) |
|||
if not employee_id.is_manager: |
|||
sql = ('''select to_char(date, 'Month YYYY') as l_month, |
|||
count(id) from hr_expense |
|||
WHERE date BETWEEN CURRENT_DATE - INTERVAL '12 months' |
|||
AND CURRENT_DATE + interval '1 month - 1 day' |
|||
AND hr_expense.employee_id = %s |
|||
group by l_month''') |
|||
self.env.cr.execute(sql, (employee[0]['id'],)) |
|||
else: |
|||
sql = ('''select to_char(date, 'Month YYYY') as l_month, |
|||
count(id) from hr_expense WHERE date |
|||
BETWEEN CURRENT_DATE - INTERVAL |
|||
'12 months' AND CURRENT_DATE + interval '1 month - 1 day' |
|||
group by l_month''') |
|||
self.env.cr.execute(sql) |
|||
approved_data = cr.fetchall() |
|||
for line in approved_data: |
|||
match = list(filter(lambda d: d['l_month'].replace( |
|||
' ', '') == line[0].replace(' ', ''), approved_trend)) |
|||
if match: |
|||
match[0]['count'] = line[1] |
|||
for expense in approved_trend: |
|||
expense['l_month'] = expense[ |
|||
'l_month'].split(' ')[:1][0].strip()[ |
|||
:3] |
|||
graph_result = [{ |
|||
'values': approved_trend |
|||
}] |
|||
return graph_result |
|||
else: |
|||
return False |
@ -0,0 +1,50 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class HrLeave(models.Model): |
|||
""" |
|||
This class extends the HR Leave model to include additional fields |
|||
and functionalities specific to the requirements of the application. |
|||
""" |
|||
_inherit = 'hr.leave' |
|||
|
|||
state_string = fields.Char(compute="_compute_state_string", store=True, |
|||
help="A representation of the leave state.") |
|||
|
|||
@api.depends('state') |
|||
def _compute_state_string(self): |
|||
"""Compute the label of the leave state""" |
|||
for rec in self: |
|||
rec.state_string = dict(self._fields[ |
|||
'state'].selection).get(rec.state) |
|||
|
|||
@api.model |
|||
def get_employee_time_off(self): |
|||
"""Return employee time off details""" |
|||
self._cr.execute("""SELECT hr_leave.state_string, count(*) |
|||
FROM hr_employee JOIN hr_leave ON hr_leave.employee_id=hr_employee.id |
|||
GROUP BY hr_leave.state_string""") |
|||
dat = self._cr.fetchall() |
|||
data = [{'label': d[0], 'value': d[1]} for d in dat] |
|||
return data |
@ -0,0 +1,53 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
|||
# Author: Anfas Faisal K (odoo@cybrosys.info) |
|||
# |
|||
# You can modify it under the terms of the GNU AFFERO |
|||
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
|||
# |
|||
# This program is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
|||
# (AGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import api ,fields, models |
|||
|
|||
|
|||
class HrPayslip(models.Model): |
|||
""" |
|||
This class extends the Hr Payslip model to include additional fields |
|||
and functionalities. |
|||
""" |
|||
_inherit = 'hr.payslip' |
|||
_description = 'Employee Payroll' |
|||
|
|||
payslip_state = fields.Char(compute="_compute_payslip_state", store=True, |
|||
help="A representation of the payslip state.") |
|||
|
|||
@api.depends('state') |
|||
def _compute_payslip_state(self): |
|||
"""Compute the label value of the payslip state""" |
|||
for rec in self: |
|||
rec.payslip_state = dict(self._fields[ |
|||
'state'].selection).get(rec.state) |
|||
|
|||
@api.model |
|||
def get_employee_payslips(self): |
|||
"""Return employee payslip details""" |
|||
self._cr.execute( |
|||
"""SELECT hr_payslip.payslip_state,count(*) FROM hr_employee |
|||
JOIN hr_payslip ON hr_payslip.employee_id=hr_employee.id |
|||
GROUP BY hr_payslip.payslip_state |
|||
""") |
|||
dat = self._cr.fetchall() |
|||
data = [{'label': d[0], 'value': d[1]} for d in dat if d[0] is not None] |
|||
return data |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 79 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 158 KiB |
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,699 @@ |
|||
<div style="background-color: #714B67; min-height: 600px; width: 100%; padding: 15px; position: relative;"> |
|||
<!-- TITLE BAR --> |
|||
<div |
|||
style="border-bottom: 1px solid #875A7B; padding: 15px; display: flex; justify-content: space-between; align-items: center;"> |
|||
<img src="assets/misc/cybrosys-logo.png" width="42" height="42" |
|||
style="width: 42px; height: 42px;"/> |
|||
<div> |
|||
<div style="color: #7C7BAD; font-size: 14px; font-family: 'Montserrat', sans-serif; font-weight: bold; background-color: white; display: inline-block; padding: 3px 10px; border-radius: 50px;" |
|||
class="mr-2"> |
|||
<i class="fa fa-check mr-1"></i>Community |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<!-- END OF TITLE BAR --> |
|||
|
|||
<!-- APP HERO --> |
|||
<h1 style="color: #FFFFFF; font-weight: bolder; font-size: 50px; text-align: center; margin-top: 50px;"> |
|||
Hr Payroll Dashboard |
|||
</h1> |
|||
<p style="color:#FFFFFF; padding: 8px 15px; text-align: center; font-size: 24px;"> |
|||
Detailed Dashboard View for Payroll Module. |
|||
</p> |
|||
<!-- END OF APP HERO --> |
|||
<img src="assets/screenshots/v16-hero.gif" |
|||
style="width: 75%; height: auto; position: absolute; margin-left: auto; margin-right: auto; top: 45%; left: 12%; right: auto;"/> |
|||
|
|||
</div> |
|||
|
|||
<!-- NAVIGATION SECTION --> |
|||
<div class="d-flex align-items-center" |
|||
style="border-bottom: 2px solid #714B67; padding: 15px 0px; margin-top: 300px;"> |
|||
<div class="d-flex justify-content-center align-items-center mr-2" |
|||
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
|||
<img src="assets/misc/compass.png"/> |
|||
</div> |
|||
<h2 class="mt-2" |
|||
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
|||
Explore This |
|||
Module</h2> |
|||
</div> |
|||
<div class="row my-4" style="font-family: 'Montserrat', sans-serif;"> |
|||
<div class="col-sm-12 col-md-6 my-3"> |
|||
<a href="#overview"> |
|||
<div class="d-flex justify-content-between align-items-center" |
|||
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
|||
<div> |
|||
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Overview</span> |
|||
<span |
|||
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">Learn |
|||
more about this |
|||
module</span> |
|||
</div> |
|||
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
<div class="col-sm-12 col-md-6 my-3"> |
|||
<a href="#features"> |
|||
<div class="d-flex justify-content-between align-items-center" |
|||
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
|||
<div> |
|||
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Features</span> |
|||
<span |
|||
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View |
|||
features of this |
|||
module</span> |
|||
</div> |
|||
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
<div class="col-sm-12 col-md-6 my-3"> |
|||
<a href="#screenshots"> |
|||
<div class="d-flex justify-content-between align-items-center" |
|||
style="background-color: #f5f5f5; padding: 30px; width: 100%;"> |
|||
<div> |
|||
<span style="color: #714B67; font-size: 24px; font-weight: 500; display: block;">Screenshots</span> |
|||
<span |
|||
style="color: #714B67; font-size: 16px; font-weight: 400; color:#282F33; display: block;">View |
|||
screenshots of this |
|||
module</span> |
|||
</div> |
|||
<img src="assets/misc/right-arrow.png" width="36" height="36"/> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
<!-- END OF NAVIGATION SECTION --> |
|||
|
|||
<!-- OVERVIEW SECTION --> |
|||
<div class="d-flex align-items-center" |
|||
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" |
|||
id="overview"> |
|||
<div class="d-flex justify-content-center align-items-center mr-2" |
|||
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
|||
<img src="assets/misc/pie-chart.png"/> |
|||
</div> |
|||
<h2 class="mt-2" |
|||
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
|||
Overview |
|||
</h2> |
|||
</div> |
|||
<div class="row" |
|||
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> |
|||
<div class="col-sm-12 py-4"> |
|||
This module helps you to see the Overview of Payroll, here You can see the details of Attendance, Leaves, Payslip contracts, etc. |
|||
</div> |
|||
<div class="alert alert-primary mt-4"> |
|||
<hr/> |
|||
You must need module - Odoo 16 Payroll in your system |
|||
<br/> |
|||
<div class="col-lg-12 d-flex justify-content-center align-items-center" |
|||
style="margin: 2rem 0;"> |
|||
<p class=" mt8" style="font-family:Roboto ; color: #280135;">Odoo 16 Payroll - link to download : <a href="https://apps.odoo.com/apps/modules/16.0/hr_payroll_community/"> https://apps.odoo.com/apps/modules/16.0/hr_payroll_community/</a> </p> |
|||
</div> |
|||
</div> |
|||
<!-- END OF OVERVIEW SECTION --> |
|||
|
|||
<!-- FEATURES SECTION --> |
|||
<div class="d-flex align-items-center" |
|||
style="border-bottom: 2px solid #714B67; padding: 15px 0px;" |
|||
id="features"> |
|||
<div class="d-flex justify-content-center align-items-center mr-2" |
|||
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
|||
<img src="assets/misc/features.png"/> |
|||
</div> |
|||
<h2 class="mt-2" |
|||
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
|||
Features |
|||
</h2> |
|||
</div> |
|||
<div class="row" |
|||
style="font-family: 'Montserrat', sans-serif; font-weight: 400; font-size: 14px; line-height: 200%;"> |
|||
<div class="col-sm-12 col-md-6"> |
|||
<div class="d-flex align-items-center" |
|||
style="margin-top: 40px; margin-bottom: 40px"> |
|||
<img src="assets/misc/check-box.png" class="mr-2"/> |
|||
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Payslip Details</span> |
|||
</div> |
|||
<div class="d-flex align-items-center" |
|||
style="margin-top: 30px; margin-bottom: 30px"> |
|||
<img src="assets/misc/check-box.png" class="mr-2"/> |
|||
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Contract Details </span> |
|||
</div> |
|||
<div class="d-flex align-items-center" |
|||
style="margin-top: 30px; margin-bottom: 30px"> |
|||
<img src="assets/misc/check-box.png" class="mr-2"/> |
|||
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Leave Request Details |
|||
</span> |
|||
</div> |
|||
<div class="d-flex align-items-center" |
|||
style="margin-top: 30px; margin-bottom: 30px"> |
|||
<img src="assets/misc/check-box.png" class="mr-2"/> |
|||
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Attendance Details |
|||
</span> |
|||
</div> |
|||
<div class="d-flex align-items-center" |
|||
style="margin-top: 30px; margin-bottom: 30px"> |
|||
<img src="assets/misc/check-box.png" class="mr-2"/> |
|||
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Salary Rules Details |
|||
</span> |
|||
</div> |
|||
<div class="d-flex align-items-center" |
|||
style="margin-top: 30px; margin-bottom: 30px"> |
|||
<img src="assets/misc/check-box.png" class="mr-2"/> |
|||
<span style="font-family: 'Montserrat', sans-serif; font-size: 18px; font-weight: bold;">Salary Structures Details |
|||
</span> |
|||
</div> |
|||
|
|||
</div> |
|||
</div> |
|||
|
|||
<!-- END OF FEATURES SECTION --> |
|||
|
|||
<!-- SCREENSHOTS SECTION --> |
|||
<div class="row" id="screenshots"> |
|||
<div class="col-md-12" |
|||
style="border-bottom: 1px solid #d5d5d5 !important; margin: 2rem 0 !important"> |
|||
<h2 |
|||
style="font-family: 'Montserrat', sans-serif !important; font-weight: 600 !important; color: #714B67 !important; font-size: 1.5rem !important;"> |
|||
<i class="fa fa-image mr-2"></i>Screenshots |
|||
</h2> |
|||
</div> |
|||
<div class="col-lg-12 my-2"> |
|||
<h4 class="mt-2" |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
|||
Open the Payroll Module to view the Payroll Dashboard</h4> |
|||
|
|||
<img src="assets/screenshots/1.png" |
|||
class="img-responsive img-thumbnail border" width="100%" |
|||
height="auto"/> |
|||
</div> |
|||
<div class="col-lg-12 my-3"> |
|||
<h4 class="mt-2" |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
|||
Dashboard Tiles</h4> |
|||
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
|||
The Tiles display a list of the Attendance orders,Leaves, |
|||
Payslips,Contracts,Salary Rules, and Salary Structures. </p> |
|||
<img src="assets/screenshots/2.png" |
|||
class="img-responsive img-thumbnail border" width="100%" |
|||
height="auto"/> |
|||
</div> |
|||
<div class="col-lg-12 my-3"> |
|||
<h4 class="mt-3" |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
|||
Monthly Expense Analysis</h4> |
|||
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
|||
Monthly expense can be analysed from the chart. |
|||
</p> |
|||
<img src="assets/screenshots/3.png" |
|||
class="img-responsive img-thumbnail border" width="100%" |
|||
height="auto"/> |
|||
</div> |
|||
|
|||
<div class="col-lg-12 my-3"> |
|||
<h4 class="mt-3" |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
|||
My Leave Analysis</h4> |
|||
<p style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
|||
User Leave Analysis |
|||
</p> |
|||
<img src="assets/screenshots/4.png" |
|||
class="img-responsive img-thumbnail border" width="100%" |
|||
height="auto"/> |
|||
</div> |
|||
|
|||
<div class="col-lg-12 my-3"> |
|||
<h4 class="mt-3" |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
|||
Monthly Leave Analysis</h4> |
|||
<p |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
|||
Monthly leave analysis can be view here. |
|||
</p> |
|||
<img src="assets/screenshots/5.png" |
|||
class="img-responsive img-thumbnail border" width="100%" |
|||
height="auto"/> |
|||
</div> |
|||
<div class="col-lg-12 my-3"> |
|||
<h4 class="mt-3" |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 600 !important; color: #282F33 !important; font-size: 1.3rem !important;"> |
|||
Details in Charts</h4> |
|||
<p |
|||
style="font-family: 'Roboto', sans-serif !important; font-weight: 400 !important; color: #282F33 !important; font-size: 1rem !important;"> |
|||
Payslip, Contract and Time off analysis can be done from the chart. |
|||
</p> |
|||
<img src="assets/screenshots/6.png" |
|||
class="img-responsive img-thumbnail border" width="100%" |
|||
height="auto"/> |
|||
</div> |
|||
<!-- END OF SCREENSHOTS SECTION --> |
|||
<!-- RELATED PRODUCTS --> |
|||
<div class="d-flex align-items-center" |
|||
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
|||
<div class="d-flex justify-content-center align-items-center mr-2" |
|||
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
|||
<img src="assets/misc/categories.png"/> |
|||
</div> |
|||
<h2 class="mt-2" |
|||
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
|||
Related |
|||
Products |
|||
</h2> |
|||
</div> |
|||
<div class="row"> |
|||
<div class="col-sm-12"> |
|||
<div id="demo1" class="row carousel slide" data-ride="carousel"> |
|||
<!-- The slideshow --> |
|||
<div class="carousel-inner" style="padding: 30px;"> |
|||
<div class="carousel-item" style="min-height: 198.656px;"> |
|||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
|||
style="float:left"> |
|||
<a href="https://apps.odoo.com/apps/modules/16.0/odoo_dynamic_dashboard/" |
|||
target="_blank"> |
|||
<div style="border-radius:10px"> |
|||
<img class="img img-responsive center-block" |
|||
style="border-radius: 0px;" |
|||
src="assets/modules/11.png"> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
|||
style="float:left"> |
|||
<a href="https://apps.odoo.com/apps/modules/16.0/crm_dashboard/" |
|||
target="_blank"> |
|||
<div style="border-radius:10px"> |
|||
<img class="img img-responsive center-block" |
|||
style="border-radius: 0px;" |
|||
src="assets/modules/12.png"> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
|||
style="float:left"> |
|||
<a href="https://apps.odoo.com/apps/modules/16.0/dashboard_pos/" |
|||
target="_blank"> |
|||
<div style="border-radius:10px"> |
|||
<img class="img img-responsive center-block" |
|||
style="border-radius: 0px;" |
|||
src="assets/modules/13.png"> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
<div class="carousel-item active" |
|||
style="min-height: 198.656px;"> |
|||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
|||
style="float:left"> |
|||
<a href="https://apps.odoo.com/apps/modules/16.0/inventory_stock_dashboard_odoo/" |
|||
target="_blank"> |
|||
<div style="border-radius:10px"> |
|||
<img class="img img-responsive center-block" |
|||
style="border-radius: 0px;" |
|||
src="assets/modules/14.png"> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
|||
style="float:left"> |
|||
<a href="https://apps.odoo.com/apps/modules/16.0/project_dashboard_odoo/" |
|||
target="_blank"> |
|||
<div style="border-radius:10px"> |
|||
<img class="img img-responsive center-block" |
|||
style="border-radius: 0px;" |
|||
src="assets/modules/15.png"> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
<div class="col-xs-12 col-sm-4 col-md-4 mb16 mt16" |
|||
style="float:left"> |
|||
<a href="https://apps.odoo.com/apps/modules/16.0/portal_dashboard/" |
|||
target="_blank"> |
|||
<div style="border-radius:10px"> |
|||
<img class="img img-responsive center-block" |
|||
style="border-radius: 0px;" |
|||
src="assets/modules/16.png"> |
|||
</div> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<!-- Left and right controls --> |
|||
<a class="carousel-control-prev" href="#demo1" |
|||
data-slide="prev" |
|||
style="width:35px; color:#000"> <span |
|||
class="carousel-control-prev-icon"><i |
|||
class="fa fa-chevron-left" |
|||
style="font-size:24px"></i></span> |
|||
</a> <a class="carousel-control-next" href="#demo1" |
|||
data-slide="next" style="width:35px; color:#000"> |
|||
<span class="carousel-control-next-icon"><i |
|||
class="fa fa-chevron-right" |
|||
style="font-size:24px"></i></span> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<!-- END OF RELATED PRODUCTS --> |
|||
|
|||
<!-- OUR SERVICES --> |
|||
|
|||
<div class="d-flex align-items-center" |
|||
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
|||
<div class="d-flex justify-content-center align-items-center mr-2" |
|||
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
|||
<img src="assets/misc/star.png"/> |
|||
</div> |
|||
<h2 class="mt-2" |
|||
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
|||
Our Services |
|||
</h2> |
|||
</div> |
|||
|
|||
<div class="container my-5"> |
|||
<div class="row"> |
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #1dd1a1 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/cogs.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Customization</h6> |
|||
</div> |
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #ff6b6b !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/wrench.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Implementation</h6> |
|||
</div> |
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #6462CD !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/lifebuoy.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Support</h6> |
|||
</div> |
|||
|
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #ffa801 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/user.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Hire |
|||
Odoo |
|||
Developer</h6> |
|||
</div> |
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #54a0ff !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/puzzle.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Integration</h6> |
|||
</div> |
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #6d7680 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/update.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Migration</h6> |
|||
</div> |
|||
|
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #786fa6 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/consultation.png" |
|||
class="img-responsive" height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Consultancy</h6> |
|||
</div> |
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #f8a5c2 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/training.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Implementation</h6> |
|||
</div> |
|||
|
|||
<div class="col-lg-4 d-flex flex-column justify-content-center align-items-center my-4"> |
|||
<div class="d-flex justify-content-center align-items-center mx-3 my-3" |
|||
style="background-color: #e6be26 !important; border-radius: 15px !important; height: 80px; width: 80px;"> |
|||
<img src="assets/icons/license.png" class="img-responsive" |
|||
height="48px" width="48px"> |
|||
</div> |
|||
<h6 class="text-center" |
|||
style="font-family: Montserrat, 'sans-serif' !important; font-weight: bold;"> |
|||
Odoo |
|||
Licensing Consultancy</h6> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<!-- END OF OUR SERVICES --> |
|||
|
|||
<!-- OUR INDUSTRIES --> |
|||
|
|||
<div class="d-flex align-items-center" |
|||
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
|||
<div class="d-flex justify-content-center align-items-center mr-2" |
|||
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
|||
<img src="assets/misc/corporate.png"/> |
|||
</div> |
|||
<h2 class="mt-2" |
|||
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
|||
Our |
|||
Industries |
|||
</h2> |
|||
</div> |
|||
|
|||
<div class="container my-5"> |
|||
<div class="row"> |
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/trading-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
Trading |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
Easily procure |
|||
and |
|||
sell your products</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/pos-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
POS |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
Easy |
|||
configuration |
|||
and convivial experience</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/education-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
Education |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
A platform for |
|||
educational management</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/manufacturing-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
Manufacturing |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
Plan, track and |
|||
schedule your operations</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/ecom-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
E-commerce & Website |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
Mobile |
|||
friendly, |
|||
awe-inspiring product pages</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/service-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
Service Management |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
Keep track of |
|||
services and invoice</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/restaurant-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
Restaurant |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
Run your bar or |
|||
restaurant methodically</p> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-lg-3"> |
|||
<div class="my-4 d-flex flex-column justify-content-center" |
|||
style="background-color: #f6f8f9 !important; border-radius: 0px; padding: 2rem !important; height: 250px !important;"> |
|||
<img src="assets/icons/hotel-black.png" |
|||
class="img-responsive mb-3" height="48px" |
|||
width="48px"> |
|||
<h5 style="font-family: Montserrat, sans-serif !important; color: #000 !important; font-weight: bold;"> |
|||
Hotel Management |
|||
</h5> |
|||
<p style="font-family: Montserrat, sans-serif !important; font-size: 0.9rem !important;"> |
|||
An |
|||
all-inclusive |
|||
hotel management application</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- END OF OUR INDUSTRIES --> |
|||
|
|||
<!-- SUPPORT --> |
|||
<div class="d-flex align-items-center" |
|||
style="border-bottom: 2px solid #714B67; padding: 15px 0px;"> |
|||
<div class="d-flex justify-content-center align-items-center mr-2" |
|||
style="background-color: #F5F5F5; border-radius: 0px; width: 40px; height: 40px;"> |
|||
<img src="assets/misc/customer-support.png"/> |
|||
</div> |
|||
<h2 class="mt-2" |
|||
style="font-family: 'Montserrat', sans-serif; font-size: 24px; font-weight: bold;"> |
|||
Support |
|||
</h2> |
|||
</div> |
|||
<div class="container mt-5"> |
|||
<div class="row"> |
|||
<div class="col-sm-12 col-md-6"> |
|||
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> |
|||
<div class="mr-4" |
|||
style="background-color: #714B67; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> |
|||
<img src="assets/misc/support.png" height="48" |
|||
width="48" style="width: 42px; height: 42px;"/> |
|||
</div> |
|||
<div> |
|||
<h4>Need Help?</h4> |
|||
<p style="line-height: 100%;">Got questions or need |
|||
help? Get in touch.</p> |
|||
<a href="mailto:odoo@cybrosys.com"> |
|||
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;"> |
|||
odoo@cybrosys.com</p> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-sm-12 col-md-6"> |
|||
<div style="background-color: #F6F8F9; padding: 30px; display: flex; align-items: center;"> |
|||
<div class="mr-4" |
|||
style="background-color: #2AC44D; display: inline-block; height: 70px; width: 70px; display: flex; align-items: center; justify-content: center;"> |
|||
<img src="assets/misc/whatsapp.png" height="52" |
|||
width="52" style="width: 52px; height: 52px;"/> |
|||
</div> |
|||
<div> |
|||
<h4>WhatsApp</h4> |
|||
<p style="line-height: 100%;">Say hi to us on |
|||
WhatsApp!</p> |
|||
<a href="https://api.whatsapp.com/send?phone=918606827707"> |
|||
<p style="font-weight: 400; font-size: 28px; line-height: 80%; color: #714B67;"> |
|||
+91 86068 |
|||
27707</p> |
|||
</a> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="row"> |
|||
<div class="col-sm-12 my-5 d-flex justify-content-center align-items-center"> |
|||
<img src="assets/misc/logo.png" width="144" height="31" |
|||
style="width:144px; height: 31px; margin-top: 40px;"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<!-- END OF SUPPORT --> |
|||
</div> |
|||
</div> |
@ -0,0 +1,426 @@ |
|||
.nvd3 .nv-axis { |
|||
pointer-events:none; |
|||
opacity: 1; |
|||
} |
|||
|
|||
.nvd3 .nv-axis path { |
|||
fill: none; |
|||
stroke: #000; |
|||
stroke-opacity: .75; |
|||
shape-rendering: crispEdges; |
|||
} |
|||
|
|||
.nvd3 .nv-axis path.domain { |
|||
stroke-opacity: .75; |
|||
} |
|||
|
|||
.nvd3 .nv-axis.nv-x path.domain { |
|||
stroke-opacity: 0; |
|||
} |
|||
|
|||
.nvd3 .nv-axis line { |
|||
fill: none; |
|||
stroke: #e5e5e5; |
|||
shape-rendering: crispEdges; |
|||
} |
|||
|
|||
.nvd3 .nv-axis .zero line, |
|||
/*this selector may not be necessary*/ .nvd3 .nv-axis line.zero { |
|||
stroke-opacity: .75; |
|||
} |
|||
|
|||
.nvd3 .nv-axis .nv-axisMaxMin text { |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.nvd3 .x .nv-axis .nv-axisMaxMin text, |
|||
.nvd3 .x2 .nv-axis .nv-axisMaxMin text, |
|||
.nvd3 .x3 .nv-axis .nv-axisMaxMin text { |
|||
text-anchor: middle |
|||
} |
|||
|
|||
.nvd3 .nv-axis.nv-disabled { |
|||
opacity: 0; |
|||
} |
|||
|
|||
/* scatter */ |
|||
.nvd3 .nv-groups .nv-point.hover { |
|||
stroke-width: 20px; |
|||
stroke-opacity: .5; |
|||
} |
|||
|
|||
.nvd3 .nv-scatter .nv-point.hover { |
|||
fill-opacity: 1; |
|||
} |
|||
.nv-noninteractive { |
|||
pointer-events: none; |
|||
} |
|||
|
|||
.nv-distx, .nv-disty { |
|||
pointer-events: none; |
|||
} |
|||
|
|||
|
|||
|
|||
.nvtooltip { |
|||
position: absolute; |
|||
background-color: rgba(255,255,255,1.0); |
|||
color: rgba(0,0,0,1.0); |
|||
padding: 1px; |
|||
border: 1px solid rgba(0,0,0,.2); |
|||
z-index: 10000; |
|||
display: block; |
|||
|
|||
font-family: Arial; |
|||
font-size: 13px; |
|||
text-align: left; |
|||
pointer-events: none; |
|||
|
|||
white-space: nowrap; |
|||
|
|||
-webkit-touch-callout: none; |
|||
-webkit-user-select: none; |
|||
-khtml-user-select: none; |
|||
-moz-user-select: none; |
|||
-ms-user-select: none; |
|||
user-select: none; |
|||
} |
|||
|
|||
.nvtooltip { |
|||
background: rgba(255,255,255, 0.8); |
|||
border: 1px solid rgba(0,0,0,0.5); |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
/*Give tooltips that old fade in transition by |
|||
putting a "with-transitions" class on the container div. |
|||
*/ |
|||
.nvtooltip.with-transitions, .with-transitions .nvtooltip { |
|||
transition: opacity 50ms linear; |
|||
-moz-transition: opacity 50ms linear; |
|||
-webkit-transition: opacity 50ms linear; |
|||
|
|||
transition-delay: 200ms; |
|||
-moz-transition-delay: 200ms; |
|||
-webkit-transition-delay: 200ms; |
|||
} |
|||
|
|||
.nvtooltip.x-nvtooltip, |
|||
.nvtooltip.y-nvtooltip { |
|||
padding: 8px; |
|||
} |
|||
|
|||
.nvtooltip h3 { |
|||
margin: 0; |
|||
padding: 4px 14px; |
|||
line-height: 18px; |
|||
font-weight: normal; |
|||
background-color: rgba(247,247,247,0.75); |
|||
color: rgba(0,0,0,1.0); |
|||
text-align: center; |
|||
|
|||
border-bottom: 1px solid #ebebeb; |
|||
|
|||
-webkit-border-radius: 5px 5px 0 0; |
|||
-moz-border-radius: 5px 5px 0 0; |
|||
border-radius: 5px 5px 0 0; |
|||
} |
|||
|
|||
.nvtooltip p { |
|||
margin: 0; |
|||
padding: 5px 14px; |
|||
text-align: center; |
|||
} |
|||
|
|||
.nvtooltip span { |
|||
display: inline-block; |
|||
margin: 2px 0; |
|||
} |
|||
|
|||
.nvtooltip table { |
|||
margin: 6px; |
|||
border-spacing:0; |
|||
} |
|||
|
|||
|
|||
.nvtooltip table td { |
|||
padding: 2px 9px 2px 0; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.nvtooltip table td.key { |
|||
font-weight: normal; |
|||
} |
|||
.nvtooltip table td.key.total { |
|||
font-weight: bold; |
|||
} |
|||
.nvtooltip table td.value { |
|||
text-align: right; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
.nvtooltip table tr.highlight td { |
|||
padding: 1px 9px 1px 0; |
|||
border-bottom-style: solid; |
|||
border-bottom-width: 1px; |
|||
border-top-style: solid; |
|||
border-top-width: 1px; |
|||
} |
|||
|
|||
.nvtooltip table td.legend-color-guide div { |
|||
width: 8px; |
|||
height: 8px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
.nvtooltip table td.legend-color-guide div { |
|||
width: 12px; |
|||
height: 12px; |
|||
border: 1px solid #999; |
|||
} |
|||
|
|||
.nvtooltip .footer { |
|||
padding: 3px; |
|||
text-align: center; |
|||
} |
|||
|
|||
.nvtooltip-pending-removal { |
|||
pointer-events: none; |
|||
display: none; |
|||
} |
|||
|
|||
|
|||
/**** |
|||
Interactive Layer |
|||
*/ |
|||
.nvd3 .nv-interactiveGuideLine { |
|||
pointer-events:none; |
|||
} |
|||
.nvd3 line.nv-guideline { |
|||
stroke: #ccc; |
|||
} |
|||
|
|||
.nvd3 .nv-bars rect { |
|||
fill-opacity: .75; |
|||
|
|||
transition: fill-opacity 250ms linear; |
|||
-moz-transition: fill-opacity 250ms linear; |
|||
-webkit-transition: fill-opacity 250ms linear; |
|||
} |
|||
|
|||
.nvd3 .nv-bars rect.hover { |
|||
fill-opacity: 1; |
|||
} |
|||
|
|||
.nvd3 .nv-bars .hover rect { |
|||
fill: lightblue; |
|||
} |
|||
|
|||
.nvd3 .nv-bars text { |
|||
fill: rgba(0,0,0,0); |
|||
} |
|||
|
|||
.nvd3 .nv-bars .hover text { |
|||
fill: rgba(0,0,0,1); |
|||
} |
|||
|
|||
.nvd3 .nv-multibar .nv-groups rect, |
|||
.nvd3 .nv-multibarHorizontal .nv-groups rect, |
|||
.nvd3 .nv-discretebar .nv-groups rect { |
|||
stroke-opacity: 0; |
|||
|
|||
transition: fill-opacity 250ms linear; |
|||
-moz-transition: fill-opacity 250ms linear; |
|||
-webkit-transition: fill-opacity 250ms linear; |
|||
} |
|||
|
|||
.nvd3 .nv-multibar .nv-groups rect:hover, |
|||
.nvd3 .nv-multibarHorizontal .nv-groups rect:hover, |
|||
.nvd3 .nv-candlestickBar .nv-ticks rect:hover, |
|||
.nvd3 .nv-discretebar .nv-groups rect:hover { |
|||
fill-opacity: 1; |
|||
} |
|||
|
|||
.nvd3 .nv-discretebar .nv-groups text, |
|||
.nvd3 .nv-multibarHorizontal .nv-groups text { |
|||
font-weight: bold; |
|||
fill: rgba(0,0,0,1); |
|||
stroke: rgba(0,0,0,0); |
|||
} |
|||
|
|||
.nvd3 .nv-groups path.nv-line { |
|||
fill: none; |
|||
} |
|||
|
|||
.nvd3 .nv-groups path.nv-area { |
|||
stroke: none; |
|||
} |
|||
|
|||
.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point { |
|||
fill-opacity: 0; |
|||
stroke-opacity: 0; |
|||
} |
|||
|
|||
.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point { |
|||
fill-opacity: .5 !important; |
|||
stroke-opacity: .5 !important; |
|||
} |
|||
|
|||
|
|||
.with-transitions .nvd3 .nv-groups .nv-point { |
|||
transition: stroke-width 250ms linear, stroke-opacity 250ms linear; |
|||
-moz-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; |
|||
-webkit-transition: stroke-width 250ms linear, stroke-opacity 250ms linear; |
|||
|
|||
} |
|||
|
|||
.nvd3.nv-scatter .nv-groups .nv-point.hover, |
|||
.nvd3 .nv-groups .nv-point.hover { |
|||
stroke-width: 7px; |
|||
fill-opacity: .95 !important; |
|||
stroke-opacity: .95 !important; |
|||
} |
|||
|
|||
|
|||
.nvd3 .nv-point-paths path { |
|||
stroke: #aaa; |
|||
stroke-opacity: 0; |
|||
fill: #eee; |
|||
fill-opacity: 0; |
|||
} |
|||
|
|||
|
|||
|
|||
.nvd3 .nv-indexLine { |
|||
cursor: ew-resize; |
|||
} |
|||
|
|||
/******************** |
|||
* SVG CSS |
|||
*/ |
|||
|
|||
/******************** |
|||
Default CSS for an svg element nvd3 used |
|||
*/ |
|||
svg.nvd3-svg { |
|||
-webkit-touch-callout: none; |
|||
-webkit-user-select: none; |
|||
-khtml-user-select: none; |
|||
-ms-user-select: none; |
|||
-moz-user-select: none; |
|||
user-select: none; |
|||
display: block; |
|||
width:100%; |
|||
height:100%; |
|||
} |
|||
|
|||
/******************** |
|||
Box shadow and border radius styling |
|||
*/ |
|||
.nvtooltip.with-3d-shadow, .with-3d-shadow .nvtooltip { |
|||
-moz-box-shadow: 0 5px 10px rgba(0,0,0,.2); |
|||
-webkit-box-shadow: 0 5px 10px rgba(0,0,0,.2); |
|||
box-shadow: 0 5px 10px rgba(0,0,0,.2); |
|||
|
|||
-webkit-border-radius: 5px; |
|||
-moz-border-radius: 5px; |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
|
|||
.nvd3 text { |
|||
font: normal 12px Arial; |
|||
} |
|||
|
|||
.nvd3 .title { |
|||
font: bold 14px Arial; |
|||
} |
|||
|
|||
.nvd3 .nv-background { |
|||
fill: white; |
|||
fill-opacity: 0; |
|||
} |
|||
|
|||
.nvd3.nv-noData { |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
} |
|||
|
|||
|
|||
/********** |
|||
* Brush |
|||
*/ |
|||
|
|||
.nv-brush .extent { |
|||
fill-opacity: .125; |
|||
shape-rendering: crispEdges; |
|||
} |
|||
|
|||
.nv-brush .resize path { |
|||
fill: #eee; |
|||
stroke: #666; |
|||
} |
|||
|
|||
|
|||
/********** |
|||
* Legend |
|||
*/ |
|||
|
|||
.nvd3 .nv-legend .nv-series { |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.nvd3 .nv-legend .nv-disabled circle { |
|||
fill-opacity: 0; |
|||
} |
|||
|
|||
/* focus */ |
|||
.nvd3 .nv-brush .extent { |
|||
fill-opacity: 0 !important; |
|||
} |
|||
|
|||
.nvd3 .nv-brushBackground rect { |
|||
stroke: #000; |
|||
stroke-width: .4; |
|||
fill: #fff; |
|||
fill-opacity: .7; |
|||
} |
|||
|
|||
|
|||
.nvd3.nv-pie path { |
|||
stroke-opacity: 0; |
|||
transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; |
|||
-moz-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; |
|||
-webkit-transition: fill-opacity 250ms linear, stroke-width 250ms linear, stroke-opacity 250ms linear; |
|||
|
|||
} |
|||
|
|||
.nvd3.nv-pie .nv-pie-title { |
|||
font-size: 24px; |
|||
fill: rgba(19, 196, 249, 0.59); |
|||
} |
|||
|
|||
.nvd3.nv-pie .nv-slice text { |
|||
stroke: #000; |
|||
stroke-width: 0; |
|||
} |
|||
|
|||
.nvd3.nv-pie path { |
|||
stroke: #fff; |
|||
stroke-width: 1px; |
|||
stroke-opacity: 1; |
|||
} |
|||
|
|||
.nvd3.nv-pie path { |
|||
fill-opacity: .7; |
|||
} |
|||
.nvd3.nv-pie .hover path { |
|||
fill-opacity: 1; |
|||
} |
|||
.nvd3.nv-pie .nv-label { |
|||
pointer-events: none; |
|||
} |
|||
.nvd3.nv-pie .nv-label rect { |
|||
fill-opacity: 0; |
|||
stroke-opacity: 0; |
|||
} |
@ -0,0 +1,431 @@ |
|||
#chartdiv { |
|||
width : 100%; |
|||
height : 500px; |
|||
font-size : 11px; |
|||
} |
|||
.card-title { |
|||
float: left; |
|||
font-size: 1.1rem; |
|||
font-weight: 400; |
|||
margin: 0; |
|||
text-transform: uppercase; |
|||
} |
|||
|
|||
.col-md-3, .col-sm-12, .col-md-8, .col-md-4, .col-6, .col-4,.col-2 { |
|||
position: relative; |
|||
padding-right: 7.5px !important; |
|||
padding-left: 7.5px !important; |
|||
} |
|||
|
|||
|
|||
.card-header { |
|||
background-color: |
|||
transparent; |
|||
border-bottom: 1px solid |
|||
rgba(0,0,0,.125); |
|||
padding: .75rem 1.25rem; |
|||
position: relative; |
|||
border-top-left-radius: .25rem; |
|||
border-top-right-radius: .25rem; |
|||
} |
|||
|
|||
|
|||
.container-fluid.o_in_dashboard { |
|||
padding: 0px !important; |
|||
} |
|||
.o_action_manager{ |
|||
overflow-y: scroll !important; |
|||
max-width:100%; |
|||
} |
|||
|
|||
// new tile |
|||
|
|||
|
|||
.o_dashboards { |
|||
color: #2a2a2a; |
|||
background-color: #f2f2f2 !important; |
|||
} |
|||
.dash-header { |
|||
|
|||
margin: 15px 0px 12px 0 !important; |
|||
display: block; |
|||
padding: 7px 25px 7px 0; |
|||
color: #0e1319; |
|||
font-size: 2rem; |
|||
font-weight: 400; |
|||
background-color: |
|||
rgba(255, 255, 255, 0.9) !important; |
|||
color:#212529; |
|||
padding: 1.5rem; |
|||
border-radius: 3px; |
|||
box-shadow: 0 0px 10px 0px |
|||
rgba(0, 0, 0, 0.05) !important; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
|
|||
} |
|||
h1.dashboard-h1 { |
|||
|
|||
display: block; |
|||
padding: 7px 25px 7px 0; |
|||
color: #0e1319; |
|||
font-size: 2rem; |
|||
font-weight: 400; |
|||
color: |
|||
|
|||
#212529; |
|||
float: left; |
|||
margin-bottom: 0; |
|||
|
|||
} |
|||
.card { |
|||
position: relative !important; |
|||
border-top: 0 !important; |
|||
margin-bottom: 30px !important; |
|||
width: 100% !important; |
|||
background-color: #ffffff !important; |
|||
border-radius: 0.25rem !important; |
|||
padding: 0px !important; |
|||
-webkit-transition: .5s !important; |
|||
transition: .5s !important; |
|||
display: -ms-flexbox !important; |
|||
display: flex !important; |
|||
-ms-flex-direction: column !important; |
|||
flex-direction: column !important; |
|||
box-shadow: 0 0px 10px 0px rgba(0, 0, 0, 0.05) !important; |
|||
border-radius: 0.25rem; |
|||
} |
|||
.card-header { |
|||
border: 0; |
|||
padding: 0; |
|||
} |
|||
.card-header > .card-tools { |
|||
float: right; |
|||
margin-right: 0.375rem; |
|||
margin-top: 5px; |
|||
margin-bottom: 10px; |
|||
} |
|||
.card-header i.fa { |
|||
font-size: 1.3rem; |
|||
display: inline-block; |
|||
padding: 0 0px; |
|||
margin: 0 0px; |
|||
color: #57769c; |
|||
opacity: .8; |
|||
-webkit-transition: 0.3s linear; |
|||
transition: 0.3s linear; |
|||
} |
|||
|
|||
.main-title { |
|||
display: block; |
|||
margin-bottom: 5px; |
|||
font-size: 13px; |
|||
font-weight: 600; |
|||
color: #fff !important; |
|||
text-transform: uppercase; |
|||
padding: 1rem; |
|||
border-radius: 5px; |
|||
border-bottom-left-radius: 0; |
|||
border-bottom-right-radius: 0; |
|||
} |
|||
.card-body { |
|||
background-color: rgba(255, 255, 255, 0.9) !important; |
|||
color: #212529; |
|||
padding-top: 0; |
|||
} |
|||
.tile.wide.invoice { |
|||
margin-bottom: 27px; |
|||
-webkit-box-shadow: 1px 5px 24px 0 rgba(68, 102, 242, 0.05); |
|||
box-shadow: 1px 5px 24px 0 rgba(68, 102, 242, 0); |
|||
background-color: #ffffff; |
|||
border-radius: 5px; |
|||
position: relative; |
|||
width: 100%; |
|||
padding: 0rem 0rem; |
|||
border: 1px solid rgba(0, 0, 0, 0.07); |
|||
} |
|||
.box-1 .main-title { |
|||
background: #67b7dc; |
|||
color: #fff; |
|||
} |
|||
.box-2 .main-title { |
|||
background: #6794dc !important; |
|||
color: #fff; |
|||
} |
|||
.box-3 .main-title { |
|||
background:#8067dc; |
|||
color: #fff; |
|||
} |
|||
.box-4 .main-title { |
|||
background: #c767dc; |
|||
color: #fff; |
|||
} |
|||
.count { |
|||
margin-bottom: 1rem; |
|||
} |
|||
|
|||
.count > span > sapn { |
|||
font-size: 20px !important; |
|||
} |
|||
|
|||
span#total_invoices_ span, span#total_invoices_last span, span#total_incomes_ span, span#total_incomes_last span, span#total_expenses_ span, span#total_expense_last span, span#unreconciled_items_ span, span#unreconciled_items_last span,span#unreconciled_counts_last_year span,span#unreconciled_counts_this_year span,span#total_expense_last_year span,span#total_expense_this_year span, span#total_incomes_last_year span,span#total_incomes_this_year span,span#total_invoices_last_year span,span#total_invoices_this_year span,span#net_profit_current_months span,span#net_profit_current_year span { |
|||
padding-right: 8px; |
|||
font-size: 16px; |
|||
font-weight: 600; |
|||
} |
|||
|
|||
span#total_invoices_, span#total_invoices_last, span#total_incomes_, span#total_incomes_last, span#total_expenses_, span#total_expense_last, span#unreconciled_items_, span#unreconciled_items_last,span#unreconciled_counts_last_year,span#unreconciled_counts_this_year,span#total_expense_last_year,span#total_expense_this_year, span#total_incomes_last_year,span#total_incomes_this_year,span#total_invoices_last_year,span#total_invoices_this_year,span#net_profit_current_months,span#net_profit_current_year { |
|||
display: -webkit-box; |
|||
display: -webkit-flex; |
|||
display: flex; |
|||
flex-direction: column; |
|||
color: #455e7b !important; |
|||
} |
|||
.main-title~div { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
margin-top: 1rem; |
|||
padding: 1rem; |
|||
background:#fff; |
|||
} |
|||
.card-header { |
|||
color: #0e1319 !important; |
|||
display: block !important; |
|||
padding: 1.5rem 1.5rem !important; |
|||
position: relative !important; |
|||
border-bottom: 1px solid rgba(0, 0, 0, 0.07) !important; |
|||
border-top-left-radius: 0.25rem !important; |
|||
border-top-right-radius: 0.25rem !important; |
|||
} |
|||
.card-header i.fa { |
|||
font-size: 1rem; |
|||
display: inline-block; |
|||
padding: 0 0px; |
|||
margin: 0 0px; |
|||
color: #57769c; |
|||
opacity: .8; |
|||
-webkit-transition: 0.3s linear; |
|||
transition: 0.3s linear; |
|||
} |
|||
.card-header > .card-tools { |
|||
float: right; |
|||
margin-right: 0; |
|||
margin-top: 0px !important; |
|||
margin-bottom: 0; |
|||
} |
|||
h3, .h3 { |
|||
margin: 0; |
|||
} |
|||
.card-tools .btn { |
|||
padding: 0 10px; |
|||
margin: 0; |
|||
line-height: normal !important; |
|||
} |
|||
|
|||
|
|||
|
|||
#col-graph .card { |
|||
height: 366px; |
|||
} |
|||
|
|||
#top_10_customers_this_month{ |
|||
padding:0; |
|||
} |
|||
#top_10_customers_this_month li { |
|||
list-style: none; |
|||
padding-top: 6px; |
|||
padding-bottom: 6px; |
|||
font-size: 13px; |
|||
color:#455e7b !important; |
|||
border-bottom: 1px solid |
|||
rgba(0, 0, 0, 0.07) !important; |
|||
padding-left: 2rem; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
padding-right: 2rem; |
|||
} |
|||
#top_10_customers_this_month li a{ |
|||
color: |
|||
#455e7b !important; |
|||
} |
|||
|
|||
#top_10_customers{ |
|||
padding:0; |
|||
} |
|||
#top_10_customers li { |
|||
list-style: none; |
|||
padding-top: 6px; |
|||
padding-bottom: 6px; |
|||
font-size: 13px; |
|||
color:#455e7b !important; |
|||
border-bottom: 1px solid |
|||
rgba(0, 0, 0, 0.07) !important; |
|||
padding-left: 2rem; |
|||
display: flex; |
|||
justify-content: space-between; |
|||
padding-right: 2rem; |
|||
} |
|||
#top_10_customers li a{ |
|||
color: |
|||
#455e7b !important; |
|||
} |
|||
|
|||
progress, progress[role] { |
|||
-webkit-appearance: none; |
|||
-moz-appearance: none; |
|||
appearance: none; |
|||
border: none; |
|||
background-size: auto; |
|||
height: 20px; |
|||
width: 100%; |
|||
background-color: #8067dc; |
|||
} |
|||
|
|||
// The unordered list |
|||
.skill-list { |
|||
list-style: none; |
|||
margin: 0; |
|||
padding: 1em; |
|||
} |
|||
|
|||
// The list item |
|||
.skill { |
|||
margin-bottom: 1em; |
|||
position: relative; |
|||
h3 { |
|||
color: #000; |
|||
left: 1em; |
|||
line-height: 1; |
|||
position: absolute; |
|||
top: 1em; |
|||
} |
|||
::-webkit-progress-value { |
|||
-webkit-animation: bar-fill 2s; |
|||
width: 0px; |
|||
} |
|||
} |
|||
|
|||
// Style the bar colors |
|||
.skill-1::-webkit-progress-value { |
|||
background: #c767dc; |
|||
} |
|||
|
|||
.skill-1::-moz-progress-bar { |
|||
background: #c767dc; |
|||
} |
|||
|
|||
// Animation Keyframes |
|||
@-webkit-keyframes bar-fill { |
|||
0% { width: 0; } |
|||
} |
|||
|
|||
@keyframes bar-fill { |
|||
0% { width: 0; } |
|||
} |
|||
|
|||
#total_supplier_invoice{ |
|||
color: #696969; |
|||
} |
|||
|
|||
#total_customer_invoice_names{ |
|||
color: #696969; |
|||
} |
|||
|
|||
#total_customer_invoice{ |
|||
color: #696969; |
|||
} |
|||
|
|||
#total_invoice_difference, #total_supplier_difference{ |
|||
color: #4ecdc4; |
|||
} |
|||
progress { |
|||
border: 0; |
|||
border-radius: 20px; |
|||
} |
|||
progress::-webkit-progress-bar { |
|||
border: 0; |
|||
border-radius: 20px; |
|||
} |
|||
progress::-webkit-progress-value { |
|||
border: 0; |
|||
border-radius: 20px; |
|||
} |
|||
progress::-moz-progress-bar { |
|||
border: 0; |
|||
border-radius: 20px; |
|||
} |
|||
#total_customer_invoice_paid .logo, #total_customer_invoice .logo, #total_supplier_invoice_paid .logo, #total_supplier_invoice .logo, |
|||
#total_customer_invoice_paid_current_year .logo, #total_customer_invoice_current_year .logo, #total_supplier_invoice_paid_current_year .logo, #total_supplier_invoice_current_year .logo, |
|||
#total_customer_invoice_paid_current_month .logo, #total_customer_invoice_current_month .logo, #total_supplier_invoice_paid_current_month .logo, #total_supplier_invoice_current_month .logo { |
|||
|
|||
display: -webkit-box; |
|||
display: -webkit-flex; |
|||
display: flex; |
|||
justify-content: left; |
|||
flex-direction: column-reverse; |
|||
color:#455e7b !important; |
|||
|
|||
} |
|||
|
|||
#total_customer_invoice_paid .logo span:nth-child(2), #total_customer_invoice .logo span:nth-child(2), #total_supplier_invoice_paid .logo span:nth-child(2), #total_supplier_invoice .logo span:nth-child(2), |
|||
#total_customer_invoice_paid_current_year .logo span:nth-child(2), #total_customer_invoice_current_year .logo span:nth-child(2), #total_supplier_invoice_paid_current_year .logo span:nth-child(2), #total_supplier_invoice_current_year .logo span:nth-child(2), |
|||
#total_customer_invoice_paid_current_month .logo span:nth-child(2), #total_customer_invoice_current_month .logo span:nth-child(2), #total_supplier_invoice_paid_current_month .logo span:nth-child(2), #total_supplier_invoice_current_month .logo span:nth-child(2) { |
|||
|
|||
font-weight: 600; |
|||
|
|||
} |
|||
|
|||
.toggle-on.btn { |
|||
|
|||
padding-right: 7px; |
|||
right: 45%; |
|||
|
|||
} |
|||
|
|||
.toggle.btn.btn-default.off { |
|||
|
|||
border: 1px solid #aaa; |
|||
|
|||
background: #fff; |
|||
font-weight: 600 !important; |
|||
|
|||
} |
|||
.toggle-off.btn { |
|||
|
|||
padding-left: 9px; |
|||
|
|||
} |
|||
|
|||
#canvas { |
|||
height: 277px !important; |
|||
width: 100% !important; |
|||
} |
|||
#collection { |
|||
height: 277px !important; |
|||
width: 100% !important; |
|||
} |
|||
#hiring { |
|||
height: 260px !important; |
|||
width: 100% !important; |
|||
} |
|||
|
|||
#payroll_status { |
|||
height: 260px !important; |
|||
width: 100% !important; |
|||
} |
|||
|
|||
#evm { |
|||
height: 277px !important; |
|||
width: 100% !important; |
|||
} |
|||
|
|||
.custom-h1 { |
|||
font-size: 1em; |
|||
margin-bottom: 0rem; |
|||
} |
|||
|
|||
.custom-h3 { |
|||
font-size: 1em; |
|||
margin: 0; |
|||
} |
@ -0,0 +1,748 @@ |
|||
odoo.define('hr_payroll_dashboard.PayrollDashboard', function (require) { |
|||
"use strict"; |
|||
var AbstractAction = require('web.AbstractAction'); |
|||
var core = require('web.core'); |
|||
var rpc = require('web.rpc'); |
|||
var _t = core._t; |
|||
var QWeb = core.qweb; |
|||
|
|||
var PayrollDashboard = AbstractAction.extend({ |
|||
template: 'PayrollDashboardMain', |
|||
|
|||
events: { |
|||
'click .o_payslips': '_onclickPayslips', |
|||
'click .o_contracts': '_onclickContracts', |
|||
'click .o_attendances': '_onclickAttendances', |
|||
'click .o_leave': '_onclickLeave', |
|||
'click .o_salary_structures': '_onClickSalaryStructure', |
|||
'click .o_salary_rules': '_onClickSalaryRules', |
|||
}, |
|||
|
|||
init: function(parent, context) { |
|||
this._super(parent, context); |
|||
this.login_employee = []; |
|||
this.dashboards_templates = ['PayrollManagerDashboard','EmployeeDetails','ManagerLeaveDashboard', 'PayrollChart']; |
|||
}, |
|||
|
|||
start: function() { |
|||
var self = this; |
|||
this.set("title", 'Dashboard'); |
|||
self.update_leave_trend(); |
|||
return this._super().then(function() { |
|||
self.render_dashboards(); |
|||
self.render_graphs(); |
|||
|
|||
}); |
|||
}, |
|||
|
|||
willStart: function() { |
|||
var self = this; |
|||
this.login_employee = {}; |
|||
return this._super() |
|||
.then(function() { |
|||
var emp_details = self._rpc({ |
|||
model: 'hr.employee', |
|||
method: 'get_user_employee_info' |
|||
}).then(function(result) { |
|||
self.login_employee = result[0]; |
|||
}); |
|||
return $.when(emp_details); |
|||
}); |
|||
}, |
|||
render_dashboards: function() { |
|||
var self = this; |
|||
if (this.login_employee){ |
|||
var templates = [] |
|||
if( self.login_employee.is_manager == true){templates = ['EmployeeDetails','ManagerLeaveDashboard','PayrollManagerDashboard']; |
|||
} |
|||
else{ |
|||
templates = ['EmployeeDetails'];} |
|||
_.each(templates, function(template) { |
|||
self.$('.o_payroll_dashboard').append(QWeb.render(template, {widget: self})); |
|||
}); |
|||
} |
|||
else{ |
|||
self.$('.o_payroll_dashboard').append(QWeb.render('EmployeeWarning', {widget: self})); |
|||
} |
|||
}, |
|||
_onclickPayslips: function(){ |
|||
/* |
|||
* Open the Employee Payslips window. |
|||
*/ |
|||
var self = this; |
|||
var domain = [] |
|||
if( self.login_employee.is_manager == false){ |
|||
domain = [['employee_id','=', this.login_employee.id]] |
|||
|
|||
} |
|||
return this.do_action({ |
|||
name: _t("Employee Payslips"), |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'hr.payslip', |
|||
view_mode: 'tree,form,calendar', |
|||
views: [[false, 'list'],[false, 'form']], |
|||
domain: domain, |
|||
target: 'current' |
|||
}); |
|||
}, |
|||
_onclickContracts: function(){ |
|||
/* |
|||
* Open the Employee Contracts window. |
|||
*/ |
|||
var self = this; |
|||
var domain = [] |
|||
if( self.login_employee.is_manager == false){ |
|||
domain = [['employee_id','=', this.login_employee.id]] |
|||
|
|||
} |
|||
return self.do_action({ |
|||
name: _t("Employee Contracts"), |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'hr.contract', |
|||
view_mode: 'tree,form', |
|||
views: [[false, 'list'],[false, 'form']], |
|||
context: {"create": false}, |
|||
domain: domain, |
|||
}); |
|||
}, |
|||
_onclickAttendances: function(){ |
|||
/* |
|||
* Open the Attendance window. |
|||
*/ |
|||
var self = this; |
|||
var today = new Date(); |
|||
var dd = String(today.getDate()).padStart(2, '0'); |
|||
var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
|
|||
var yyyy = today.getFullYear(); |
|||
today = yyyy + '-' + mm + '-' + dd; |
|||
var domain = [['attendance_date', '=', today]] |
|||
if( self.login_employee.is_manager == false){ |
|||
domain = [['employee_id','=', this.login_employee.id],['attendance_date', '=', today]] |
|||
|
|||
} |
|||
return self.do_action({ |
|||
name: _t("Employee Attendances"), |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'hr.attendance', |
|||
view_mode: 'tree,form', |
|||
views: [[false, 'list'],[false, 'form']], |
|||
context: {"create": false}, |
|||
domain: domain, |
|||
}); |
|||
}, |
|||
_onclickLeave: function(){ |
|||
/* |
|||
* Open the Employee Leave window. |
|||
*/ |
|||
var today = new Date(); |
|||
var dd = String(today.getDate()).padStart(2, '0'); |
|||
var mm = String(today.getMonth() + 1).padStart(2, '0'); //January is 0!
|
|||
var yyyy = today.getFullYear(); |
|||
today = yyyy + '-' + mm + '-' + dd; |
|||
var self = this; |
|||
var domain = [['request_date_from', '=', today]] |
|||
if( self.login_employee.is_manager == false){ |
|||
domain = [['employee_id','=', this.login_employee.id],['request_date_from', '=', today]] |
|||
} |
|||
return self.do_action({ |
|||
name: _t("Employee Leave"), |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'hr.leave', |
|||
view_mode: 'tree,form', |
|||
views: [[false, 'list'],[false, 'form']], |
|||
context: {"create": false}, |
|||
domain: domain, |
|||
}); |
|||
}, |
|||
_onClickSalaryRules: function(){ |
|||
/* |
|||
* Open the Salary Rules window. |
|||
*/ |
|||
var self = this; |
|||
return self.do_action({ |
|||
name: _t("Salary Rules"), |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'hr.salary.rule', |
|||
view_mode: 'tree,form', |
|||
views: [[false, 'list'],[false, 'form']], |
|||
context: {"create": false}, |
|||
}); |
|||
}, |
|||
_onClickSalaryStructure: function(){ |
|||
/* |
|||
* Open the Salary Structures window. |
|||
*/ |
|||
var self = this; |
|||
return self.do_action({ |
|||
name: _t("Salary Structures"), |
|||
type: 'ir.actions.act_window', |
|||
res_model: 'hr.payroll.structure', |
|||
view_mode: 'tree,form', |
|||
views: [[false, 'list'],[false, 'form']], |
|||
context: {"create": false}, |
|||
}); |
|||
}, |
|||
|
|||
render_expense_graph:function(){ |
|||
/** |
|||
* Render the expense graph. |
|||
* This function retrieves employee expense data and renders it as a graph using D3.js. |
|||
* The graph displays the monthly expenses of employees. |
|||
*/ |
|||
var elem = this.$('.expense_graph'); |
|||
var colors = ['#70cac1', '#659d4e', '#208cc2', '#4d6cb1', '#584999', '#8e559e', '#cf3650', '#f65337', '#fe7139', |
|||
'#ffa433', '#ffc25b', '#f8e54b']; |
|||
var color = d3.scale.ordinal().range(colors); |
|||
rpc.query({ |
|||
model: "hr.expense", |
|||
method: "get_employee_expense", |
|||
}).then(function (data) { |
|||
if(data){ |
|||
data.forEach(function(d) { |
|||
d.values.forEach(function(d) { |
|||
d.l_month = d.l_month; |
|||
d.count = +d.count; |
|||
}); |
|||
}); |
|||
var margin = {top: 30, right: 10, bottom: 30, left: 30}, |
|||
width = 400 - margin.left - margin.right, |
|||
height = 250 - margin.top - margin.bottom |
|||
// Set the ranges
|
|||
var x = d3.scale.ordinal() |
|||
.rangeRoundBands([0, width], 1); |
|||
var y = d3.scale.linear() |
|||
.range([height, 0]); |
|||
// Define the axes
|
|||
var xAxis = d3.svg.axis().scale(x) |
|||
.orient("bottom"); |
|||
var yAxis = d3.svg.axis().scale(y) |
|||
.orient("left").ticks(5); |
|||
x.domain(data[0].values.map(function(d) { return d.l_month; })); |
|||
y.domain([0, d3.max(data[0].values, d => d.count)]) |
|||
var svg = d3.select(elem[0]).append("svg") |
|||
.attr("width", width + margin.left + margin.right) |
|||
.attr("height", height + margin.top + margin.bottom) |
|||
.append("g") |
|||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|||
// Add the X Axis
|
|||
svg.append("g") |
|||
.attr("class", "x axis") |
|||
.attr("transform", "translate(0," + height + ")") |
|||
.call(xAxis); |
|||
// Add the Y Axis
|
|||
svg.append("g") |
|||
.attr("class", "y axis") |
|||
.call(yAxis); |
|||
var line = d3.svg.line() |
|||
.x(function(d) {return x(d.l_month); }) |
|||
.y(function(d) {return y(d.count); }); |
|||
let lines = svg.append('g') |
|||
.attr('class', 'lines'); |
|||
lines.selectAll('.line-group') |
|||
.data(data).enter() |
|||
.append('g') |
|||
.attr('class', 'line-group') |
|||
.append('path') |
|||
.attr('class', 'line') |
|||
.attr('d', function(d) { return line(d.values); }) |
|||
.style('stroke', (d, i) => color(i)); |
|||
lines.selectAll("circle-group") |
|||
.data(data).enter() |
|||
.append("g") |
|||
.selectAll("circle") |
|||
.data(function(d) { return d.values;}).enter() |
|||
.append("g") |
|||
.attr("class", "circle") |
|||
.append("circle") |
|||
.attr("cx", function(d) { return x(d.l_month)}) |
|||
.attr("cy", function(d) { return y(d.count)}) |
|||
.attr("r", 3); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
render_leave_graph:function(){ |
|||
/** |
|||
* Render the leave graph. |
|||
* This function retrieves department leave data and renders it as a graph using D3.js. |
|||
* The graph displays the total leave taken by each department over a period of time. |
|||
*/ |
|||
var self = this; |
|||
var colors = ['#70cac1', '#659d4e', '#208cc2', '#4d6cb1', '#584999', '#8e559e', '#cf3650', '#f65337', '#fe7139', |
|||
'#ffa433', '#ffc25b', '#f8e54b']; |
|||
var color = d3.scale.ordinal().range(colors); |
|||
rpc.query({ |
|||
model: "hr.employee", |
|||
method: "get_department_leave", |
|||
}).then(function (data) { |
|||
if (data){ |
|||
var fData = data[0]; |
|||
var dept = data[1]; |
|||
var id = self.$('.leave_graph')[0]; |
|||
var barColor = '#ff618a'; |
|||
fData.forEach(function(d){ |
|||
var total = 0; |
|||
for (var dpt in dept){ |
|||
total += d.leave[dept[dpt]]; |
|||
} |
|||
d.total=total; |
|||
}); |
|||
// function to handle histogram.
|
|||
function histoGram(fD){ |
|||
var hG={}, hGDim = {t: 60, r: 0, b: 30, l: 0}; |
|||
hGDim.w = 350 - hGDim.l - hGDim.r, |
|||
hGDim.h = 200 - hGDim.t - hGDim.b; |
|||
//create svg for histogram.
|
|||
var hGsvg = d3.select(id).append("svg") |
|||
.attr("width", hGDim.w + hGDim.l + hGDim.r) |
|||
.attr("height", hGDim.h + hGDim.t + hGDim.b).append("g") |
|||
.attr("transform", "translate(" + hGDim.l + "," + hGDim.t + ")"); |
|||
// create function for x-axis mapping.
|
|||
var x = d3.scale.ordinal().rangeRoundBands([0, hGDim.w], 0.1) |
|||
.domain(fD.map(function(d) { return d[0]; })); |
|||
// Add x-axis to the histogram svg.
|
|||
hGsvg.append("g").attr("class", "x axis") |
|||
.attr("transform", "translate(0," + hGDim.h + ")") |
|||
.call(d3.svg.axis().scale(x).orient("bottom")); |
|||
// Create function for y-axis map.
|
|||
var y = d3.scale.linear().range([hGDim.h, 0]) |
|||
.domain([0, d3.max(fD, function(d) { return d[1]; })]); |
|||
// Create bars for histogram to contain rectangles and freq labels.
|
|||
var bars = hGsvg.selectAll(".bar").data(fD).enter() |
|||
.append("g").attr("class", "bar"); |
|||
//create the rectangles.
|
|||
bars.append("rect") |
|||
.attr("x", function(d) { return x(d[0]); }) |
|||
.attr("y", function(d) { return y(d[1]); }) |
|||
.attr("width", x.rangeBand()) |
|||
.attr("height", function(d) { return hGDim.h - y(d[1]); }) |
|||
.attr('fill',barColor) |
|||
.on("mouseover",mouseover)// mouseover is defined below.
|
|||
.on("mouseout",mouseout);// mouseout is defined below.
|
|||
|
|||
//Create the frequency labels above the rectangles.
|
|||
bars.append("text").text(function(d){ return d3.format(",")(d[1])}) |
|||
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; }) |
|||
.attr("y", function(d) { return y(d[1])-5; }) |
|||
.attr("text-anchor", "middle"); |
|||
|
|||
function mouseover(d){ // utility function to be called on mouseover.
|
|||
// filter for selected state.
|
|||
var st = fData.filter(function(s){ return s.l_month == d[0];})[0], |
|||
nD = d3.keys(st.leave).map(function(s){ return {type:s, leave:st.leave[s]};}); |
|||
|
|||
// call update functions of pie-chart and legend.
|
|||
pC.update(nD); |
|||
leg.update(nD); |
|||
} |
|||
|
|||
function mouseout(d){ // utility function to be called on mouseout.
|
|||
// reset the pie-chart and legend.
|
|||
pC.update(tF); |
|||
leg.update(tF); |
|||
} |
|||
|
|||
// create function to update the bars. This will be used by pie-chart.
|
|||
hG.update = function(nD, color){ |
|||
// update the domain of the y-axis map to reflect change in frequencies.
|
|||
y.domain([0, d3.max(nD, function(d) { return d[1]; })]); |
|||
|
|||
// Attach the new data to the bars.
|
|||
var bars = hGsvg.selectAll(".bar").data(nD); |
|||
|
|||
// transition the height and color of rectangles.
|
|||
bars.select("rect").transition().duration(500) |
|||
.attr("y", function(d) {return y(d[1]); }) |
|||
.attr("height", function(d) { return hGDim.h - y(d[1]); }) |
|||
.attr("fill", color); |
|||
|
|||
// transition the frequency labels location and change value.
|
|||
bars.select("text").transition().duration(500) |
|||
.text(function(d){ return d3.format(",")(d[1])}) |
|||
.attr("y", function(d) {return y(d[1])-5; }); |
|||
} |
|||
return hG; |
|||
} |
|||
|
|||
// function to handle pieChart.
|
|||
function pieChart(pD){ |
|||
var pC ={}, pieDim ={w:250, h: 250}; |
|||
pieDim.r = Math.min(pieDim.w, pieDim.h) / 2; |
|||
|
|||
// create svg for pie chart.
|
|||
var piesvg = d3.select(id).append("svg") |
|||
.attr("width", pieDim.w).attr("height", pieDim.h).append("g") |
|||
.attr("transform", "translate("+pieDim.w/2+","+pieDim.h/2+")"); |
|||
|
|||
// create function to draw the arcs of the pie slices.
|
|||
var arc = d3.svg.arc().outerRadius(pieDim.r - 10).innerRadius(0); |
|||
|
|||
// create a function to compute the pie slice angles.
|
|||
var pie = d3.layout.pie().sort(null).value(function(d) { return d.leave; }); |
|||
|
|||
// Draw the pie slices.
|
|||
piesvg.selectAll("path").data(pie(pD)).enter().append("path").attr("d", arc) |
|||
.each(function(d) { this._current = d; }) |
|||
.attr("fill", function(d, i){return color(i);}) |
|||
.on("mouseover",mouseover).on("mouseout",mouseout); |
|||
|
|||
// create function to update pie-chart. This will be used by histogram.
|
|||
pC.update = function(nD){ |
|||
piesvg.selectAll("path").data(pie(nD)).transition().duration(500) |
|||
.attrTween("d", arcTween); |
|||
} |
|||
// Utility function to be called on mouseover a pie slice.
|
|||
function mouseover(d, i){ |
|||
// call the update function of histogram with new data.
|
|||
hG.update(fData.map(function(v){ |
|||
return [v.l_month,v.leave[d.data.type]];}),color(i)); |
|||
} |
|||
//Utility function to be called on mouseout a pie slice.
|
|||
function mouseout(d){ |
|||
// call the update function of histogram with all data.
|
|||
hG.update(fData.map(function(v){ |
|||
return [v.l_month,v.total];}), barColor); |
|||
|
|||
} |
|||
// Animating the pie-slice requiring a custom function which specifies
|
|||
// how the intermediate paths should be drawn.
|
|||
function arcTween(a) { |
|||
var i = d3.interpolate(this._current, a); |
|||
this._current = i(0); |
|||
return function(t) { return arc(i(t)); }; |
|||
} |
|||
return pC; |
|||
} |
|||
|
|||
// function to handle legend.
|
|||
function legend(lD){ |
|||
var leg = {}; |
|||
|
|||
// create table for legend.
|
|||
var legend = d3.select(id).append("table").attr('class','legend'); |
|||
|
|||
// create one row per segment.
|
|||
var tr = legend.append("tbody").selectAll("tr").data(lD).enter().append("tr"); |
|||
|
|||
// create the first column for each segment.
|
|||
tr.append("td").append("svg").attr("width", '16').attr("height", '16').append("rect") |
|||
.attr("width", '16').attr("height", '16') |
|||
.attr("fill", function(d, i){return color(i);}) |
|||
|
|||
// create the second column for each segment.
|
|||
tr.append("td").text(function(d){ return d.type;}); |
|||
|
|||
// create the third column for each segment.
|
|||
tr.append("td").attr("class",'legendFreq') |
|||
.text(function(d){ return d.l_month;}); |
|||
|
|||
// create the fourth column for each segment.
|
|||
tr.append("td").attr("class",'legendPerc') |
|||
.text(function(d){ return getLegend(d,lD);}); |
|||
|
|||
// Utility function to be used to update the legend.
|
|||
leg.update = function(nD){ |
|||
// update the data attached to the row elements.
|
|||
var l = legend.select("tbody").selectAll("tr").data(nD); |
|||
|
|||
// update the frequencies.
|
|||
l.select(".legendFreq").text(function(d){ return d3.format(",")(d.leave);}); |
|||
|
|||
// update the percentage column.
|
|||
l.select(".legendPerc").text(function(d){ return getLegend(d,nD);}); |
|||
} |
|||
|
|||
function getLegend(d,aD){ // Utility function to compute percentage.
|
|||
var perc = (d.leave/d3.sum(aD.map(function(v){ return v.leave; }))); |
|||
if (isNaN(perc)){ |
|||
return d3.format("%")(0); |
|||
} |
|||
else{ |
|||
return d3.format("%")(d.leave/d3.sum(aD.map(function(v){ return v.leave; }))); |
|||
} |
|||
} |
|||
|
|||
return leg; |
|||
} |
|||
// calculate total frequency by segment for all state.
|
|||
var tF = dept.map(function(d){ |
|||
return {type:d, leave: d3.sum(fData.map(function(t){ return t.leave[d];}))}; |
|||
}); |
|||
|
|||
// calculate total frequency by state for all segment.
|
|||
var sF = fData.map(function(d){return [d.l_month,d.total];}); |
|||
|
|||
var hG = histoGram(sF), // create the histogram.
|
|||
pC = pieChart(tF), // create the pie-chart.
|
|||
leg= legend(tF); // create the legend.
|
|||
} |
|||
}); |
|||
}, |
|||
render_graphs: function(){ |
|||
/** |
|||
* Render various graphs related to employee data. |
|||
* This function renders different graphs including employee contracts, time off, payslips, |
|||
* leave, and expenses. It checks if the current user is logged in as an employee and then |
|||
* calls individual functions to render each graph. |
|||
*/ |
|||
var self = this; |
|||
if (this.login_employee){ |
|||
self.render_employee_contracts_graph(); |
|||
self.render_employee_time_off_graph(); |
|||
self.render_employee_payslips_graph(); |
|||
self.render_leave_graph(); |
|||
self.render_expense_graph(); |
|||
|
|||
} |
|||
}, |
|||
render_employee_time_off_graph(){ |
|||
/** |
|||
* Render the employee time off graph. |
|||
*/ |
|||
var self = this; |
|||
var w = 200; |
|||
var h = 200; |
|||
var r = h/2; |
|||
var elem = this.$('.time_off_graph'); |
|||
var colors = ['#70cac1', '#659d4e', '#208cc2', '#4d6cb1', '#584999', '#8e559e', '#cf3650', '#f65337', '#fe7139', |
|||
'#ffa433', '#ffc25b', '#f8e54b']; |
|||
var color = d3.scale.ordinal().range(colors); |
|||
rpc.query({ |
|||
model: "hr.leave", |
|||
method: "get_employee_time_off", |
|||
|
|||
}).then(function (data) { |
|||
if (data){ |
|||
var segColor = {}; |
|||
var vis = d3.select(elem[0]).append("svg:svg").data([data]).attr("width", w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")"); |
|||
var pie = d3.layout.pie().value(function(d){return d.value;}); |
|||
var arc = d3.svg.arc().outerRadius(r); |
|||
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice"); |
|||
arcs.append("svg:path") |
|||
.attr("fill", function(d, i){ |
|||
return color(i); |
|||
}) |
|||
.attr("d", function (d) { |
|||
return arc(d); |
|||
}); |
|||
|
|||
var legend = d3.select(elem[0]).append("table").attr('class','legend'); |
|||
// create one row per segment.
|
|||
var tr = legend.append("tbody").selectAll("tr").data(data).enter().append("tr"); |
|||
// create the first column for each segment.
|
|||
tr.append("td").append("svg").attr("width", '16').attr("height", '16').append("rect") |
|||
.attr("width", '16').attr("height", '16') |
|||
.attr("fill",function(d, i){ return color(i) }); |
|||
// create the second column for each segment.
|
|||
tr.append("td").attr("style","font-weight: bold;").text(function(d){ return d.label;}); |
|||
// create the third column for each segment.
|
|||
tr.append("td").attr("class",'legendFreq').attr("style","border: 5px solid transparent; font-weight: bold;") |
|||
.text(function(d){ return d.value;}); |
|||
} |
|||
}); |
|||
|
|||
}, |
|||
render_employee_payslips_graph(){ |
|||
/** |
|||
* Render the Employee payslips graph. |
|||
*/ |
|||
var self = this; |
|||
var w = 200; |
|||
var h = 200; |
|||
var r = h/2; |
|||
var elem = this.$('.emp_payslips_graph'); |
|||
var colors = ['#70cac1', '#659d4e', '#208cc2', '#4d6cb1', '#584999', '#8e559e', '#cf3650', '#f65337', '#fe7139', |
|||
'#ffa433', '#ffc25b', '#f8e54b']; |
|||
var color = d3.scale.ordinal().range(colors); |
|||
rpc.query({ |
|||
model: "hr.payslip", |
|||
method: "get_employee_payslips", |
|||
}).then(function (data) { |
|||
if(data){ |
|||
var segColor = {}; |
|||
var vis = d3.select(elem[0]).append("svg:svg").data([data]).attr("width", w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")"); |
|||
var pie = d3.layout.pie().value(function(d){return d.value;}); |
|||
var arc = d3.svg.arc().outerRadius(r); |
|||
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice"); |
|||
arcs.append("svg:path") |
|||
.attr("fill", function(d, i){ |
|||
return color(i); |
|||
}) |
|||
.attr("d", function (d) { |
|||
return arc(d); |
|||
}); |
|||
|
|||
var legend = d3.select(elem[0]).append("table").attr('class','legend'); |
|||
|
|||
// create one row per segment.
|
|||
var tr = legend.append("tbody").selectAll("tr").data(data).enter().append("tr"); |
|||
|
|||
// create the first column for each segment.
|
|||
tr.append("td").append("svg").attr("width", '16').attr("height", '16').append("rect") |
|||
.attr("width", '16').attr("height", '16') |
|||
.attr("fill",function(d, i){ return color(i) }); |
|||
|
|||
// create the second column for each segment.
|
|||
tr.append("td").attr("style","font-weight: bold;").text(function(d){ return d.label;}); |
|||
|
|||
// create the third column for each segment.
|
|||
tr.append("td").attr("class",'legendFreq').attr("style","border: 5px solid transparent; font-weight: bold;") |
|||
.text(function(d){ return d.value;}); |
|||
} |
|||
}); |
|||
|
|||
}, |
|||
render_employee_contracts_graph(){ |
|||
/** |
|||
* Render the employee contracts graph. |
|||
*/ |
|||
var self = this; |
|||
var w = 200; |
|||
var h = 200; |
|||
var r = h/2; |
|||
var elem = this.$('.emp_contracts_graph'); |
|||
var colors = ['#70cac1', '#659d4e', '#208cc2', '#4d6cb1', '#584999', '#8e559e', '#cf3650', '#f65337', '#fe7139', |
|||
'#ffa433', '#ffc25b', '#f8e54b']; |
|||
var color = d3.scale.ordinal().range(colors); |
|||
rpc.query({ |
|||
model: "hr.contract", |
|||
method: "get_employee_contract", |
|||
}).then(function (data) { |
|||
if(data){ |
|||
var segColor = {}; |
|||
var vis = d3.select(elem[0]).append("svg:svg").data([data]).attr("width", w).attr("height", h).append("svg:g").attr("transform", "translate(" + r + "," + r + ")"); |
|||
var pie = d3.layout.pie().value(function(d){return d.value;}); |
|||
var arc = d3.svg.arc().outerRadius(r); |
|||
var arcs = vis.selectAll("g.slice").data(pie).enter().append("svg:g").attr("class", "slice"); |
|||
arcs.append("svg:path") |
|||
.attr("fill", function(d, i){ |
|||
return color(i); |
|||
}) |
|||
.attr("d", function (d) { |
|||
return arc(d); |
|||
}); |
|||
|
|||
var legend = d3.select(elem[0]).append("table").attr('class','legend'); |
|||
|
|||
// create one row per segment.
|
|||
var tr = legend.append("tbody").selectAll("tr").data(data).enter().append("tr"); |
|||
|
|||
// create the first column for each segment.
|
|||
tr.append("td").append("svg").attr("width", '16').attr("height", '16').append("rect") |
|||
.attr("width", '16').attr("height", '16') |
|||
.attr("fill",function(d, i){ return color(i) }); |
|||
|
|||
// create the second column for each segment.
|
|||
tr.append("td").attr("style","font-weight: bold;").text(function(d){ return d.label;}); |
|||
|
|||
// create the third column for each segment.
|
|||
tr.append("td").attr("class",'legendFreq').attr("style","border: 5px solid transparent; font-weight: bold;") |
|||
.text(function(d){ return d.value;}); |
|||
} |
|||
}); |
|||
|
|||
}, |
|||
update_leave_trend: function(){ |
|||
/** |
|||
* Update the leave trend graph. |
|||
*/ |
|||
var self = this; |
|||
rpc.query({ |
|||
model: "hr.employee", |
|||
method: "employee_leave_trend", |
|||
}).then(function (data) { |
|||
if(data){ |
|||
var elem = self.$('.leave_trend'); |
|||
var margin = {top: 30, right: 20, bottom: 30, left: 80}, |
|||
width = 500 - margin.left - margin.right, |
|||
height = 250 - margin.top - margin.bottom; |
|||
|
|||
// Set the ranges
|
|||
var x = d3.scale.ordinal() |
|||
.rangeRoundBands([0, width], 1); |
|||
|
|||
var y = d3.scale.linear() |
|||
.range([height, 0]); |
|||
|
|||
// Define the axes
|
|||
var xAxis = d3.svg.axis().scale(x) |
|||
.orient("bottom"); |
|||
|
|||
var yAxis = d3.svg.axis().scale(y) |
|||
.orient("left").ticks(5); |
|||
|
|||
var valueline = d3.svg.line() |
|||
.x(function(d) { return x(d.l_month); }) |
|||
.y(function(d) { return y(d.leave); }); |
|||
|
|||
|
|||
var svg = d3.select(elem[0]).append("svg") |
|||
.attr("width", width + margin.left + margin.right) |
|||
.attr("height", height + margin.top + margin.bottom) |
|||
.append("g") |
|||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|||
|
|||
x.domain(data.map(function(d) { return d.l_month; })); |
|||
y.domain([0, d3.max(data, function(d) { return d.leave; })]); |
|||
|
|||
// Add the X Axis
|
|||
svg.append("g") |
|||
.attr("class", "x axis") |
|||
.attr("transform", "translate(0," + height + ")") |
|||
.call(xAxis); |
|||
|
|||
// Add the Y Axis
|
|||
svg.append("g") |
|||
.attr("class", "y axis") |
|||
.call(yAxis); |
|||
|
|||
svg.append("path") |
|||
.attr("class", "line") |
|||
.attr("d", valueline(data)); |
|||
|
|||
// Add the scatterplot
|
|||
svg.selectAll("dot") |
|||
.data(data) |
|||
.enter().append("circle") |
|||
.attr("r", 3) |
|||
.attr("cx", function(d) { return x(d.l_month); }) |
|||
.attr("cy", function(d) { return y(d.leave); }) |
|||
// .on('mouseover', function() { d3.select(this).transition().duration(500).ease("elastic").attr('r', 3 * 2) })
|
|||
// .on('mouseout', function() { d3.select(this).transition().duration(500).ease("in-out").attr('r', 3) });
|
|||
.on("mouseover", function() { tooltip.style("display", null); |
|||
d3.select(this).transition().duration(500).ease("elastic").attr('r', 3 * 2) |
|||
}) |
|||
.on("mouseout", function() { tooltip.style("display", "none"); |
|||
d3.select(this).transition().duration(500).ease("in-out").attr('r', 3) |
|||
}) |
|||
.on("mousemove", function(d) { |
|||
var xPosition = d3.mouse(this)[0] - 15; |
|||
var yPosition = d3.mouse(this)[1] - 25; |
|||
tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")"); |
|||
tooltip.select("text").text(d.leave); |
|||
}); |
|||
|
|||
var tooltip = svg.append("g") |
|||
.attr("class", "tooltip") |
|||
.style("display", "none"); |
|||
|
|||
tooltip.append("rect") |
|||
.attr("width", 30) |
|||
.attr("height", 20) |
|||
.attr("fill", "black") |
|||
.style("opacity", 0.5); |
|||
|
|||
tooltip.append("text") |
|||
.attr("x", 15) |
|||
.attr("dy", "1.2em") |
|||
.style("text-anchor", "middle") |
|||
.attr("font-size", "12px") |
|||
.attr("font-weight", "bold"); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
}); |
|||
core.action_registry.add('payroll_dashboard', PayrollDashboard); |
|||
return PayrollDashboard; |
|||
}); |
@ -0,0 +1,268 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!--Payroll Dashboard View--> |
|||
<templates id="template" xml:space="preserve"> |
|||
<t t-name="PayrollDashboardMain"> |
|||
<div class="oh_dashboards"> |
|||
<div class="container-fluid o_payroll_dashboard"> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
<t t-name="EmployeeWarning"> |
|||
<div class="row" style="margin-top:1%;margin-left:40%;"> |
|||
<div class="col-md-12 "> |
|||
<div> |
|||
<div class="rounded mx-auto d-block"> |
|||
<div> |
|||
<p style="color:red;">Error : Could not find employee linked to user</p> |
|||
<p style="color:red;">Please contact system admin for the setup</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
<t t-name="EmployeeDetails"> |
|||
<link rel="stylesheet" |
|||
href="/hr_payroll_dashboard/static/src/css/dashboard.css"/> |
|||
<div class="row main-section"> |
|||
<div class="col-md-4 col-sm-6 oh-payroll"> |
|||
<div class="oh-card o_attendances" style="width: 288px;"> |
|||
<div class="oh-card-body"> |
|||
<div class="stat-widget-one"> |
|||
<div class="stat-icon" style="background:#5bcbd0"> |
|||
<i class="fa fa-calendar"/> |
|||
</div> |
|||
<div class="stat-content"> |
|||
<div class="stat-head">Attendances</div> |
|||
<div class="stat_count"> |
|||
<t t-esc="widget.login_employee['emp_timesheets']"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-4 col-sm-6 oh-payroll"> |
|||
<div class="oh-card o_leave" style="width: 288px;"> |
|||
<div class="oh-card-body"> |
|||
<div class="stat-widget-one"> |
|||
<div class="stat-icon" style="background:#645bd0"> |
|||
<i class="fa fa-calendar-minus-o"/> |
|||
</div> |
|||
<div class="stat-content"> |
|||
<div class="stat-head">Leave Requests</div> |
|||
<div class="stat_count"> |
|||
<t t-esc="widget.login_employee['emp_leave']"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-4 col-sm-6 oh-payroll"> |
|||
<div class="oh-card o_payslips" style="width: 350px;"> |
|||
<div class="oh-card-body"> |
|||
<div class="stat-widget-one"> |
|||
<div class="stat-icon" style="background:#85d05b"> |
|||
<i class="fa fa-money"/> |
|||
</div> |
|||
<div class="stat-content"> |
|||
<div class="stat-head">Payslips</div> |
|||
<div class="stat_count"> |
|||
<t t-esc="widget.login_employee['payslip_count']"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-4 col-sm-6 oh-payroll"> |
|||
<div class="oh-card o_contracts" style="width: 288px;"> |
|||
<div class="oh-card-body"> |
|||
<div class="stat-widget-one"> |
|||
<div class="stat-icon" style="background:#d05bb8"> |
|||
<i class="fa fa-handshake-o"/> |
|||
</div> |
|||
<div class="stat-content"> |
|||
<div class="stat-head">Contracts</div> |
|||
<div class="stat_count"> |
|||
<t t-esc="widget.login_employee['emp_contracts_count']"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-4 col-sm-6 oh-payroll"> |
|||
<div class="oh-card o_salary_rules" style="width: 288px;"> |
|||
<div class="oh-card-body"> |
|||
<div class="stat-widget-one"> |
|||
<div class="stat-icon" style="background:#FCF030"> |
|||
<i class='fa fa-money'/> |
|||
</div> |
|||
<div class="stat-content"> |
|||
<div class="stat-head">Salary Rules</div> |
|||
<div class="stat_count"> |
|||
<t t-esc="widget.login_employee['salary_rule_count']"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-4 col-sm-6 oh-payroll"> |
|||
<div class="oh-card o_salary_structures" style="width: 350px;"> |
|||
<div class="oh-card-body"> |
|||
<div class="stat-widget-one"> |
|||
<div class="stat-icon" style="background:#FFA742"> |
|||
<i class='fa fa-money'/> |
|||
</div> |
|||
<div class="stat-content"> |
|||
<div class="stat-head">Salary Structures</div> |
|||
<div class="stat_count"> |
|||
<t t-esc="widget.login_employee['salary_structure_count']"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<t t-name="PayrollChart"> |
|||
<div class="col-xs-12 col-sm-12 col-lg-12 col-md-12"> |
|||
<div class="row" style="margin:0px;"> |
|||
<div class="col-md-6 monthly_expense_trend"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="card-title"> |
|||
<b> |
|||
<h3 class="custom-h3"> |
|||
Monthly Expense Analysis |
|||
</h3> |
|||
</b> |
|||
</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div class="expense_graph"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-6 my_leave_graph"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="card-title"> |
|||
<b> |
|||
<h3 class="custom-h3"> |
|||
My Leave Analysis |
|||
</h3> |
|||
</b> |
|||
</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div class="leave_trend"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
</t> |
|||
<t t-name="ManagerLeaveDashboard"> |
|||
<div class="row" style="margin:0px;"> |
|||
<div class="col-md-12 monthly_leave_graph_view"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="card-title"> |
|||
<b> |
|||
<h3 class="custom-h3"> |
|||
Monthly Leave Analysis |
|||
</h3> |
|||
</b> |
|||
</div> |
|||
</div> |
|||
<div class="card-body"> |
|||
<div class="leave_graph justify-content-center"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
|
|||
<t t-name="PayrollManagerDashboard"> |
|||
<br/> |
|||
<br/> |
|||
<div class="row" style="margin:0px;"> |
|||
<div class="col-md-4" id="col-graph"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="custom-h3 card-title"> |
|||
<b> |
|||
<span style="font-weight:bold;"> |
|||
PAYSLIPS ANALYSIS |
|||
</span> |
|||
</b> |
|||
</div> |
|||
</div> |
|||
<div class="card-body mt-3" id="in_ex_body_hide"> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
<div class="chart"> |
|||
<div style="text-align: center;" |
|||
class="emp_payslips_graph"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div class="col-md-4" id="col-graph"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="custom-h3 card-title"> |
|||
<b> |
|||
<span style="font-weight:bold;"> |
|||
CONTRACT ANALYSIS |
|||
</span> |
|||
</b> |
|||
</div> |
|||
</div> |
|||
<div class="card-body mt-3" id="in_ex_body_hide"> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
<div class="chart"> |
|||
<div style="text-align: center;" |
|||
class="emp_contracts_graph"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="col-md-4" id="col-graph"> |
|||
<div class="card"> |
|||
<div class="card-header"> |
|||
<div class="custom-h3 card-title"> |
|||
<b> |
|||
<span style="font-weight:bold;"> |
|||
Time Off Analysis |
|||
</span> |
|||
</b> |
|||
</div> |
|||
</div> |
|||
<div class="card-body mt-3" id="in_ex_body_hide"> |
|||
<div class="row"> |
|||
<div class="col-md-12"> |
|||
<div class="chart"> |
|||
<div style="text-align: center;" |
|||
class="time_off_graph"/> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</t> |
|||
</templates> |
@ -0,0 +1,12 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<odoo> |
|||
<record id="action_hr_payroll_dashboard_menu" model="ir.actions.client"> |
|||
<field name="name">Dashboard</field> |
|||
<field name="tag">payroll_dashboard</field> |
|||
</record> |
|||
<menuitem id="hr_payroll_dashboard_menu" |
|||
name="Dashboard" |
|||
action="action_hr_payroll_dashboard_menu" |
|||
parent="hr_payroll_community.menu_hr_payroll_community_root" |
|||
sequence="1"/> |
|||
</odoo> |