@ -1,266 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
import datetime as DT |
|||
from odoo import http |
|||
from odoo.http import request |
|||
|
|||
|
|||
class HelpDeskDashboard(http.Controller): |
|||
"""Website helpdesk dashboard""" |
|||
|
|||
@http.route(['/helpdesk_dashboard'], type='json', auth="public") |
|||
def helpdesk_dashboard(self): |
|||
"""Helpdesk dashboard controller""" |
|||
stage_new = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Inbox')], limit=1).id |
|||
stage_draft = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Draft')], limit=1).id |
|||
stage_inprogress = request.env['ticket.stage'].search( |
|||
[('name', '=', 'In Progress')], limit=1).id |
|||
stage_canceled = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Canceled')], limit=1).id |
|||
stage_done = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Done')], limit=1).id |
|||
stage_closed = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Closed')], limit=1).id |
|||
stage_ids = [stage_new, stage_draft] |
|||
new = request.env["help.ticket"].search_count( |
|||
[('stage_id', 'in', stage_ids)]) |
|||
new_id = request.env["help.ticket"].search( |
|||
[('stage_id', 'in', stage_ids)]) |
|||
new_id_ls = [data.id for data in new_id] |
|||
in_progress = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_inprogress)]) |
|||
in_progress_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_inprogress)]) |
|||
in_progress_ls = [data.id for data in in_progress_id] |
|||
canceled = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_canceled)]) |
|||
canceled_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_canceled)]) |
|||
canceled_id_ls = [data.id for data in canceled_id] |
|||
done = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_done)]) |
|||
done_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_done)]) |
|||
done_id_ls = [data.id for data in done_id] |
|||
closed = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_closed)]) |
|||
closed_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_closed)]) |
|||
closed_id_ls = [data.id for data in closed_id] |
|||
dashboard_values = { |
|||
'new': new, |
|||
'in_progress': in_progress, |
|||
'canceled': canceled, |
|||
'done': done, |
|||
'closed': closed, |
|||
'new_id': new_id_ls, |
|||
'in_progress_id': in_progress_ls, |
|||
'canceled_id': canceled_id_ls, |
|||
'done_id': done_id_ls, |
|||
'closed_id': closed_id_ls, |
|||
} |
|||
return dashboard_values |
|||
|
|||
@http.route(['/helpdesk_dashboard_week'], type='json', auth="public") |
|||
def helpdesk_dashboard_week(self): |
|||
"""Week based sorting controller""" |
|||
today = DT.date.today() |
|||
stage_new = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Inbox')], limit=1).id |
|||
stage_draft = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Draft')], limit=1).id |
|||
stage_inprogress = request.env['ticket.stage'].search( |
|||
[('name', '=', 'In Progress')], limit=1).id |
|||
stage_canceled = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Canceled')], limit=1).id |
|||
stage_done = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Done')], limit=1).id |
|||
stage_closed = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Closed')], limit=1).id |
|||
stage_ids = [stage_new, stage_draft] |
|||
week_ago = str(today - DT.timedelta(days=7)) + ' ' |
|||
new = request.env["help.ticket"].search_count( |
|||
[('stage_id', 'in', stage_ids), ('create_date', '>', week_ago)]) |
|||
new_id = request.env["help.ticket"].search( |
|||
[('stage_id', 'in', stage_ids), ('create_date', '>', week_ago)]) |
|||
new_id_ls = [data.id for data in new_id] |
|||
in_progress = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_inprogress), |
|||
('create_date', '>', week_ago)]) |
|||
in_progress_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_inprogress), |
|||
('create_date', '>', week_ago)]) |
|||
in_progress_ls = [data.id for data in in_progress_id] |
|||
canceled = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_canceled), |
|||
('create_date', '>', week_ago)]) |
|||
canceled_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_canceled), |
|||
('create_date', '>', week_ago)]) |
|||
canceled_id_ls = [data.id for data in canceled_id] |
|||
done = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_done), ('create_date', '>', week_ago)]) |
|||
done_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_done), ('create_date', '>', week_ago)]) |
|||
done_id_ls = [data.id for data in done_id] |
|||
closed = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_closed), ('create_date', '>', week_ago)]) |
|||
closed_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_closed), ('create_date', '>', week_ago)]) |
|||
closed_id_ls = [data.id for data in closed_id] |
|||
dashboard_values = { |
|||
'new': new, |
|||
'in_progress': in_progress, |
|||
'canceled': canceled, |
|||
'done': done, |
|||
'closed': closed, |
|||
'new_id': new_id_ls, |
|||
'in_progress_id': in_progress_ls, |
|||
'canceled_id': canceled_id_ls, |
|||
'done_id': done_id_ls, |
|||
'closed_id': closed_id_ls, |
|||
} |
|||
return dashboard_values |
|||
|
|||
@http.route(['/helpdesk_dashboard_month'], type='json', auth="public") |
|||
def helpdesk_dashboard_month(self): |
|||
"""Month based sorting controller""" |
|||
today = DT.date.today() |
|||
stage_new = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Inbox')], limit=1).id |
|||
stage_draft = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Draft')], limit=1).id |
|||
stage_inprogress = request.env['ticket.stage'].search( |
|||
[('name', '=', 'In Progress')], limit=1).id |
|||
stage_canceled = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Canceled')], limit=1).id |
|||
stage_done = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Done')], limit=1).id |
|||
stage_closed = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Closed')], limit=1).id |
|||
stage_ids = [stage_new, stage_draft] |
|||
week_ago = str(today - DT.timedelta(days=30)) + ' ' |
|||
new = request.env["help.ticket"].search_count( |
|||
[('stage_id', 'in', stage_ids), ('create_date', '>', week_ago)]) |
|||
new_id = request.env["help.ticket"].search( |
|||
[('stage_id', 'in', stage_ids), ('create_date', '>', week_ago)]) |
|||
new_id_ls = [data.id for data in new_id] |
|||
in_progress = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_inprogress), |
|||
('create_date', '>', week_ago)]) |
|||
in_progress_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_inprogress), |
|||
('create_date', '>', week_ago)]) |
|||
in_progress_ls = [data.id for data in in_progress_id] |
|||
canceled = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_canceled), |
|||
('create_date', '>', week_ago)]) |
|||
canceled_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_canceled), |
|||
('create_date', '>', week_ago)]) |
|||
canceled_id_ls = [data.id for data in canceled_id] |
|||
done = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_done), ('create_date', '>', week_ago)]) |
|||
done_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_done), ('create_date', '>', week_ago)]) |
|||
done_id_ls = [data.id for data in done_id] |
|||
closed = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_closed), ('create_date', '>', week_ago)]) |
|||
closed_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_closed), ('create_date', '>', week_ago)]) |
|||
closed_id_ls = [data.id for data in closed_id] |
|||
dashboard_values = { |
|||
'new': new, |
|||
'in_progress': in_progress, |
|||
'canceled': canceled, |
|||
'done': done, |
|||
'closed': closed, |
|||
'new_id': new_id_ls, |
|||
'in_progress_id': in_progress_ls, |
|||
'canceled_id': canceled_id_ls, |
|||
'done_id': done_id_ls, |
|||
'closed_id': closed_id_ls, |
|||
} |
|||
return dashboard_values |
|||
|
|||
@http.route(['/helpdesk_dashboard_year'], type='json', auth="public") |
|||
def helpdesk_dashboard_year(self): |
|||
"""Year based sorting""" |
|||
today = DT.date.today() |
|||
stage_new = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Inbox')], limit=1).id |
|||
stage_draft = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Draft')], limit=1).id |
|||
stage_inprogress = request.env['ticket.stage'].search( |
|||
[('name', '=', 'In Progress')], limit=1).id |
|||
stage_canceled = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Canceled')], limit=1).id |
|||
stage_done = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Done')], limit=1).id |
|||
stage_closed = request.env['ticket.stage'].search( |
|||
[('name', '=', 'Closed')], limit=1).id |
|||
stage_ids = [stage_new, stage_draft] |
|||
week_ago = str(today - DT.timedelta(days=360)) + ' ' |
|||
new = request.env["help.ticket"].search_count( |
|||
[('stage_id', 'in', stage_ids), ('create_date', '>', week_ago)]) |
|||
new_id = request.env["help.ticket"].search( |
|||
[('stage_id', 'in', stage_ids), ('create_date', '>', week_ago)]) |
|||
new_id_ls = [data.id for data in new_id] |
|||
in_progress = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_inprogress), |
|||
('create_date', '>', week_ago)]) |
|||
in_progress_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_inprogress), |
|||
('create_date', '>', week_ago)]) |
|||
in_progress_ls = [data.id for data in in_progress_id] |
|||
canceled = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_canceled), |
|||
('create_date', '>', week_ago)]) |
|||
canceled_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_canceled), |
|||
('create_date', '>', week_ago)]) |
|||
canceled_id_ls = [data.id for data in canceled_id] |
|||
done = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_done), ('create_date', '>', week_ago)]) |
|||
done_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_done), ('create_date', '>', week_ago)]) |
|||
done_id_ls = [data.id for data in done_id] |
|||
closed = request.env["help.ticket"].search_count( |
|||
[('stage_id', '=', stage_closed), ('create_date', '>', week_ago)]) |
|||
closed_id = request.env["help.ticket"].search( |
|||
[('stage_id', '=', stage_closed), ('create_date', '>', week_ago)]) |
|||
closed_id_ls = [data.id for data in closed_id] |
|||
dashboard_values = { |
|||
'new': new, |
|||
'in_progress': in_progress, |
|||
'canceled': canceled, |
|||
'done': done, |
|||
'closed': closed, |
|||
'new_id': new_id_ls, |
|||
'in_progress_id': in_progress_ls, |
|||
'canceled_id': canceled_id_ls, |
|||
'done_id': done_id_ls, |
|||
'closed_id': closed_id_ls, |
|||
} |
|||
return dashboard_values |
@ -0,0 +1,162 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Bhagyadev KP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
import datetime as DT |
|||
from odoo import http |
|||
from odoo.http import request |
|||
|
|||
|
|||
class HelpDeskDashboard(http.Controller): |
|||
"""Controller for handling Help Desk dashboard requests.""" |
|||
|
|||
@http.route(['/helpdesk_dashboard'], type='json', auth="public") |
|||
def helpdesk_dashboard(self): |
|||
"""Retrieves statistics for tickets in different stages. |
|||
Returns:dict: Dashboard statistics including counts and IDs for each |
|||
stage. |
|||
""" |
|||
stage_names = ['Inbox', 'Draft', 'In Progress', 'Canceled', 'Done', |
|||
'Closed'] |
|||
stage_ids = { |
|||
name: request.env['ticket.stage'].search([('name', '=', name)], |
|||
limit=1).id for name in |
|||
stage_names} |
|||
new_stages = [stage_ids['Inbox'], stage_ids['Draft']] |
|||
def get_ticket_data(stage_ids): |
|||
tickets = request.env["ticket.helpdesk"].search( |
|||
[('stage_id', 'in', stage_ids)]) |
|||
return len(tickets), [ticket.id for ticket in tickets] |
|||
dashboard_values = { |
|||
'new': (get_ticket_data(new_stages))[0], |
|||
'new_id': (get_ticket_data(new_stages))[1], |
|||
'in_progress': (get_ticket_data([stage_ids['In Progress']]))[0], |
|||
'in_progress_id': (get_ticket_data([stage_ids['In Progress']]))[1], |
|||
'canceled': (get_ticket_data([stage_ids['Canceled']]))[0], |
|||
'canceled_id': (get_ticket_data([stage_ids['Canceled']]))[1], |
|||
'done': (get_ticket_data([stage_ids['Done']]))[0], |
|||
'done_id': (get_ticket_data([stage_ids['Done']]))[1], |
|||
'closed': (get_ticket_data([stage_ids['Closed']]))[0], |
|||
'closed_id': (get_ticket_data([stage_ids['Closed']]))[1]} |
|||
return dashboard_values |
|||
|
|||
def helpdesk_dashboard_week(self): |
|||
""" Retrieves statistics for tickets created in the past week. |
|||
Returns: |
|||
dict: Dashboard statistics including counts and IDs for each stage.""" |
|||
today = DT.date.today() |
|||
week_ago = str(today - DT.timedelta(days=7)) + ' ' |
|||
stage_names = ['Inbox', 'Draft', 'In Progress', 'Canceled', 'Done', |
|||
'Closed'] |
|||
stages = { |
|||
name: request.env['ticket.stage'].search([('name', '=', name)], |
|||
limit=1).id for name in |
|||
stage_names} |
|||
stage_ids = [stages['Inbox'], stages['Draft']] |
|||
def get_ticket_data(stage_id): |
|||
count = request.env["ticket.helpdesk"].search_count( |
|||
[('stage_id', '=', stage_id), ('create_date', '>', week_ago)]) |
|||
ids = request.env["ticket.helpdesk"].search( |
|||
[('stage_id', '=', stage_id), |
|||
('create_date', '>', week_ago)]).ids |
|||
return count, ids |
|||
new_count, new_ids = get_ticket_data(stage_ids) |
|||
in_progress_count, in_progress_ids = get_ticket_data( |
|||
stages['In Progress']) |
|||
canceled_count, canceled_ids = get_ticket_data(stages['Canceled']) |
|||
done_count, done_ids = get_ticket_data(stages['Done']) |
|||
closed_count, closed_ids = get_ticket_data(stages['Closed']) |
|||
dashboard_values = { |
|||
'new': new_count, |
|||
'in_progress': in_progress_count, |
|||
'canceled': canceled_count, |
|||
'done': done_count, |
|||
'closed': closed_count, |
|||
'new_id': new_ids, |
|||
'in_progress_id': in_progress_ids, |
|||
'canceled_id': canceled_ids, |
|||
'done_id': done_ids, |
|||
'closed_id': closed_ids, |
|||
} |
|||
return dashboard_values |
|||
|
|||
@http.route(['/helpdesk_dashboard_month'], type='json', auth="public") |
|||
def helpdesk_dashboard_month(self): |
|||
"""Retrieves statistics for tickets created in the past month. |
|||
Returns: |
|||
dict: Dashboard statistics including counts and IDs for each stage.""" |
|||
today = DT.date.today() |
|||
month_ago = today - DT.timedelta(days=30) |
|||
week_ago = str(month_ago) + ' ' |
|||
stages = request.env['ticket.stage'].search([('name', 'in', |
|||
['Inbox', 'Draft', |
|||
'In Progress', |
|||
'Canceled', 'Done', |
|||
'Closed'])]) |
|||
stage_ids = {stage.name: stage.id for stage in stages} |
|||
def get_stage_data(stage_names): |
|||
stage_ids_list = [stage_ids[name] for name in stage_names] |
|||
tickets = request.env["ticket.helpdesk"].search( |
|||
[('stage_id', 'in', stage_ids_list), |
|||
('create_date', '>', week_ago)]) |
|||
return len(tickets), [ticket.id for ticket in tickets] |
|||
new_count, new_ids = get_stage_data(['Inbox', 'Draft']) |
|||
in_progress_count, in_progress_ids = get_stage_data(['In Progress']) |
|||
canceled_count, canceled_ids = get_stage_data(['Canceled']) |
|||
done_count, done_ids = get_stage_data(['Done']) |
|||
closed_count, closed_ids = get_stage_data(['Closed']) |
|||
dashboard_values = { |
|||
'new': new_count, |
|||
'in_progress': in_progress_count, |
|||
'canceled': canceled_count, |
|||
'done': done_count, |
|||
'closed': closed_count, |
|||
'new_id': new_ids, |
|||
'in_progress_id': in_progress_ids, |
|||
'canceled_id': canceled_ids, |
|||
'done_id': done_ids, |
|||
'closed_id': closed_ids, |
|||
} |
|||
return dashboard_values |
|||
|
|||
@http.route(['/helpdesk_dashboard_year'], type='json', auth="public") |
|||
def helpdesk_dashboard_year(self): |
|||
"""Retrieves statistics for tickets created in the past year. |
|||
Returns: |
|||
dict: Dashboard statistics including counts and IDs for each stage. |
|||
""" |
|||
today = DT.date.today() |
|||
year_ago = today - DT.timedelta(days=360) |
|||
stages = ['Inbox', 'Draft', 'In Progress', 'Canceled', 'Done', 'Closed'] |
|||
stage_ids = { |
|||
stage: request.env['ticket.stage'].search([('name', '=', stage)], |
|||
limit=1).id for stage in |
|||
stages} |
|||
def get_ticket_data(stage_name): |
|||
stage_id = stage_ids[stage_name] |
|||
tickets = request.env["ticket.helpdesk"].search( |
|||
[('stage_id', '=', stage_id), ('create_date', '>', year_ago)]) |
|||
return len(tickets), [ticket.id for ticket in tickets] |
|||
dashboard_values = {} |
|||
for stage in stages: |
|||
count, ids = get_ticket_data(stage) |
|||
dashboard_values[stage.lower()] = count |
|||
dashboard_values[f'{stage.lower()}_id'] = ids |
|||
return dashboard_values |
@ -1,173 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
import base64 |
|||
import json |
|||
from odoo import _, http |
|||
from psycopg2 import IntegrityError |
|||
from odoo.http import request |
|||
from odoo.exceptions import ValidationError |
|||
from odoo.addons.website.controllers.form import WebsiteForm |
|||
|
|||
|
|||
class HelpdeskProduct(http.Controller): |
|||
"""It controls the website products and return the product.""" |
|||
@http.route('/product', auth='public', type='json') |
|||
def product(self): |
|||
"""Product control function""" |
|||
products = request.env['product.template'].sudo().search_read([], |
|||
['name', |
|||
'id']) |
|||
return products |
|||
|
|||
|
|||
class WebsiteFormInherit(WebsiteForm): |
|||
"""This module extends the functionality of the website form controller |
|||
to handle the creation of new help desk tickets. It provides a new |
|||
controller to display a list of tickets for the current user in their |
|||
portal, and overrides the website form controller's method for handling |
|||
form submissions to create a new help desk ticket instead.""" |
|||
def _handle_website_form(self, model_name, **kwargs): |
|||
"""Website Help Desk Form""" |
|||
if model_name == 'help.ticket': |
|||
tickets = request.env['ticket.stage'].sudo().search([]) |
|||
for rec in tickets: |
|||
sequence = tickets.mapped('sequence') |
|||
lowest_sequence = tickets.filtered( |
|||
lambda x: x.sequence == min(sequence)) |
|||
if rec == lowest_sequence: |
|||
lowest_stage_id = lowest_sequence |
|||
products = kwargs.get('product') |
|||
partner_create = request.env['res.partner'].sudo().create({ |
|||
'name':kwargs.get('customer_name'), |
|||
'company_name':kwargs.get('company'), |
|||
'phone':kwargs.get('phone'), |
|||
'email':kwargs.get('email_from') |
|||
}) |
|||
if products: |
|||
splited_product = products.split(',') |
|||
product_list = [int(i) for i in splited_product] |
|||
rec_val = { |
|||
'customer_name': kwargs.get('customer_name'), |
|||
'subject': kwargs.get('subject'), |
|||
'description': kwargs.get('description'), |
|||
'email': kwargs.get('email_from'), |
|||
'phone': kwargs.get('phone'), |
|||
'priority': kwargs.get('priority'), |
|||
'product_ids': product_list, |
|||
'stage_id': lowest_stage_id.id, |
|||
'customer_id': partner_create.id, |
|||
'ticket_type': kwargs.get('ticket_type'), |
|||
'category_id': kwargs.get('category'), |
|||
} |
|||
ticket_id = request.env['help.ticket'].sudo().create(rec_val) |
|||
request.session['ticket_number'] = ticket_id.name |
|||
request.session['ticket_id'] = ticket_id.id |
|||
model_record = request.env['ir.model'].sudo().search( |
|||
[('model', '=', model_name)]) |
|||
data = self.extract_data(model_record, request.params) |
|||
if ('ticket_attachment' in request.params or |
|||
request.httprequest.files or data.get( |
|||
'attachments')): |
|||
attached_files = data.get('attachments') |
|||
for attachment in attached_files: |
|||
attached_file = attachment.read() |
|||
request.env['ir.attachment'].sudo().create({ |
|||
'name': attachment.filename, |
|||
'res_model': 'help.ticket', |
|||
'res_id': ticket_id.id, |
|||
'type': 'binary', |
|||
'datas': base64.encodebytes(attached_file), |
|||
}) |
|||
request.session[ |
|||
'form_builder_model_model'] = model_record.model |
|||
request.session['form_builder_model'] = model_record.name |
|||
request.session['form_builder_id'] = ticket_id.id |
|||
return json.dumps({'id': ticket_id.id}) |
|||
else: |
|||
rec_val = { |
|||
'customer_name': kwargs.get('customer_name'), |
|||
'subject': kwargs.get('subject'), |
|||
'description': kwargs.get('description'), |
|||
'email': kwargs.get('email_from'), |
|||
'phone': kwargs.get('phone'), |
|||
'priority': kwargs.get('priority'), |
|||
'stage_id': lowest_stage_id.id, |
|||
'customer_id': partner_create.id, |
|||
'ticket_type': kwargs.get('ticket_type'), |
|||
'category_id': kwargs.get('category'), |
|||
} |
|||
ticket_id = request.env['help.ticket'].sudo().create(rec_val) |
|||
request.session['ticket_number'] = ticket_id.name |
|||
request.session['ticket_id'] = ticket_id.id |
|||
model_record = request.env['ir.model'].sudo().search( |
|||
[('model', '=', model_name)]) |
|||
data = self.extract_data(model_record, request.params) |
|||
if ('ticket_attachment' in request.params or |
|||
request.httprequest.files or data.get( |
|||
'attachments')): |
|||
attached_files = data.get('attachments') |
|||
for attachment in attached_files: |
|||
attached_file = attachment.read() |
|||
request.env['ir.attachment'].sudo().create({ |
|||
'name': attachment.filename, |
|||
'res_model': 'help.ticket', |
|||
'res_id': ticket_id.id, |
|||
'type': 'binary', |
|||
'datas': base64.encodebytes(attached_file), |
|||
}) |
|||
request.session['form_builder_model_model'] = model_record.model |
|||
request.session['form_builder_model'] = model_record.name |
|||
request.session['form_builder_id'] = ticket_id.id |
|||
return json.dumps({'id': ticket_id.id}) |
|||
else: |
|||
model_record = request.env['ir.model'].sudo().search( |
|||
[('model', '=', model_name)]) |
|||
if not model_record: |
|||
return json.dumps({ |
|||
'error': _("The form's specified model does not exist") |
|||
}) |
|||
try: |
|||
data = self.extract_data(model_record, request.params) |
|||
# If we encounter an issue while extracting data |
|||
except ValidationError as e: |
|||
return json.dumps({'error_fields': e.args[0]}) |
|||
try: |
|||
id_record = self.insert_record(request, model_record, |
|||
data['record'], data['custom'], |
|||
data.get('meta')) |
|||
if id_record: |
|||
self.insert_attachment(model_record, id_record, |
|||
data['attachments']) |
|||
# In case of an email, we want to send it immediately instead of waiting |
|||
# For the email queue to process |
|||
if model_name == 'mail.mail': |
|||
request.env[model_name].sudo().browse(id_record).send() |
|||
|
|||
# Some fields have additional SQL constraints that we can't check generically |
|||
# Ex: crm.lead.probability which is a float between 0 and 1 |
|||
# TODO: How to get the name of the erroneous field ? |
|||
except IntegrityError: |
|||
return json.dumps(False) |
|||
request.session['form_builder_model_model'] = model_record.model |
|||
request.session['form_builder_model'] = model_record.name |
|||
request.session['form_builder_id'] = id_record |
|||
return json.dumps({'id': id_record}) |
@ -0,0 +1,169 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Bhagyadev KP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
import base64 |
|||
import json |
|||
from psycopg2 import IntegrityError |
|||
from odoo import http, _ |
|||
from odoo.http import request |
|||
from odoo.exceptions import ValidationError |
|||
from odoo.addons.website.controllers.form import WebsiteForm |
|||
|
|||
|
|||
class HelpdeskProduct(http.Controller): |
|||
""" Controller for handling helpdesk products. |
|||
""" |
|||
|
|||
@http.route('/product', auth='public', type='json') |
|||
def product(self): |
|||
prols = [] |
|||
acc = request.env['product.template'].sudo().search([]) |
|||
for i in acc: |
|||
dic = {'name': i['name'], |
|||
'id': i['id']} |
|||
prols.append(dic) |
|||
return prols |
|||
|
|||
|
|||
class WebsiteFormInherit(WebsiteForm): |
|||
|
|||
def _handle_website_form(self, model_name, **kwargs): |
|||
""" |
|||
Handle the submission of website forms. |
|||
:param model_name: The name of the model associated with the form. |
|||
:type model_name: str |
|||
:param kwargs: Keyword arguments containing form data. |
|||
:type kwargs: dict |
|||
:return: JSON response indicating the success or failure of form submission. |
|||
:rtype: str |
|||
""" |
|||
lowest_stage_id = None |
|||
if model_name == 'ticket.helpdesk': |
|||
tickets = request.env['ticket.stage'].sudo().search([]) |
|||
if tickets: |
|||
sequence = tickets.mapped('sequence') |
|||
lowest_sequence = tickets.filtered( |
|||
lambda x: x.sequence == min(sequence)) |
|||
if lowest_sequence: |
|||
lowest_stage_id = lowest_sequence[0] |
|||
if lowest_stage_id is None: |
|||
return json.dumps( |
|||
{'error': "No stage found with the lowest sequence."}) |
|||
products = kwargs.get('product') |
|||
partner_create = request.env['res.partner'].sudo().create({ |
|||
'name': kwargs.get('customer_name'), |
|||
'company_name': kwargs.get('company'), |
|||
'phone': kwargs.get('phone'), |
|||
'email': kwargs.get('email_from') |
|||
}) |
|||
if products: |
|||
split_product = products.split(',') |
|||
product_list = [int(i) for i in split_product] |
|||
rec_val = { |
|||
'customer_name': kwargs.get('customer_name'), |
|||
'subject': kwargs.get('subject'), |
|||
'description': kwargs.get('description'), |
|||
'email': kwargs.get('email_from'), |
|||
'phone': kwargs.get('phone'), |
|||
'priority': kwargs.get('priority'), |
|||
'product_ids': product_list, |
|||
'stage_id': lowest_stage_id.id, |
|||
'customer_id': partner_create.id, |
|||
'ticket_type_id': kwargs.get('ticket_type_id'), |
|||
'category_id': kwargs.get('category'), |
|||
} |
|||
else: |
|||
rec_val = { |
|||
'customer_name': kwargs.get('customer_name'), |
|||
'subject': kwargs.get('subject'), |
|||
'description': kwargs.get('description'), |
|||
'email': kwargs.get('email_from'), |
|||
'phone': kwargs.get('phone'), |
|||
'priority': kwargs.get('priority'), |
|||
'stage_id': lowest_stage_id.id, |
|||
'customer_id': partner_create.id, |
|||
'ticket_type_id': kwargs.get('ticket_type_id'), |
|||
'category_id': kwargs.get('category'), |
|||
} |
|||
ticket_id = request.env['ticket.helpdesk'].sudo().create(rec_val) |
|||
if ticket_id and partner_create.email: |
|||
request.env['mail.mail'].sudo().create({ |
|||
'subject': 'Your Ticket Has Been Created', |
|||
'body_html': f"<p>Hello {partner_create.name},</p><p>Your ticket <strong>{ticket_id.name}</strong> with the subject <strong>{ticket_id.subject}</strong> has been successfully submitted. Our support team will contact you soon.</p> <p>Thank You.</p>", |
|||
'email_to': partner_create.email, |
|||
'email_from': request.env.user.email or 'support@example.com', |
|||
}).send() |
|||
ticket_id.message_post( |
|||
body="A confirmation email regarding the ticket creation has been sent to the customer.", |
|||
subject="Ticket Confirmation Email", |
|||
message_type='email', |
|||
subtype_xmlid="mail.mt_comment", |
|||
) |
|||
request.session['ticket_number'] = ticket_id.name |
|||
request.session['ticket_id'] = ticket_id.id |
|||
model_record = request.env['ir.model'].sudo().search( |
|||
[('model', '=', model_name)]) |
|||
attachments = [] |
|||
attachment_index = 0 |
|||
while f"ticket_attachment[0][{attachment_index}]" in kwargs: |
|||
attachment_key = f"ticket_attachment[0][{attachment_index}]" |
|||
if attachment_key in kwargs: |
|||
attachment = kwargs[attachment_key] |
|||
attachments.append(attachment) |
|||
attachment_index += 1 |
|||
for attachment in attachments: |
|||
attached_file = attachment.read() |
|||
request.env['ir.attachment'].sudo().create({ |
|||
'name': attachment.filename, |
|||
'res_model': 'ticket.helpdesk', |
|||
'res_id': ticket_id.id, |
|||
'type': 'binary', |
|||
'datas': base64.encodebytes(attached_file), |
|||
}) |
|||
request.session['form_builder_model_model'] = model_record.model |
|||
request.session['form_builder_model'] = model_record.name |
|||
request.session['form_builder_id'] = ticket_id.id |
|||
return json.dumps({'id': ticket_id.id}) |
|||
else: |
|||
model_record = request.env['ir.model'].sudo().search( |
|||
[('model', '=', model_name)]) |
|||
if not model_record: |
|||
return json.dumps( |
|||
{'error': _("The form's specified model does not exist")}) |
|||
try: |
|||
data = self.extract_data(model_record, request.params) |
|||
except ValidationError as e: |
|||
return json.dumps({'error_fields': e.args[0]}) |
|||
try: |
|||
id_record = self.insert_record(request, model_record, |
|||
data['record'], data['custom'], |
|||
data.get('meta')) |
|||
if id_record: |
|||
self.insert_attachment(model_record, id_record, |
|||
data['attachments']) |
|||
if model_name == 'mail.mail': |
|||
request.env[model_name].sudo().browse(id_record).send() |
|||
except IntegrityError: |
|||
return json.dumps(False) |
|||
request.session['form_builder_model_model'] = model_record.model |
|||
request.session['form_builder_model'] = model_record.name |
|||
request.session['form_builder_id'] = id_record |
|||
return json.dumps({'id': id_record}) |
@ -0,0 +1,74 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Bhagyadev KP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import http |
|||
from odoo.http import request |
|||
|
|||
|
|||
class WebsiteDesk(http.Controller): |
|||
@http.route(['/helpdesk_ticket'], type='http', auth="public", website=True, |
|||
sitemap=True) |
|||
def helpdesk_ticket(self, **kwargs): |
|||
""" |
|||
Route to display the helpdesk ticket creation form. |
|||
Returns: |
|||
http.Response: The HTTP response rendering the helpdesk ticket form. |
|||
""" |
|||
types = request.env['helpdesk.type'].sudo().search([]) |
|||
categories = request.env['helpdesk.category'].sudo().search([]) |
|||
product = request.env['product.template'].sudo().search([]) |
|||
values = {} |
|||
values.update({ |
|||
'types': types, |
|||
'categories': categories, |
|||
'product_website': product |
|||
}) |
|||
return request.render('odoo_website_helpdesk.ticket_form', values) |
|||
|
|||
@http.route(['/rating/<int:ticket_id>'], type='http', auth="public", |
|||
website=True, |
|||
sitemap=True) |
|||
def rating(self, ticket_id): |
|||
""" |
|||
Route to display the rating form for a specific ticket. Args: |
|||
ticket_id (int): The ID of the ticket for which the rating form is |
|||
displayed. Returns: http.Response: The HTTP response rendering the |
|||
rating form. |
|||
""" |
|||
ticket = request.env['ticket.helpdesk'].browse(ticket_id) |
|||
data = { |
|||
'ticket': ticket.id, |
|||
} |
|||
return request.render('odoo_website_helpdesk.rating_form', data) |
|||
|
|||
@http.route(['/rating/<int:ticket_id>/submit'], type='http', auth="user", |
|||
website=True, csrf=False, |
|||
sitemap=True) |
|||
def rating_backend(self, ticket_id, **post): |
|||
""" |
|||
Customer Rating |
|||
""" |
|||
ticket = request.env['ticket.helpdesk'].browse(ticket_id) |
|||
ticket.write({ |
|||
'customer_rating': post['rating'], |
|||
'review': post['message'], |
|||
}) |
|||
return request.render('odoo_website_helpdesk.rating_thanks') |
@ -0,0 +1,13 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<data noupdate="1"> |
|||
<!-- Record for a helpdesk ticket category representing internal tickets.--> |
|||
<record id="ticket_categories_1" model="helpdesk.category"> |
|||
<field name="name">Internal</field> |
|||
</record> |
|||
<!-- Record for a helpdesk ticket category representing technical tickets.--> |
|||
<record id="ticket_categories_2" model="helpdesk.category"> |
|||
<field name="name">Technical</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<data noupdate="1"> |
|||
<!-- Record for a helpdesk ticket type representing a general question.--> |
|||
<record id="ticket_type_1" model="helpdesk.type"> |
|||
<field name="name">Question</field> |
|||
</record> |
|||
<!-- Record for a helpdesk ticket type representing an issue.--> |
|||
<record id="ticket_type_2" model="helpdesk.type"> |
|||
<field name="name">Issue</field> |
|||
</record> |
|||
<!-- Record for a helpdesk ticket type representing a repair request.--> |
|||
<record id="ticket_type_3" model="helpdesk.type"> |
|||
<field name="name">Repair</field> |
|||
</record> |
|||
<!-- Record for a helpdesk ticket type representing a maintenance request.--> |
|||
<record id="ticket_type_4" model="helpdesk.type"> |
|||
<field name="name">Maintenance</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -1,22 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!-- Ticket types--> |
|||
<record id="helpdesk_types_question" model="helpdesk.types"> |
|||
<field name="name">Question</field> |
|||
</record> |
|||
<record id="helpdesk_types_issue" model="helpdesk.types"> |
|||
<field name="name">Issue</field> |
|||
</record> |
|||
<record id="helpdesk_types_repair" model="helpdesk.types"> |
|||
<field name="name">Repair</field> |
|||
</record> |
|||
<record id="helpdesk_types_maintenance" model="helpdesk.types"> |
|||
<field name="name">Maintenance</field> |
|||
</record> |
|||
<record id="helpdesk_categories_internal" model="helpdesk.categories"> |
|||
<field name="name">Internal</field> |
|||
</record> |
|||
<record id="helpdesk_categories_technical" model="helpdesk.categories"> |
|||
<field name="name">Technical</field> |
|||
</record> |
|||
</odoo> |
@ -1,17 +1,15 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<odoo> |
|||
<data noupdate="1"> |
|||
<!-- Auto close ticket--> |
|||
<record id="ir_cron_auto_close_ticket" model="ir.cron"> |
|||
<!-- Scheduled task (cron job) for automatically closing tickets--> |
|||
<record id="auto_close_ticket" model="ir.cron"> |
|||
<field name="name">Auto Close Ticket</field> |
|||
<field name="model_id" |
|||
ref="odoo_website_helpdesk.model_help_ticket"/> |
|||
<field name="model_id" ref="odoo_website_helpdesk.model_ticket_helpdesk"/> |
|||
<field name="state">code</field> |
|||
<field name="code">model.auto_close_ticket()</field> |
|||
<field name="user_id" ref="base.user_root"/> |
|||
<field name="interval_number">1</field> |
|||
<field name="interval_type">days</field> |
|||
<field name="numbercall">-1</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
|||
|
@ -1,20 +1,23 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<odoo> |
|||
<!-- Sequence of ticket--> |
|||
<record id="sequence_help_ticket_seq" model="ir.sequence"> |
|||
<field name="name">Helpdesk</field> |
|||
<field name="code">help.ticket</field> |
|||
<field name="prefix">TKT</field> |
|||
<field name="padding">5</field> |
|||
<field name="number_increment">1</field> |
|||
</record> |
|||
<!-- Sequence of invoiced ticket --> |
|||
<record id="sequence_help_ticket_inv_seq" model="ir.sequence"> |
|||
<field name="name">Helpdesk Invoice</field> |
|||
<field name="code">ticket.invoice</field> |
|||
<field name="prefix">INV/TKT/%(year)s/</field> |
|||
<field name="padding">3</field> |
|||
<field eval="1" name="number_increment"/> |
|||
<field eval="False" name="company_id"/> |
|||
</record> |
|||
<data noupdate="1"> |
|||
<!-- Define a sequence for help tickets --> |
|||
<record id="sequence_help_ticket_seq" model="ir.sequence"> |
|||
<field name="name">Helpdesk</field> |
|||
<field name="code">ticket.helpdesk</field> |
|||
<field name="prefix">TKT</field> |
|||
<field name="padding">5</field> |
|||
<field eval="1" name="number_increment"/> |
|||
<field eval="False" name="company_id"/> |
|||
</record> |
|||
<!-- Define a sequence for help ticket invoices --> |
|||
<record id="sequence_help_ticket_inv_seq" model="ir.sequence"> |
|||
<field name="name">Helpdesk Invoice</field> |
|||
<field name="code">ticket.invoice</field> |
|||
<field name="prefix">INV/TKT/%(year)s/</field> |
|||
<field name="padding">3</field> |
|||
<field eval="1" name="number_increment"/> |
|||
<field eval="False" name="company_id"/> |
|||
</record> |
|||
</data> |
|||
</odoo> |
|||
|
@ -1,39 +1,39 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!-- Project Stages --> |
|||
<record id="ticket_stage_inbox" model="ticket.stage"> |
|||
<field name="sequence">10</field> |
|||
<field name="name">Inbox</field> |
|||
</record> |
|||
<!-- Draft--> |
|||
<record id="ticket_stage_draft" model="ticket.stage"> |
|||
<field name="sequence">15</field> |
|||
<field name="name">Draft</field> |
|||
</record> |
|||
<!--In progress--> |
|||
<record id="ticket_stage_in_progress" model="ticket.stage"> |
|||
<field name="sequence">20</field> |
|||
<field name="starting_stage" eval="True"/> |
|||
<field name="name">In Progress</field> |
|||
</record> |
|||
<!--Done--> |
|||
<record id="ticket_stage_done" model="ticket.stage"> |
|||
<field name="sequence">25</field> |
|||
<field name="name">Done</field> |
|||
<field name="folded" eval="True"/> |
|||
</record> |
|||
<!--Cancelled--> |
|||
<record id="ticket_stage_cancel" model="ticket.stage"> |
|||
<field name="sequence">30</field> |
|||
<field name="name">Canceled</field> |
|||
<field name="cancel_stage" eval="True"/> |
|||
<field name="folded" eval="True"/> |
|||
</record> |
|||
<!-- Closed--> |
|||
<record id="ticket_stage_closed" model="ticket.stage"> |
|||
<field name="sequence">29</field> |
|||
<field name="name">Closed</field> |
|||
<field name="closing_stage" eval="True"/> |
|||
<field name="folded" eval="True"/> |
|||
</record> |
|||
</odoo> |
|||
<data noupdate="1"> |
|||
<!-- Project Stages --> |
|||
<!-- - stage_inbox: Initial stage where tickets are received.--> |
|||
<record id="stage_inbox" model="ticket.stage"> |
|||
<field name="sequence">10</field> |
|||
<field name="name">Inbox</field> |
|||
</record> |
|||
<!-- - stage_draft: Tickets in the drafting phase.--> |
|||
<record id="stage_draft" model="ticket.stage"> |
|||
<field name="sequence">15</field> |
|||
<field name="name">Draft</field> |
|||
</record> |
|||
<!-- - stage_done: Final stage indicating completion.--> |
|||
<record id="stage_done" model="ticket.stage"> |
|||
<field name="sequence">25</field> |
|||
<field name="name">Done</field> |
|||
</record> |
|||
<!-- - stage_in_progress: Tickets actively being worked on.--> |
|||
<record id="stage_in_progress" model="ticket.stage"> |
|||
<field name="sequence">20</field> |
|||
<field name="starting_stage" eval="True"/> |
|||
<field name="name">In Progress</field> |
|||
</record> |
|||
<!-- - stage_closed: Closing stage for resolved tickets.--> |
|||
<record id="stage_closed" model="ticket.stage"> |
|||
<field name="sequence">30</field> |
|||
<field name="closing_stage">True</field> |
|||
<field name="name">Closed</field> |
|||
</record> |
|||
<!-- - stage_canceled: Stage for canceled or invalidated tickets.--> |
|||
<record id="stage_canceled" model="ticket.stage"> |
|||
<field name="sequence">35</field> |
|||
<field name="cancel_stage">True</field> |
|||
<field name="name">Canceled</field> |
|||
</record> |
|||
</data> |
|||
</odoo> |
@ -1,11 +1,25 @@ |
|||
## Module <odoo_website_helpdesk> |
|||
|
|||
#### 04.12.2023 |
|||
#### Version 16.0.1.0.0 |
|||
#### 30.10.2024 |
|||
#### Version 18.0.1.0.0 |
|||
#### ADD |
|||
|
|||
- Initial commit for Website Helpdesk Support Ticket Management |
|||
|
|||
#### 12.02.2025 |
|||
#### Version 16.0.3.0.1 |
|||
#### UPDT |
|||
#### Version 18.0.1.0.1 |
|||
##### UPDT |
|||
-A new contact record is created upon form submission. |
|||
|
|||
#### 13.05.2025 |
|||
#### Version 18.0.1.0.2 |
|||
##### UPDT |
|||
-A confirmation email will be sent to the customer upon ticket creation. |
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
@ -1,65 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class HelpTeam(models.Model): |
|||
""" This class represents a Helpdesk Team in the system, providing |
|||
information about the team members, leader, and related project.""" |
|||
_name = 'help.team' |
|||
_description = 'Helpdesk Team' |
|||
|
|||
name = fields.Char(string='Name', help='Name of the Helpdesk Team. It ' |
|||
'identify the helpdesk team') |
|||
team_lead_id = fields.Many2one( |
|||
'res.users', |
|||
string='Team Leader', |
|||
help='Name of the Helpdesk Team Leader.', |
|||
domain=lambda self: [('groups_id', 'in', self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_team_leader').id)]) |
|||
member_ids = fields.Many2many( |
|||
'res.users', |
|||
string='Members', |
|||
help='Users who belong to that Helpdesk Team', |
|||
domain=lambda self: [('groups_id', 'in', self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_user').id)]) |
|||
email = fields.Char(string='Email', help='Email') |
|||
project_id = fields.Many2one('project.project', |
|||
string='Project', |
|||
help='The Project they are currently in') |
|||
create_task = fields.Boolean(string="Create Task", |
|||
help="Enable for allowing team to " |
|||
"create tasks from tickets") |
|||
|
|||
@api.onchange('team_lead_id') |
|||
def members_choose(self): |
|||
""" This method is triggered when the Team Leader is changed. It |
|||
updates the available team members based on the selected leader and |
|||
filters out the leader from the list of potential members.""" |
|||
fetch_members = self.env['res.users'].search([]) |
|||
filtered_members = fetch_members.filtered( |
|||
lambda x: x.id != self.team_lead_id.id) |
|||
return {'domain': {'member_ids': [ |
|||
('id', '=', filtered_members.ids), |
|||
('groups_id', 'in', self.env.ref('base.group_user').id), |
|||
('groups_id', 'not in', self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_team_leader').id)]}} |
@ -1,465 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
############################################################################# |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2023-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Cybrosys Techno Solutions(<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
############################################################################# |
|||
import logging |
|||
from odoo import api, fields, models, _ |
|||
from odoo.exceptions import UserError |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
_logger = logging.getLogger(__name__) |
|||
|
|||
PRIORITIES = [ |
|||
('0', 'Very Low'), |
|||
('1', 'Low'), |
|||
('2', 'Normal'), |
|||
('3', 'High'), |
|||
('4', 'Very High'), |
|||
] |
|||
RATING = [ |
|||
('0', 'Very Low'), |
|||
('1', 'Low'), |
|||
('2', 'Normal'), |
|||
('3', 'High'), |
|||
('4', 'Very High'), |
|||
('5', 'Extreme High') |
|||
] |
|||
|
|||
|
|||
class HelpTicket(models.Model): |
|||
"""This model represents the Helpdesk Ticket, which allows users to raise |
|||
tickets related to products, services or any other issues. Each ticket has a |
|||
name, customer information, description, team responsible for handling |
|||
requests, associated project, priority level, stage, cost per hour, service |
|||
product, start and end dates, and related tasks and invoices.""" |
|||
|
|||
_name = 'help.ticket' |
|||
_description = 'Help Ticket' |
|||
_inherit = ['mail.thread', 'mail.activity.mixin'] |
|||
|
|||
name = fields.Char(string='Name', default=lambda self: _('New'), |
|||
help='The name of the help ticket. By default, a new ' |
|||
'unique sequence number is assigned to each ' |
|||
'help ticket, unless a name is provided.', |
|||
readonly=True) |
|||
active = fields.Boolean(default=True, help='Active', string='Active') |
|||
customer_id = fields.Many2one('res.partner', |
|||
string='Customer Name', |
|||
help='Select the Customer Name') |
|||
customer_name = fields.Char(string='Customer Name', |
|||
help='Add the Customer Name') |
|||
subject = fields.Text(string='Subject', required=True, |
|||
help='Subject of the Ticket') |
|||
description = fields.Text(string='Description', required=True, |
|||
help='Issue Description') |
|||
email = fields.Char(string='Email', help='Email of the User.') |
|||
phone = fields.Char(string='Phone', help='Phone Number of the user') |
|||
team_id = fields.Many2one('help.team', string='Helpdesk Team', |
|||
help='The helpdesk team responsible for ' |
|||
'handling requests related to this ' |
|||
'record') |
|||
product_ids = fields.Many2many('product.template', |
|||
string='Product', |
|||
help='The product associated with this ' |
|||
'record.This field allows you to select' |
|||
'an existing product from the product ' |
|||
'catalog.') |
|||
project_id = fields.Many2one('project.project', |
|||
string='Project', |
|||
readonly=False, |
|||
related='team_id.project_id', |
|||
store=True, |
|||
help='The project associated with this team.' |
|||
'This field is automatically filled ' |
|||
'based on the project assigned to ' |
|||
'the team.') |
|||
priority = fields.Selection(PRIORITIES, |
|||
default='1', |
|||
help='Set the priority level', |
|||
string='Priority') |
|||
stage_id = fields.Many2one('ticket.stage', string='Stage', |
|||
default=lambda self: self.env[ |
|||
'ticket.stage'].search( |
|||
[('name', '=', 'Draft')], limit=1).id, |
|||
tracking=True, |
|||
group_expand='_read_group_stage_ids', |
|||
help='Stages of the ticket.') |
|||
user_id = fields.Many2one('res.users', |
|||
default=lambda self: self.env.user, |
|||
check_company=True, |
|||
index=True, tracking=True, |
|||
help='Login User') |
|||
cost = fields.Float(string='Cost per hour', |
|||
help='The cost per hour for this record. This field ' |
|||
'specifies the hourly cost associated with the' |
|||
'record, which can be used in various ' |
|||
'calculations or reports.') |
|||
service_product_id = fields.Many2one('product.product', |
|||
string='Service Product', |
|||
help='The product associated with this' |
|||
'service. Only service products ' |
|||
'are available for selection.', |
|||
domain=[ |
|||
('detailed_type', '=', 'service')]) |
|||
create_date = fields.Datetime(string='Creation Date', help='Created date of' |
|||
'the Ticket') |
|||
start_date = fields.Datetime(string='Start Date', help='Start Date of the ' |
|||
'Ticket') |
|||
end_date = fields.Datetime(string='End Date', help='End Date of the Ticket') |
|||
public_ticket = fields.Boolean(string="Public Ticket", help='Public Ticket') |
|||
invoice_ids = fields.Many2many('account.move', |
|||
string='Invoices', |
|||
help='To Generate Invoice based on hours ' |
|||
'spent on the ticket' |
|||
) |
|||
task_ids = fields.Many2many('project.task', |
|||
string='Tasks', |
|||
help='Related Task of the Ticket') |
|||
color = fields.Integer(string="Color", help='Color') |
|||
replied_date = fields.Datetime(string='Replied date', |
|||
help='Replied Date of the Ticket') |
|||
last_update_date = fields.Datetime(string='Last Update Date', |
|||
help='Last Update Date of Ticket') |
|||
ticket_type = fields.Many2one('helpdesk.types', |
|||
string='Ticket Type', help='Ticket Type') |
|||
team_head = fields.Many2one('res.users', string='Team Leader', |
|||
compute='_compute_team_head', |
|||
help='Team Leader Name') |
|||
assigned_user = fields.Many2one( |
|||
'res.users', |
|||
string='Assigned User', |
|||
domain=lambda self: [('groups_id', 'in', self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_user').id)], |
|||
help='Choose the Assigned User Name') |
|||
category_id = fields.Many2one('helpdesk.categories', |
|||
help='Choose the Category', string='Category') |
|||
tags = fields.Many2many('helpdesk.tag', help='Choose the Tags', |
|||
string='Tag') |
|||
assign_user = fields.Boolean(string='Assigned User', help='Assign User') |
|||
attachment_ids = fields.One2many('ir.attachment', |
|||
'res_id', |
|||
help='Attachment Line', |
|||
string='Attachment') |
|||
merge_ticket_invisible = fields.Boolean(string='Merge Ticket', |
|||
help='Merge Ticket Invisible or ' |
|||
'Not') |
|||
merge_count = fields.Integer(string='Merge Count', help='Merged Tickets ' |
|||
'Count') |
|||
|
|||
@api.onchange('team_id', 'team_head') |
|||
def team_leader_domain(self): |
|||
"""Update the domain for the assigned user based on the selected team. |
|||
|
|||
This onchange method is triggered when the helpdesk team or team leader |
|||
is changed. It updates the domain for the assigned user field to include |
|||
only the members of the selected team.""" |
|||
teams = [] |
|||
for rec in self.team_id.member_ids: |
|||
teams.append(rec.id) |
|||
return {'domain': {'assigned_user': [('id', 'in', teams)]}} |
|||
|
|||
@api.depends('team_id') |
|||
def _compute_team_head(self): |
|||
"""Compute the team head based on the selected team. |
|||
|
|||
This method is triggered when the helpdesk team is changed. It computes |
|||
and updates the team head field based on the team's lead. |
|||
""" |
|||
self.team_head = self.team_id.team_lead_id.id |
|||
|
|||
@api.onchange('stage_id') |
|||
def mail_snd(self): |
|||
"""Send an email when the stage of the ticket is changed. |
|||
|
|||
This onchange method is triggered when the stage of the ticket is |
|||
changed. It updates the last update date, start date, and end date |
|||
fields accordingly. If a template is associated with the stage, it |
|||
sends an email using that template.""" |
|||
rec_id = self._origin.id |
|||
data = self.env['help.ticket'].search([('id', '=', rec_id)]) |
|||
data.last_update_date = fields.Datetime.now() |
|||
if self.stage_id.starting_stage: |
|||
data.start_date = fields.Datetime.now() |
|||
if self.stage_id.closing_stage or self.stage_id.cancel_stage: |
|||
data.end_date = fields.Datetime.now() |
|||
if self.stage_id.template_id: |
|||
mail_template = self.stage_id.template_id |
|||
mail_template.send_mail(self._origin.id, force_send=True) |
|||
|
|||
def assign_to_teamleader(self): |
|||
"""Assign the ticket to the team leader and send a notification. |
|||
|
|||
This function checks if a helpdesk team is selected and assigns the |
|||
team leader to the ticket. It then sends a notification email to the |
|||
team leader.""" |
|||
if self.team_id: |
|||
self.team_head = self.team_id.team_lead_id.id |
|||
mail_template = self.env.ref( |
|||
'odoo_website_helpdesk.' |
|||
'mail_template_odoo_website_helpdesk_assign') |
|||
mail_template.sudo().write({ |
|||
'email_to': self.team_head.email, |
|||
'subject': self.name |
|||
}) |
|||
mail_template.sudo().send_mail(self.id, force_send=True) |
|||
else: |
|||
raise ValidationError("Please choose a Helpdesk Team") |
|||
|
|||
def _default_show_create_task(self): |
|||
"""Get the default value for the 'show_create_task' field. |
|||
|
|||
This method retrieves the default value for the 'show_create_task' |
|||
field from the configuration settings.""" |
|||
return self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.show_create_task') |
|||
|
|||
show_create_task = fields.Boolean(string="Create Task", |
|||
default=_default_show_create_task, |
|||
compute='_compute_show_create_task', |
|||
help='Determines whether the Create Task' |
|||
' button should be shown for this ' |
|||
'ticket.') |
|||
create_task = fields.Boolean(string="Create Task", readonly=False, |
|||
related='team_id.create_task', |
|||
store=True, |
|||
help='Defines if a task should be created when' |
|||
' this ticket is created.') |
|||
billable = fields.Boolean(string="Billable", help='Indicates whether the ' |
|||
'ticket is billable or ' |
|||
'not.') |
|||
|
|||
def _default_show_category(self): |
|||
"""Its display the default category""" |
|||
return self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.show_category') |
|||
|
|||
show_category = fields.Boolean(default=_default_show_category, |
|||
compute='_compute_show_category', |
|||
help='Display the default category') |
|||
customer_rating = fields.Selection(RATING, default='0', readonly=True, |
|||
string='Customer Rating', |
|||
help='Display the customer rating.') |
|||
|
|||
review = fields.Char(string='Review', readonly=True, |
|||
help='Customer review of the ticket.') |
|||
kanban_state = fields.Selection([ |
|||
('normal', 'Ready'), |
|||
('done', 'In Progress'), |
|||
('blocked', 'Blocked'), ], default='normal') |
|||
|
|||
def _compute_show_category(self): |
|||
"""Compute show category""" |
|||
show_category = self._default_show_category() |
|||
for rec in self: |
|||
rec.show_category = show_category |
|||
|
|||
def _compute_show_create_task(self): |
|||
"""Compute the value of the 'show_create_task' field for each record in |
|||
the current recordset.""" |
|||
show_create_task = self._default_show_create_task() |
|||
for record in self: |
|||
record.show_create_task = show_create_task |
|||
|
|||
def auto_close_ticket(self): |
|||
"""Automatically closing the ticket based on the closing date.""" |
|||
auto_close = self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.auto_close_ticket') |
|||
if auto_close: |
|||
no_of_days = self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.no_of_days') |
|||
records = self.env['help.ticket'].search([]) |
|||
for rec in records: |
|||
days = (fields.Datetime.today() - rec.create_date).days |
|||
if days >= int(no_of_days): |
|||
close_stage_id = self.env['ticket.stage'].search( |
|||
[('closing_stage', '=', True)]) |
|||
if close_stage_id: |
|||
rec.stage_id = close_stage_id |
|||
|
|||
def default_stage_id(self): |
|||
"""Search your stage""" |
|||
return self.env['ticket.stage'].search( |
|||
[('name', '=', 'Draft')], limit=1).id |
|||
|
|||
@api.model |
|||
def _read_group_stage_ids(self, stages, domain, order): |
|||
""" |
|||
Return the available stages for grouping. |
|||
|
|||
This static method is used to provide the available stages for |
|||
grouping when displaying records in a grouped view. |
|||
|
|||
""" |
|||
stage_ids = self.env['ticket.stage'].search([]) |
|||
return stage_ids |
|||
|
|||
@api.model |
|||
def create(self, vals_list): |
|||
"""Create a new helpdesk ticket. |
|||
This method is called when creating a new helpdesk ticket. It |
|||
generates a unique name for the ticket using a sequence if no |
|||
name is provided. |
|||
""" |
|||
if vals_list.get('name', _('New')) == _('New'): |
|||
vals_list['name'] = self.env['ir.sequence'].next_by_code( |
|||
'help.ticket') or _('New') |
|||
return super().create(vals_list) |
|||
|
|||
def action_create_invoice(self): |
|||
"""Create Invoice for Help Desk Ticket. |
|||
This function creates an invoice for the help desk ticket based on |
|||
the associated tasks with billed hours. |
|||
""" |
|||
tasks = self.env['project.task'].search( |
|||
[('project_id', '=', self.project_id.id), |
|||
('ticket_id', '=', self.id)]).filtered( |
|||
lambda line: line.ticket_billed == True) |
|||
if not tasks: |
|||
raise UserError('No Tasks to Bill') |
|||
total = sum(x.effective_hours for x in tasks if x.effective_hours > 0) |
|||
invoice_no = self.env['ir.sequence'].next_by_code( |
|||
'ticket.invoice') |
|||
self.env['account.move'].create([ |
|||
{ |
|||
'name': invoice_no, |
|||
'move_type': 'out_invoice', |
|||
'partner_id': self.customer_id.id, |
|||
'ticket_id': self.id, |
|||
'date': fields.Date.today(), |
|||
'invoice_date': fields.Date.today(), |
|||
'invoice_line_ids': |
|||
[(0, 0, {'product_id': self.service_product_id.id, |
|||
'name': self.service_product_id.name, |
|||
'quantity': total, |
|||
'product_uom_id': self.service_product_id.uom_id.id, |
|||
'price_unit': self.cost, |
|||
'account_id': |
|||
self.service_product_id.categ_id.property_account_income_categ_id.id, |
|||
})], |
|||
}, ]) |
|||
for task in tasks: |
|||
task.ticket_billed = True |
|||
return { |
|||
'effect': { |
|||
'fadeout': 'medium', |
|||
'message': 'Billed Successfully!', |
|||
'type': 'rainbow_man', |
|||
} |
|||
} |
|||
|
|||
def action_create_tasks(self): |
|||
"""Create Task for HelpDesk Ticket |
|||
This function creates a task associated with the helpdesk ticket |
|||
and updates the task_ids field. |
|||
""" |
|||
task_id = self.env['project.task'].create({ |
|||
'name': self.name + '-' + self.subject, |
|||
'project_id': self.project_id.id, |
|||
'company_id': self.env.company.id, |
|||
'ticket_id': self.id, |
|||
}) |
|||
self.write({ |
|||
'task_ids': [(4, task_id.id)] |
|||
}) |
|||
return { |
|||
'name': 'Tasks', |
|||
'res_model': 'project.task', |
|||
'view_id': False, |
|||
'res_id': task_id.id, |
|||
'view_mode': 'form', |
|||
'type': 'ir.actions.act_window', |
|||
'target': 'new', |
|||
} |
|||
|
|||
def action_open_tasks(self): |
|||
"""Smart Button of Task to view the Tasks of HelpDesk Ticket""" |
|||
return { |
|||
'name': 'Tasks', |
|||
'domain': [('ticket_id', '=', self.id)], |
|||
'res_model': 'project.task', |
|||
'view_id': False, |
|||
'view_mode': 'tree,form', |
|||
'type': 'ir.actions.act_window', |
|||
} |
|||
|
|||
def action_open_invoices(self): |
|||
"""Smart Button of Invoice to view the Invoices for HelpDesk Ticket""" |
|||
return { |
|||
'name': 'Invoice', |
|||
'domain': [('ticket_id', '=', self.id)], |
|||
'res_model': 'account.move', |
|||
'view_id': False, |
|||
'view_mode': 'tree,form', |
|||
'type': 'ir.actions.act_window', |
|||
} |
|||
|
|||
def action_open_merged_tickets(self): |
|||
""" Smart button of the merged tickets""" |
|||
ticket_ids = self.env['support.tickets'].search( |
|||
[('merged_ticket', '=', self.id)]) |
|||
# Get the display_name matching records from the support.tickets |
|||
helpdesk_ticket_ids = ticket_ids.mapped('display_name') |
|||
# Get the IDs of the help.ticket records matching the display names |
|||
help_ticket_records = self.env['help.ticket'].search( |
|||
[('name', 'in', helpdesk_ticket_ids)]) |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'Helpdesk Ticket', |
|||
'view_mode': 'tree,form', |
|||
'res_model': 'help.ticket', |
|||
'domain': [('id', 'in', help_ticket_records.ids)], |
|||
'context': self.env.context, |
|||
} |
|||
|
|||
def action_send_reply(self): |
|||
"""Compose and send a reply to the customer. |
|||
This function opens a window for composing and sending a reply to |
|||
the customer. It uses the configured email template for replies. |
|||
""" |
|||
template_id = self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.reply_template_id' |
|||
) |
|||
template_id = self.env['mail.template'].browse(int(template_id)) |
|||
if template_id: |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'mail', |
|||
'res_model': 'mail.compose.message', |
|||
'view_mode': 'form', |
|||
'target': 'new', |
|||
'views': [[False, 'form']], |
|||
'context': { |
|||
'default_model': 'help.ticket', |
|||
'default_res_id': self.id, |
|||
'default_template_id': template_id.id |
|||
} |
|||
} |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'mail', |
|||
'res_model': 'mail.compose.message', |
|||
'view_mode': 'form', |
|||
'target': 'new', |
|||
'views': [[False, 'form']], |
|||
'context': { |
|||
'default_model': 'help.ticket', |
|||
'default_res_id': self.id, |
|||
} |
|||
} |
@ -0,0 +1,59 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Bhagyadev KP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
from odoo import api, fields, models |
|||
|
|||
|
|||
class TeamHelpDesk(models.Model): |
|||
"""Helpdesk team""" |
|||
_name = 'team.helpdesk' |
|||
_description = 'Helpdesk Team' |
|||
|
|||
name = fields.Char('Name', help='Helpdesk Team Name') |
|||
team_lead_id = fields.Many2one('res.users', string='Team Leader', |
|||
help='Team Leader Name', |
|||
domain=lambda self: [ |
|||
('groups_id', 'in', self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_team_leader').id)]) |
|||
member_ids = fields.Many2many('res.users', string='Members', |
|||
help='Team Members', |
|||
domain=lambda self: [ |
|||
('groups_id', 'in', self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_user').id)]) |
|||
email = fields.Char('Email', help='Email of the team member.') |
|||
project_id = fields.Many2one('project.project', |
|||
string='Project', |
|||
help='Projects related helpdesk team.') |
|||
create_task = fields.Boolean(string="Create Task", |
|||
help="Task created or not") |
|||
|
|||
@api.onchange('team_lead_id') |
|||
def _onchange_team_lead_id(self): |
|||
"""Members selection function""" |
|||
fetch_members = self.env['res.users'].search([]) |
|||
filtered_members = fetch_members.filtered( |
|||
lambda x: x.id != self.team_lead_id.id) |
|||
return {'domain': {'member_ids': |
|||
[('id', '=', filtered_members.ids), ( |
|||
'groups_id', 'in', |
|||
self.env.ref('base.group_user').id), |
|||
('groups_id', 'not in', self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_team_leader').id)]}} |
@ -0,0 +1,387 @@ |
|||
# -*- coding: utf-8 -*- |
|||
################################################################################ |
|||
# |
|||
# Cybrosys Technologies Pvt. Ltd. |
|||
# |
|||
# Copyright (C) 2024-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
|||
# Author: Bhagyadev KP (<https://www.cybrosys.com>) |
|||
# |
|||
# You can modify it under the terms of the GNU LESSER |
|||
# GENERAL PUBLIC LICENSE (LGPL 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 LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. |
|||
# |
|||
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE |
|||
# (LGPL v3) along with this program. |
|||
# If not, see <http://www.gnu.org/licenses/>. |
|||
# |
|||
################################################################################ |
|||
import logging |
|||
from odoo import api, fields, models, _ |
|||
from odoo.exceptions import UserError |
|||
from odoo.exceptions import ValidationError |
|||
|
|||
_logger = logging.getLogger(__name__) |
|||
|
|||
PRIORITIES = [ |
|||
('0', 'Very Low'), |
|||
('1', 'Low'), |
|||
('2', 'Normal'), |
|||
('3', 'High'), |
|||
('4', 'Very High'), |
|||
] |
|||
RATING = [ |
|||
('0', 'Very Low'), |
|||
('1', 'Low'), |
|||
('2', 'Normal'), |
|||
('3', 'High'), |
|||
('4', 'Very High'), |
|||
('5', 'Extreme High') |
|||
] |
|||
|
|||
|
|||
class TicketHelpDesk(models.Model): |
|||
"""Help_ticket model""" |
|||
_name = 'ticket.helpdesk' |
|||
_description = 'Helpdesk Ticket' |
|||
_inherit = ['mail.thread', 'mail.activity.mixin'] |
|||
|
|||
def _default_show_create_task(self): |
|||
"""Task creation""" |
|||
return self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.show_create_task') |
|||
|
|||
def _default_show_category(self): |
|||
"""Show category default""" |
|||
return self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.show_category') |
|||
|
|||
name = fields.Char('Name', default=lambda self: self.env['ir.sequence']. |
|||
next_by_code('ticket.helpdesk') or _('New'), |
|||
help='Ticket Name') |
|||
customer_id = fields.Many2one('res.partner', |
|||
string='Customer Name', |
|||
help='Customer Name') |
|||
customer_name = fields.Char('Customer Name', help='Customer Name') |
|||
subject = fields.Text('Subject', required=True, |
|||
help='Subject of the Ticket') |
|||
description = fields.Text('Description', required=True, |
|||
help='Description') |
|||
email = fields.Char('Email', help='Email') |
|||
phone = fields.Char('Phone', help='Contact Number') |
|||
team_id = fields.Many2one('team.helpdesk', string='Helpdesk Team', |
|||
help='Helpdesk Team Name') |
|||
product_ids = fields.Many2many('product.template', |
|||
string='Product', |
|||
help='Product Name') |
|||
project_id = fields.Many2one('project.project', |
|||
string='Project', |
|||
readonly=False, |
|||
related='team_id.project_id', |
|||
store=True, |
|||
help='Project Name') |
|||
priority = fields.Selection(PRIORITIES, default='1', help='Priority of the' |
|||
' Ticket') |
|||
stage_id = fields.Many2one('ticket.stage', string='Stage', |
|||
tracking=True, |
|||
group_expand='_read_group_stage_ids', |
|||
help='Stages') |
|||
user_id = fields.Many2one('res.users', |
|||
default=lambda self: self.env.user, |
|||
check_company=True, |
|||
index=True, tracking=True, |
|||
help='Login User', string='User') |
|||
cost = fields.Float('Cost per hour', help='Cost Per Unit') |
|||
service_product_id = fields.Many2one('product.product', |
|||
string='Service Product', |
|||
help='Service Product', |
|||
domain=[ |
|||
('type', '=', 'service')]) |
|||
create_date = fields.Datetime('Creation Date', help='Created date') |
|||
start_date = fields.Datetime('Start Date', help='Start Date') |
|||
end_date = fields.Datetime('End Date', help='End Date') |
|||
public_ticket = fields.Boolean(string="Public Ticket", |
|||
help='Public Ticket') |
|||
invoice_ids = fields.Many2many('account.move', |
|||
string='Invoices', |
|||
help='Invoicing id' |
|||
) |
|||
task_ids = fields.Many2many('project.task', |
|||
string='Tasks', |
|||
help='Task id') |
|||
color = fields.Integer(string="Color", help='Color') |
|||
replied_date = fields.Datetime('Replied date', help='Replied Date') |
|||
last_update_date = fields.Datetime('Last Update Date', |
|||
help='Last Update Date') |
|||
ticket_type_id = fields.Many2one('helpdesk.type', |
|||
string='Ticket Type', help='Ticket Type') |
|||
team_head_id = fields.Many2one('res.users', string='Team Leader', |
|||
compute='_compute_team_head_id', |
|||
help='Team Leader Name') |
|||
assigned_user_id = fields.Many2one('res.users', string='Assigned User', |
|||
domain=lambda self: [('groups_id', 'in', |
|||
self.env.ref( |
|||
'odoo_website_helpdesk.helpdesk_user').id)], |
|||
help='Assigned User Name') |
|||
category_id = fields.Many2one('helpdesk.category', string='Category', |
|||
help='Category') |
|||
tags_ids = fields.Many2many('helpdesk.tag', help='Tags', string='Tags') |
|||
assign_user = fields.Boolean(default=False, help='Assign User', |
|||
string='Assign User') |
|||
attachment_ids = fields.One2many('ir.attachment', 'res_id', |
|||
help='Attachment Line', |
|||
string='Attachments') |
|||
merge_ticket_invisible = fields.Boolean(string='Merge Ticket', |
|||
help='Merge Ticket Invisible or ' |
|||
'Not', default=False) |
|||
merge_count = fields.Integer(string='Merge Count', help='Merged Tickets ' |
|||
'Count') |
|||
active = fields.Boolean(default=True, help='Active', string='Active') |
|||
|
|||
show_create_task = fields.Boolean(string="Show Create Task", |
|||
help='Show created task or not', |
|||
default=_default_show_create_task, |
|||
compute='_compute_show_create_task') |
|||
create_task = fields.Boolean(string="Create Task", readonly=False, |
|||
help='Create task or not', |
|||
related='team_id.create_task', store=True) |
|||
billable = fields.Boolean(string="Billable", default=False, |
|||
help='Is billable or not', ) |
|||
show_category = fields.Boolean(default=_default_show_category, |
|||
string="Show Category", |
|||
help='Show category or not', |
|||
compute='_compute_show_category') |
|||
customer_rating = fields.Selection(RATING, default='0', readonly=True) |
|||
review = fields.Char('Review', readonly=True, help='Ticket review') |
|||
kanban_state = fields.Selection([ |
|||
('normal', 'Ready'), |
|||
('done', 'In Progress'), |
|||
('blocked', 'Blocked'), ], default='normal') |
|||
|
|||
@api.onchange('team_id', 'team_head_id') |
|||
def _onchange_team_id(self): |
|||
"""Changing the team leader when selecting the team""" |
|||
li = self.team_id.member_ids.mapped(id) |
|||
return {'domain': {'assigned_user_id': [('id', 'in', li)]}} |
|||
|
|||
@api.depends('team_id') |
|||
def _compute_team_head_id(self): |
|||
"""Compute the team head function""" |
|||
self.team_head_id = self.team_id.team_lead_id.id |
|||
|
|||
@api.onchange('stage_id') |
|||
def _onchange_stage_id(self): |
|||
"""Sending mail to the user function""" |
|||
rec_id = self._origin.id |
|||
data = self.env['ticket.helpdesk'].search([('id', '=', rec_id)]) |
|||
data.last_update_date = fields.Datetime.now() |
|||
if self.stage_id.starting_stage: |
|||
data.start_date = fields.Datetime.now() |
|||
if self.stage_id.closing_stage or self.stage_id.cancel_stage: |
|||
data.end_date = fields.Datetime.now() |
|||
if self.stage_id.template_id: |
|||
mail_template = self.stage_id.template_id |
|||
mail_template.send_mail(self._origin.id, force_send=True) |
|||
|
|||
def assign_to_teamleader(self): |
|||
"""Assigning team leader function""" |
|||
if self.team_id: |
|||
self.team_head_id = self.team_id.team_lead_id.id |
|||
mail_template = self.env.ref( |
|||
'odoo_website_helpdesk.odoo_website_helpdesk_assign') |
|||
mail_template.sudo().write({ |
|||
'email_to': self.team_head_id.email, |
|||
'subject': self.name |
|||
}) |
|||
mail_template.sudo().send_mail(self.id, force_send=True) |
|||
else: |
|||
raise ValidationError("Please choose a Helpdesk Team") |
|||
|
|||
def _compute_show_category(self): |
|||
"""Compute show category""" |
|||
show_category = self._default_show_category() |
|||
for rec in self: |
|||
rec.show_category = show_category |
|||
|
|||
def _compute_show_create_task(self): |
|||
"""Compute the created task""" |
|||
show_create_task = self._default_show_create_task() |
|||
for record in self: |
|||
record.show_create_task = show_create_task |
|||
|
|||
def auto_close_ticket(self): |
|||
"""Automatically closing the ticket""" |
|||
auto_close = self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.auto_close_ticket') |
|||
if auto_close: |
|||
no_of_days = self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.no_of_days') |
|||
records = self.env['ticket.helpdesk'].search([]) |
|||
for rec in records: |
|||
days = (fields.Datetime.today() - rec.create_date).days |
|||
if days >= int(no_of_days): |
|||
close_stage_id = self.env['ticket.stage'].search( |
|||
[('closing_stage', '=', True)]) |
|||
if close_stage_id: |
|||
rec.stage_id = close_stage_id |
|||
|
|||
def default_stage_id(self): |
|||
"""Method to return the default stage""" |
|||
return self.env['ticket.stage'].search( |
|||
[('name', '=', 'Draft')], limit=1).id |
|||
|
|||
def _read_group_stage_ids(self, stages, domain): |
|||
""" |
|||
return the stages to stage_ids |
|||
""" |
|||
stage_ids = self.env['ticket.stage'].search([]) |
|||
return stage_ids |
|||
|
|||
@api.model_create_multi |
|||
def create(self, vals_list): |
|||
"""Create function""" |
|||
for vals in vals_list: |
|||
if vals.get('name', _('New')) == _('New'): |
|||
vals['name'] = self.env['ir.sequence'].next_by_code( |
|||
'ticket.helpdesk') |
|||
return super(TicketHelpDesk, self).create(vals_list) |
|||
|
|||
def write(self, vals): |
|||
"""Write function""" |
|||
result = super(TicketHelpDesk, self).write(vals) |
|||
return result |
|||
|
|||
def action_create_invoice(self): |
|||
"""Create Invoice based on the ticket""" |
|||
tasks = self.env['project.task'].search( |
|||
[('project_id', '=', self.project_id.id), |
|||
('ticket_id', '=', self.id)]).filtered( |
|||
lambda line: not line.ticket_billed) |
|||
if not tasks: |
|||
raise UserError('No Tasks to Bill') |
|||
total = sum(x.effective_hours for x in tasks if |
|||
x.effective_hours > 0 and not x.some_flag) |
|||
invoice_no = self.env['ir.sequence'].next_by_code( |
|||
'ticket.invoice') |
|||
self.env['account.move'].create([ |
|||
{ |
|||
'name': invoice_no, |
|||
'move_type': 'out_invoice', |
|||
'partner_id': self.customer_id.id, |
|||
'ticket_id': self.id, |
|||
'date': fields.Date.today(), |
|||
'invoice_date': fields.Date.today(), |
|||
'invoice_line_ids': [(0, 0, |
|||
{ |
|||
'product_id': self.service_product_id.id, |
|||
'name': self.service_product_id.name, |
|||
'quantity': total, |
|||
'product_uom_id': self.service_product_id.uom_id.id, |
|||
'price_unit': self.cost, |
|||
'account_id': self.service_product_id.categ_id.property_account_income_categ_id.id, |
|||
})], |
|||
}, ]) |
|||
for task in tasks: |
|||
task.ticket_billed = True |
|||
return { |
|||
'effect': { |
|||
'fadeout': 'medium', |
|||
'message': 'Billed Successfully!', |
|||
'type': 'rainbow_man', |
|||
} |
|||
} |
|||
|
|||
def action_create_tasks(self): |
|||
"""Task creation""" |
|||
task_id = self.env['project.task'].create({ |
|||
'name': self.name + '-' + self.subject, |
|||
'project_id': self.project_id.id, |
|||
'company_id': self.env.company.id, |
|||
'ticket_id': self.id, |
|||
}) |
|||
self.write({ |
|||
'task_ids': [(4, task_id.id)] |
|||
}) |
|||
return { |
|||
'name': 'Tasks', |
|||
'res_model': 'project.task', |
|||
'view_id': False, |
|||
'res_id': task_id.id, |
|||
'view_mode': 'form', |
|||
'type': 'ir.actions.act_window', |
|||
'target': 'new', |
|||
} |
|||
|
|||
def action_open_tasks(self): |
|||
"""View the Created task """ |
|||
return { |
|||
'name': 'Tasks', |
|||
'domain': [('ticket_id', '=', self.id)], |
|||
'res_model': 'project.task', |
|||
'view_id': False, |
|||
'view_mode': 'list,form', |
|||
'type': 'ir.actions.act_window', |
|||
} |
|||
|
|||
def action_open_invoices(self): |
|||
"""View the Created invoice""" |
|||
return { |
|||
'name': 'Invoice', |
|||
'domain': [('ticket_id', '=', self.id)], |
|||
'res_model': 'account.move', |
|||
'view_id': False, |
|||
'view_mode': 'list,form', |
|||
'type': 'ir.actions.act_window', |
|||
} |
|||
|
|||
def action_open_merged_tickets(self): |
|||
"""Open the merged tickets list view""" |
|||
ticket_ids = self.env['support.ticket'].search( |
|||
[('merged_ticket', '=', self.id)]) |
|||
helpdesk_ticket_ids = ticket_ids.mapped('display_name') |
|||
help_ticket_records = self.env['ticket.helpdesk'].search( |
|||
[('name', 'in', helpdesk_ticket_ids)]) |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'Helpdesk Ticket', |
|||
'view_mode': 'list,form', |
|||
'res_model': 'ticket.helpdesk', |
|||
'domain': [('id', 'in', help_ticket_records.ids)], |
|||
'context': self.env.context, |
|||
} |
|||
|
|||
def action_send_reply(self): |
|||
"""Action to sent reply button""" |
|||
template_id = self.env['ir.config_parameter'].sudo().get_param( |
|||
'odoo_website_helpdesk.reply_template_id' |
|||
) |
|||
template_id = self.env['mail.template'].browse(int(template_id)) |
|||
if template_id: |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'mail', |
|||
'res_model': 'mail.compose.message', |
|||
'view_mode': 'form', |
|||
'target': 'new', |
|||
'views': [[False, 'form']], |
|||
'context': { |
|||
'default_model': 'ticket.helpdesk', |
|||
'default_res_ids': self.ids, |
|||
'default_template_id': template_id.id |
|||
} |
|||
} |
|||
return { |
|||
'type': 'ir.actions.act_window', |
|||
'name': 'mail', |
|||
'res_model': 'mail.compose.message', |
|||
'view_mode': 'form', |
|||
'target': 'new', |
|||
'views': [[False, 'form']], |
|||
'context': { |
|||
'default_model': 'ticket.helpdesk', |
|||
'default_res_ids': self.ids, |
|||
} |
|||
} |
@ -1,110 +0,0 @@ |
|||
<odoo> |
|||
<!-- Pdf report template--> |
|||
<template id="report_helpdesk_ticket"> |
|||
<t t-call="web.html_container"> |
|||
<t t-foreach="help" t-as="o"> |
|||
<t t-call="web.external_layout"> |
|||
<div class="page"> |
|||
<div style="margin-bottom: 10px;"> |
|||
<div class="text-center" |
|||
style="font-weight: 400 !important; font-size: 2rem !important;"> |
|||
<t t-esc="o.name"/> |
|||
- <t t-esc="o.subject"/> |
|||
</div><br/> |
|||
<table class="table table-bordered mt32"> |
|||
<thead> |
|||
<tr> |
|||
<th class="text-center"> |
|||
<span>Customer :</span> |
|||
</th> |
|||
<th class="text-center"> |
|||
<span>Description :</span> |
|||
</th> |
|||
<th class="text-center"> |
|||
<span>Priority :</span> |
|||
</th> |
|||
<th class="text-center"> |
|||
<span>Products :</span> |
|||
</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<tr class="text-center"> |
|||
<td> |
|||
<span t-field="o.customer_id" |
|||
t-options='{"widget": "contact", "fields": ["address", "name"], "no_marker": True}'/> |
|||
</td> |
|||
<td> |
|||
<h3 t-field="o.description"/> |
|||
</td> |
|||
<td> |
|||
<h3 t-field="o.priority"/> |
|||
</td> |
|||
<td> |
|||
<h3 t-field="o.product_ids"/> |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
<t t-set="tasks" |
|||
t-value="request.env['project.task'].sudo().search([('ticket_id', '=', o.id)])"/> |
|||
|
|||
<t t-if="tasks"> |
|||
<div> |
|||
<h3 class="text-center"> |
|||
<strong>Tasks</strong> |
|||
</h3> |
|||
</div> |
|||
<table class="table table-bordered mt32"> |
|||
<thead> |
|||
<tr> |
|||
<th class="text-center"> |
|||
<span>Task Name</span> |
|||
</th> |
|||
<th class="text-center"> |
|||
<span>Analytic Account</span> |
|||
</th> |
|||
<th class="text-center"> |
|||
<span>Assigned to</span> |
|||
</th> |
|||
<th class="text-center"> |
|||
<span>Total Hours Spend</span> |
|||
</th> |
|||
</tr> |
|||
</thead> |
|||
<t t-foreach="tasks" t-as="task"> |
|||
<tbody> |
|||
<tr class="text-center"> |
|||
<td> |
|||
<span t-field="task.name"/> |
|||
</td> |
|||
<td> |
|||
<span t-field="task.analytic_account_id"/> |
|||
</td> |
|||
<td> |
|||
<span t-esc="', '.join(map(lambda x: (x.name), task.user_ids))"/> |
|||
</td> |
|||
<td> |
|||
<span t-field="task.effective_hours"/> |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</t> |
|||
</table> |
|||
</t> |
|||
</div> |
|||
</t> |
|||
</t> |
|||
</t> |
|||
</template> |
|||
<!-- Pdf report action--> |
|||
<record id="action_report_helpdesk_ticket" model="ir.actions.report"> |
|||
<field name="name">Helpdesk Ticket Report</field> |
|||
<field name="model">help.ticket</field> |
|||
<field name="report_type">qweb-pdf</field> |
|||
<field name="report_name">odoo_website_helpdesk.report_helpdesk_ticket</field> |
|||
<field name="report_file">odoo_website_helpdesk.report_helpdesk_ticket</field> |
|||
<field name="binding_type">report</field> |
|||
</record> |
|||
</odoo> |
|
@ -0,0 +1,38 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<odoo> |
|||
<!-- Helpdesk Access Groups Category --> |
|||
<record id="module_category_helpdesk" model="ir.module.category"> |
|||
<field name="name">Helpdesk</field> |
|||
<field name="description">Helpdesk Access Groups</field> |
|||
<field name="sequence">20</field> |
|||
</record> |
|||
<!-- Helpdesk User Group --> |
|||
<record id="helpdesk_user" model="res.groups"> |
|||
<field name="name">User</field> |
|||
<field name="category_id" ref="odoo_website_helpdesk.module_category_helpdesk"/> |
|||
</record> |
|||
<!-- Helpdesk Team Leader Group --> |
|||
<record id="helpdesk_team_leader" model="res.groups"> |
|||
<field name="name">Team Leader</field> |
|||
<field name="category_id" ref="odoo_website_helpdesk.module_category_helpdesk"/> |
|||
<field name="implied_ids" eval="[(4, ref('odoo_website_helpdesk.helpdesk_user'))]"/> |
|||
</record> |
|||
<!-- Helpdesk Manager Group --> |
|||
<record id="helpdesk_manager" model="res.groups"> |
|||
<field name="name">Manager</field> |
|||
<field name="category_id" ref="odoo_website_helpdesk.module_category_helpdesk"/> |
|||
<field name="implied_ids" eval="[(4, ref('odoo_website_helpdesk.helpdesk_team_leader'))]"/> |
|||
</record> |
|||
<!-- Group Show Category --> |
|||
<record id="group_show_category" model="res.groups"> |
|||
<field name="name">group_show_category</field> |
|||
</record> |
|||
<!-- Group Show Subcategory --> |
|||
<record id="group_show_subcategory" model="res.groups"> |
|||
<field name="name">group_show_subcategory</field> |
|||
</record> |
|||
<!-- Default User with Helpdesk Manager Group --> |
|||
<record id="base.default_user" model="res.users"> |
|||
<field name="groups_id" eval="[(4,ref('odoo_website_helpdesk.helpdesk_manager'))]"/> |
|||
</record> |
|||
</odoo> |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 628 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 926 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 875 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 589 B |
Before Width: | Height: | Size: 3.4 KiB |