@ -0,0 +1,46 @@ |
|||||
|
.. 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 |
||||
|
|
||||
|
Project Dashboard |
||||
|
================= |
||||
|
In this dashboard you can get Detailed Dashboard View for Project |
||||
|
|
||||
|
Configuration |
||||
|
============= |
||||
|
* No need of any configuration. |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
General Public License, Version 3 (AGPL v3). |
||||
|
(https://www.gnu.org/licenses/agpl-3.0-standalone.html) |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
* Developer: (V17) Mruthul Raj, 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 `Our Website <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Mruthul Raj @cybrosys(odoo@cybrosys.com) |
||||
|
# |
||||
|
# 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 controllers |
||||
|
from . import models |
@ -0,0 +1,48 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Mruthul Raj @cybrosys(odoo@cybrosys.com) |
||||
|
# |
||||
|
# 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': 'Project Dashboard', |
||||
|
'version': '17.0.1.0.0', |
||||
|
'category': 'Extra Tools', |
||||
|
'summary': """Get a Detailed View for Project.""", |
||||
|
'description': """In this dashboard user can get the Detailed Information |
||||
|
about Project, Task, Employee, Hours recorded, Total Margin and Total |
||||
|
Sale Orders.""", |
||||
|
'author': 'Cybrosys Techno Solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': 'https://www.cybrosys.com', |
||||
|
'depends': ['sale_management', 'project', 'sale_timesheet'], |
||||
|
'data': ['views/dashboard_views.xml'], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'project_dashboard_odoo/static/src/js/dashboard.js', |
||||
|
'project_dashboard_odoo/static/src/css/dashboard.css', |
||||
|
'project_dashboard_odoo/static/src/xml/dashboard_templates.xml', |
||||
|
'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js' |
||||
|
]}, |
||||
|
'images': ['static/description/banner.png'], |
||||
|
'license': 'AGPL-3', |
||||
|
'installable': True, |
||||
|
'application': False, |
||||
|
'auto_install': False, |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Mruthul Raj @cybrosys(odoo@cybrosys.com) |
||||
|
# |
||||
|
# 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 project_dashboard_odoo |
@ -0,0 +1,380 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Mruthul Raj @cybrosys(odoo@cybrosys.com) |
||||
|
# |
||||
|
# 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 datetime |
||||
|
from odoo import http |
||||
|
from odoo.http import request |
||||
|
|
||||
|
|
||||
|
class ProjectFilter(http.Controller): |
||||
|
"""The ProjectFilter class provides the filter option to the js. |
||||
|
When applying the filter returns the corresponding data.""" |
||||
|
|
||||
|
@http.route('/project/task/count', auth='public', type='json') |
||||
|
def get_project_task_count(self): |
||||
|
"""Summary: |
||||
|
when the page is loaded, get the data from different models and |
||||
|
transfer to the js file. |
||||
|
Return a dictionary variable. |
||||
|
Return: |
||||
|
type:It is a dictionary variable. This dictionary contains data for |
||||
|
the project task graph.""" |
||||
|
project_name = [] |
||||
|
total_task = [] |
||||
|
colors = [] |
||||
|
user_employee = request.env.user.partner_id |
||||
|
if user_employee.user_has_groups('project.group_project_manager'): |
||||
|
project_ids = request.env['project.project'].search([]) |
||||
|
else: |
||||
|
project_ids = request.env['project.project'].search( |
||||
|
[('user_id', '=', request.env.uid)]) |
||||
|
for project_id in project_ids: |
||||
|
project_name.append(project_id.name) |
||||
|
task = request.env['project.task'].search_count( |
||||
|
[('project_id', '=', project_id.id)]) |
||||
|
total_task.append(task) |
||||
|
color_code = request.env['project.project'].get_color_code() |
||||
|
colors.append(color_code) |
||||
|
return { |
||||
|
'project': project_name, |
||||
|
'task': total_task, |
||||
|
'color': colors |
||||
|
} |
||||
|
|
||||
|
@http.route('/employee/timesheet', auth='public', type='json') |
||||
|
def get_top_timesheet_employees(self): |
||||
|
"""Summary: |
||||
|
when the page is loaded, get the data for the timesheet graph. |
||||
|
Return: |
||||
|
type:It is a list. This list contains data that affects the graph |
||||
|
of employees.""" |
||||
|
query = '''select hr_employee.name as employee,sum(unit_amount) as unit |
||||
|
from account_analytic_line |
||||
|
inner join hr_employee on hr_employee.id = |
||||
|
account_analytic_line.employee_id |
||||
|
group by hr_employee.id ORDER |
||||
|
BY unit DESC Limit 10 ''' |
||||
|
request._cr.execute(query) |
||||
|
top_product = request._cr.dictfetchall() |
||||
|
unit = [record.get('unit') for record in top_product] |
||||
|
employee = [record.get('employee') for record in top_product] |
||||
|
return [unit, employee] |
||||
|
|
||||
|
@http.route('/project/filter', auth='public', type='json') |
||||
|
def project_filter(self): |
||||
|
"""Summary: |
||||
|
transferring data to the selection field that works as a filter |
||||
|
Returns: |
||||
|
type:list of lists, it contains the data for the corresponding |
||||
|
filter.""" |
||||
|
project_list = [] |
||||
|
employee_list = [] |
||||
|
project_ids = request.env['project.project'].search([]) |
||||
|
employee_ids = request.env['hr.employee'].search([]) |
||||
|
# getting partner data |
||||
|
for employee_id in employee_ids: |
||||
|
dic = {'name': employee_id.name, |
||||
|
'id': employee_id.id} |
||||
|
employee_list.append(dic) |
||||
|
for project_id in project_ids: |
||||
|
dic = {'name': project_id.name, |
||||
|
'id': project_id.id} |
||||
|
project_list.append(dic) |
||||
|
return [project_list, employee_list] |
||||
|
|
||||
|
@http.route('/project/filter-apply', auth='public', type='json') |
||||
|
def project_filter_apply(self, **kw): |
||||
|
"""Summary: |
||||
|
transferring data after filter 9th applied |
||||
|
Args: |
||||
|
kw(dict):This parameter contains the value of selection field |
||||
|
Returns: |
||||
|
type:dict, it contains the data for the corresponding |
||||
|
filtrated transferring data to ui after filtration.""" |
||||
|
data = kw['data'] |
||||
|
# checking the employee selected or not |
||||
|
if data['employee'] == 'null': |
||||
|
emp_selected = [employee.id for employee in |
||||
|
request.env['hr.employee'].search([])] |
||||
|
else: |
||||
|
emp_selected = [int(data['employee'])] |
||||
|
start_date = data['start_date'] |
||||
|
end_date = data['end_date'] |
||||
|
# checking the dates are selected or not |
||||
|
if start_date != 'null' and end_date != 'null': |
||||
|
start_date = datetime.datetime.strptime(start_date, |
||||
|
"%Y-%m-%d").date() |
||||
|
end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d").date() |
||||
|
if data['project'] == 'null': |
||||
|
pro_selected = [project.id for project in |
||||
|
request.env['project.project'].search( |
||||
|
[('date_start', '>', start_date), |
||||
|
('date_start', '<', end_date)])] |
||||
|
else: |
||||
|
pro_selected = [int(data['project'])] |
||||
|
elif start_date == 'null' and end_date != 'null': |
||||
|
end_date = datetime.datetime.strptime(end_date, "%Y-%m-%d").date() |
||||
|
if data['project'] == 'null': |
||||
|
pro_selected = [project.id for project in |
||||
|
request.env['project.project'].search( |
||||
|
[('date_start', '<', end_date)])] |
||||
|
else: |
||||
|
pro_selected = [int(data['project'])] |
||||
|
elif start_date != 'null' and end_date == 'null': |
||||
|
start_date = datetime.datetime.strptime(start_date, |
||||
|
"%Y-%m-%d").date() |
||||
|
if data['project'] == 'null': |
||||
|
pro_selected = [project.id for project in |
||||
|
request.env['project.project'].search( |
||||
|
[('date_start', '>', start_date)])] |
||||
|
else: |
||||
|
pro_selected = [int(data['project'])] |
||||
|
else: |
||||
|
if data['project'] == 'null': |
||||
|
pro_selected = [project.id for project in |
||||
|
request.env['project.project'].search([])] |
||||
|
else: |
||||
|
pro_selected = [int(data['project'])] |
||||
|
report_project = request.env['timesheets.analysis.report'].search( |
||||
|
[('project_id', 'in', pro_selected), |
||||
|
('employee_id', 'in', emp_selected)]) |
||||
|
analytic_project = request.env['account.analytic.line'].search( |
||||
|
[('project_id', 'in', pro_selected), |
||||
|
('employee_id', 'in', emp_selected)]) |
||||
|
margin = round(sum(report_project.mapped('margin')), 2) |
||||
|
sale_orders = [] |
||||
|
for rec in analytic_project: |
||||
|
if rec.order_id.id and rec.order_id.id not in sale_orders: |
||||
|
sale_orders.append(rec.order_id.id) |
||||
|
total_time = sum(analytic_project.mapped('unit_amount')) |
||||
|
return { |
||||
|
'total_project': pro_selected, |
||||
|
'total_emp': emp_selected, |
||||
|
'total_task': [rec.id for rec in request.env['project.task'].search( |
||||
|
[('project_id', 'in', pro_selected)])], |
||||
|
'hours_recorded': total_time, |
||||
|
'list_hours_recorded': [rec.id for rec in analytic_project], |
||||
|
'total_margin': margin, |
||||
|
'total_so': sale_orders |
||||
|
} |
||||
|
|
||||
|
@http.route('/get/tiles/data', auth='public', type='json') |
||||
|
def get_tiles_data(self): |
||||
|
"""Summary: |
||||
|
when the page is loaded, get the data from different models and |
||||
|
transfer to the js file. |
||||
|
Return a dictionary variable. |
||||
|
Return: |
||||
|
type:It is a dictionary variable. This dictionary contains data that |
||||
|
affects the dashboard view.""" |
||||
|
user_employee = request.env.user.partner_id |
||||
|
if user_employee.user_has_groups('project.group_project_manager'): |
||||
|
all_project = request.env['project.project'].search([]) |
||||
|
all_task = request.env['project.task'].search([]) |
||||
|
analytic_project = request.env['account.analytic.line'].search([]) |
||||
|
report_project = request.env['timesheets.analysis.report'].search( |
||||
|
[]) |
||||
|
margin = round(sum(report_project.mapped('margin')), 2) |
||||
|
total_time = sum(analytic_project.mapped('unit_amount')) |
||||
|
employees = request.env['hr.employee'].search([]) |
||||
|
task = request.env['project.task'].sudo().search_read([ |
||||
|
('sale_order_id', '!=', False) |
||||
|
], ['sale_order_id']) |
||||
|
task_so_ids = [o['sale_order_id'][0] for o in task] |
||||
|
sale_orders = request.env['sale.order'].browse(task_so_ids) |
||||
|
project_stage_ids = request.env['project.project.stage'].search([]) |
||||
|
project_stage_list = [] |
||||
|
for project_stage_id in project_stage_ids: |
||||
|
total_projects = request.env[ |
||||
|
'project.project'].sudo().search_count( |
||||
|
[('stage_id', '=', project_stage_id.id)]) |
||||
|
project_stage_list.append({'name': project_stage_id.name, |
||||
|
'projects': total_projects}) |
||||
|
return { |
||||
|
'total_projects': len(all_project), |
||||
|
'total_projects_ids': all_project.ids, |
||||
|
'total_tasks': len(all_task), |
||||
|
'total_tasks_ids': all_task.ids, |
||||
|
'total_hours': total_time, |
||||
|
'total_profitability': margin, |
||||
|
'total_employees': len(employees), |
||||
|
'total_sale_orders': len(sale_orders), |
||||
|
'sale_orders_ids': sale_orders.mapped('id'), |
||||
|
'project_stage_list': project_stage_list, |
||||
|
'flag': 1} |
||||
|
else: |
||||
|
all_project = request.env['project.project'].search( |
||||
|
[('user_id', '=', request.env.uid)]) |
||||
|
all_task = [] |
||||
|
for task in request.env['project.task'].search([]): |
||||
|
for assignee in task.user_ids: |
||||
|
if assignee.id == request.env.uid: |
||||
|
all_task.append(task.id) |
||||
|
analytic_project = request.env['account.analytic.line'].search( |
||||
|
[('project_id', 'in', all_project.ids)]) |
||||
|
total_time = sum(analytic_project.mapped('unit_amount')) |
||||
|
task = request.env['project.task'].sudo().search_read([ |
||||
|
('sale_order_id', '!=', False), |
||||
|
('project_id', 'in', all_project.ids) |
||||
|
], ['sale_order_id']) |
||||
|
task_so_ids = [o['sale_order_id'][0] for o in task] |
||||
|
sale_orders = request.mapped('sale_line_id.order_id') | request.env[ |
||||
|
'sale.order'].browse(task_so_ids) |
||||
|
project_stage_ids = request.env['project.project.stage'].search([]) |
||||
|
project_stage_list = [] |
||||
|
for project_stage_id in project_stage_ids: |
||||
|
total_projects = request.env['project.project'].search_count( |
||||
|
[('stage_id', '=', project_stage_id.id), |
||||
|
('id', 'in', all_project.ids)]) |
||||
|
project_stage_list.append({ |
||||
|
'name': project_stage_id.name, |
||||
|
'projects': total_projects |
||||
|
}) |
||||
|
return { |
||||
|
'total_projects': len(all_project), |
||||
|
'total_projects_ids': all_project.ids, |
||||
|
'total_tasks': len(all_task), |
||||
|
'total_tasks_ids': all_task, |
||||
|
'total_hours': total_time, |
||||
|
'total_sale_orders': len(sale_orders), |
||||
|
'sale_orders_ids': sale_orders.mapped('id'), |
||||
|
'project_stage_list': project_stage_list, |
||||
|
'flag': 2} |
||||
|
|
||||
|
@http.route('/get/hours', auth='public', type='json') |
||||
|
def get_hours_data(self): |
||||
|
"""Summary: |
||||
|
when the page is loaded get the data for the hour table. |
||||
|
Return: |
||||
|
type:It is a dictionary variable. This dictionary contains data that |
||||
|
hours table.""" |
||||
|
user_employee = request.env.user.partner_id |
||||
|
if user_employee.user_has_groups('project.group_project_manager'): |
||||
|
query = '''SELECT sum(unit_amount) as hour_recorded FROM |
||||
|
account_analytic_line WHERE |
||||
|
timesheet_invoice_type='non_billable_project' ''' |
||||
|
request._cr.execute(query) |
||||
|
data = request._cr.dictfetchall() |
||||
|
hour_recorded = [] |
||||
|
for record in data: |
||||
|
hour_recorded.append(record.get('hour_recorded')) |
||||
|
query = '''SELECT sum(unit_amount) as hour_recorde FROM |
||||
|
account_analytic_line WHERE |
||||
|
timesheet_invoice_type='billable_time' ''' |
||||
|
request._cr.execute(query) |
||||
|
data = request._cr.dictfetchall() |
||||
|
hour_recorde = [] |
||||
|
for record in data: |
||||
|
hour_recorde.append(record.get('hour_recorde')) |
||||
|
query = '''SELECT sum(unit_amount) as billable_fix FROM |
||||
|
account_analytic_line WHERE |
||||
|
timesheet_invoice_type='billable_fixed' ''' |
||||
|
request._cr.execute(query) |
||||
|
data = request._cr.dictfetchall() |
||||
|
billable_fix = [] |
||||
|
for record in data: |
||||
|
billable_fix.append(record.get('billable_fix')) |
||||
|
query = '''SELECT sum(unit_amount) as non_billable FROM |
||||
|
account_analytic_line WHERE timesheet_invoice_type='non_billable' |
||||
|
''' |
||||
|
request._cr.execute(query) |
||||
|
data = request._cr.dictfetchall() |
||||
|
non_billable = [] |
||||
|
for record in data: |
||||
|
non_billable.append(record.get('non_billable')) |
||||
|
query = '''SELECT sum(unit_amount) as total_hr FROM |
||||
|
account_analytic_line WHERE |
||||
|
timesheet_invoice_type='non_billable_project' or |
||||
|
timesheet_invoice_type='billable_time' or |
||||
|
timesheet_invoice_type='billable_fixed' or |
||||
|
timesheet_invoice_type='non_billable' ''' |
||||
|
request._cr.execute(query) |
||||
|
data = request._cr.dictfetchall() |
||||
|
total_hr = [] |
||||
|
for record in data: |
||||
|
total_hr.append(record.get('total_hr')) |
||||
|
return { |
||||
|
'hour_recorded': hour_recorded, |
||||
|
'hour_recorde': hour_recorde, |
||||
|
'billable_fix': billable_fix, |
||||
|
'non_billable': non_billable, |
||||
|
'total_hr': total_hr, |
||||
|
} |
||||
|
else: |
||||
|
all_project = request.env['project.project'].search( |
||||
|
[('user_id', '=', request.env.uid)]).ids |
||||
|
analytic_project = request.env['account.analytic.line'].search( |
||||
|
[('project_id', 'in', all_project)]) |
||||
|
all_hour_recorded = analytic_project.filtered( |
||||
|
lambda x: x.timesheet_invoice_type == 'non_billable_project') |
||||
|
all_hour_recorde = analytic_project.filtered( |
||||
|
lambda x: x.timesheet_invoice_type == 'billable_time') |
||||
|
all_billable_fix = analytic_project.filtered( |
||||
|
lambda x: x.timesheet_invoice_type == 'billable_fixed') |
||||
|
all_non_billable = analytic_project.filtered( |
||||
|
lambda x: x.timesheet_invoice_type == 'non_billable') |
||||
|
hour_recorded = [sum(all_hour_recorded.mapped('unit_amount'))] |
||||
|
hour_recorde = [sum(all_hour_recorde.mapped('unit_amount'))] |
||||
|
billable_fix = [sum(all_billable_fix.mapped('unit_amount'))] |
||||
|
non_billable = [sum(all_non_billable.mapped('unit_amount'))] |
||||
|
total_hr = [ |
||||
|
sum(hour_recorded + hour_recorde + billable_fix + non_billable)] |
||||
|
return { |
||||
|
'hour_recorded': hour_recorded, |
||||
|
'hour_recorde': hour_recorde, |
||||
|
'billable_fix': billable_fix, |
||||
|
'non_billable': non_billable, |
||||
|
'total_hr': total_hr, |
||||
|
} |
||||
|
|
||||
|
@http.route('/get/task/data', auth='public', type='json') |
||||
|
def get_task_data(self): |
||||
|
""" |
||||
|
Summary: |
||||
|
when the page is loaded, get the data from different models and |
||||
|
transfer to the js file. |
||||
|
Return a dictionary variable. |
||||
|
Return: |
||||
|
type:It is a dictionary variable. This dictionary contains data that |
||||
|
affecting project task table.""" |
||||
|
user_employee = request.env.user.partner_id |
||||
|
if user_employee.user_has_groups('project.group_project_manager'): |
||||
|
request._cr.execute('''select project_task.name as task_name, |
||||
|
pro.name as project_name from project_task |
||||
|
Inner join project_project as pro on project_task.project_id |
||||
|
= pro.id ORDER BY project_name ASC''') |
||||
|
data = request._cr.fetchall() |
||||
|
project_name = [] |
||||
|
for rec in data: |
||||
|
project_name.append(list(rec)) |
||||
|
return { |
||||
|
'project': project_name |
||||
|
} |
||||
|
else: |
||||
|
all_project = request.env['project.project'].search( |
||||
|
[('user_id', '=', request.env.uid)]).ids |
||||
|
all_tasks = request.env['project.task'].search( |
||||
|
[('project_id', 'in', all_project)]) |
||||
|
task_project = [[task.name, task.project_id.name] for task in |
||||
|
all_tasks] |
||||
|
return { |
||||
|
'project': task_project |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
## Module <project_dashboard_odoo> |
||||
|
|
||||
|
#### 30.11.2023 |
||||
|
#### Version 17.0.1.0.0 |
||||
|
##### ADD |
||||
|
|
||||
|
- Initial Commit for Project Dashboard |
@ -0,0 +1,22 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Mruthul Raj @cybrosys(odoo@cybrosys.com) |
||||
|
# |
||||
|
# 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 project_project |
@ -0,0 +1,35 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Mruthul Raj @cybrosys(odoo@cybrosys.com) |
||||
|
# |
||||
|
# 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 random |
||||
|
from odoo import models |
||||
|
|
||||
|
|
||||
|
class Project(models.Model): |
||||
|
"""This class inherits from 'project.project' and adds custom functionality |
||||
|
to it.It provides methods to work with project data.""" |
||||
|
_inherit = 'project.project' |
||||
|
|
||||
|
def get_color_code(self): |
||||
|
"""Generate a random color code in hexadecimal format. |
||||
|
:return: A random color code in the format '#RRGGBB.'""" |
||||
|
color = f"#{random.randint(0, 0xFFFFFF):06x}" |
||||
|
return color |
@ -0,0 +1,35 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################## |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>). |
||||
|
# Author: Mruthul Raj @cybrosys(odoo@cybrosys.com) |
||||
|
# |
||||
|
# 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 random |
||||
|
from odoo import models |
||||
|
|
||||
|
|
||||
|
class ProjectProject(models.Model): |
||||
|
"""This class inherits from 'project.project' and adds custom functionality |
||||
|
to it.It provides methods to work with project data.""" |
||||
|
_inherit = 'project.project' |
||||
|
|
||||
|
def get_color_code(self): |
||||
|
"""Generate a random color code in hexadecimal format. |
||||
|
:return: A random color code in the format '#RRGGBB.'""" |
||||
|
color = f"#{random.randint(0, 0xFFFFFF):06x}" |
||||
|
return color |
After Width: | Height: | Size: 36 KiB |
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.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 11 KiB |
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: 80 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: 3.2 KiB |
After Width: | Height: | Size: 589 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 565 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 912 KiB |
After Width: | Height: | Size: 228 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 81 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 237 KiB |
After Width: | Height: | Size: 314 KiB |
After Width: | Height: | Size: 331 KiB |
After Width: | Height: | Size: 196 KiB |
After Width: | Height: | Size: 194 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,654 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>Odoo App 3 Index</title> |
||||
|
<!-- Bootstrap CSS --> |
||||
|
<link rel="stylesheet" |
||||
|
href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" |
||||
|
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" |
||||
|
crossorigin="anonymous"> |
||||
|
<link rel="stylesheet" |
||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css"> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" |
||||
|
rel="stylesheet"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<section> |
||||
|
<div class="container" |
||||
|
style="font-family: 'Inter', sans-serif !important;background-color: #fff !important;"> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 col-md-12 col-lg-12 d-flex justify-content-between flex-wrap align-items-sm-center" |
||||
|
style="border-bottom:1px solid rgba(0, 0, 0, 0.22)"> |
||||
|
<div class="my-3"> |
||||
|
<img src="assets/misc/Cybrosys R.png" |
||||
|
style="width:auto !important; height:40px !important"> |
||||
|
</div> |
||||
|
<div class="my-3 d-flex align-items-center"> |
||||
|
<div class="text-center" |
||||
|
style="background-color:#017E84 !important;font-size: 0.8rem !important; color:#fff !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width: 120px !important;"> |
||||
|
Community |
||||
|
</div> |
||||
|
<div class="text-center" |
||||
|
style="background-color:#875A7B !important; color:#fff !important;font-size: 0.8rem !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important;min-width: 120px !important;"> |
||||
|
Enterprise |
||||
|
</div> |
||||
|
<div class="text-center" |
||||
|
style="background-color:#7C7BAD !important; color:#fff !important;font-size: 0.8rem !important; font-weight:500 !important; padding:4px !important; margin:0 3px !important; border-radius:50px !important; min-width: 120px !important;"> |
||||
|
Odoo.sh |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-12 col-md-12 col-lg-12 text-center d-flex align-items-center flex-column" |
||||
|
style="margin: 80px 0px !important;"> |
||||
|
<h1 style="font-size: 2.8rem;font-weight: 700; color: |
||||
|
#1A202C;"> |
||||
|
Project Dashboard</h1> |
||||
|
<p class="my-3 mb-4" |
||||
|
style="max-width: 80%; font-weight: 400 !important; line-height: 32px; color: #718096;"> |
||||
|
In This Dashboard, You Can Get a Detailed View for Project. |
||||
|
</p> |
||||
|
<div style="width: 80%; margin-top: 3rem;"> |
||||
|
<img src="assets/screenshots/hero.gif" |
||||
|
class="img-responsive" width="100%" height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container mt-5 mb-5"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mt-4"> |
||||
|
<p class="m-0" |
||||
|
style="font-weight: 600; font-size: 24px; color:#714b67 !important"> |
||||
|
Key Highlights |
||||
|
</p> |
||||
|
</div> |
||||
|
<div class="row py-4"> |
||||
|
<div class="col-md-6 col-sm-12 p-3"> |
||||
|
<div class="d-flex h-100" style="padding: 30px;border-radius: 12px; |
||||
|
background: #FFF; |
||||
|
box-shadow: 1px 2px 3px 0px rgba(0, 0, 0, 0.25); "> |
||||
|
<div style="width: 36px; height: 36px; border-radius: 50%; background: #714B67; |
||||
|
display: flex; justify-content: center; align-items: center; |
||||
|
margin-right: 10px; flex-shrink: 0;"> |
||||
|
<i class="fa-solid fa-star " |
||||
|
style="color: #fff;font-size:14px;"></i> |
||||
|
</div> |
||||
|
<div> |
||||
|
<p style="color: #1A202C;font-weight: 600; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;"> |
||||
|
Dashboard view for Project module</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-6 col-sm-12 p-3"> |
||||
|
<div class="d-flex h-100" style="padding: 30px;border-radius: 12px; |
||||
|
background: #FFF; |
||||
|
box-shadow: 1px 2px 3px 0px rgba(0, 0, 0, 0.25); "> |
||||
|
<div style="width: 36px; height: 36px; border-radius: 50%; background: #714B67; |
||||
|
display: flex; justify-content: center; align-items: center; |
||||
|
margin-right: 10px; flex-shrink: 0;"> |
||||
|
<i class="fa-solid fa-star " |
||||
|
style="color: #fff;font-size:14px;"></i> |
||||
|
</div> |
||||
|
<div> |
||||
|
<p style="color: #1A202C;font-weight: 600; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;"> |
||||
|
Graphs included</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container rounded"> |
||||
|
<ul class="nav nav-tabs d-flex" |
||||
|
style="width: fit-content;margin: 0 auto;gap: 1rem;"> |
||||
|
<li class="col text-center py-2 text-nowrap " |
||||
|
style="color: #fff; background-color: #714B67;border-radius: 6px 6px 0px 0px;"> |
||||
|
<a |
||||
|
class="active show" data-toggle="tab" href="#tab1" |
||||
|
style="color: #fff;font-weight: 500; background-color: #714B67; text-decoration: none;"> |
||||
|
<i class="fa-regular fa-image pr-2" |
||||
|
style="color: #fff;"></i> |
||||
|
Screenshots</a></li> |
||||
|
<li class="col text-center py-2 text-nowrap " |
||||
|
style="color: #fff; background-color: #714B67;border-radius: 6px 6px 0px 0px;"> |
||||
|
<a |
||||
|
data-toggle="tab" href="#tab2" |
||||
|
style="color: #fff;font-weight: 500; text-decoration: none;"><i |
||||
|
class="fa-solid fa-star pr-2" |
||||
|
style="color: #fff;"></i>Features</a></li> |
||||
|
<li class="col text-center py-2 text-nowrap " |
||||
|
style="color: #fff; background-color: #714B67;border-radius: 6px 6px 0px 0px;"> |
||||
|
<a |
||||
|
data-toggle="tab" href="#tab3" |
||||
|
style="color: #fff;font-weight: 500; text-decoration: none; background-color: #714B67;"><i |
||||
|
class="fa-solid fa-book-open pr-2" |
||||
|
style="color: #fff;"></i>Released Notes</a></li> |
||||
|
</ul> |
||||
|
<div class="tab-content" |
||||
|
style="background-color: rgba(121, 113, 119, 0.04);"> |
||||
|
<div id="tab1" class="tab-pane fade in active show"> |
||||
|
<div class="col-lg-12 py-2" |
||||
|
style="padding: 1rem 4rem !important;"> |
||||
|
<div |
||||
|
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<div class="row justify-content-center p-3 w-100 m-0"> |
||||
|
<img src="assets/screenshots/Screenshot.png" |
||||
|
class="img-responsive" width="100%" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
<div class="px-3"> |
||||
|
<h4 class="mt-2" |
||||
|
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important"> |
||||
|
Different Types of Graphs.</h4> |
||||
|
<p class="m-0" style="color:#718096">Project |
||||
|
Dashboard has different types of Graphs that |
||||
|
will give you a complete analyzing of the Project |
||||
|
module.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-12 py-2" |
||||
|
style="padding: 1rem 4rem !important;"> |
||||
|
<div |
||||
|
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<div class="row justify-content-center p-3 w-100 m-0"> |
||||
|
<img src="assets/screenshots/Screenshot2.png" |
||||
|
class="img-responsive" width="100%" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-lg-12 py-2" |
||||
|
style="padding: 1rem 4rem !important;"> |
||||
|
<div |
||||
|
style="border: 1px solid #d8d6d6; border-radius: 4px; background: #fff; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<div class="row justify-content-center p-3 w-100 m-0"> |
||||
|
<img src="assets/screenshots/Screenshot4.png" |
||||
|
class="img-responsive" width="100%" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
<div class="px-3"> |
||||
|
<h4 class="mt-2" |
||||
|
style=" font-weight:600 !important; color:#282F33 !important; font-size:1.3rem !important"> |
||||
|
Project Table</h4> |
||||
|
<p class="m-0" style="color:#718096"> User can |
||||
|
see all Project ad its current Status.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div id="tab2" class="tab-pane fade"> |
||||
|
<div class="col-mg-12" style="padding: 1rem 4rem;"> |
||||
|
<ul style="list-style: none; padding: 1rem 0;font-weight: 500;"> |
||||
|
<li class="py-3" |
||||
|
style="font-weight: 500;background-color: #fff; border-radius: 4px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<span style="margin-right: 12px;"><img |
||||
|
src="assets/misc/star (1) 2.svg" |
||||
|
alt="" |
||||
|
width="16px"></span>User can see |
||||
|
all details about Project and Task through |
||||
|
Graphs. |
||||
|
</li> |
||||
|
<li class="py-3" |
||||
|
style="font-weight: 500;background-color: #fff; border-radius: 4px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<span style="margin-right: 12px;"><img |
||||
|
src="assets/misc/star (1) 2.svg" |
||||
|
alt="" |
||||
|
width="16px"></span>User can see all |
||||
|
details about Timesheet through Graphs. |
||||
|
</li> |
||||
|
<li class="py-3" |
||||
|
style="font-weight: 500;background-color: #fff; border-radius: 4px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<span style="margin-right: 12px;"><img |
||||
|
src="assets/misc/star (1) 2.svg" |
||||
|
alt="" |
||||
|
width="16px"></span>User can see all |
||||
|
Projects with Stages. |
||||
|
</li> |
||||
|
<li class="py-3" |
||||
|
style="font-weight: 500;background-color: #fff; border-radius: 4px; padding: 1rem; margin-bottom: 1rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<span style="margin-right: 12px;"><img |
||||
|
src="assets/misc/star (1) 2.svg" |
||||
|
alt="" |
||||
|
width="16px"></span>User can use |
||||
|
filter based on the Employee, Project and Dates. |
||||
|
</li> |
||||
|
|
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div id="tab3" class="tab-pane fade"> |
||||
|
<div class="col-mg-12 active" style="padding: 1rem 4rem;"> |
||||
|
<div class="py-3" |
||||
|
style="font-weight: 500;background-color: #fff; border-radius: 4px; padding: 1rem; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);"> |
||||
|
<div class="d-flex mb-3" |
||||
|
style="font-size: 0.8rem; font-weight: 500;"><span>Version |
||||
|
17.0.1.0.0</span><span |
||||
|
class="px-2">|</span><span |
||||
|
style="color: #714B67;font-weight: 600;">Released on:30th Nov 2023</span> |
||||
|
</div> |
||||
|
<p class="m-0" |
||||
|
style=" color:#718096!important; font-size:1rem !important;line-height: 28px;"> |
||||
|
Initial Commit for Project Dashboard.</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container mt-5"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mt-5"> |
||||
|
<p class="m-0" |
||||
|
style="font-weight: 600; font-size: 24px; color:#000 !important"> |
||||
|
Related Products</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div id="myCarousel" class="carousel slide py-3" data-ride="carousel"> |
||||
|
<div class="carousel-inner"> |
||||
|
<div class="carousel-item active"> |
||||
|
<div class="row p-4"> |
||||
|
<div class="col"> |
||||
|
<div class="p-3"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/base_accounting_kit/" style="color: #000; text-decoration: none;"> |
||||
|
<div style="border:1px solid #CBCBCB !important;border-radius: 4px;"> |
||||
|
<div style="width: 300px; "> |
||||
|
<img src="assets/modules/1.gif" alt="" width="100%" height="auto"> |
||||
|
|
||||
|
</div> |
||||
|
<p class="text-center pt-2 text-black font-weight-bold">Odoo 17 Full Accounting Kit</p> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col"> |
||||
|
<div class="p-3"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/invoice_format_editor/" style="color: #000; text-decoration: none;"> |
||||
|
<div style="border:1px solid #CBCBCB !important;border-radius: 4px;"> |
||||
|
<div style="width: 300px; "> |
||||
|
<img src="assets/modules/2.png" alt="" width="100%" height="auto"> |
||||
|
|
||||
|
</div> |
||||
|
<p class="text-center pt-2 text-black font-weight-bold">Invoice Format Editor</p> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col"> |
||||
|
<div class="p-3"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/inventory_barcode_scanning/" style="color: #000; text-decoration: none;"> |
||||
|
<div style="border:1px solid #CBCBCB !important;border-radius: 4px;"> |
||||
|
<div style="width: 300px; "> |
||||
|
<img src="assets/modules/3.png" alt="" width="100%" height="auto"> |
||||
|
|
||||
|
</div> |
||||
|
<p class="text-center pt-2 text-black font-weight-bold">Barcode scanning in Inventory</p> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="carousel-item"> |
||||
|
<div class="row p-4"> |
||||
|
<div class="col"> |
||||
|
<div class="p-3"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/whatsapp_redirect/" style="color: #000; text-decoration: none;"> |
||||
|
<div style="border:1px solid #CBCBCB !important;border-radius: 4px;"> |
||||
|
<div style="width: 300px; "> |
||||
|
<img src="assets/modules/4.jpg" alt="" width="100%" height="auto"> |
||||
|
|
||||
|
</div> |
||||
|
<p class="text-center pt-2 text-black font-weight-bold">Send Whatsapp Message</p> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col"> |
||||
|
<div class="p-3"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/base_account_budget/" style="color: #000; text-decoration: none;"> |
||||
|
<div style="border:1px solid #CBCBCB !important;border-radius: 4px;"> |
||||
|
<div style="width: 300px;"> |
||||
|
<img src="assets/modules/5.jpg" alt="" width="100%" height="auto"> |
||||
|
|
||||
|
</div> |
||||
|
<p class="text-center pt-2 text-black font-weight-bold">Budget Management</p> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col"> |
||||
|
<div class="p-3"> |
||||
|
<a href="https://apps.odoo.com/apps/modules/17.0/product_barcode/" style="color: #000; text-decoration: none;"> |
||||
|
<div style="border:1px solid #CBCBCB !important;border-radius: 4px;"> |
||||
|
<div style="width: 300px;"> |
||||
|
<img src="assets/modules/6.png" alt="" width="100%" height="auto"> |
||||
|
</div> |
||||
|
<p class="text-center pt-2 text-black font-weight-bold">Product Barcode Generator</p> |
||||
|
</div> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<a class="carousel-control-prev" href="#myCarousel" 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="#myCarousel" 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 class="container mt-5"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mt-4"> |
||||
|
<p class="m-0" |
||||
|
style="font-weight: 600; font-size: 24px; color:#000 !important"> |
||||
|
Our Services</p> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container my-5"> |
||||
|
<div class="row py-3"> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative;border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#13EA36 ; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/cogs.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Customization</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative;border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#DBC711; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/wrench.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Implementation</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative; border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#FF6B6B ; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/lifebuoy.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Support</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative; border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#FFA801 ; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/user.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Hire |
||||
|
Odoo Developer</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative; border-radius: 4px;"> |
||||
|
|
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#54A0FF; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/puzzle.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Integration</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative;border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#6D7680 ; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/update.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Migration</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative;border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#786FA6 ; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/consultation.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Consultancy</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px;position: relative;border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#F8A5C2 ; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/training.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Implementation</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-sm-6 px-4 py-4"> |
||||
|
<div |
||||
|
style="background-color: #fff; padding: 25px; text-align: center; box-shadow: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.06) 0px 1px 2px 0px; position: relative;border-radius: 4px;"> |
||||
|
<div style="position: absolute; top: 0%; left: 50%; transform: translate(-50%, -50%);"> |
||||
|
<div style="background-color:#E6BE26; border-radius: 50%; padding: 15px; width: 68px; |
||||
|
height: 68px; display: inline-block; box-shadow:0px 4px 4px rgba(0, 0, 0, 0.25);"> |
||||
|
<img src="assets/icons/license.png" |
||||
|
alt="service-icon" width="38px" |
||||
|
height="auto"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<p style="margin-top: 20px; font-weight: bold;">Odoo |
||||
|
Licensing Consultancy</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container mt-5"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mt-4"> |
||||
|
<p class="m-0" |
||||
|
style="font-weight: 600; font-size: 24px; color:#000 !important"> |
||||
|
Our Industries</p> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container"> |
||||
|
<div class="row my-5 py-4"> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100 " |
||||
|
style="border-right: 1px solid rgb(209, 209, 209); border-bottom: 1px solid rgb(209, 209, 209); padding: 30px; box-shadow: 6px 0 10px rgba(228, 227, 227, 0.373);"> |
||||
|
<img src="assets/icons/trading-black.png" width="42px" |
||||
|
height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;">Trading</p> |
||||
|
<p>Easily procure and sell your products</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100" |
||||
|
style="border-right: 1px solid rgb(209, 209, 209);border-bottom: 1px solid rgb(209, 209, 209); padding: 30px;"> |
||||
|
<img src="assets/icons/pos-black.png" width="42px" |
||||
|
height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;">POS</p> |
||||
|
<p>Easy configuration and convivial experience</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100" |
||||
|
style="border-right: 1px solid rgb(209, 209, 209);border-bottom: 1px solid rgba(0, 0, 0, 0.2); padding: 30px; box-shadow: 0 5px 10px rgba(228, 227, 227, 0.373)"> |
||||
|
<img src="assets/icons/education-black.png" width="42px" |
||||
|
height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;"> |
||||
|
Education</p> |
||||
|
<p>A platform for educational management</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100" |
||||
|
style="border-bottom: 1px solid rgb(209, 209, 209); padding: 30px; "> |
||||
|
<img src="assets/icons/manufacturing-black.png" |
||||
|
width="42px" height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;"> |
||||
|
Manufacturing</p> |
||||
|
<p>Plan, track and schedule your operations</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100" |
||||
|
style="border-right: 1px solid rgb(209, 209, 209); padding: 30px;"> |
||||
|
<img src="assets/icons/ecom-black.png" width="42px" |
||||
|
height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;">E-commerce & |
||||
|
Website</p> |
||||
|
<p>Mobile friendly, awe-inspiring product pages</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100" |
||||
|
style="border-right: 1px solid rgb(209, 209, 209); padding: 30px;box-shadow: 0 -5px 10px rgba(228, 227, 227, 0.373);"> |
||||
|
<img src="assets/icons/service-black.png" width="42px" |
||||
|
height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;">Service |
||||
|
Management</p> |
||||
|
<p>Keep track of services and invoice</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100" |
||||
|
style="border-right: 1px solid rgb(209, 209, 209); padding: 30px; "> |
||||
|
<img src="assets/icons/restaurant-black.png" |
||||
|
width="42px" height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;"> |
||||
|
Restaurant</p> |
||||
|
<p>Run your bar or restaurant methodically</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-3 col-sm-6 p-0"> |
||||
|
<div class="d-flex flex-column h-100" |
||||
|
style=" padding: 30px;box-shadow: -5px 0 10px rgba(228, 227, 227, 0.373);"> |
||||
|
<img src="assets/icons/hotel-black.png" width="42px" |
||||
|
height="auto" alt=""> |
||||
|
<p style="color: #714B67;font-weight: 600; margin-top: 10px; |
||||
|
font-size: 1.2rem; margin-bottom: 2px;">Hotel |
||||
|
Management</p> |
||||
|
<p>An all-inclusive hotel management application</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container mt-5"> |
||||
|
<div class="col-lg-12 d-flex flex-column justify-content-center align-items-center mt-5"> |
||||
|
<p class="m-0" |
||||
|
style="font-weight: 600; font-size: 24px; color:#000 !important"> |
||||
|
Support</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="container my-5"> |
||||
|
<div class="row" style="background-color: #FFFAFE;"> |
||||
|
<div class="col-md-6 pb-4 d-flex align-items-center justify-content-center" |
||||
|
style="border-right: 1px solid #D9D9D9;"> |
||||
|
<div style="padding: 30px;"> |
||||
|
<div class="d-flex align-items-center"> |
||||
|
<img src="assets/misc/support (1) 1.svg" alt="" |
||||
|
width="60px" style="margin-right: 12px;"> |
||||
|
<div style="padding: 0px 8px;"> |
||||
|
<span |
||||
|
style="color: #714B67;font-size: 24px;font-weight: 600;padding-bottom: 1rem;">Need |
||||
|
Help?</span> |
||||
|
<p class="m-0" style="color:#718096;">Got |
||||
|
questions or need help? Get in touch.</p> |
||||
|
<div style="font-weight: 400;"><span><img |
||||
|
src="assets/misc/support-email.svg" |
||||
|
alt="" |
||||
|
width="18px" |
||||
|
style="filter: invert(1);margin-right: 0.8rem;"></span>odoo@cybrosys.com |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-6 pb-4 d-flex align-items-center justify-content-center"> |
||||
|
<div style="padding: 30px;"> |
||||
|
<div class="d-flex align-items-center"> |
||||
|
<img src="assets/misc/whatsapp 1.svg" alt="" |
||||
|
width="60px" style="margin-right: 12px;"> |
||||
|
<div> |
||||
|
<span style="color: #714B67;font-size: 24px;font-weight: 600;">WhatsApp</span> |
||||
|
<p class="m-0" style="color:#718096;">Say hi to |
||||
|
us on WhatsApp!</p> |
||||
|
<div style="font-weight: 400; font-size: 16px;"><span><img |
||||
|
src="assets/misc/phone.svg" |
||||
|
alt="" width="14px" |
||||
|
style="filter: invert(1); margin-right: 0.8rem;"></span>+91 |
||||
|
99456767686 |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
<!-- Optional JavaScript --> |
||||
|
<!-- jQuery first, then Popper.js, then Bootstrap JS --> |
||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> |
||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,426 @@ |
|||||
|
.oh-card h4 { |
||||
|
font-size: 1.1rem; |
||||
|
} |
||||
|
|
||||
|
.stat-icon { |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
.stat-widget-one .stat-icon { |
||||
|
vertical-align: top; |
||||
|
margin: auto; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.stat-widget-one .stat-icon i { |
||||
|
font-size: 30px; |
||||
|
font-weight: 900; |
||||
|
display: inline-block; |
||||
|
color: #01c490; |
||||
|
} |
||||
|
|
||||
|
.stat-widget-one .stat-text { |
||||
|
font-size: 14px; |
||||
|
color: #868e96; |
||||
|
font-weight: bold; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.stat-widget-one .stat-digit { |
||||
|
font-size: 24px; |
||||
|
color: #02448b; |
||||
|
} |
||||
|
|
||||
|
.stat_count { |
||||
|
font-size: 28px !important; |
||||
|
} |
||||
|
|
||||
|
body .text-color { |
||||
|
color: #00438b; |
||||
|
} |
||||
|
/* Leave graph */ |
||||
|
path { |
||||
|
stroke: #fff; |
||||
|
} |
||||
|
|
||||
|
path:hover { |
||||
|
opacity: 0.9; |
||||
|
} |
||||
|
|
||||
|
rect:hover { |
||||
|
fill: #934da5; |
||||
|
} |
||||
|
|
||||
|
.axis { |
||||
|
font: 10px sans-serif; |
||||
|
} |
||||
|
|
||||
|
.legend tr { |
||||
|
border-bottom: 1px solid grey; |
||||
|
} |
||||
|
|
||||
|
.legend tr:first-child { |
||||
|
border-top: 1px solid grey; |
||||
|
} |
||||
|
|
||||
|
.axis path, |
||||
|
.axis line { |
||||
|
fill: none; |
||||
|
stroke: #000; |
||||
|
shape-rendering: crispEdges; |
||||
|
} |
||||
|
|
||||
|
.x.axis path { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.legend { |
||||
|
border-collapse: collapse; |
||||
|
border-spacing: 0px; |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
.legend td, |
||||
|
.legend .legend_col { |
||||
|
padding: 4px 5px; |
||||
|
vertical-align: bottom; |
||||
|
} |
||||
|
|
||||
|
.legendFreq, |
||||
|
.legendPerc { |
||||
|
align: right; |
||||
|
width: 50px; |
||||
|
} |
||||
|
|
||||
|
/* Leave broadfactor graph */ |
||||
|
|
||||
|
.broad_factor_graph .axis path, |
||||
|
.broad_factor_graph .axis line { |
||||
|
fill: none; |
||||
|
stroke: black; |
||||
|
shape-rendering: crispEdges; |
||||
|
} |
||||
|
|
||||
|
.broad_factor_graph .axis text { |
||||
|
font-family: sans-serif; |
||||
|
font-size: 11px; |
||||
|
} |
||||
|
|
||||
|
.broad_factor_graph rect { |
||||
|
-moz-transition: all 0.3s; |
||||
|
-webkit-transition: all 0.3s; |
||||
|
-o-transition: all 0.3s; |
||||
|
transition: all 0.3s; |
||||
|
} |
||||
|
|
||||
|
.broad_factor_graph rect:hover { |
||||
|
fill: #ff618a; |
||||
|
} |
||||
|
|
||||
|
#broad_factor_pdf { |
||||
|
background-color: #ffffff; |
||||
|
border: 0; |
||||
|
color: #000000; |
||||
|
float: right; |
||||
|
} |
||||
|
|
||||
|
#broad_factor_pdf i { |
||||
|
color: red; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
/*=====================New Dashboard===========================*/ |
||||
|
|
||||
|
.oh_dashboards { |
||||
|
background-color: #f8faff !important; |
||||
|
padding: 0px !important; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.container-fluid.o_hr_dashboard { |
||||
|
padding: 0px !important; |
||||
|
} |
||||
|
|
||||
|
.employee-prof { |
||||
|
|
||||
|
padding: 0px; |
||||
|
height: 100%; |
||||
|
background-color: #3e6282; |
||||
|
/*background-image: linear-gradient(180deg, #3e6282, #41666f);*/ |
||||
|
position: fixed; |
||||
|
/*z-index: 999;*/ |
||||
|
} |
||||
|
|
||||
|
.employee-prof .oh-card:hover { |
||||
|
|
||||
|
transform: none !important; |
||||
|
box-shadow: none !important; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.oh-card { |
||||
|
|
||||
|
padding-top: 0px; |
||||
|
padding: 0px; |
||||
|
margin-bottom: 1.5rem; |
||||
|
border-radius: 0px; |
||||
|
box-shadow: none; |
||||
|
background: none; |
||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease; |
||||
|
will-change: transform, box-shadow; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.oh-card:hover { |
||||
|
|
||||
|
transform: translateY(-2px) translateZ(0) !important; |
||||
|
box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.media-body.employee-name { |
||||
|
|
||||
|
background: #466b8d; |
||||
|
float: left; |
||||
|
margin: 0; |
||||
|
width: 100% |
||||
|
} |
||||
|
|
||||
|
.oh-payslip { |
||||
|
|
||||
|
margin-top: 1.5%; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.oh-payslip .stat-icon { |
||||
|
|
||||
|
width: 30%; |
||||
|
height: 85px; |
||||
|
text-align: center; |
||||
|
padding-top: 2%; |
||||
|
color: #fff; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.oh-payslip .oh-card { |
||||
|
|
||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease; |
||||
|
will-change: transform, box-shadow; |
||||
|
box-shadow: 0 10px 40px 0 rgba(62, 57, 107, 0.07), 0 2px 9px 0 rgba(62, 57, 107, 0.06); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.stat-widget-one .stat-text { |
||||
|
|
||||
|
font-size: 14px; |
||||
|
color: #ff8762; |
||||
|
margin-top: 2.3rem; |
||||
|
margin-left: 1rem; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.stat-widget-one .stat-digit { |
||||
|
|
||||
|
font-size: 17px; |
||||
|
color: #000; |
||||
|
margin-left: 1rem; |
||||
|
padding-left: 26px; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.stat-widget-one .stat-icon i { |
||||
|
font-size: 32px; |
||||
|
display: inline-block; |
||||
|
color: #000; |
||||
|
top: 16px; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.stat-widget-one { |
||||
|
|
||||
|
background-color: white; |
||||
|
text-align: inherit !important; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.stat-widget-one { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.oh-payslip .stat-icon { |
||||
|
|
||||
|
width: 30%; |
||||
|
height: 85px; |
||||
|
text-align: center; |
||||
|
padding-top: 2%; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
h4 .stat-count { |
||||
|
font-size: 17px; |
||||
|
text-align: center; |
||||
|
color: #000 !important; |
||||
|
margin-top: 0px; |
||||
|
width: 100%; |
||||
|
float: left; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.hr-chart-1 { |
||||
|
margin: 15px 0px; |
||||
|
background: #fff; |
||||
|
padding: 0px !important; |
||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease; |
||||
|
will-change: transform, box-shadow; |
||||
|
box-shadow: 0 10px 40px 0 rgba(62, 57, 107, 0.07), 0 2px 9px 0 rgba(62, 57, 107, 0.06); |
||||
|
} |
||||
|
|
||||
|
.hr-chart-1:hover { |
||||
|
transform: translateY(-2px) translateZ(0) !important; |
||||
|
box-shadow: 0 10px 10px 0 rgba(62, 57, 107, 0.12), 0 0 0 transparent !important; |
||||
|
} |
||||
|
|
||||
|
.stat-head { |
||||
|
text-align: center !important; |
||||
|
font-weight: 300; |
||||
|
font-size: 21px; |
||||
|
margin-bottom: 12px; |
||||
|
margin-left: 2px; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.hr_birthday { |
||||
|
font-size: 17px; |
||||
|
text-align: center; |
||||
|
padding: 20px 0; |
||||
|
color: #00438b; |
||||
|
font-weight: 300; |
||||
|
} |
||||
|
|
||||
|
.hr_notification img { |
||||
|
width: 40px; |
||||
|
height: 40px; |
||||
|
border-radius: 100%; |
||||
|
} |
||||
|
|
||||
|
.hr_notification { |
||||
|
background: #fff; |
||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease; |
||||
|
will-change: transform, box-shadow; |
||||
|
box-shadow: 0 10px 40px 0 rgba(62, 57, 107, 0.07), 0 2px 9px 0 rgba(62, 57, 107, 0.06); |
||||
|
height: 757px; |
||||
|
margin-bottom: 15px; |
||||
|
} |
||||
|
|
||||
|
.hr_notification .media { |
||||
|
border-bottom: 1px solid #e6e6e6; |
||||
|
padding-bottom: 6px; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.hr_notification .text-color.display-6 { |
||||
|
margin: 0px 0 3px; |
||||
|
color: #2d2d2d; |
||||
|
} |
||||
|
|
||||
|
.hr_notification p { |
||||
|
margin: 0 0 1px; |
||||
|
color: #666; |
||||
|
font-size: 10px; |
||||
|
} |
||||
|
|
||||
|
.hr_notification_head { |
||||
|
font-size: 17px; |
||||
|
text-align: center; |
||||
|
padding: 12px 0; |
||||
|
color: #fff; |
||||
|
font-weight: 300; |
||||
|
background: #5ebade; |
||||
|
margin-bottom: 9px; |
||||
|
} |
||||
|
|
||||
|
.hr-chart-1 { |
||||
|
margin: 15px 0px; |
||||
|
background: #fff; |
||||
|
padding: 0px !important; |
||||
|
padding-top: 0px; |
||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease; |
||||
|
will-change: transform, box-shadow; |
||||
|
box-shadow: 0 10px 40px 0 rgba(62, 57, 107, 0.07), 0 2px 9px 0 rgba(62, 57, 107, 0.06); |
||||
|
padding-top: 3px !important; |
||||
|
} |
||||
|
|
||||
|
/* width */ |
||||
|
.hr_notification::-webkit-scrollbar { |
||||
|
width: 4px; |
||||
|
} |
||||
|
|
||||
|
/* Track */ |
||||
|
.hr_notification::-webkit-scrollbar-track { |
||||
|
background: #f1f1f1; |
||||
|
} |
||||
|
|
||||
|
/* Handle */ |
||||
|
.hr_notification::-webkit-scrollbar-thumb { |
||||
|
background: #495057; |
||||
|
; |
||||
|
} |
||||
|
|
||||
|
/* Handle on hover */ |
||||
|
.hr_notification::-webkit-scrollbar-thumb:hover { |
||||
|
background: #598da1; |
||||
|
} |
||||
|
|
||||
|
.oh-card-body { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.text-align { |
||||
|
margin-left: 17px; |
||||
|
} |
||||
|
|
||||
|
.inner_select { |
||||
|
min-width: 150px |
||||
|
} |
||||
|
|
||||
|
#table_status { |
||||
|
width: 90%; |
||||
|
margin-left: 5%; |
||||
|
font-family: Arial, Helvetica, sans-serif; |
||||
|
} |
||||
|
|
||||
|
#table_status tr:nth-child(even) { |
||||
|
background-color: #f2f2f2; |
||||
|
} |
||||
|
|
||||
|
#table_status tr:hover { |
||||
|
background-color: #ddd; |
||||
|
} |
||||
|
|
||||
|
.fleet-pill { |
||||
|
align-items: center; |
||||
|
font-family: "Open Sans", Arial, Verdana, sans-serif; |
||||
|
font-weight: bold; |
||||
|
font-size: 11px; |
||||
|
display: inline-block; |
||||
|
height: 100%; |
||||
|
white-space: nowrap; |
||||
|
width: auto; |
||||
|
position: relative; |
||||
|
border-radius: 100px; |
||||
|
line-height: 1; |
||||
|
overflow: hidden; |
||||
|
padding: 0px 8px 0px 7px; |
||||
|
text-overflow: ellipsis; |
||||
|
line-height: 1.25rem; |
||||
|
color: #fff; |
||||
|
word-break: break-word; |
||||
|
background: #0253e8; |
||||
|
} |
||||
|
|
||||
|
.inner_select p { |
||||
|
margin-left: 20px |
||||
|
} |
@ -0,0 +1,447 @@ |
|||||
|
/** @odoo-module */ |
||||
|
import { registry} from '@web/core/registry'; |
||||
|
import { useService } from "@web/core/utils/hooks"; |
||||
|
const { Component, onWillStart, onMounted} = owl |
||||
|
import { jsonrpc } from "@web/core/network/rpc_service"; |
||||
|
import { _t } from "@web/core/l10n/translation"; |
||||
|
|
||||
|
export class ProjectDashboard extends Component { |
||||
|
/** |
||||
|
* Setup method to initialize required services and register event handlers. |
||||
|
*/ |
||||
|
setup() { |
||||
|
this.action = useService("action"); |
||||
|
this.orm = useService("orm"); |
||||
|
this.rpc = this.env.services.rpc |
||||
|
onWillStart(this.onWillStart); |
||||
|
onMounted(this.onMounted); |
||||
|
} |
||||
|
/** |
||||
|
* Event handler for the 'onWillStart' event. |
||||
|
*/ |
||||
|
async onWillStart() { |
||||
|
await this.fetch_data(); |
||||
|
} |
||||
|
/** |
||||
|
* Event handler for the 'onMounted' event. |
||||
|
* Renders various components and charts after fetching data. |
||||
|
*/ |
||||
|
async onMounted() { |
||||
|
// Render other components after fetching data
|
||||
|
this.render_project_task(); |
||||
|
this.render_top_employees_graph(); |
||||
|
this.render_filter(); |
||||
|
} |
||||
|
/** |
||||
|
* Render the project task chart. |
||||
|
*/ |
||||
|
async render_project_task() { |
||||
|
await jsonrpc("/project/task/count").then(function(data) { |
||||
|
var ctx = $("#project_doughnut"); |
||||
|
new Chart(ctx, { |
||||
|
type: "doughnut", |
||||
|
data: { |
||||
|
labels: data.project, |
||||
|
datasets: [{ |
||||
|
backgroundColor: data.color, |
||||
|
data: data.task |
||||
|
}] |
||||
|
}, |
||||
|
options: { |
||||
|
legend: { |
||||
|
position: 'left' |
||||
|
}, |
||||
|
cutoutPercentage: 40, |
||||
|
responsive: true, |
||||
|
} |
||||
|
}); |
||||
|
}) |
||||
|
} |
||||
|
/** |
||||
|
function for getting values to employee graph |
||||
|
*/ |
||||
|
async render_top_employees_graph() { |
||||
|
var ctx = $(".top_selling_employees"); |
||||
|
await jsonrpc('/employee/timesheet').then(function(arrays) { |
||||
|
var data = { |
||||
|
labels: arrays[1], |
||||
|
datasets: [{ |
||||
|
label: "Hours Spent", |
||||
|
data: arrays[0], |
||||
|
backgroundColor: [ |
||||
|
"rgba(190, 27, 75,1)", |
||||
|
"rgba(31, 241, 91,1)", |
||||
|
"rgba(103, 23, 252,1)", |
||||
|
"rgba(158, 106, 198,1)", |
||||
|
"rgba(250, 217, 105,1)", |
||||
|
"rgba(255, 98, 31,1)", |
||||
|
"rgba(255, 31, 188,1)", |
||||
|
"rgba(75, 192, 192,1)", |
||||
|
"rgba(153, 102, 255,1)", |
||||
|
"rgba(10,20,30,1)" |
||||
|
], |
||||
|
borderColor: [ |
||||
|
"rgba(190, 27, 75, 0.2)", |
||||
|
"rgba(190, 223, 122, 0.2)", |
||||
|
"rgba(103, 23, 252, 0.2)", |
||||
|
"rgba(158, 106, 198, 0.2)", |
||||
|
"rgba(250, 217, 105, 0.2)", |
||||
|
"rgba(255, 98, 31, 0.2)", |
||||
|
"rgba(255, 31, 188, 0.2)", |
||||
|
"rgba(75, 192, 192, 0.2)", |
||||
|
"rgba(153, 102, 255, 0.2)", |
||||
|
"rgba(10,20,30,0.3)" |
||||
|
], |
||||
|
borderWidth: 1 |
||||
|
}, |
||||
|
|
||||
|
] |
||||
|
}; |
||||
|
//options
|
||||
|
var options = { |
||||
|
responsive: true, |
||||
|
title: { |
||||
|
display: true, |
||||
|
position: "top", |
||||
|
text: " Time by Employees", |
||||
|
fontSize: 18, |
||||
|
fontColor: "#111" |
||||
|
}, |
||||
|
legend: { |
||||
|
display: false, |
||||
|
}, |
||||
|
scales: { |
||||
|
yAxes: [{ |
||||
|
ticks: { |
||||
|
min: 0 |
||||
|
} |
||||
|
}] |
||||
|
} |
||||
|
}; |
||||
|
//create Chart class object
|
||||
|
var chart = new Chart(ctx, { |
||||
|
type: 'bar', |
||||
|
data: data, |
||||
|
options: options |
||||
|
}); |
||||
|
|
||||
|
}); |
||||
|
} |
||||
|
/** |
||||
|
* Function for getting employees for filter. |
||||
|
*/ |
||||
|
render_filter() { |
||||
|
jsonrpc('/project/filter').then(function(data) { |
||||
|
var projects = data[0] |
||||
|
var employees = data[1] |
||||
|
$(projects).each(function(project) { |
||||
|
$('#project_selection').append("<option value=" + projects[project].id + ">" + projects[project].name + "</option>"); |
||||
|
}); |
||||
|
$(employees).each(function(employee) { |
||||
|
$('#employee_selection').append("<option value=" + employees[employee].id + ">" + employees[employee].name + "</option>"); |
||||
|
}); |
||||
|
}) |
||||
|
} |
||||
|
/** |
||||
|
* Event handler to apply filters based on user selections and update the dashboard data accordingly. |
||||
|
*/ |
||||
|
_onchangeFilter(ev) { |
||||
|
this.flag = 1 |
||||
|
var start_date = $('#start_date').val(); |
||||
|
var end_date = $('#end_date').val(); |
||||
|
var employee_selection = $('#employee_selection').val(); |
||||
|
var project_selection = $('#project_selection').val(); |
||||
|
var self = this; |
||||
|
if (!start_date) { |
||||
|
start_date = "null" |
||||
|
} |
||||
|
if (!end_date) { |
||||
|
end_date = "null" |
||||
|
} |
||||
|
if (!employee_selection) { |
||||
|
employee_selection = "null" |
||||
|
} |
||||
|
if (!project_selection) { |
||||
|
project_selection = "null" |
||||
|
} |
||||
|
jsonrpc('/project/filter-apply', { |
||||
|
'data': { |
||||
|
'start_date': start_date, |
||||
|
'end_date': end_date, |
||||
|
'project': project_selection, |
||||
|
'employee': employee_selection |
||||
|
} |
||||
|
}).then(function(data) { |
||||
|
self.tot_hrs = data['list_hours_recorded'] |
||||
|
self.tot_employee = data['total_emp'] |
||||
|
self.tot_project = data['total_project'] |
||||
|
self.tot_task = data['total_task'] |
||||
|
self.tot_so = data['total_so'] |
||||
|
$('#tot_project')[0].innerHTML = data['total_project'].length |
||||
|
$('#tot_employee')[0].innerHTML = data['total_emp'].length |
||||
|
$("#tot_task")[0].innerHTML = data['total_task'].length |
||||
|
$("#tot_hrs")[0].innerHTML = data['hours_recorded'] |
||||
|
$("#tot_margin")[0].innerHTML = data['total_margin'] |
||||
|
$("#tot_so")[0].innerHTML = data['total_so'].length |
||||
|
}) |
||||
|
} |
||||
|
/** |
||||
|
* Event handler to open a list of employees and display them to the user. |
||||
|
*/ |
||||
|
tot_emp(e) { |
||||
|
e.stopPropagation(); |
||||
|
e.preventDefault(); |
||||
|
var options = { |
||||
|
on_reverse_breadcrumb: this.on_reverse_breadcrumb, |
||||
|
}; |
||||
|
if (this.flag == 0) { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Employees"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'hr.employee', |
||||
|
view_mode: 'tree,form', |
||||
|
views: [ |
||||
|
[false, 'list'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} else { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Employees"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'hr.employee', |
||||
|
domain: [ |
||||
|
["id", "in", this.tot_employee] |
||||
|
], |
||||
|
view_mode: 'tree,form', |
||||
|
views: [ |
||||
|
[false, 'list'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
function for getting values when page is loaded |
||||
|
*/ |
||||
|
fetch_data() { |
||||
|
this.flag = 0 |
||||
|
var self = this; |
||||
|
var def1 = jsonrpc('/get/tiles/data').then(function(result) { |
||||
|
if (result['flag'] == 1) { |
||||
|
self.total_projects = result['total_projects'] |
||||
|
self.total_tasks = result['total_tasks'] |
||||
|
self.tot_task = result['total_tasks_ids'] |
||||
|
self.total_hours = result['total_hours'] |
||||
|
self.total_profitability = result['total_profitability'] |
||||
|
self.total_employees = result['total_employees'] |
||||
|
self.total_sale_orders = result['total_sale_orders'] |
||||
|
self.project_stage_list = result['project_stage_list'] |
||||
|
self.tot_so = result['sale_orders_ids'] |
||||
|
self.flag_user = result['flag'] |
||||
|
self.total_projects_ids = result['total_projects_ids'] |
||||
|
} else { |
||||
|
self.tot_task = result['total_tasks_ids'] |
||||
|
self.total_projects = result['total_projects'] |
||||
|
self.total_tasks = result['total_tasks'] |
||||
|
self.total_hours = result['total_hours'] |
||||
|
self.total_sale_orders = result['total_sale_orders'] |
||||
|
self.project_stage_list = result['project_stage_list'] |
||||
|
self.flag_user = result['flag'] |
||||
|
self.tot_so = result['sale_orders_ids'] |
||||
|
self.total_projects_ids = result['total_projects_ids'] |
||||
|
} |
||||
|
}); |
||||
|
/** |
||||
|
function for getting values to hours table |
||||
|
*/ |
||||
|
var def3 = jsonrpc('/get/hours') |
||||
|
.then(function(res) { |
||||
|
self.hour_recorded = res['hour_recorded']; |
||||
|
self.hour_recorde = res['hour_recorde']; |
||||
|
self.billable_fix = res['billable_fix']; |
||||
|
self.non_billable = res['non_billable']; |
||||
|
self.total_hr = res['total_hr']; |
||||
|
}); |
||||
|
|
||||
|
var def4 = jsonrpc('/get/task/data') |
||||
|
.then(function(res) { |
||||
|
self.task_data = res['project']; |
||||
|
}); |
||||
|
return $.when(def1, def3, def4); |
||||
|
} |
||||
|
/** |
||||
|
* Event handler to open a list of projects and display them to the user. |
||||
|
*/ |
||||
|
tot_projects(e) { |
||||
|
e.stopPropagation(); |
||||
|
e.preventDefault(); |
||||
|
var options = { |
||||
|
on_reverse_breadcrumb: this.on_reverse_breadcrumb, |
||||
|
}; |
||||
|
if (this.flag == 0) { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Projects"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'project.project', |
||||
|
domain: [ |
||||
|
["id", "in", this.total_projects_ids] |
||||
|
], |
||||
|
view_mode: 'kanban,form', |
||||
|
views: [ |
||||
|
[false, 'kanban'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} else { |
||||
|
if (this.tot_project) { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Projects"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'project.project', |
||||
|
domain: [ |
||||
|
["id", "in", this.tot_project] |
||||
|
], |
||||
|
view_mode: 'kanban,form', |
||||
|
views: [ |
||||
|
[false, 'kanban'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
* Event handler to open a list of tasks and display them to the user. |
||||
|
*/ |
||||
|
tot_tasks(e) { |
||||
|
e.stopPropagation(); |
||||
|
e.preventDefault(); |
||||
|
var options = { |
||||
|
on_reverse_breadcrumb: this.on_reverse_breadcrumb, |
||||
|
}; |
||||
|
this.action.doAction({ |
||||
|
name: _t("Tasks"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'project.task', |
||||
|
domain: [ |
||||
|
["id", "in", this.tot_task] |
||||
|
], |
||||
|
view_mode: 'tree,kanban,form', |
||||
|
views: [ |
||||
|
[false, 'list'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} |
||||
|
/** |
||||
|
for opening account analytic line view |
||||
|
*/ |
||||
|
hr_recorded(e) { |
||||
|
e.stopPropagation(); |
||||
|
e.preventDefault(); |
||||
|
var options = { |
||||
|
on_reverse_breadcrumb: this.on_reverse_breadcrumb, |
||||
|
}; |
||||
|
if (this.flag == 0) { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Timesheets"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'account.analytic.line', |
||||
|
view_mode: 'tree,form', |
||||
|
views: [ |
||||
|
[false, 'list'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} else { |
||||
|
if (this.tot_hrs) { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Timesheets"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'account.analytic.line', |
||||
|
domain: [ |
||||
|
["id", "in", this.tot_hrs] |
||||
|
], |
||||
|
view_mode: 'tree,form', |
||||
|
views: [ |
||||
|
[false, 'list'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
for opening sale order view |
||||
|
*/ |
||||
|
tot_sale(e) { |
||||
|
e.stopPropagation(); |
||||
|
e.preventDefault(); |
||||
|
var options = { |
||||
|
on_reverse_breadcrumb: this.on_reverse_breadcrumb, |
||||
|
}; |
||||
|
this.action.doAction({ |
||||
|
name: _t("Sale Order"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'sale.order', |
||||
|
domain: [ |
||||
|
["id", "in", this.tot_so] |
||||
|
], |
||||
|
view_mode: 'tree,form', |
||||
|
views: [ |
||||
|
[false, 'list'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} |
||||
|
/** |
||||
|
* Event handler to view a list of employees. |
||||
|
* @param {Event} e - The click event. |
||||
|
*/ |
||||
|
tot_emp(e) { |
||||
|
e.stopPropagation(); |
||||
|
e.preventDefault(); |
||||
|
var options = { |
||||
|
on_reverse_breadcrumb: this.on_reverse_breadcrumb, |
||||
|
}; |
||||
|
if (this.flag == 0) { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Employees"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'hr.employee', |
||||
|
view_mode: 'tree,form', |
||||
|
views: [ |
||||
|
[false, 'list'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
} else { |
||||
|
this.action.doAction({ |
||||
|
name: _t("Employees"), |
||||
|
type: 'ir.actions.act_window', |
||||
|
res_model: 'hr.employee', |
||||
|
domain: [ |
||||
|
["id", "in", this.tot_employee] |
||||
|
], |
||||
|
view_mode: 'tree,form', |
||||
|
views: [ |
||||
|
[false, 'list'], |
||||
|
[false, 'form'] |
||||
|
], |
||||
|
target: 'current' |
||||
|
}, options) |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
ProjectDashboard.template = "ProjectDashboard" |
||||
|
registry.category("actions").add("project_dashboard", ProjectDashboard) |
@ -0,0 +1,369 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!-- Template for the project dashboard --> |
||||
|
<templates id="template" xml:space="preserve"> |
||||
|
<t t-name="ProjectDashboard"> |
||||
|
<div class="oh_dashboards" |
||||
|
style="margin-top: 20px; overflow-y: scroll;vertical-align: middle;overflow-x: clip;max-height: -webkit-fill-available;"> |
||||
|
<div class="container-fluid o_pj_dashboard" |
||||
|
style="margin-left:4%;"> |
||||
|
<t t-call="DashboardProject"/> |
||||
|
<t t-call="DashboardChart"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
<t t-name="DashboardProject"> |
||||
|
<!-- Template for filter items and cards --> |
||||
|
<div class="row main-section"> |
||||
|
<t t-if="flag_user == 1"> |
||||
|
<div class="inner_select" style="display: flex;"> |
||||
|
<p style="margin-left: 20px;">Start Date :</p> |
||||
|
<p> |
||||
|
<input type="date" class="inner_select" id="start_date" |
||||
|
name="start_date" t-on-change="(ev) => this._onchangeFilter(ev)"/> |
||||
|
</p> |
||||
|
<p>End Date :</p> |
||||
|
<p> |
||||
|
<input type="date" class="inner_select" id="end_date" |
||||
|
name="end_date" t-on-change="(ev) => this._onchangeFilter(ev)"/> |
||||
|
</p> |
||||
|
<p>Project :</p> |
||||
|
<p> |
||||
|
<select class="inner_select" id="project_selection" t-on-change="(ev) => this._onchangeFilter(ev)"> |
||||
|
<option value="null">All Projects</option> |
||||
|
</select> |
||||
|
</p> |
||||
|
<p>Employees :</p> |
||||
|
<p> |
||||
|
<select class="inner_select" id="employee_selection" t-on-change="(ev) => this._onchangeFilter(ev)"> |
||||
|
<option value="null">All Employees</option> |
||||
|
</select> |
||||
|
</p> |
||||
|
<button class="btn btn-danger g-col-6 p-2" onclick="location.reload()"> |
||||
|
Reset |
||||
|
</button> |
||||
|
</div> |
||||
|
</t> |
||||
|
<div class="col-md-4 col-sm-6 oh-payslip"> |
||||
|
<div class="oh-card" style="width: 410px;"> |
||||
|
<div class="oh-card-body tot_projects" t-on-click="(e) => this.tot_projects(e)" |
||||
|
style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="stat-widget-one" style="display:flex;"> |
||||
|
<div class="stat-icon"><i class="fa fa-puzzle-piece" /></div> |
||||
|
<div class="stat-head" |
||||
|
style="padding: 5%;width: 60%;">Total Project</div> |
||||
|
<div class="stat_count" |
||||
|
style="padding: 4%;width: 30%;" |
||||
|
id="tot_project"> |
||||
|
<t t-esc="total_projects"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<t t-if="flag_user == 1"> |
||||
|
<div class="col-md-4 col-sm-6 oh-payslip"> |
||||
|
<div class="oh-card" style="width: 410px;"> |
||||
|
<div class="oh-card-body tot_emp" t-on-click="(e) => this.tot_emp(e)" |
||||
|
style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="stat-widget-one" style="display:flex;"> |
||||
|
<div class="stat-icon"><i class="fa fa-user" /></div> |
||||
|
<div class="stat-head" |
||||
|
style="padding: 5%;width: 60%;">Total Employees</div> |
||||
|
<div class="stat_count" |
||||
|
style="padding: 4%;width: 30%;" |
||||
|
id="tot_employee"> |
||||
|
<t t-esc="total_employees"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
<div class="col-md-4 col-sm-6 oh-payslip"> |
||||
|
<div class="oh-card" style="width: 410px;"> |
||||
|
<div class="oh-card-body tot_tasks" t-on-click="(e) => this.tot_tasks(e)" |
||||
|
style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="stat-widget-one" style="display:flex;"> |
||||
|
<div class="stat-icon"><i class="fa fa-tasks" /></div> |
||||
|
<div class="stat-head" |
||||
|
style="padding: 5%;width: 60%;">Total tasks</div> |
||||
|
<div class="stat_count" |
||||
|
style="padding: 4%;width: 30%;" id="tot_task"> |
||||
|
<t t-esc="total_tasks"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<t t-if="flag_user == 1"> |
||||
|
<div class="col-md-4 col-sm-6 oh-payslip"> |
||||
|
<div class="oh-card" style="width: 410px;"> |
||||
|
<div class="oh-card-body hr_recorded" t-on-click="(e) => this.hr_recorded(e)" |
||||
|
style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="stat-widget-one" style="display:flex;"> |
||||
|
<div class="stat-icon"><i class="fa fa-clock-o" /></div> |
||||
|
<div class="stat-head" |
||||
|
style="padding: 5%;width: 60%;">Hours Recorded</div> |
||||
|
<div class="stat_count" |
||||
|
style="padding: 4%;width: 30%;" |
||||
|
id="tot_hrs"> |
||||
|
<t t-esc="total_hours"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
<t t-if="flag_user == 1"> |
||||
|
<div class="col-md-4 col-sm-6 oh-payslip"> |
||||
|
<div class="oh-card" style="width: 410px;"> |
||||
|
<div class="oh-card-body tot_profitability" t-on-click="(e) => this.tot_sale(e)" |
||||
|
style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="stat-widget-one" style="display:flex;"> |
||||
|
<div class="stat-icon"><i class="fa fa-dollar" /></div> |
||||
|
<div class="stat-head" style="padding: 5%;width: 60%;">Total Margin</div> |
||||
|
<div class="stat_count" style="padding: 4%;width: 30%;display:inline-table;" |
||||
|
id="tot_margin"> |
||||
|
<t t-esc="total_profitability"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
<div class="col-md-4 col-sm-6 oh-payslip"> |
||||
|
<div class="oh-card" style="width: 410px;"> |
||||
|
<div class="oh-card-body tot_sale" t-on-click="(e) => this.tot_emp(e)" |
||||
|
style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="stat-widget-one" style="display:flex;"> |
||||
|
<div class="stat-icon"><i class="fa fa-ticket" /></div> |
||||
|
<div class="stat-head" |
||||
|
style="padding: 5%;width: 60%;">Total Sale Orders</div> |
||||
|
<div class="stat_count" |
||||
|
style="padding: 4%;width: 30%;" id="tot_so"> |
||||
|
<t t-esc="total_sale_orders"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
<t t-name="DashboardChart"> |
||||
|
<!-- Template for charts --> |
||||
|
<div class="col-xs-12 col-sm-12 col-lg-12 col-md-12"> |
||||
|
<div class="row main-section"> |
||||
|
<div class="col-sm-7 col-lg-7"> |
||||
|
<div class="graph_view" style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="text-color hr-chart-1"> |
||||
|
<div class="oh-card-body pb-0" |
||||
|
style="text-align:center;"> |
||||
|
<h2 style="margin-left:45%;padding-top:2%;">Project Task Analysis</h2> |
||||
|
</div> |
||||
|
<canvas id="project_doughnut" |
||||
|
style="background:#fff;" width="200" |
||||
|
height="120"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="selling_product_graph_view" |
||||
|
style="box-shadow:5px 11px 30px;"> |
||||
|
<div class="oh-card text-color"> |
||||
|
<canvas class="top_selling_employees" |
||||
|
style="background:#fff;" width="200" |
||||
|
height="120"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4 col-lg-4" style="top: 82px;right: -100px;"> |
||||
|
<div class="hr_notification" style="background: #fff;transition: transform 0.2s ease, box-shadow 0.2s ease;will-change: transform, box-shadow;box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); |
||||
|
height: 763px;margin-bottom: 15px;margin-top: 15px;box-shadow:5px 11px 30px;"> |
||||
|
<div class="hr_notification_head" |
||||
|
style="font-size: 17px;text-align: center;padding: 12px 0;color: #fff;font-weight: 300;background: #000080;margin-bottom: 9px;"> |
||||
|
Project Task Details |
||||
|
</div> |
||||
|
<div class="col-sm-12 col-lg-12" style="padding:0;"> |
||||
|
<div class="text-color"> |
||||
|
<div class="media" |
||||
|
style="overflow-y: auto;height: 704px;"> |
||||
|
<div class="media-body"> |
||||
|
<table class="table table-sm"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th rowspan="14">Project Name</th> |
||||
|
<th rowspan="14">Task Name</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<t t-foreach="task_data" |
||||
|
t-as="proj" t-key="proj"> |
||||
|
<tr> |
||||
|
<td> |
||||
|
<t t-if="flag_user == 1"> |
||||
|
<t t-esc="proj[1]['en_US']"/> |
||||
|
</t> |
||||
|
<t t-els=""> |
||||
|
<t t-esc="proj[1]"/> |
||||
|
</t> |
||||
|
</td> |
||||
|
<td> |
||||
|
<t t-esc="proj[0]"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</t> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="hr_notification" style="background: #fff;transition: transform 0.2s ease, box-shadow 0.2s ease;will-change: transform, box-shadow;box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); |
||||
|
overflow-y: scroll;vertical-align: middle;overflow-x: clip;max-height: 37%;margin-bottom: 15px;box-shadow:5px 11px 30px;"> |
||||
|
<div class="hr_notification_head" |
||||
|
style="font-size: 17px;text-align: center;padding: 12px 0;color: #fff;font-weight: 300;background: #000080;margin-bottom: 9px;"> |
||||
|
Hours Recorded |
||||
|
</div> |
||||
|
<!-- Updated code with right-aligned values for all tables --> |
||||
|
<div class="col-sm-12 col-lg-12" style="padding: 0;"> |
||||
|
<div class="text-color"> |
||||
|
<div class=""> |
||||
|
<div class="media"> |
||||
|
<div class="media-body"> |
||||
|
<!-- Table 1: Billed on Timesheet --> |
||||
|
<table class="table table-sm"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th rowspan="14">Billed on Timesheet</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<t t-foreach="hour_recorde" t-as="hour_recorde" t-key="hour_recorde"> |
||||
|
<tr> |
||||
|
<td> |
||||
|
<!-- Right-align the value --> |
||||
|
<h2 class="text-color display-6" style="font-size: 15px; text-align: right; margin-left: 400px; margin-top: -30px;"> |
||||
|
<t t-esc="hour_recorde"/> |
||||
|
</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</t> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<!-- Table 2: Billed at a Fixed Price --> |
||||
|
<table class="table table-sm"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th rowspan="14">Billed at a Fixed Price</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<t t-foreach="billable_fix" t-as="billable_fix" t-key="billable_fix"> |
||||
|
<tr> |
||||
|
<td> |
||||
|
<!-- Right-align the value --> |
||||
|
<h2 class="text-color display-6" style="font-size: 15px; text-align: right; margin-left: 400px; margin-top: -30px;"> |
||||
|
<t t-esc="billable_fix"/> |
||||
|
</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</t> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
|
||||
|
<!-- Table 3: No Task Found --> |
||||
|
<table class="table table-sm"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th rowspan="14">No Task Found</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<t t-foreach="hour_recorded" t-as="hour_recorded" t-key="hour_recorded"> |
||||
|
<tr> |
||||
|
<td> |
||||
|
<!-- Right-align the value --> |
||||
|
<h2 class="text-color display-6" style="font-size: 15px; text-align: right; margin-left: 400px; margin-top: -30px;"> |
||||
|
<t t-esc="hour_recorded"/> |
||||
|
</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</t> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
|
||||
|
<!-- Table 4: Non Billable Tasks --> |
||||
|
<table class="table table-sm"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th rowspan="14">Non Billable Tasks</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<t t-foreach="non_billable" t-as="non_billable" t-key="non_billable"> |
||||
|
<tr> |
||||
|
<td> |
||||
|
<!-- Right-align the value --> |
||||
|
<h2 class="text-color display-6" style="font-size: 15px; text-align: right; margin-left: 400px; margin-top: -30px;"> |
||||
|
<t t-esc="non_billable"/> |
||||
|
</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</t> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
<!-- Table 5: Total --> |
||||
|
<table class="table table-sm"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th rowspan="14">Total:</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<t t-foreach="total_hr" t-as="total_hr" t-key="total_hr"> |
||||
|
<tr> |
||||
|
<td> |
||||
|
<!-- Right-align the value --> |
||||
|
<h2 class="text-color display-6" style="font-size: 15px; text-align: right; margin-left: 400px; margin-top: -30px;"> |
||||
|
<t t-esc="total_hr"/> |
||||
|
</h2> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</t> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="hr_notification" style="background: #fff;transition: transform 0.2s ease, box-shadow 0.2s ease;will-change: transform, box-shadow;box-shadow: 0 10px 40px 0 rgba(62,57,107,0.07), 0 2px 9px 0 rgba(62,57,107,0.06); |
||||
|
height: auto;padding-bottom: 15px;box-shadow:5px 11px 30px;"> |
||||
|
<div class="hr_notification_head" |
||||
|
style="font-size: 17px;text-align: center;padding: 12px 0;color: #fff;font-weight: 300;background: #000080;margin-bottom: 9px;"> |
||||
|
Stage Wise Total Projects |
||||
|
</div> |
||||
|
<table id="table_status" style="width"> |
||||
|
<tr> |
||||
|
<th/> |
||||
|
<th/> |
||||
|
</tr> |
||||
|
<t t-foreach="project_stage_list" |
||||
|
t-as="data" t-key="project_stage_list"> |
||||
|
<tr> |
||||
|
<td style="text-align:center;"> |
||||
|
<h4 t-esc="data['name']"/> |
||||
|
</td> |
||||
|
<td style="text-align:center;"> |
||||
|
<h4 class="fleet-pill" |
||||
|
t-esc="data['projects']"/> |
||||
|
</td> |
||||
|
</tr> |
||||
|
</t> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
</templates> |
@ -0,0 +1,14 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<odoo> |
||||
|
<!-- Action for Project Dashboard--> |
||||
|
<record id="project_dashboard_action" model="ir.actions.client"> |
||||
|
<field name="name">Dashboard</field> |
||||
|
<field name="tag">project_dashboard</field> |
||||
|
</record> |
||||
|
<!-- Dashboard menu item --> |
||||
|
<menuitem id="project_menu_open_Dashboard" |
||||
|
name="Dashboard" |
||||
|
action="project_dashboard_action" |
||||
|
parent="project.menu_main_pm" |
||||
|
sequence="1"/> |
||||
|
</odoo> |