diff --git a/odex25_helpdesk/odex25_helpdesk/__init__.py b/odex25_helpdesk/odex25_helpdesk/__init__.py
new file mode 100644
index 000000000..29fd41cc7
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/__init__.py
@@ -0,0 +1,5 @@
+# -*- coding: utf-8 -*-
+
+from . import controllers
+from . import models
+from . import report
diff --git a/odex25_helpdesk/odex25_helpdesk/__manifest__.py b/odex25_helpdesk/odex25_helpdesk/__manifest__.py
new file mode 100644
index 000000000..015eec16b
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/__manifest__.py
@@ -0,0 +1,61 @@
+# -*- coding: utf-8 -*-
+
+{
+ 'name': 'Helpdesk',
+ 'version': '1.3',
+ 'sequence': 110,
+ 'summary': 'Track, prioritize, and solve customer tickets',
+ 'author': "Expert Co Ltd",
+ 'website': "http://www.ex.com",
+ 'category': 'Odex25-Helpdesk/Odex25-Helpdesk',
+ 'depends': [
+ 'base_setup',
+ 'mail',
+ 'utm',
+ 'rating',
+ 'web_tour',
+ 'resource',
+ 'portal',
+ 'digest',
+ ],
+ 'description': """
+Odex25 helpdesk - Ticket Management App
+================================
+
+Features:
+
+ - Process tickets through different stages to solve them.
+ - Add priorities, types, descriptions and tags to define your tickets.
+ - Use the chatter to communicate additional information and ping co-workers on tickets.
+ - Enjoy the use of an adapted dashboard, and an easy-to-use kanban view to handle your tickets.
+ - Make an in-depth analysis of your tickets through the pivot view in the reports menu.
+ - Create a team and define its members, use an automatic assignment method if you wish.
+ - Use a mail alias to automatically create tickets and communicate with your customers.
+ - Add Service Level Agreement deadlines automatically to your tickets.
+ - Get customer feedback by using ratings.
+ - Install additional features easily using your team form view.
+
+ """,
+ 'data': [
+ 'security/odex25_helpdesk_security.xml',
+ 'security/ir.model.access.csv',
+ 'data/digest_data.xml',
+ 'data/mail_data.xml',
+ # 'data/odex25_helpdesk_data.xml',
+ 'views/odex25_helpdesk_views.xml',
+ 'views/odex25_helpdesk_views.xml',
+ 'views/zfp_config_setting.xml',
+ 'views/odex25_helpdesk_team_views.xml',
+ 'views/assets.xml',
+ 'views/digest_views.xml',
+ 'views/odex25_helpdesk_portal_templates.xml',
+ # 'views/res_partner_views.xml',
+ 'views/mail_activity_views.xml',
+ 'report/odex25_helpdesk_sla_report_analysis_views.xml',
+ ],
+ 'qweb': [
+ "static/src/xml/odex25_helpdesk_team_templates.xml",
+ ],
+ # 'demo': ['data/odex25_helpdesk_demo.xml'],
+ 'application': True,
+}
diff --git a/odex25_helpdesk/odex25_helpdesk/controllers/__init__.py b/odex25_helpdesk/odex25_helpdesk/controllers/__init__.py
new file mode 100644
index 000000000..38a97908f
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/controllers/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import portal
+from . import rating
diff --git a/odex25_helpdesk/odex25_helpdesk/controllers/portal.py b/odex25_helpdesk/odex25_helpdesk/controllers/portal.py
new file mode 100644
index 000000000..0a9a5662c
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/controllers/portal.py
@@ -0,0 +1,192 @@
+# -*- coding: utf-8 -*-
+
+from operator import itemgetter
+
+from odoo import http
+from odoo.exceptions import AccessError, MissingError, UserError
+from odoo.http import request
+from odoo.tools.translate import _
+from odoo.tools import groupby as groupbyelem
+from odoo.addons.portal.controllers.portal import pager as portal_pager, CustomerPortal
+from odoo.osv.expression import OR
+
+
+class CustomerPortal(CustomerPortal):
+
+ def _prepare_portal_layout_values(self):
+ values = super(CustomerPortal, self)._prepare_portal_layout_values()
+ if values.get('sales_user', False):
+ values['title'] = _("Salesperson")
+ return values
+
+ def _prepare_home_portal_values(self, counters):
+ values = super()._prepare_home_portal_values(counters)
+ if 'ticket_count' in counters:
+ values['ticket_count'] = request.env['odex25_helpdesk.ticket'].search_count([])
+ return values
+
+ def _ticket_get_page_view_values(self, ticket, access_token, **kwargs):
+ values = {
+ 'page_name': 'ticket',
+ 'ticket': ticket,
+ }
+ return self._get_page_view_values(ticket, access_token, values, 'my_tickets_history', False, **kwargs)
+
+ @http.route(['/my/tickets', '/my/tickets/page/'], type='http', auth="user", website=True)
+ def my_odex25_helpdesk_tickets(self, page=1, date_begin=None, date_end=None, sortby=None, filterby='all', search=None, groupby='none', search_in='content', **kw):
+ values = self._prepare_portal_layout_values()
+
+ searchbar_sortings = {
+ 'date': {'label': _('Newest'), 'order': 'create_date desc'},
+ 'name': {'label': _('Subject'), 'order': 'name'},
+ 'stage': {'label': _('Stage'), 'order': 'stage_id'},
+ 'reference': {'label': _('Reference'), 'order': 'id'},
+ 'update': {'label': _('Last Stage Update'), 'order': 'date_last_stage_update desc'},
+ }
+ searchbar_filters = {
+ 'all': {'label': _('All'), 'domain': []},
+ 'assigned': {'label': _('Assigned'), 'domain': [('user_id', '!=', False)]},
+ 'unassigned': {'label': _('Unassigned'), 'domain': [('user_id', '=', False)]},
+ 'open': {'label': _('Open'), 'domain': [('close_date', '=', False)]},
+ 'closed': {'label': _('Closed'), 'domain': [('close_date', '!=', False)]},
+ 'last_message_sup': {'label': _('Last message is from support')},
+ 'last_message_cust': {'label': _('Last message is from customer')},
+ }
+ searchbar_inputs = {
+ 'content': {'input': 'content', 'label': _('Search (in Content)')},
+ 'message': {'input': 'message', 'label': _('Search in Messages')},
+ 'customer': {'input': 'customer', 'label': _('Search in Customer')},
+ 'id': {'input': 'id', 'label': _('Search in Reference')},
+ 'status': {'input': 'status', 'label': _('Search in Stage')},
+ 'all': {'input': 'all', 'label': _('Search in All')},
+ }
+ searchbar_groupby = {
+ 'none': {'input': 'none', 'label': _('None')},
+ 'stage': {'input': 'stage_id', 'label': _('Stage')},
+ }
+
+ # default sort by value
+ if not sortby:
+ sortby = 'date'
+ order = searchbar_sortings[sortby]['order']
+
+ if filterby in ['last_message_sup', 'last_message_cust']:
+ discussion_subtype_id = request.env.ref('mail.mt_comment').id
+ messages = request.env['mail.message'].search_read([('model', '=', 'odex25_helpdesk.ticket'), ('subtype_id', '=', discussion_subtype_id)], fields=['res_id', 'author_id'], order='date desc')
+ last_author_dict = {}
+ for message in messages:
+ if message['res_id'] not in last_author_dict:
+ last_author_dict[message['res_id']] = message['author_id'][0]
+
+ ticket_author_list = request.env['odex25_helpdesk.ticket'].search_read(fields=['id', 'partner_id'])
+ ticket_author_dict = dict([(ticket_author['id'], ticket_author['partner_id'][0] if ticket_author['partner_id'] else False) for ticket_author in ticket_author_list])
+
+ last_message_cust = []
+ last_message_sup = []
+ for ticket_id in last_author_dict.keys():
+ if last_author_dict[ticket_id] == ticket_author_dict[ticket_id]:
+ last_message_cust.append(ticket_id)
+ else:
+ last_message_sup.append(ticket_id)
+
+ if filterby == 'last_message_cust':
+ domain = [('id', 'in', last_message_cust)]
+ else:
+ domain = [('id', 'in', last_message_sup)]
+
+ else:
+ domain = searchbar_filters[filterby]['domain']
+
+ if date_begin and date_end:
+ domain += [('create_date', '>', date_begin), ('create_date', '<=', date_end)]
+
+ # search
+ if search and search_in:
+ search_domain = []
+ if search_in in ('id', 'all'):
+ search_domain = OR([search_domain, [('id', 'ilike', search)]])
+ if search_in in ('content', 'all'):
+ search_domain = OR([search_domain, ['|', ('name', 'ilike', search), ('description', 'ilike', search)]])
+ if search_in in ('customer', 'all'):
+ search_domain = OR([search_domain, [('partner_id', 'ilike', search)]])
+ if search_in in ('message', 'all'):
+ discussion_subtype_id = request.env.ref('mail.mt_comment').id
+ search_domain = OR([search_domain, [('message_ids.body', 'ilike', search), ('message_ids.subtype_id', '=', discussion_subtype_id)]])
+ if search_in in ('status', 'all'):
+ search_domain = OR([search_domain, [('stage_id', 'ilike', search)]])
+ domain += search_domain
+
+ # pager
+ tickets_count = len(request.env['odex25_helpdesk.ticket'].search(domain))
+ pager = portal_pager(
+ url="/my/tickets",
+ url_args={'date_begin': date_begin, 'date_end': date_end, 'sortby': sortby, 'search_in': search_in, 'search': search},
+ total=tickets_count,
+ page=page,
+ step=self._items_per_page
+ )
+
+ tickets = request.env['odex25_helpdesk.ticket'].search(domain, order=order, limit=self._items_per_page, offset=pager['offset'])
+ request.session['my_tickets_history'] = tickets.ids[:100]
+
+ if groupby == 'stage':
+ grouped_tickets = [request.env['odex25_helpdesk.ticket'].concat(*g) for k, g in groupbyelem(tickets, itemgetter('stage_id'))]
+ else:
+ grouped_tickets = [tickets]
+
+ values.update({
+ 'date': date_begin,
+ 'grouped_tickets': grouped_tickets,
+ 'page_name': 'ticket',
+ 'default_url': '/my/tickets',
+ 'pager': pager,
+ 'searchbar_sortings': searchbar_sortings,
+ 'searchbar_filters': searchbar_filters,
+ 'searchbar_inputs': searchbar_inputs,
+ 'searchbar_groupby': searchbar_groupby,
+ 'sortby': sortby,
+ 'groupby': groupby,
+ 'search_in': search_in,
+ 'search': search,
+ 'filterby': filterby,
+ })
+ return request.render("odex25_helpdesk.portal_odex25_helpdesk_ticket", values)
+
+ @http.route([
+ "/odex25_helpdesk/ticket/",
+ "/odex25_helpdesk/ticket//",
+ '/my/ticket/',
+ '/my/ticket//'
+ ], type='http', auth="public", website=True)
+ def tickets_followup(self, ticket_id=None, access_token=None, **kw):
+ try:
+ ticket_sudo = self._document_check_access('odex25_helpdesk.ticket', ticket_id, access_token)
+ except (AccessError, MissingError):
+ return request.redirect('/my')
+
+ values = self._ticket_get_page_view_values(ticket_sudo, access_token, **kw)
+ return request.render("odex25_helpdesk.tickets_followup", values)
+
+ @http.route([
+ '/my/ticket/close/',
+ '/my/ticket/close//',
+ ], type='http', auth="public", website=True)
+ def ticket_close(self, ticket_id=None, access_token=None, **kw):
+ try:
+ ticket_sudo = self._document_check_access('odex25_helpdesk.ticket', ticket_id, access_token)
+ except (AccessError, MissingError):
+ return request.redirect('/my')
+
+ if not ticket_sudo.team_id.allow_portal_ticket_closing:
+ raise UserError(_("The team does not allow ticket closing through portal"))
+
+ if not ticket_sudo.closed_by_partner:
+ closing_stage = ticket_sudo.team_id._get_closing_stage()
+ if ticket_sudo.stage_id != closing_stage:
+ ticket_sudo.write({'stage_id': closing_stage[0].id, 'closed_by_partner': True})
+ else:
+ ticket_sudo.write({'closed_by_partner': True})
+ body = _('Ticket closed by the customer')
+ ticket_sudo.with_context(mail_create_nosubscribe=True).message_post(body=body, message_type='comment', subtype_xmlid='mail.mt_note')
+
+ return request.redirect('/my/ticket/%s/%s' % (ticket_id, access_token or ''))
diff --git a/odex25_helpdesk/odex25_helpdesk/controllers/rating.py b/odex25_helpdesk/odex25_helpdesk/controllers/rating.py
new file mode 100644
index 000000000..bfa11c6d8
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/controllers/rating.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+
+import datetime
+
+from odoo import http
+from odoo.http import request
+from odoo.osv.expression import AND
+
+
+class Websiteodex25_helpdesk(http.Controller):
+
+ @http.route(['/odex25_helpdesk/rating', '/odex25_helpdesk/rating/'], type='http', auth="public", website=True, sitemap=True)
+ def page(self, team=False, **kw):
+ # to avoid giving any access rights on Helpdesk team to the public user, let's use sudo
+ # and check if the user should be able to view the team (team managers only if it's not published or has no rating)
+ user = request.env.user
+ team_domain = [('id', '=', team.id)] if team else []
+ if user.has_group('odex25_helpdesk.group_heldpesk_manager'):
+ domain = AND([[('use_rating', '=', True)], team_domain])
+ else:
+ domain = AND([[('use_rating', '=', True), ('portal_show_rating', '=', True)], team_domain])
+ teams = request.env['odex25_helpdesk.team'].search(domain)
+ team_values = []
+ for team in teams:
+ tickets = request.env['odex25_helpdesk.ticket'].sudo().search([('team_id', '=', team.id)])
+ domain = [
+ ('res_model', '=', 'odex25_helpdesk.ticket'), ('res_id', 'in', tickets.ids),
+ ('consumed', '=', True), ('rating', '>=', 1),
+ ]
+ ratings = request.env['rating.rating'].sudo().search(domain, order="id desc", limit=100)
+
+ yesterday = (datetime.date.today()-datetime.timedelta(days=-1)).strftime('%Y-%m-%d 23:59:59')
+ stats = {}
+ any_rating = False
+ for x in (7, 30, 90):
+ todate = (datetime.date.today()-datetime.timedelta(days=x)).strftime('%Y-%m-%d 00:00:00')
+ domdate = domain + [('create_date', '<=', yesterday), ('create_date', '>=', todate)]
+ stats[x] = {1: 0, 3: 0, 5: 0}
+ rating_stats = request.env['rating.rating'].sudo().read_group(domdate, [], ['rating'])
+ total = sum(st['rating_count'] for st in rating_stats)
+ for rate in rating_stats:
+ any_rating = True
+ stats[x][rate['rating']] = (rate['rating_count'] * 100) / total
+ values = {
+ 'team': team,
+ 'ratings': ratings if any_rating else False,
+ 'stats': stats,
+ }
+ team_values.append(values)
+ return request.render('odex25_helpdesk.team_rating_page', {'page_name': 'rating', 'teams': team_values})
diff --git a/odex25_helpdesk/odex25_helpdesk/data/digest_data.xml b/odex25_helpdesk/odex25_helpdesk/data/digest_data.xml
new file mode 100644
index 000000000..171c8292e
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/data/digest_data.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ True
+
+
+
+
+
+ Tip: Create tickets from incoming emails
+ 1800
+
+
+
+ % set record = object.env['odex25_helpdesk.team'].search([('alias_name', '!=', False)],limit=1)
+ Tip: Create tickets from incoming emails
+ % if record and record.alias_domain
+
+ Dear ${object.sudo().partner_id.name or 'Madam/Sir'},
+ Your request
+ % if object.get_portal_url():
+ ${object.name}
+ % endif
+ has been received and is being reviewed by our ${object.team_id.name or ''} team.
+ The reference of your ticket is ${object.id}.
+
+ To add additional comments, reply to this email.
+
+ Thank you,
+ ${object.team_id.name or 'Helpdesk'} Team.
+
+
+ ${object.partner_id.lang or object.user_id.lang or user.lang}
+
+
+
+
+ Ticket: Solved
+
+ ${object.display_name}
+ ${(object.user_id.email_formatted or user.email_formatted) | safe}
+ ${(object.partner_email if not object.sudo().partner_id.email or object.sudo().partner_id.email != object.partner_email else '') | safe}
+ ${object.partner_id.id if object.sudo().partner_id.email and object.sudo().partner_id.email == object.partner_email else ''}
+
+
+ Dear ${object.sudo().partner_id.name or 'Madam/Sir'},
+ This automatic message informs you that we have closed your ticket (reference ${object.id}).
+ We hope that the services provided have met your expectations.
+ If you have any more questions or comments, don't hesitate to reply to this e-mail to re-open your ticket.
+ Thank you for your cooperation.
+ Kind regards,
+ ${object.team_id.name or 'Helpdesk'} Team.
+
+
+ ${object.partner_id.lang or object.user_id.lang or user.lang}
+
+
+
+
+ Ticket: Rating Request (requires rating enabled on team)
+
+ ${object.company_id.name or object.user_id.company_id.name or 'Helpdesk'}: Service Rating Request
+ ${object.rating_get_rated_partner_id().email_formatted | safe}
+ ${(object.partner_email if not object.sudo().partner_id.email or object.sudo().partner_id.email != object.partner_email else '') | safe}
+ ${object.partner_id.id if object.sudo().partner_id.email and object.sudo().partner_id.email == object.partner_email else ''}
+
+
+ % set access_token = object.rating_get_access_token()
+ % set partner = object.rating_get_partner_id()
+
+
+
+ % if partner.name:
+ Hello ${partner.name},
+ % else:
+ Hello,
+ % endif
+ Please take a moment to rate our services related to the ticket "${object.name}"
+ % if object.rating_get_rated_partner_id().name:
+ assigned to ${object.rating_get_rated_partner_id().name}.
+ % else:
+ .
+ % endif
+
+
+
+
+ Tell us how you feel about our service
+ (click on one of these smileys)
+
+ We appreciate your feedback. It helps us to improve continuously.
+ This customer survey has been sent because your ticket has been moved to the stage ${object.stage_id.name}
+
+
+
+
+
+ ${object.partner_id.lang or object.user_id.lang or user.lang}
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_data.xml b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_data.xml
new file mode 100644
index 000000000..ca2004aed
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_data.xml
@@ -0,0 +1,83 @@
+
+
+
+
+ Customer Care
+ support
+
+
+
+
+
+
+ New
+ 0
+
+
+
+
+
+ In Progress
+ 1
+
+
+
+
+ Solved
+
+ 2
+
+
+
+
+ Cancelled
+ 3
+
+
+
+
+
+
+ Question
+
+
+ Issue
+
+
+
+
+ Share
+
+
+ form
+ code
+ action = records.action_share()
+
+
+
+
+ Status Per Deadline
+ odex25_helpdesk.sla.report.analysis
+ {
+ 'pivot_column_groupby': ['sla_deadline:day'],
+ 'pivot_row_groupby': ['team_id', 'ticket_id', 'sla_id']
+ }
+ []
+
+
+
+
+
+ Failed SLA Stage per Month
+ odex25_helpdesk.sla.report.analysis
+ {
+ 'pivot_measures': ['__count'],
+ 'pivot_column_groupby': ['create_date:month'],
+ 'pivot_row_groupby': ['team_id', 'sla_stage_id']
+ }
+ []
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_demo.xml b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_demo.xml
new file mode 100644
index 000000000..b17232eef
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/data/odex25_helpdesk_demo.xml
@@ -0,0 +1,384 @@
+
+
+
+ CRM
+
+
+ Website
+
+
+ Service
+
+
+ Repair
+
+
+
+
+
+
+
+
+
+ Done
+ 2
+
+
+
+
+
+
+
+ VIP Support
+
+
+
+
+
+
+
+ 2 days to start
+
+
+ 2
+
+
+
+ 7 days to finish
+
+
+ 7
+
+
+
+ Assigned within 24 hrs
+
+
+ 1
+ 3
+
+
+ assigning
+
+
+
+
+ Kitchen collapsing
+
+ 3
+
+
+
+
+ Where can I download a catalog ?
+ 0
+
+
+
+
+ Warranty
+
+
+
+ 2
+
+
+
+Hello,
+
+I would like to know what kind of warranties you are offering for your products.
+
+Here is my contact number: 123456789
+
+Thank you,
+Chester Reed
+
+
+
+
+
+
+
+
+
+ Wood Treatment
+
+
+
+
+
+
+Hello,
+
+Is the wood from your furniture treated with a particular product? What would you recommend to maintain the quality of a dining table?
+
+Your assistance would be greatly appreciated.
+
+Thanks in Advance,
+Azure Interior
+
+
+
+ Chair dimensions
+
+
+
+
+
+
+Can you please tell me the dimensions of your “Office chair Black”? Also I am unable to find the information on your official site.
+
+I look forward to your kind response.
+
+Thank you!
+
+
+
+ Lost key
+
+
+
+
+
+
+Hello,
+
+I bought a locker a few years ago and I, unfortunately, lost the key. I cannot retrieve the documents I had left in there without damaging the furniture item. What solution do you offer?
+
+Thanks in advance for your help.
+Kind regards,
+Gemini Furniture
+
+
+
+ Furniture delivery
+
+
+
+
+
+Hi,
+
+I was wondering if you were delivering the furniture or if we needed to pick it up at your warehouse?
+If you do take care of the delivery, are there any extra costs?
+
+Regards,
+Deco Addict
+
+
+
+ Cabinets in kit
+
+
+
+
+
+Hello,
+
+I would like to know if your cabinets come in a kit? They seem quite large and I am not sure they will fit through my front door.
+
+Thank you for your help.
+Best regards,
+Jackson Group
+
+
+
+ Missing user manual
+
+
+
+
+
+Hello,
+
+I recently purchased one of your wardrobes in a kit. Unfortunately, I didn’t receive the user manual, so I cannot assemble the item. Could you send me this document?
+
+Thank you.
+Kind regards,
+
+
+
+ Ugly Chair
+
+
+
+
+
+
+Hello,
+
+I purchased a chair from you last week. I now realize it doesn’t go well with the rest of my furniture, so I would like to return it and to get a refund.
+
+Regards,
+Deco Addict
+
+
+
+ Couch
+
+
+
+
+
+
+Hello,
+
+The couch I ordered was scratched during the delivery. Would it be possible to have a gesture of goodwill?
+
+Thank you for considering my request.
+Best regards,
+
+
+
+ Chair wheels aren’t working
+
+
+
+
+ 3
+
+
+
+The chair I bought last year isn't turning correctly anymore. Are you selling spare parts for the wheels?
+
+Thank you in advance for your help.
+Chester Reed
+
+
+
+
+
+
+
+
+
+ Cabinet Colour and Lock aren't proper
+
+
+
+
+
+ 3
+
+
+Hi,
+
+I purchased a "Cabinet With Doors" from your store a few days ago. The lock is not working properly and the color is wrong. This is unacceptable! I am asking for a product that corresponds to my order and that matches the quality you are advertising.
+
+Regards,
+The Jackson Group
+
+
+
+ Lamp Stand is bent
+
+
+
+
+
+ 2
+
+Hello,
+
+Yesterday I purchased a lamp stand from your site but the product I received is bent.
+
+Would it be possible to get a replacement?
+
+Regards,
+Ready Mat
+
+
+
+ Table legs are unbalanced
+
+
+
+
+
+ 3
+
+
+Hi,
+
+A few days ago, I bought a Four Persons Desk. While assembling it in my office, I found that the legs of the table were not properly balanced. Could you please come and fix this?
+
+Kindly do this as early as possible.
+
+Best,
+Azure Interior
+
+
+
+ Drawer’s slides and handle have a defect
+
+
+
+
+
+
+Hi,
+
+I have purchased a "Drawer" from your store but the slides and the handle seem to have a defect.
+
+Would it be possible for you to fix it?
+
+Regards,
+Deco
+
+
+
+ Want to change the place of the dining area
+
+
+
+
+
+
+Hello,
+
+I want to change the location of the dining area and would like your advice.
+
+Hope to hear from you soon.
+
+Best,
+Gemini Furniture
+
+
+
+ Received Product is damaged
+
+
+
+
+
+
+Hi,
+
+I ordered a "Table Kit" from your store but the delivered product is damaged. I demand a refund as soon as possible.
+
+Regards,
+
+
+
+ Delivered wood panel is not what I ordered
+
+
+
+
+
+
+Hello,
+
+I ordered a wood panel from your online store, but the delivered product is not what I had ordered.
+
+Could you please replace it with the right product?
+Waiting for your response.
+
+Best,
+Wood Corner
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk/i18n/ar.po b/odex25_helpdesk/odex25_helpdesk/i18n/ar.po
new file mode 100644
index 000000000..056288563
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/i18n/ar.po
@@ -0,0 +1,3489 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * odex25_helpdesk
+#
+# Translators:
+# Sherif Abd Ekmoniem , 2020
+# Yihya Hugirat , 2020
+# Sadig Adam , 2020
+# Ghaith Gammar , 2020
+# Zuhair Hammadi , 2020
+# Talal Kamal , 2020
+# Akram Alfusayal , 2020
+# amrnegm , 2020
+# Martin Trigaux, 2020
+# hoxhe Aits , 2020
+# Ali zuaby , 2020
+# Osoul , 2020
+# Mohammed Ibrahim , 2020
+# Osama Ahmaro , 2020
+# amal ahmed , 2020
+# Shaima Safar , 2020
+# Amer Hazaa , 2020
+# Mostafa Hanafy , 2020
+# Nisrine Tagri , 2020
+# Tasneem Sarhan , 2020
+# Talal Albahra , 2020
+# Mustafa Rawi , 2020
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-29 14:04+0000\n"
+"PO-Revision-Date: 2020-09-07 08:20+0000\n"
+"Last-Translator: Mustafa Rawi , 2020\n"
+"Language-Team: Arabic (https://www.transifex.com/odoo/teams/41243/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#. module: odex25_helpdesk
+#: model:mail.template,subject:odex25_helpdesk.rating_ticket_request_email_template
+msgid ""
+"${object.company_id.name or object.user_id.company_id.name or 'Helpdesk'}: "
+"Service Rating Request"
+msgstr ""
+"${object.company_id.name or object.user_id.company_id.name or 'مكتب الدعم'}:"
+" طلب تقييم الخدمة"
+
+#. module: odex25_helpdesk
+#: model:mail.template,subject:odex25_helpdesk.new_ticket_request_email_template
+#: model:mail.template,subject:odex25_helpdesk.solved_ticket_request_email_template
+msgid "${object.display_name}"
+msgstr "${object.display_name}"
+
+#. module: odex25_helpdesk
+#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid "${record.alias_id.display_name}"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid "% else"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid "% endif"
+msgstr "% endif"
+
+#. module: odex25_helpdesk
+#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid ""
+"% set record = object.env['odex25_helpdesk.team'].search([('alias_name', '!=', False)],limit=1)\n"
+" Tip: Create tickets from incoming emails\n"
+" % if record and record.alias_domain"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "×"
+msgstr "×"
+
+#. module: odex25_helpdesk
+#: model:mail.template,body_html:odex25_helpdesk.rating_ticket_request_email_template
+msgid ""
+"\n"
+"
\n"
+" % set access_token = object.rating_get_access_token()\n"
+" % set partner = object.rating_get_partner_id()\n"
+"
\n"
+" \n"
+"
\n"
+" % if partner.name:\n"
+" Hello ${partner.name}, \n"
+" % else:\n"
+" Hello, \n"
+" % endif\n"
+" Please take a moment to rate our services related to the ticket \"${object.name}\"\n"
+" % if object.rating_get_rated_partner_id().name:\n"
+" assigned to ${object.rating_get_rated_partner_id().name}. \n"
+" % else:\n"
+" . \n"
+" % endif\n"
+"
\n"
+"
\n"
+"
\n"
+"
\n"
+" Tell us how you feel about our service \n"
+" (click on one of these smileys)\n"
+"
\n"
+" We appreciate your feedback. It helps us to improve continuously.\n"
+" This customer survey has been sent because your ticket has been moved to the stage ${object.stage_id.name}\n"
+"
\n"
+" Dear ${object.sudo().partner_id.name or 'Madam/Sir'},
\n"
+" This automatic message informs you that we have closed your ticket (reference ${object.id}).\n"
+" We hope that the services provided have met your expectations.\n"
+" If you have any more questions or comments, don't hesitate to reply to this e-mail to re-open your ticket.
\n"
+" Thank you for your cooperation. \n"
+" Kind regards,
\n"
+" ${object.team_id.name or 'Helpdesk'} Team.\n"
+"
\n"
+" "
+msgstr ""
+"\n"
+"
\n"
+" عزيزنا ${object.sudo().partner_id.name or 'Madam/Sir'}،
\n"
+" هذه رسالة تلقائية لإخطاركم أننا أغلقنا تذكرتكم (رقم الإشارة: ${object.id}).\n"
+" نأمل أن تكون خدماتنا قد ارتقت لمستوى توقعاتكم.\n"
+" إذا كان لديكم أية تساؤلات أو تعليقات، لا تترددوا في الرد على هذه الرسالة لإعادة فتح التذكرة.
\n"
+" شكرًا على تعاونكم. \n"
+" مع أطيب تحياتنا،
\n"
+" فريق ${object.team_id.name or 'Helpdesk'}.\n"
+"
\n"
+" Dear ${object.sudo().partner_id.name or 'Madam/Sir'},
\n"
+" Your request\n"
+" % if object.get_portal_url():\n"
+" ${object.name}\n"
+" % endif\n"
+" has been received and is being reviewed by our ${object.team_id.name or ''} team.\n"
+" The reference of your ticket is ${object.id}.
\n"
+"\n"
+" To add additional comments, reply to this email.
\n"
+"\n"
+" Thank you,
\n"
+" ${object.team_id.name or 'Helpdesk'} Team.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid ""
+"Stars mark the ticket priority. You can change it directly "
+"from here!"
+msgstr ""
+"عدد النجوم يحدد أولوية التذكرة. يمكنك تغييرهم مباشرة من هنا!"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket
+msgid "Tickets in stage:"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "No description"
+msgstr "لا يوجد وصف"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+msgid ""
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+msgid ""
+" "
+msgstr ""
+" "
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid ""
+" Enable the External Email "
+"Servers feature in the General Settings and indicate an alias domain"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Status:"
+msgstr "الحالة:"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid ""
+"\n"
+" If the issue has been solved, you can close the request.\n"
+" "
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Close this ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid ""
+""
+msgstr ""
+""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.view_partner_form_inherit_odex25_helpdesk
+msgid " Tickets"
+msgstr " التذاكر"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Assigned to"
+msgstr "مُسند إلى"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Description"
+msgstr "الوصف"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Managed by"
+msgstr "يديره"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Reported by"
+msgstr "قُدم التقرير بواسطة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Reported on"
+msgstr "قُدم التقرير عن"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_defaults
+msgid ""
+"A Python dictionary that will be evaluated to provide default values when "
+"creating new records for this alias."
+msgstr ""
+"قاموس بايثون سيتم تقديره لتوفير قيم افتراضية عند إنشاء سجلات جديدة لهذا "
+"اللقب."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__description
+msgid "About Team"
+msgstr "تعريف بالفريق"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__access_warning
+msgid "Access warning"
+msgstr "تحذير من خطأ بالوصول"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction
+msgid "Action Needed"
+msgstr "إجراء مطلوب"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__active
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__active
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__active
+msgid "Active"
+msgstr "نشط"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_ids
+msgid "Activities"
+msgstr "الأنشطة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_decoration
+msgid "Activity Exception Decoration"
+msgstr "زخرفة استثناء النشاط"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_state
+msgid "Activity State"
+msgstr "حالة النشاط"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_type_icon
+msgid "Activity Type Icon"
+msgstr "أيقونة نوع النشاط"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.mail_activity_type_action_config_odex25_helpdesk
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_config_activity_type
+msgid "Activity Types"
+msgstr "أنواع النشاطات"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_stage_action
+msgid ""
+"Adapt your pipeline to your workflow and easily track the progress of your "
+"tickets."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid "Add a description..."
+msgstr "إضافة وصف..."
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid ""
+"Add columns to configure stages for your tickets. e.g. "
+"Awaiting Customer Feedback, Customer Followup, ..."
+msgstr ""
+"إضافة أعمدة لإعداد مراحل تذاكرك. مثلًا: بانتظار ملاحظات "
+"العميل، ردود العميل, ..."
+
+#. module: odex25_helpdesk
+#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_manager
+msgid "Administrator"
+msgstr "مدير"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "After Sales"
+msgstr "بعد البيع"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_id
+msgid "Alias"
+msgstr "لقب"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_contact
+msgid "Alias Contact Security"
+msgstr "لقب الاتصال الآمن"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_name
+msgid "Alias Name"
+msgstr "اسم اللقب"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_domain
+msgid "Alias domain"
+msgstr "نطاق اللقب"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_model_id
+msgid "Aliased Model"
+msgstr "الكائن الملقب"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__0
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__0
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__0
+#, python-format
+msgid "All"
+msgstr "الكل"
+
+#. module: odex25_helpdesk
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_menu_all
+msgid "All Tickets"
+msgstr "كافة التذاكر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__allow_portal_ticket_closing
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Allow customers to close their tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Allow product returns from tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid ""
+"Allow your customers to easily rate your services. Activate this option will"
+" add a default email template on non folded closing stages"
+msgstr ""
+"اسمح لعملائك بتقييم خدماتك بسهولة. تفعيل هذا الخيار سيضيف قالب بريد إلكتروني"
+" افتراضي في مراحل الإغلاق غير المطوية"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__is_self_assigned
+msgid "Am I assigned"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "Applies To Stage"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "Apply on"
+msgstr "يُطبق على"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid "Archive"
+msgstr "أرشفة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Archived"
+msgstr "مؤرشف"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Are you sure you wish to proceed?"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+msgid "Assign To Me"
+msgstr "إسنادها لي"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid "Assign the ticket to someone."
+msgstr "إسناد التذكرة لشخص."
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0
+#, python-format
+msgid "Assign to me"
+msgstr "إسنادها لي"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Assigned"
+msgstr "إسناد"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__user_id
+msgid "Assigned To"
+msgstr "أسندت إلى"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__user_id
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Assigned to"
+msgstr "مسند إلى"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+msgid "Assignee"
+msgstr "المُسند إليه"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__target_type__assigning
+msgid "Assigning Ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__assign_method
+msgid "Assignment Method"
+msgstr "طريقة التعيين"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid ""
+"At each stage employees can block or make task/issue ready for next stage.\n"
+" You can define here labels that will be displayed for the state instead\n"
+" of the default labels."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_attachment_count
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_attachment_count
+msgid "Attachment Count"
+msgstr "عدد المرفقات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__template_id
+msgid ""
+"Automated email sent to the ticket's customer when the ticket reaches this "
+"stage."
+msgstr ""
+"رسالة الرد الآلي المرسلة للعميل مُقدم التذكرة عند وصولها لهذه المرحلة."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__assign_method
+msgid ""
+"Automatic assignment method for new tickets:\n"
+"\tManually: manual\n"
+"\tRandomly: randomly but everyone gets the same amount\n"
+"\tBalanced: to the person with the least amount of open tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_progress_data
+msgid "Average"
+msgstr "متوسط"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Avg 7 Days Happy Rating"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Avg 7 Success Rate"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Avg 7 days"
+msgstr "متوسط 7 أيام"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Avg Open Hours"
+msgstr "متوسط ساعات الفتح"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_progress_data
+msgid "Bad"
+msgstr "سيئ"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_team__assign_method__balanced
+msgid "Balanced"
+msgstr "متوازن"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0
+#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_cancelled
+#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_done
+#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_in_progress
+#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_new
+#: model:odex25_helpdesk.stage,legend_blocked:odex25_helpdesk.stage_solved
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_1
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_10
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_11
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_12
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_13
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_14
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_15
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_16
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_17
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_18
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_19
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_2
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_3
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_4
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_5
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_6
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_7
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_8
+#: model:odex25_helpdesk.ticket,legend_blocked:odex25_helpdesk.odex25_helpdesk_ticket_9
+#, python-format
+msgid "Blocked"
+msgstr "محجوب"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__campaign_id
+msgid "Campaign"
+msgstr "الحملة"
+
+#. module: odex25_helpdesk
+#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_cancelled
+msgid "Cancelled"
+msgstr "ملغي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.constraint,message:odex25_helpdesk.constraint_odex25_helpdesk_team_not_portal_show_rating_if_not_use_rating
+msgid "Cannot show ratings in portal if not using them"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Channels"
+msgstr "القنوات"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid "Click here to view this team's tickets."
+msgstr "اضغط هنا لعرض تذاكر هذا الفريق."
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid ""
+"Click these cards to open their form view, or drag & drop them "
+"through the different stages of this team."
+msgstr ""
+"اضغط على هذه البطاقات لفتح واجهة عروضهم، أو اسحبهم وأسقطهم لنقلهم عبر"
+" المراحل المختلفة لهذا الفريق."
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Click to set"
+msgstr "انقر لضبط"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Close"
+msgstr "اغلاق"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__close_date
+msgid "Close date"
+msgstr "تاريخ الإقفال"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Close the ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Close ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+#, python-format
+msgid "Closed"
+msgstr "مغلق"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "Closed Ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#, python-format
+msgid "Closed Tickets"
+msgstr "التذاكر المقفلة"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_7days_analysis
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_close_analysis
+msgid "Closed Tickets Analysis"
+msgstr "تحليل التذاكر المقفلة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__closed_by_partner
+msgid "Closed by Partner"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__is_close
+msgid "Closing Stage"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__color
+msgid "Color"
+msgstr "اللون"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__color
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__color
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__color
+msgid "Color Index"
+msgstr "معرف اللون"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__kanban_state_label
+msgid "Column Status"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__company_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__company_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__company_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__company_id
+msgid "Company"
+msgstr "شركة"
+
+#. module: odex25_helpdesk
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_config
+msgid "Configuration"
+msgstr "إعدادات التكوين"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Configure SLA Policies"
+msgstr "إعداد سياسات اتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Configure a custom domain"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.action_upcoming_sla_fail_all_tickets
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_sla
+msgid "Congratulations!"
+msgstr "تهانينا!"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_res_partner
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Contact"
+msgstr "جهة الاتصال"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_coupons
+msgid "Coupons"
+msgstr "الكوبونات"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.email_template_action_odex25_helpdesk
+msgid "Create a new template"
+msgstr "إنشاء قالب جديد"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7days_analysis
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7dayssuccess
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_close_analysis
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_dashboard
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_success
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team_performance
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_analysis_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_team_analysis_action
+msgid "Create tickets to get statistics."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__create_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__create_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__create_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__create_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__create_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__create_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__create_uid
+msgid "Created by"
+msgstr "أنشئ بواسطة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__create_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__create_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__create_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__create_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__create_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__create_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__create_date
+msgid "Created on"
+msgstr "أنشئ في"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Creation Date"
+msgstr "تاريخ الإنشاء"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Current stage of this ticket"
+msgstr "المرحلة الحالية لهذه التذكرة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_bounced_content
+msgid "Custom Bounced Message"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__partner_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_id
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+#, python-format
+msgid "Customer"
+msgstr "الموظف / العميل"
+
+#. module: odex25_helpdesk
+#: model:odex25_helpdesk.team,name:odex25_helpdesk.odex25_helpdesk_team1
+msgid "Customer Care"
+msgstr "خدمة العملاء"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_email
+#, python-format
+msgid "Customer Email"
+msgstr "البريد الإلكتروني"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_name
+msgid "Customer Name"
+msgstr "اسم العميل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__access_url
+msgid "Customer Portal URL"
+msgstr "رابط بوابة العميل"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk_ticket.py:0
+#, python-format
+msgid "Customer Tickets"
+msgstr "تذاكر العميل"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Daily Target"
+msgstr "الهدف اليومي"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/odex25_helpdesk_dashboard.js:0
+#, python-format
+msgid "Dashboard"
+msgstr "لوحة المعلومات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_status__reached_datetime
+msgid "Datetime at which the SLA stage was reached for the first time"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_exceeded_days
+msgid "Day to reach SLA"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_exceeded_days
+msgid ""
+"Day to reach the stage of the SLA, without taking the working calendar into "
+"account"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__time_days
+msgid "Days"
+msgstr "الأيام"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__time_days
+msgid "Days to reach given stage based on ticket creation date"
+msgstr "عدد الأيام المتبقية للوصول لمرحلة معينة حسب تاريخ إنشاء التذكرة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__deadline
+msgid "Deadline"
+msgstr "الموعد النهائي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_defaults
+msgid "Default Values"
+msgstr "القيم الافتراضية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid "Delete"
+msgstr "حذف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__description
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__description
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket
+msgid "Description"
+msgstr "الوصف"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Description for customer portal"
+msgstr "وصف بوابة العميل"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "Description of the policy..."
+msgstr "وصف السياسة..."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+msgid "Description of the ticket..."
+msgstr "وصف التذكرة..."
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_digest_digest
+msgid "Digest"
+msgstr "الملخص"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Discard"
+msgstr "تجاهل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__display_name
+msgid "Display Name"
+msgstr "الاسم المعروض"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__portal_show_rating
+msgid "Display Rating on Customer Portal"
+msgstr "عرض التقييم على بوابة العميل"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/digest.py:0
+#, python-format
+msgid "Do not have access, skip this data for user's digest email"
+msgstr "لا تملك صلاحيات الوصول. تخطِ هذه البيانات لبريد الملخص للمستخدم."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Documentation"
+msgstr "التوثيق"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+msgid "Documents"
+msgstr "المستندات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__domain_user_ids
+msgid "Domain User"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_done
+msgid "Done"
+msgstr "المنتهية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid "Dropdown menu"
+msgstr "القائمة المنسدلة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid "Edit"
+msgstr "تحرير"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__template_id
+msgid "Email Template"
+msgstr "قالب البريد الإلكتروني"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_alias
+msgid "Email alias"
+msgstr "لقب البريد الإلكتروني"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__email_cc
+msgid "Email cc"
+msgstr "البريد الإلكتروني cc"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__email
+msgid "Email on Customer"
+msgstr "البريد الإلكتروني"
+
+#. module: odex25_helpdesk
+#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid "Emails sent to"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid ""
+"Emails sent to a Helpdesk Team alias generate tickets in your pipeline."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_slides
+msgid "Enable eLearning"
+msgstr "تفعيل eLearning"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid ""
+"Enter a subject or title for this ticket. (e.g. Problem with "
+"installation, Wrong order, Can't understand bill, etc.)"
+msgstr ""
+"أدخل موضوع أو عنوان لهذه التذكرة. (مثلًا: مشكلة أثناء التثبيت، طلب "
+"خطأ، فاتورة غير مفهومة، إلخ.)"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid "Enter the customer. Feel free to create it on the fly."
+msgstr "أدخل اسم العميل. يمكنك إنشاء واحد جديد سريعًا."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__exceeded_days
+msgid "Excedeed Working Days"
+msgstr "أيام عمل متقطعة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__exclude_stage_ids
+msgid "Exclude Stages"
+msgstr "استبعاد المراحل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__sla_status__failed
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_status__status__failed
+msgid "Failed"
+msgstr "فشل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_fail
+msgid "Failed SLA Policy"
+msgstr "حدث فشل باتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.filters,name:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_filter_stage_failed
+msgid "Failed SLA Stage per Month"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "Failed Ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+msgid "First Assignment Date"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__assign_date
+msgid "First assignment date"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__fold
+msgid "Folded in Kanban"
+msgstr "مطوي في عرض كانبان"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid ""
+"Follow this team to automatically track the events associated to tickets of "
+"this team."
+msgstr "تابع هذا الفريق لتتمكن من تتبع الفعاليات المرتبطة بتذاكرهم تلقائيًا."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_follower_ids
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_follower_ids
+msgid "Followers"
+msgstr "المتابعون"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_channel_ids
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_channel_ids
+msgid "Followers (Channels)"
+msgstr "المتابعون (القنوات)"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_partner_ids
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_partner_ids
+msgid "Followers (Partners)"
+msgstr "المتابعون (الشركاء)"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_type_icon
+msgid "Font awesome icon e.g. fa-tasks"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Future Activities"
+msgstr "الأنشطة المستقبلية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Generate credit notes from tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Get in touch with your website visitors"
+msgstr "تواصل مع زوار موقعك"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Grant coupons from tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__kanban_state__done
+msgid "Green"
+msgstr "أخضر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__legend_done
+msgid "Green Kanban Label"
+msgstr "بطاقة عنوان كانبان خضراء"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__kanban_state__normal
+msgid "Grey"
+msgstr "رمادي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__legend_normal
+msgid "Grey Kanban Label"
+msgstr "بطاقة عنوان كانبان رمادية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Group By"
+msgstr "تجميع حسب"
+
+#. module: odex25_helpdesk
+#: model:mail.activity.type,name:odex25_helpdesk.mail_act_odex25_helpdesk_handle
+msgid "Handle Ticket"
+msgstr "التعامل مع التذكرة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_progress_data
+msgid "Happy"
+msgstr "سعيد"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Happy Rating"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__has_external_mail_server
+msgid "Has External Mail Server"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_reached_late
+msgid "Has SLA reached late"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_forum
+msgid "Help Center"
+msgstr "مركز المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.ui.menu,name:odex25_helpdesk.menu_odex25_helpdesk_root
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.digest_digest_view_form
+msgid "Helpdesk"
+msgstr "مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main
+msgid "Helpdesk Overview"
+msgstr "نظرة عامة على مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_sla
+msgid "Helpdesk SLA Policies"
+msgstr "سياسات اتفاق مستوى الخدمة لمكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_stage
+msgid "Helpdesk Stage"
+msgstr "مرحلة مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_tag
+msgid "Helpdesk Tags"
+msgstr "وسوم مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_team
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__team_id
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_tree
+msgid "Helpdesk Team"
+msgstr "فريق مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Helpdesk Team..."
+msgstr "فريق مكتب المساعدة..."
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_team_action
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_team_menu
+msgid "Helpdesk Teams"
+msgstr "فرق مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_ticket
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+msgid "Helpdesk Ticket"
+msgstr "تذكرة مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_analysis_action
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_graph_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_pivot_analysis
+msgid "Helpdesk Ticket Analysis"
+msgstr "تحليل تذكرة مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_ticket_type
+msgid "Helpdesk Ticket Type"
+msgstr "نوع تذكرة مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_graph_main
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_pivot_main
+msgid "Helpdesk Tickets"
+msgstr "تذاكر مكتب المساعدة"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "High Priority ("
+msgstr "أولوية عالية ("
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__2
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__2
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__2
+msgid "High priority"
+msgstr "أولوية عالية"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__time_hours
+msgid "Hours"
+msgstr "الساعات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__time_hours
+msgid "Hours to reach given stage based on ticket creation date"
+msgstr "عدد الساعات المتبقية للوصول لمرحلة معينة حسب تاريخ إنشاء التذكرة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "How to assign newly created tickets to the right person"
+msgstr "كيفية إسناد الرسائل المُنشأة حديثًا للشخص الصحيح"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner__id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__id
+msgid "ID"
+msgstr "المُعرف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_thread_id
+msgid ""
+"ID of the parent record holding the alias (example: project holding the task"
+" creation alias)"
+msgstr ""
+"مُعرف السجل الأصلي الذي يحتوي اللقب (مثال: 'المشروع' يحمل لقب إنشاء المهمة)"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_icon
+msgid "Icon"
+msgstr "الأيقونة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_icon
+msgid "Icon to indicate an exception activity."
+msgstr "الأيقونة للإشارة إلى استثناء النشاط"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_unread
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread
+msgid "If checked, new messages require your attention."
+msgstr "إذا كان محددًا، فهناك رسائل جديدة تحتاج لرؤيتها."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_has_sms_error
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_sms_error
+msgid "If checked, some messages have a delivery error."
+msgstr "إذا كان محددًا، فقد حدث خطأ في تسليم بعض الرسائل."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__closed_by_partner
+msgid ""
+"If checked, this means the ticket was closed through the customer portal by "
+"the customer."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_bounced_content
+msgid ""
+"If set, this content will automatically be sent out to unauthorized users "
+"instead of the default message."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid "In #{kanban_getcolorname(record.color.raw_value)}"
+msgstr "في #{kanban_getcolorname(record.color.raw_value)}"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_livechat
+msgid ""
+"In Channel: You can create a new ticket by typing /Helpdesk [ticket title]. "
+"You can search ticket by typing /odex25_helpdesk_search [Keyword1],[Keyword2],."
+msgstr ""
+"في القناة: يمكنك إنشاء تذكرة جديدة بكتابة /Helpdesk [عنوان التذكرة]. كما "
+"يمكنك البحث عن تذكرة عبر كتابة /odex25_helpdesk_search [الكلمة الدلالية1]،[الكلمة "
+"الدلالية2]."
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0
+#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_cancelled
+#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_done
+#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_in_progress
+#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_new
+#: model:odex25_helpdesk.stage,legend_normal:odex25_helpdesk.stage_solved
+#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_in_progress
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_1
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_10
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_11
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_12
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_13
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_14
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_15
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_16
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_17
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_18
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_19
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_2
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_3
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_4
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_5
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_6
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_7
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_8
+#: model:odex25_helpdesk.ticket,legend_normal:odex25_helpdesk.odex25_helpdesk_ticket_9
+#, python-format
+msgid "In Progress"
+msgstr "قيد التنفيذ"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Incoming emails create tickets"
+msgstr "الرسائل الواردة تنشئ تذاكر"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid ""
+"Individuals to whom the tickets will be automatically assigned. Keep empty "
+"for everyone to be part of the team."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_is_follower
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_is_follower
+msgid "Is Follower"
+msgstr "متابع"
+
+#. module: odex25_helpdesk
+#: model:odex25_helpdesk.ticket.type,name:odex25_helpdesk.type_incident
+msgid "Issue"
+msgstr "المشكلة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_blocked
+msgid "Kanban Blocked Explanation"
+msgstr "تفسير حالة متوقف في واجهة كانبان"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_normal
+msgid "Kanban Ongoing Explanation"
+msgstr "تفسير حالة قيد التقدم في واجهة كانبان"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__kanban_state
+msgid "Kanban State"
+msgstr "حالة كانبان"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_done
+msgid "Kanban Valid Explanation"
+msgstr "تفسير حالة صالح في واجهة كانبان"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__kpi_odex25_helpdesk_tickets_closed_value
+msgid "Kpi Helpdesk Tickets Closed Value"
+msgstr "قيمة مؤشر أداء تذاكر الدعم المقفلة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data
+msgid "Last 3 months"
+msgstr "آخر 3 أشهر"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data
+msgid "Last 30 days"
+msgstr "آخر 30 يوم"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data
+msgid "Last 7 days"
+msgstr "آخر 7 أيام"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users____last_update
+msgid "Last Modified on"
+msgstr "آخر تعديل في"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__date_last_stage_update
+#, python-format
+msgid "Last Stage Update"
+msgstr "آخر تحديث للمرحلة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__write_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__write_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__write_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__write_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__write_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__write_uid
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__write_uid
+msgid "Last Updated by"
+msgstr "آخر تحديث بواسطة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__write_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__write_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__write_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__write_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__write_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__write_date
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__write_date
+msgid "Last Updated on"
+msgstr "آخر تحديث في"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Last message is from customer"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Last message is from support"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Late Activities"
+msgstr "الأنشطة المتأخرة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_data
+msgid "Latest Feedbacks"
+msgstr "أحدث الملاحظات"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid "Let's create your first ticket."
+msgstr "فلننشئ تذكرتك الأولى."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__email_cc
+msgid "List of cc from incoming emails."
+msgstr "قائمة cc من رسائل البريد الإلكتروني الواردة."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_livechat
+msgid "Live chat"
+msgstr "دردشة حية"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__1
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__1
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__1
+msgid "Low priority"
+msgstr "أولوية منخفضة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_main_attachment_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_main_attachment_id
+msgid "Main Attachment"
+msgstr "المرفق الرئيسي"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action_main
+msgid ""
+"Make sure tickets are handled in a timely manner by using SLA Policies. "
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_team__assign_method__manual
+msgid "Manually"
+msgstr "يدويًا"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__medium_id
+msgid "Medium"
+msgstr "وسط"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error
+msgid "Message Delivery error"
+msgstr "خطأ في تسليم الرسائل"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.tickets_followup
+msgid "Message and communication history"
+msgstr "رسالة وتاريخ الاتصالات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_ids
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_ids
+msgid "Messages"
+msgstr "الرسائل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__priority
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__priority
+msgid "Minimum Priority"
+msgstr "الأولوية الأدنى"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__stage_id
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_status__sla_stage_id
+msgid "Minimum stage a ticket needs to reach in order to satisfy this SLA."
+msgstr "أدنى مرحلة يجب أن تصل لها التذكرة لتحقيق اتفاق مستوى الخدمة."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__time_minutes
+msgid "Minutes"
+msgstr "الدقائق"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__time_minutes
+msgid "Minutes to reach given stage based on ticket creation date"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My Failed Tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My High Priority Failed Tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My High Priority Tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My High Priority Tickets Analysis"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My Open Tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My Open Tickets Analysis"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My Performance"
+msgstr "أدائي"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "My Ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_menu_my
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+#, python-format
+msgid "My Tickets"
+msgstr "تذاكري"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My Urgent Failed Tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My Urgent Tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "My Urgent Tickets Analysis"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0
+#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_new
+#, python-format
+msgid "New"
+msgstr "جديد"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Newest"
+msgstr "الأحدث"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_date_deadline
+msgid "Next Activity Deadline"
+msgstr "الموعد النهائي للنشاط التالي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_summary
+msgid "Next Activity Summary"
+msgstr "ملخص النشاط التالي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_type_id
+msgid "Next Activity Type"
+msgstr "نوع النشاط التالي"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_action_main
+msgid "No SLA policies found. Let's create one!"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7days_analysis
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_close_analysis
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_success
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team_performance
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_analysis_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_team_analysis_action
+msgid "No data yet !"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_7dayssuccess
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_dashboard
+msgid "No data yet!"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_stage_action
+msgid "No stages found. Let's create one!"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_tag_action
+msgid "No tags found. Let's create one!"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main
+msgid "No teams found"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_my_ticket_action_no_create
+msgid "No tickets found"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_my
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_unassigned
+msgid "No tickets found. Let's create one!"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_type_action
+msgid "No types found. Let's create one!"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "None"
+msgstr "لا شيء"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction_counter
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction_counter
+msgid "Number of Actions"
+msgstr "عدد الإجراءات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__attachment_number
+msgid "Number of Attachments"
+msgstr "عدد المرفقات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__partner_ticket_count
+msgid "Number of closed tickets from the same partner"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error_counter
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error_counter
+msgid "Number of errors"
+msgstr "عدد الاخطاء"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_needaction_counter
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_needaction_counter
+msgid "Number of messages which requires an action"
+msgstr "عدد الرسائل التي تتطلب إجراء"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_has_error_counter
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_error_counter
+msgid "Number of messages with delivery error"
+msgstr "عدد الرسائل الحادث بها خطأ في التسليم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__message_unread_counter
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread_counter
+msgid "Number of unread messages"
+msgstr "عدد الرسائل الجديدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__sla_status__ongoing
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_status__status__ongoing
+msgid "Ongoing"
+msgstr "جاري"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__ticket_type_id
+msgid ""
+"Only apply the SLA to a specific ticket type. If left empty it will apply to"
+" all types."
+msgstr ""
+"تطبيق اتفاق مستوى الخدمة على نوع تذاكر معين فقط. إذا تُركت هذه الخانة فارغة "
+"سيتم تطبيقه على كافة الأنواع."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__tag_ids
+msgid ""
+"Only apply the SLA to tickets with specific tags. If left empty it will "
+"apply to all tags."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+#, python-format
+msgid "Open"
+msgstr "فتح"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+msgid "Open Tickets"
+msgstr "فتح تذاكر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_open_hours
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__open_hours
+msgid "Open Time (hours)"
+msgstr "وقت الفتح (بالساعات)"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_force_thread_id
+msgid ""
+"Optional ID of a thread (record) to which all incoming messages will be "
+"attached, even if they did not reply to it. If set, this will disable the "
+"creation of new records completely."
+msgstr ""
+"معرف اختياري لمناقشة (سجل) سيتم إرفاق كافة الرسائل الواردة به، حتى لو لم "
+"يردوا عليه. إذا تم تعيين قيمة له، سيعطل هذا إنشاء السجلات الجديدة بالكامل."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_page
+msgid "Our Customer Satisfaction"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_my_home_menu_odex25_helpdesk
+msgid "Our Ratings"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__legend_blocked
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_blocked
+msgid ""
+"Override the default value displayed for the blocked state for kanban "
+"selection, when the task or issue is in that stage."
+msgstr ""
+"تجاوز القيمة الافتراضية المعروضة لحالة \"محظورة\" باختيارات كانبان، عندما "
+"تكون المهمة أو المشكلة في تلك المرحلة."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__legend_done
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_done
+msgid ""
+"Override the default value displayed for the done state for kanban "
+"selection, when the task or issue is in that stage."
+msgstr ""
+"تجاوز القيمة الافتراضية المعروضة لحالة \"منتهية\" باختيارات كانبان، عندما "
+"تكون المهمة أو المشكلة في تلك المرحلة."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__legend_normal
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__legend_normal
+msgid ""
+"Override the default value displayed for the normal state for kanban "
+"selection, when the task or issue is in that stage."
+msgstr ""
+"تجاوز القيمة الافتراضية المعروضة لحالة \"عادية\" باختيارات كانبان، عندما "
+"تكون المهمة أو المشكلة في تلك المرحلة."
+
+#. module: odex25_helpdesk
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_team_dashboard
+msgid "Overview"
+msgstr "نظرة عامة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_user_id
+msgid "Owner"
+msgstr "المالك"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_model_id
+msgid "Parent Model"
+msgstr "الكائن الأصل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_thread_id
+msgid "Parent Record Thread ID"
+msgstr "معرف مناقشة السجل الأصلي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_parent_model_id
+msgid ""
+"Parent model holding the alias. The model holding the alias reference is not"
+" necessarily the model given by alias_model_id (example: project "
+"(parent_model) and task (model))"
+msgstr ""
+"الكائن الأب الذي يحتفظ بلقب البريد الإلكتروني. الكائن الذي يحتفظ بالإشارة "
+"إلى لقب البريد الإلكتروني لا يشترط بالضرورة أن يكون هو الكائن المحدد في "
+"الحقل alias_model_id (مثال: المشروع (الكائن الأب) والمهمة (الكائن))"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__rating_percentage_satisfaction
+msgid "Percentage of happy ratings"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Performance"
+msgstr "الأداء"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_team_performance
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_graph_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_pivot_analysis
+msgid "Performance Analysis"
+msgstr "تحليل الأداء"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/odex25_helpdesk_dashboard.js:0
+#, python-format
+msgid "Please enter an integer value"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_contact
+msgid ""
+"Policy to post a message on the document using the mailgateway.\n"
+"- everyone: everyone can post\n"
+"- partners: only authenticated partners\n"
+"- followers: only followers of the related document or members of following channels\n"
+msgstr ""
+"سياسة لنشر رسالة على المستند باستخدام بوابة البريد الإلكتروني.\n"
+"- الجميع: يمكن للجميع النشر\n"
+"- الشركاء: الشركاء المعتمدون فقط\n"
+"- المتابعون: فقط متابعو الوثيقة ذات الصلة أو أعضاء القنوات التالية.\n"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__access_url
+msgid "Portal Access URL"
+msgstr "رابط الوصول لبوابة العملاء"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__priority
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_helpdesk_service__priority
+msgid "Priority"
+msgstr "الأولوية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Productivity & Visibility"
+msgstr "الإنتاجية والرؤية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Publish this team's ratings on your website"
+msgstr "نشر تقييمات هذا الفريق على موقعك"
+
+#. module: odex25_helpdesk
+#: model:odex25_helpdesk.ticket.type,name:odex25_helpdesk.type_question
+msgid "Question"
+msgstr "السؤال"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Question and answer section on your website"
+msgstr "قسم الأسئلة والأجوبة على موقعك"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_team__assign_method__randomly
+msgid "Random"
+msgstr "عشوائي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_ids
+msgid "Rating"
+msgstr "التقييم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_avg
+msgid "Rating Average"
+msgstr "متوسط التقييم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_feedback
+msgid "Rating Last Feedback"
+msgstr "آخر ملاحظات التقييم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_image
+msgid "Rating Last Image"
+msgstr "آخر صورة للتقييم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_value
+msgid "Rating Last Value"
+msgstr "آخر قيمة للتقييم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__rating_percentage_satisfaction
+msgid "Rating Satisfaction"
+msgstr "نسبة الرضا"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_count
+msgid "Rating count"
+msgstr "عدد التقييمات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__rating_ids
+msgid "Ratings"
+msgstr "تقييمات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_rating
+msgid "Ratings on tickets"
+msgstr "التقييم على التذاكر"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "Reach In"
+msgstr "الوصول في"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "Reach Stage"
+msgstr "الوصول للمرحلة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__sla_status__reached
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_status__status__reached
+msgid "Reached"
+msgstr "وصل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__reached_datetime
+msgid "Reached Date"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__target_type__stage
+msgid "Reaching Stage"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0
+#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_cancelled
+#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_done
+#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_in_progress
+#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_new
+#: model:odex25_helpdesk.stage,legend_done:odex25_helpdesk.stage_solved
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_1
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_10
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_11
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_12
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_13
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_14
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_15
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_16
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_17
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_18
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_19
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_2
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_3
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_4
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_5
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_6
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_7
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_8
+#: model:odex25_helpdesk.ticket,legend_done:odex25_helpdesk.odex25_helpdesk_ticket_9
+#, python-format
+msgid "Ready"
+msgstr "جاهز"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__rating_last_feedback
+msgid "Reason of the rating"
+msgstr "سبب التقييم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__alias_force_thread_id
+msgid "Record Thread ID"
+msgstr "معرف مناقشة السجل"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Record timesheets on your tickets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__kanban_state__blocked
+msgid "Red"
+msgstr "أحمر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__legend_blocked
+msgid "Red Kanban Label"
+msgstr "بطاقة عنوان كانبان حمراء"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket
+msgid "Ref"
+msgstr "رقم الإشارة"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Reference"
+msgstr "رقم الإشارة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_credit_notes
+msgid "Refunds"
+msgstr "المرتجعات"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_sale_timesheet
+msgid "Reinvoice the time spent on ticket through tasks."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Reinvoice time to your customer through tasks"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Repair broken products"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_product_repairs
+msgid "Repairs"
+msgstr "تعديلات"
+
+#. module: odex25_helpdesk
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_report_menu_main
+msgid "Reporting"
+msgstr "التقارير"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_user_id
+msgid "Responsible User"
+msgstr "المستخدم المسؤول"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid "Restore"
+msgstr "استعادة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_product_returns
+msgid "Returns"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_id
+msgid "SLA"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_deadline
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_deadline
+msgid "SLA Deadline"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+#, python-format
+msgid "SLA Failed"
+msgstr "حدث فشل باتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+msgid "SLA Issues"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_sla_action
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_sla_action_main
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_sla
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_sla_menu_main
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_tree
+msgid "SLA Policies"
+msgstr "سياسات اتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "SLA Policy"
+msgstr "سياسة اتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__description
+msgid "SLA Policy Description"
+msgstr "وصف سياسة اتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__name
+msgid "SLA Policy Name"
+msgstr "اسم سياسة اتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_reached_datetime
+msgid "SLA Reached Date"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_stage_id
+msgid "SLA Stage"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_status_ids
+msgid "SLA Status"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_action
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_sla_report_analysis
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_report_menu_sla_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_graph
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_pivot
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "SLA Status Analysis"
+msgstr "تحليل حالة اتفاقية مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "SLA Status Deadline"
+msgstr "الموعد النهائي لحالة SLA"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_status_failed
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "SLA Status Failed"
+msgstr "فشل حالة SLA"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+msgid "SLA Success"
+msgstr "نجاح SLA"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+msgid "SLA in Progress"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_ids
+msgid "SLAs"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_has_sms_error
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_has_sms_error
+msgid "SMS Delivery error"
+msgstr "خطأ في تسليم الرسائل القصيرة"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Salesperson"
+msgstr "مسئول المبيعات"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Sample"
+msgstr "عينة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Save this page and refresh to activate the feature"
+msgstr "احفظ هذه الصفحة وقم بتحديثها لتفعيل الخاصية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Save this page and refresh to activate the feature."
+msgstr "احفظ هذه الصفحة وقم بتحديثها لتفعيل الخاصية."
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid "Save this ticket and the modifications you've made to it."
+msgstr "حفظ هذه التذكرة والتعديلات التي قمت بها عليها."
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Search (in Content)"
+msgstr "البحث (في المحتوى)"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_search
+msgid "Search SLA Policies"
+msgstr "البحث في سياسات اتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Search in All"
+msgstr "البحث في الكل"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Search in Customer"
+msgstr "البحث في العملاء"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Search in Messages"
+msgstr "البحث في الرسائل"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Search in Reference"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Search in Stage"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__access_token
+msgid "Security Token"
+msgstr "رمز الحماية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+msgid "See Customer Satisfaction"
+msgstr "رؤية نسبة رضا العميل"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Self-Service"
+msgstr "خدمة ذاتية"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Sell & Track Hours"
+msgstr "ساعات البيع والتتبع"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Send emails to"
+msgstr "إرسال رسائل بريد إلكتروني إلى"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Send emails to:"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__sequence
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__sequence
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__sequence
+msgid "Sequence"
+msgstr "المسلسل"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Set the calendar used to compute SLA target"
+msgstr "إعداد التقويم المُستخدم لحساب اتفاق مستوى الخدمة المستهدف"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Set up your Service Level Agreements to track performance"
+msgstr "ضبط اتفاق مستوى الخدمة الخاص بك لمتابعة الأداء"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+msgid "Settings"
+msgstr "الإعدادات"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.server,name:odex25_helpdesk.model_odex25_helpdesk_ticket_action_share
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_kanban
+msgid "Share"
+msgstr "مشاركة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Share presentation and videos, and organize into courses"
+msgstr "مشاركة العروض والفيديوهات، وتنظيمهم في دورات"
+
+#. module: odex25_helpdesk
+#: model:res.groups,name:odex25_helpdesk.group_use_sla
+msgid "Show SLA Policies"
+msgstr "عرض سياسات اتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Show all records which has next action date is before today"
+msgstr "عرض كافة السجلات المُعين لها تاريخ إجراء تالي يسبق تاريخ اليوم الجاري"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__sla_id
+msgid "Sla"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:odex25_helpdesk.stage,name:odex25_helpdesk.stage_solved
+msgid "Solved"
+msgstr "تم حلّها"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__source_id
+msgid "Source"
+msgstr "المصدر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__team_ids
+msgid ""
+"Specific team that uses this stage. Other teams will not be able to see or "
+"use this stage."
+msgstr ""
+"الفريق المحدد الذي يستخدم هذه المرحلة. لن تتمكن الفرق الأخرى من رؤية أو "
+"استخدام هذه المرحلة."
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__stage_id
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_tree
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket
+#, python-format
+msgid "Stage"
+msgstr "المرحلة"
+
+#. module: odex25_helpdesk
+#: model:mail.message.subtype,description:odex25_helpdesk.mt_ticket_stage
+#: model:mail.message.subtype,name:odex25_helpdesk.mt_ticket_stage
+msgid "Stage Changed"
+msgstr "تم تغيير المرحلة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid "Stage Description and Tooltips"
+msgstr "وصف وتلميحات المرحلة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__name
+msgid "Stage Name"
+msgstr "اسم المرحلة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid "Stage Search"
+msgstr "البحث في المراحل"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_stage_action
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__stage_ids
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_stage_menu
+msgid "Stages"
+msgstr "المراحل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__stage_ids
+msgid ""
+"Stages the team will use. This team's tickets will only be able to be in "
+"these stages."
+msgstr ""
+"المراحل التي سيستخدمها الفريق. سيُسمح بوضع تذاكر هذا الفريق في هذه المراحل "
+"فقط."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__sla_status
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__status
+msgid "Status"
+msgstr "الحالة"
+
+#. module: odex25_helpdesk
+#: model:ir.filters,name:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_filter_status_per_deadline
+msgid "Status Per Deadline"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_state
+msgid ""
+"Status based on activities\n"
+"Overdue: Due date is already passed\n"
+"Today: Activity date is today\n"
+"Planned: Future activities."
+msgstr ""
+"الحالة على أساس الأنشطة\n"
+"المتأخرة: تاريخ الاستحقاق مر\n"
+"اليوم: تاريخ النشاط هو اليوم\n"
+"المخطط: الأنشطة المستقبلية."
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__name
+#, python-format
+msgid "Subject"
+msgstr "العنوان"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+msgid "Subject..."
+msgstr "الموضوع..."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "Submit tickets with an online form"
+msgstr "تقديم التذاكر بنموذج أونلاين"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Success Rate"
+msgstr "نسبة النجاح"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_7dayssuccess
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_success
+msgid "Success Rate Analysis"
+msgstr "تحليل نسبة النجاح"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_success
+msgid "Success SLA Policy"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tag_view_tree
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_type_view_tree
+msgid "Tag"
+msgstr "الوسم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_tag__name
+msgid "Tag Name"
+msgstr "اسم الوسم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.constraint,message:odex25_helpdesk.constraint_odex25_helpdesk_tag_name_uniq
+msgid "Tag name already exists !"
+msgstr "اسم الوسم موجود بالفعل!"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__tag_ids
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__tag_ids
+msgid "Tags"
+msgstr "الوسوم"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_tag_action
+msgid "Tags are perfect to organize your tickets."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "Target"
+msgstr "الهدف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__odex25_helpdesk_target_rating
+msgid "Target Customer Rating"
+msgstr "التقييم المستهدف من العميل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__stage_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__sla_stage_id
+msgid "Target Stage"
+msgstr "المرحلة المستهدفة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__odex25_helpdesk_target_success
+msgid "Target Success Rate"
+msgstr "معدل تحقيق الهدف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__odex25_helpdesk_target_closed
+msgid "Target Tickets to Close"
+msgstr "التذاكر المطلوب إقفالها"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__target_type
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__target_type
+msgid "Target Type"
+msgstr "نوع الهدف"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid "Task in progress. Click to block or set as done."
+msgstr "المهمة قيد التنفيذ. اضغط لحظرها أو تعيينها كمكتملة."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid "Task is blocked. Click to unblock or set as done."
+msgstr "المهمة محظورة. اضغط لإلغاء حظرها أو تعيينها كمكتملة."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__team_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__team_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_stage__team_ids
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Team"
+msgstr "فريق"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__member_ids
+msgid "Team Members"
+msgstr "أعضاء الفريق"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__visibility_member_ids
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid ""
+"Team Members to whom this team will be visible. Keep empty for everyone to "
+"see this team."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_search
+msgid "Team Search"
+msgstr "فريق البحث"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__visibility_member_ids
+msgid "Team Visibility"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_action
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main
+msgid ""
+"Teams regroup tickets for people sharing the same expertise or from the same"
+" area."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.email_template_action_odex25_helpdesk
+msgid "Templates"
+msgstr "قوالب"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__exclude_stage_ids
+msgid ""
+"The amount of time the ticket spends in this stage will not be taken into "
+"account when evaluating the status of the SLA Policy."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__sla_deadline
+msgid "The closest deadline of all SLA applied on this ticket"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_model_id
+msgid ""
+"The model (Odoo Document Kind) to which this alias corresponds. Any incoming"
+" email that does not reply to an existing record will cause the creation of "
+"a new record of this model (e.g. a Project Task)"
+msgstr ""
+"الكائن (مستندات أودو) الذي يقترن معه هذا اللقب. أي رسالة واردة لا ترد على "
+"سجل موجود ستقوم بإنشاء سجل جديد من نفس نوع هذا الكائن (مثلًا: مهمة مشروع)"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_name
+msgid ""
+"The name of the email alias, e.g. 'jobs' if you want to catch emails for "
+""
+msgstr ""
+"اسم لقب البريد الإلكتروني، مثلًا: 'وظائف' إذا كنت جمع الرسائل المرسلة "
+"لـ"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__alias_user_id
+msgid ""
+"The owner of records created upon receiving emails on this alias. If this "
+"field is not set the system will attempt to find the right owner based on "
+"the sender (From) address, or will use the Administrator account if no "
+"system user is found for that address."
+msgstr ""
+"مالك السجلات المنشأة عند استلام رسائل بريد إلكتروني على هذا اللقب. إذا لم "
+"يتم تعيين قيمة لهذا الحقل، سيحاول النظام معرفة المالك الصحيح حسب عنوان "
+"البريد الإلكتروني للمرسل، أو سيستخدم حساب المشرف إذا لم يجد حسابًا مرتبطًا "
+"بعنوان البريد الإلكتروني."
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "The team does not allow ticket closing through portal"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket
+msgid "There are currently no Ticket for your account."
+msgstr "لا توجد حاليًا تذاكر لحسابك."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.team_rating_page
+msgid "There are no ratings yet."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__assign_hours
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__close_hours
+msgid "This duration is based on the working calendar of the team"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__open_hours
+msgid "This duration is not based on the working calendar of the team"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__campaign_id
+msgid ""
+"This is a name that helps you keep track of your different campaign efforts,"
+" e.g. Fall_Drive, Christmas_Special"
+msgstr ""
+"يساعدك هذا الاسم علي تتبع جهود حملتك المختلفة، مثلًا: fall_drive "
+"،christmas_special"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__medium_id
+msgid "This is the method of delivery, e.g. Postcard, Email, or Banner Ad"
+msgstr ""
+"هذه هي طريقة التسليم، مثلًا: بطاقة بريدية، أو البريد الإلكتروني أو لافتة "
+"إعلانية"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__source_id
+msgid ""
+"This is the source of the link, e.g. Search Engine, another domain, or name "
+"of email list"
+msgstr ""
+"هذا هو مصدر الرابط، مثلًا: محرك بحث، أو نطاق آخر، أو اسم في قائمة البريد "
+"الإلكتروني"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_timesheet
+msgid "This required to have project module installed."
+msgstr "يتطلب هذا تثبيت موديول المشروع."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__fold
+msgid ""
+"This stage is folded in the kanban view when there are no records in that "
+"stage to display."
+msgstr ""
+"يتم طي هذه المرحلة عند استخدام طريقة كانبان للعرض عندما لا توجد سجلات يمكن "
+"عرضها في هذه المرحلة."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid "This step is done. Click to block or set in progress."
+msgstr "هذه الخطوة مكتملة. اضغط لحظرها أو تعيينها قيد التنفيذ."
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Three stars, maximum score"
+msgstr "ثلاثة نجوم، أقصى درجة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_status__ticket_id
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_activity
+msgid "Ticket"
+msgstr "التذكرة"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_dashboard
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_team_analysis_action
+msgid "Ticket Analysis"
+msgstr "تحليل التذكرة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_closed
+msgid "Ticket Closed"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__create_date
+msgid "Ticket Create Date"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:mail.message.subtype,name:odex25_helpdesk.mt_team_ticket_new
+#: model:mail.message.subtype,name:odex25_helpdesk.mt_ticket_new
+msgid "Ticket Created"
+msgstr "تم إنشاء التذكرة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "Ticket Creation Date"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_deadline
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_report_analysis_view_search
+msgid "Ticket Deadline"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_failed
+msgid "Ticket Failed"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Ticket ID"
+msgstr "مُعرف التذكرة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_tree
+msgid "Ticket Name"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:mail.message.subtype,name:odex25_helpdesk.mt_team_ticket_rated
+#: model:mail.message.subtype,name:odex25_helpdesk.mt_ticket_rated
+msgid "Ticket Rated"
+msgstr "تم تقييم التذكرة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_odex25_helpdesk_sla_status
+msgid "Ticket SLA Status"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_stage_id
+msgid "Ticket Stage"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:mail.message.subtype,name:odex25_helpdesk.mt_team_ticket_stage
+msgid "Ticket Stage Changed"
+msgstr "تم تغيير مرحلة التذكرة"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_tag_action
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_tag_menu
+msgid "Ticket Tags"
+msgstr "وسوم التذكرة"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_type_action
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__ticket_type_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_type_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__ticket_type_id
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_type_menu
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Ticket Type"
+msgstr "نوع التذكرة"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#, python-format
+msgid "Ticket closed by the customer"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__allow_portal_ticket_closing
+msgid "Ticket closing"
+msgstr "إغلاق التذكرة"
+
+#. module: odex25_helpdesk
+#: model:mail.message.subtype,description:odex25_helpdesk.mt_ticket_new
+msgid "Ticket created"
+msgstr "تم إنشاء التذكرة"
+
+#. module: odex25_helpdesk
+#: model:mail.message.subtype,description:odex25_helpdesk.mt_ticket_rated
+msgid "Ticket rated"
+msgstr "تم تقييم التذكرة"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: model:ir.actions.act_window,name:odex25_helpdesk.action_upcoming_sla_fail_all_tickets
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_my_ticket_action_no_create
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_main_my
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_sla
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_team
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_ticket_action_unassigned
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__ticket_ids
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_partner__ticket_count
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_res_users__ticket_count
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_menu_main
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_ticket_report_menu
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_cohort
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_tree
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_odex25_helpdesk_ticket
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_my_home_odex25_helpdesk_ticket
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.portal_my_home_menu_odex25_helpdesk
+#, python-format
+msgid "Tickets"
+msgstr "تذاكر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_digest_digest__kpi_odex25_helpdesk_tickets_closed
+msgid "Tickets Closed"
+msgstr "تم قفل التذكرة"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Tickets Closed Avg 7 Days"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Tickets Closed Today"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Tickets Search"
+msgstr "البحث في التذاكر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_stage__is_close
+msgid ""
+"Tickets in this stage are considered as done. This is used notably when "
+"computing SLAs and KPIs on tickets."
+msgstr ""
+"تُعتبر التذاكر في هذه المرحلة منتهية. يُستخدم هذا بصورة ملحوظة عند احتساب "
+"سياسات اتفاق مستوى الخدمة وقيم المؤشر الرئيسي للتذاكر."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla__priority
+msgid "Tickets under this priority will not be taken into account."
+msgstr "لن توضع التذاكر ذات هذه الأولوية في الحساب."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_sale_timesheet
+msgid "Time Reinvoicing"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_close_hours
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__close_hours
+msgid "Time to close (hours)"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla_report_analysis__ticket_assignation_hours
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__assign_hours
+msgid "Time to first assignment (hours)"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_odex25_helpdesk_timesheet
+msgid "Timesheet on Ticket"
+msgstr "سجل النشاط على التذكرة"
+
+#. module: odex25_helpdesk
+#: model:digest.tip,name:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid "Tip: Create tickets from incoming emails"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_my
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_team
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_unassigned
+msgid ""
+"To get things done, use activities and status on tickets. \n"
+" Chat in real time or by email to collaborate efficiently."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_my_ticket_action_no_create
+msgid ""
+"To get things done, use activities and status on tickets. \n"
+" Chat in real time or by email to collaborate efficiently."
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Today"
+msgstr "اليوم"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Today Activities"
+msgstr "نشاطات اليوم"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Today Happy Rating"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Today's Success Rate"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_twitter
+msgid "Twitter"
+msgstr "تويتر"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Two stars, with a maximum of three"
+msgstr "نجمتان، بحد أقصى ثلاثة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket_type__name
+msgid "Type"
+msgstr "النوع"
+
+#. module: odex25_helpdesk
+#: model:ir.model.constraint,message:odex25_helpdesk.constraint_odex25_helpdesk_ticket_type_name_uniq
+msgid "Type name already exists !"
+msgstr "اسم النوع موجود بالفعل!"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__activity_exception_decoration
+msgid "Type of the exception activity on record."
+msgstr "نوع النشاط الاستثنائي المسجل."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_type_action
+msgid "Types are perfect to categorize your tickets."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__portal_rating_url
+msgid "URL to Submit an Issue"
+msgstr "رابط الإبلاغ عن مشكلة"
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/controllers/portal.py:0
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+#, python-format
+msgid "Unassigned"
+msgstr "غير مكلف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__unassigned_tickets
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_kanban
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_search_analysis_closed
+msgid "Unassigned Tickets"
+msgstr "التذاكر غير المسندة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_unread
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Unread Messages"
+msgstr "الرسائل الجديدة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__message_unread_counter
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__message_unread_counter
+msgid "Unread Messages Counter"
+msgstr "عدد الرسائل الجديدة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_tickets_view_search
+msgid "Upcoming SLA Fail"
+msgstr "فشل مترقب باتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__upcoming_sla_fail_tickets
+msgid "Upcoming SLA Fail Tickets"
+msgstr "تذاكر الفشل المترقب باتفاق مستوى الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla__priority__3
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_sla_report_analysis__priority__3
+#: model:ir.model.fields.selection,name:odex25_helpdesk.selection__odex25_helpdesk_ticket__priority__3
+msgid "Urgent"
+msgstr "عاجل"
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/xml/odex25_helpdesk_team_templates.xml:0
+#, python-format
+msgid "Urgent ("
+msgstr "عاجل ("
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_coupons
+msgid "Use Coupons"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_credit_notes
+msgid "Use Credit Notes"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_product_repairs
+msgid "Use Repairs"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__use_product_returns
+msgid "Use Returns"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid "Use the breadcrumbs to go back to the Kanban view."
+msgstr "استخدم فتات الخبز للعودة لواجهة عرض كانبان."
+
+#. module: odex25_helpdesk
+#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_user
+msgid "User"
+msgstr "المستخدم"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_res_users
+msgid "Users"
+msgstr "المستخدمون"
+
+#. module: odex25_helpdesk
+#: model:odex25_helpdesk.team,name:odex25_helpdesk.odex25_helpdesk_team3
+msgid "VIP Support"
+msgstr ""
+
+#. module: odex25_helpdesk
+#. openerp-web
+#: code:addons/odex25_helpdesk/static/src/js/tours/odex25_helpdesk.js:0
+#, python-format
+msgid ""
+"Want to boost your customer satisfaction? Click Helpdesk to "
+"start."
+msgstr ""
+"أتريد رفع نسبة رضا العميل؟ اضغط على مكتب المساعدة لتبدأ."
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__use_website_helpdesk_form
+msgid "Website Form"
+msgstr "نموذج الموقع"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__website_message_ids
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__website_message_ids
+msgid "Website Messages"
+msgstr "رسائل الموقع"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_team__website_message_ids
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_ticket__website_message_ids
+msgid "Website communication history"
+msgstr "سجل تواصل الموقع"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid ""
+"With random assignation, every user gets the same number of tickets. With "
+"balanced assignation, tickets are assigned to the user with the least amount"
+" of open tickets."
+msgstr ""
+"من خلال الإسناد العشوائي، ينال كل مستخدم نفس عدد التذاكر. من خلال الإسناد "
+"المتزن، يتم إسناد التذاكر للمستخدم صاحب أقل عدد من التذاكر المفتوحة."
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "Within"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__resource_calendar_id
+msgid "Working Hours"
+msgstr "ساعات العمل"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,help:odex25_helpdesk.field_odex25_helpdesk_sla_status__exceeded_days
+msgid ""
+"Working days exceeded for reached SLAs compared with deadline. Positive "
+"number means the SLA was eached after the deadline."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_stage_view_form
+msgid ""
+"You can also add a description to help your coworkers understand the meaning"
+" and purpose of the stage."
+msgstr "يمكنك أيضًا إضافة وصف لمساعدة زملائك على فهم معنى وغرض المرحلة."
+
+#. module: odex25_helpdesk
+#: model:ir.model.constraint,message:odex25_helpdesk.constraint_res_users_target_closed_not_zero
+#: model:ir.model.constraint,message:odex25_helpdesk.constraint_res_users_target_rating_not_zero
+#: model:ir.model.constraint,message:odex25_helpdesk.constraint_res_users_target_success_not_zero
+msgid "You cannot have negative targets"
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.action_upcoming_sla_fail_all_tickets
+#: model_terms:ir.actions.act_window,help:odex25_helpdesk.odex25_helpdesk_ticket_action_sla
+msgid "You completed all your tickets on time."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: code:addons/odex25_helpdesk/models/odex25_helpdesk.py:0
+#, python-format
+msgid "You must have team members assigned to change the assignment method."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "days "
+msgstr "أيام "
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "e.g. Close urgent tickets within 36 hours"
+msgstr "مثلًا: إغلاق التذاكر العاجلة خلال 36 ساعة"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "eLearning"
+msgstr "eLearning"
+
+#. module: odex25_helpdesk
+#: model_terms:digest.tip,tip_description:odex25_helpdesk.digest_tip_odex25_helpdesk_0
+msgid "generate tickets in your pipeline."
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "hours "
+msgstr "ساعات "
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_sla_view_form
+msgid "minutes "
+msgstr ""
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "team search"
+msgstr "فريق البحث"
+
+#. module: odex25_helpdesk
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_team_view_form
+msgid "to create tickets"
+msgstr "لإنشاء تذاكر"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_helpdesk_service__category_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__category_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__category_id
+msgid "Category"
+msgstr "التصنيف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_helpdesk_service__name
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_sla__service_id
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__service_id
+msgid "Service"
+msgstr "الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.model,name:odex25_helpdesk.model_hr_employee
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__employee_id
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+msgid "Employee"
+msgstr "الموظف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__assistance_user_id
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk.odex25_helpdesk_ticket_view_form
+msgid "Assistance"
+msgstr "المساعد"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__work_email
+msgid "Work Email"
+msgstr "إيميل الموظف"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__work_location
+msgid "Work Location"
+msgstr "مكان العمل"
+
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__department_id
+msgid "Administrative Structure"
+msgstr "الهيكل الإداري"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__deb_name
+msgid "Department Name"
+msgstr "القسم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__project_no
+msgid "Project Number"
+msgstr "رقم المشروع"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__on_behalf
+msgid "On behalf of"
+msgstr "نيابة عن"
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_category_action
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_service_category__name
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_service_category
+msgid "Service Category"
+msgstr "تصنيف الخدمة"
+
+
+#. module: odex25_helpdesk
+#: model:ir.actions.act_window,name:odex25_helpdesk.helpdesk_service_action
+#: model:ir.actions.act_window,name:odex25_helpdesk.odex25_helpdesk_service_action
+#: model:ir.ui.menu,name:odex25_helpdesk.odex25_helpdesk_menu_service
+msgid "Helpdesk Service"
+msgstr "الخدمة"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__is_internal_team
+msgid "Internal Team"
+msgstr "فريق داخلي"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_team__is_vip_team
+msgid "VIP Team"
+msgstr "VIP فريق"
+
+#. module: odex25_helpdesk
+#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_assignment
+msgid "Assignment User"
+msgstr "مستخدم التعيين"
+
+#. module: odex25_helpdesk
+#: model:res.groups,name:odex25_helpdesk.group_odex25_helpdesk_on_behalf
+msgid "On Behalf User"
+msgstr "نيابة عن المستخدم"
+
+#. module: odex25_helpdesk
+#: model:ir.model.fields,field_description:odex25_helpdesk.field_odex25_helpdesk_ticket__phone_no
+msgid "Phone Number"
+msgstr "رقم الهاتف"
+
diff --git a/odex25_helpdesk/odex25_helpdesk/migrations/10.0.1.2/pre-nonulls.py b/odex25_helpdesk/odex25_helpdesk/migrations/10.0.1.2/pre-nonulls.py
new file mode 100644
index 000000000..d68aa3c73
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/migrations/10.0.1.2/pre-nonulls.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+
+def migrate(cr, version):
+ cr.execute("""
+ UPDATE odex25_helpdesk_sla
+ SET time_days = COALESCE(time_days, 0),
+ time_hours = COALESCE(time_hours, 0),
+ time_minutes = COALESCE(time_minutes, 0)
+ """)
diff --git a/odex25_helpdesk/odex25_helpdesk/models/__init__.py b/odex25_helpdesk/odex25_helpdesk/models/__init__.py
new file mode 100644
index 000000000..f7b7cf4e3
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/models/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+from . import digest
+from . import odex25_helpdesk
+from . import odex25_helpdesk_ticket
+from . import res_users
+from . import res_partner
+from . import res_setting
diff --git a/odex25_helpdesk/odex25_helpdesk/models/digest.py b/odex25_helpdesk/odex25_helpdesk/models/digest.py
new file mode 100644
index 000000000..2a08cfdaf
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/models/digest.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+from odoo import fields, models, _
+from odoo.exceptions import AccessError
+
+
+class Digest(models.Model):
+ _inherit = 'digest.digest'
+
+ kpi_odex25_helpdesk_tickets_closed = fields.Boolean('Tickets Closed')
+ kpi_odex25_helpdesk_tickets_closed_value = fields.Integer(compute='_compute_kpi_odex25_helpdesk_tickets_closed_value')
+
+ def _compute_kpi_odex25_helpdesk_tickets_closed_value(self):
+ if not self.env.user.has_group('odex25_helpdesk.group_odex25_helpdesk_user'):
+ raise AccessError(_("Do not have access, skip this data for user's digest email"))
+ for record in self:
+ start, end, company = record._get_kpi_compute_parameters()
+ closed_ticket = self.env['odex25_helpdesk.ticket'].search_count([
+ ('close_date', '>=', start),
+ ('close_date', '<', end),
+ ('company_id', '=', company.id)
+ ])
+ record.kpi_odex25_helpdesk_tickets_closed_value = closed_ticket
+
+ def _compute_kpis_actions(self, company, user):
+ res = super(Digest, self)._compute_kpis_actions(company, user)
+ res['kpi_odex25_helpdesk_tickets_closed'] = 'odex25_helpdesk.odex25_helpdesk_team_dashboard_action_main&menu_id=%s' % self.env.ref('odex25_helpdesk.menu_odex25_helpdesk_root').id
+ return res
diff --git a/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk.py b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk.py
new file mode 100644
index 000000000..f1015aa26
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk.py
@@ -0,0 +1,534 @@
+# -*- coding: utf-8 -*-
+
+import ast
+import datetime
+
+from dateutil import relativedelta
+from odoo import api, fields, models, _
+from odoo.addons.odex25_helpdesk.models.odex25_helpdesk_ticket import TICKET_PRIORITY
+from odoo.addons.http_routing.models.ir_http import slug
+from odoo.exceptions import UserError, ValidationError
+from odoo.osv import expression
+
+
+class odex25_helpdeskTeam(models.Model):
+ _name = "odex25_helpdesk.team"
+ _inherit = ['mail.alias.mixin', 'mail.thread', 'rating.parent.mixin']
+ _description = "Helpdesk Team"
+ _order = 'sequence,name'
+ _rating_satisfaction_days = False # takes all existing ratings
+
+ _sql_constraints = [('not_portal_show_rating_if_not_use_rating',
+ 'check (portal_show_rating = FALSE OR use_rating = TRUE)',
+ 'Cannot show ratings in portal if not using them'), ]
+
+ def _default_stage_ids(self):
+ default_stage = self.env['odex25_helpdesk.stage'].search([('name', '=', _('New'))], limit=1)
+ if default_stage:
+ return [(4, default_stage.id)]
+ return [(0, 0, {'name': _('New'), 'sequence': 0, 'template_id': self.env.ref('odex25_helpdesk.new_ticket_request_email_template', raise_if_not_found=False) or None})]
+
+ def _default_domain_member_ids(self):
+ return [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)]
+
+ name = fields.Char('Helpdesk Team', required=True, translate=True)
+ description = fields.Text('About Team', translate=True)
+ active = fields.Boolean(default=True)
+ company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
+ sequence = fields.Integer("Sequence", default=10)
+ color = fields.Integer('Color Index', default=1)
+ stage_ids = fields.Many2many(
+ 'odex25_helpdesk.stage', relation='team_stage_rel', string='Stages',
+ default=_default_stage_ids,
+ help="Stages the team will use. This team's tickets will only be able to be in these stages.")
+ assign_method = fields.Selection([
+ ('manual', 'Manually'),
+ ('randomly', 'Random'),
+ ('balanced', 'Balanced')], string='Assignment Method', default='manual',
+ compute='_compute_assign_method', store=True, readonly=False, required=True,
+ help='Automatic assignment method for new tickets:\n'
+ '\tManually: manual\n'
+ '\tRandomly: randomly but everyone gets the same amount\n'
+ '\tBalanced: to the person with the least amount of open tickets')
+ member_ids = fields.Many2many('res.users', string='Team Members', domain=lambda self: self._default_domain_member_ids())
+ visibility_member_ids = fields.Many2many('res.users', 'odex25_helpdesk_visibility_team', string='Team Visibility', domain=lambda self: self._default_domain_member_ids(),
+ help="Team Members to whom this team will be visible. Keep empty for everyone to see this team.")
+ ticket_ids = fields.One2many('odex25_helpdesk.ticket', 'team_id', string='Tickets')
+
+ use_alias = fields.Boolean('Email alias', default=True)
+ has_external_mail_server = fields.Boolean(compute='_compute_has_external_mail_server')
+ allow_portal_ticket_closing = fields.Boolean('Ticket closing', help="Allow customers to close their tickets")
+ use_website_helpdesk_form = fields.Boolean('Website Form')
+ use_website_helpdesk_livechat = fields.Boolean('Live chat',
+ help="In Channel: You can create a new ticket by typing /Helpdesk [ticket title]. You can search ticket by typing /odex25_helpdesk_search [Keyword1],[Keyword2],.")
+ use_website_helpdesk_forum = fields.Boolean('Help Center')
+ use_website_helpdesk_slides = fields.Boolean('Enable eLearning')
+ use_odex25_helpdesk_timesheet = fields.Boolean('Timesheet on Ticket', help="This required to have project module installed.")
+ use_odex25_helpdesk_sale_timesheet = fields.Boolean(
+ 'Time Reinvoicing', compute='_compute_use_odex25_helpdesk_sale_timesheet', store=True,
+ readonly=False, help="Reinvoice the time spent on ticket through tasks.")
+ use_credit_notes = fields.Boolean('Refunds')
+ use_coupons = fields.Boolean('Coupons')
+ use_product_returns = fields.Boolean('Returns')
+ use_product_repairs = fields.Boolean('Repairs')
+ use_twitter = fields.Boolean('Twitter')
+ use_api = fields.Boolean('API')
+ use_rating = fields.Boolean('Ratings on tickets')
+ portal_show_rating = fields.Boolean(
+ 'Display Rating on Customer Portal', compute='_compute_portal_show_rating', store=True,
+ readonly=False)
+ portal_rating_url = fields.Char('URL to Submit an Issue', readonly=True, compute='_compute_portal_rating_url')
+ use_sla = fields.Boolean('SLA Policies')
+ upcoming_sla_fail_tickets = fields.Integer(string='Upcoming SLA Fail Tickets', compute='_compute_upcoming_sla_fail_tickets')
+ unassigned_tickets = fields.Integer(string='Unassigned Tickets', compute='_compute_unassigned_tickets')
+ resource_calendar_id = fields.Many2one('resource.calendar', 'Working Hours',
+ default=lambda self: self.env.company.resource_calendar_id, domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]")
+
+ is_internal_team = fields.Boolean('Internal Team', default=False)
+ is_vip_team = fields.Boolean('VIP Team', default=False)
+
+ @api.depends('name', 'portal_show_rating')
+ def _compute_portal_rating_url(self):
+ base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
+ for team in self:
+ if team.name and team.portal_show_rating and team.id:
+ team.portal_rating_url = '%s/odex25_helpdesk/rating/%s' % (base_url, slug(team))
+ else:
+ team.portal_rating_url = False
+
+ def _compute_has_external_mail_server(self):
+ self.has_external_mail_server = self.env['ir.config_parameter'].sudo().get_param('base_setup.default_external_email_server')
+
+ def _compute_upcoming_sla_fail_tickets(self):
+ ticket_data = self.env['odex25_helpdesk.ticket'].read_group([
+ ('team_id', 'in', self.ids),
+ ('sla_deadline', '!=', False),
+ ('sla_deadline', '<=', fields.Datetime.to_string((datetime.date.today() + relativedelta.relativedelta(days=1)))),
+ ], ['team_id'], ['team_id'])
+ mapped_data = dict((data['team_id'][0], data['team_id_count']) for data in ticket_data)
+ for team in self:
+ team.upcoming_sla_fail_tickets = mapped_data.get(team.id, 0)
+
+ def _compute_unassigned_tickets(self):
+ ticket_data = self.env['odex25_helpdesk.ticket'].read_group([('user_id', '=', False), ('team_id', 'in', self.ids), ('stage_id.is_close', '!=', True)], ['team_id'], ['team_id'])
+ mapped_data = dict((data['team_id'][0], data['team_id_count']) for data in ticket_data)
+ for team in self:
+ team.unassigned_tickets = mapped_data.get(team.id, 0)
+
+ @api.depends('use_rating')
+ def _compute_portal_show_rating(self):
+ without_rating = self.filtered(lambda t: not t.use_rating)
+ without_rating.update({'portal_show_rating': False})
+
+ @api.depends('member_ids', 'visibility_member_ids')
+ def _compute_assign_method(self):
+ with_manual = self.filtered(lambda t: not t.member_ids and not t.visibility_member_ids)
+ with_manual.update({'assign_method': 'manual'})
+
+ @api.onchange('use_alias', 'name')
+ def _onchange_use_alias(self):
+ if not self.use_alias:
+ self.alias_name = False
+
+ @api.depends('use_odex25_helpdesk_timesheet')
+ def _compute_use_odex25_helpdesk_sale_timesheet(self):
+ without_timesheet = self.filtered(lambda t: not t.use_odex25_helpdesk_timesheet)
+ without_timesheet.update({'use_odex25_helpdesk_sale_timesheet': False})
+
+ @api.constrains('assign_method', 'member_ids', 'visibility_member_ids')
+ def _check_member_assignation(self):
+ if not self.member_ids and not self.visibility_member_ids and self.assign_method != 'manual':
+ raise ValidationError(_("You must have team members assigned to change the assignment method."))
+
+ # ------------------------------------------------------------
+ # ORM overrides
+ # ------------------------------------------------------------
+
+ @api.model
+ def create(self, vals):
+ team = super(odex25_helpdeskTeam, self.with_context(mail_create_nosubscribe=True)).create(vals)
+ team.sudo()._check_sla_group()
+ team.sudo()._check_modules_to_install()
+ # If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install.
+ return team
+
+ def write(self, vals):
+ result = super(odex25_helpdeskTeam, self).write(vals)
+ if 'active' in vals:
+ self.with_context(active_test=False).mapped('ticket_ids').write({'active': vals['active']})
+ self.sudo()._check_sla_group()
+ self.sudo()._check_modules_to_install()
+ # If you plan to add something after this, use a new environment. The one above is no longer valid after the modules install.
+ return result
+
+ def unlink(self):
+ stages = self.mapped('stage_ids').filtered(lambda stage: stage.team_ids <= self) # remove stages that only belong to team in self
+ stages.unlink()
+ return super(odex25_helpdeskTeam, self).unlink()
+
+ def _check_sla_group(self):
+ for team in self:
+ if team.use_sla and not self.user_has_groups('odex25_helpdesk.group_use_sla'):
+ self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').write({'implied_ids': [(4, self.env.ref('odex25_helpdesk.group_use_sla').id)]})
+ if team.use_sla:
+ self.env['odex25_helpdesk.sla'].with_context(active_test=False).search([('team_id', '=', team.id), ('active', '=', False)]).write({'active': True})
+ else:
+ self.env['odex25_helpdesk.sla'].search([('team_id', '=', team.id)]).write({'active': False})
+ if not self.search_count([('use_sla', '=', True)]):
+ self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').write({'implied_ids': [(3, self.env.ref('odex25_helpdesk.group_use_sla').id)]})
+ self.env.ref('odex25_helpdesk.group_use_sla').write({'users': [(5, 0, 0)]})
+
+ def _check_modules_to_install(self):
+ # mapping of field names to module names
+ FIELD_MODULE = {
+ 'use_website_helpdesk_form': 'odex25_website_helpdesk_form',
+ 'use_website_helpdesk_livechat': 'odex25_website_helpdesk_livechat',
+ 'use_website_helpdesk_forum': 'odex25_website_helpdesk_forum',
+ 'use_website_helpdesk_slides': 'odex25_website_helpdesk_slides',
+ 'use_odex25_helpdesk_timesheet': 'odex25_helpdesk_timesheet',
+ 'use_odex25_helpdesk_sale_timesheet': 'odex25_helpdesk_sale_timesheet',
+ 'use_credit_notes': 'odex25_helpdesk_account',
+ 'use_product_returns': 'odex25_helpdesk_stock',
+ 'use_product_repairs': 'odex25_helpdesk_repair',
+ 'use_coupons': 'odex25_helpdesk_sale_coupon',
+ }
+
+ # determine the modules to be installed
+ expected = [
+ mname
+ for fname, mname in FIELD_MODULE.items()
+ if any(team[fname] for team in self)
+ ]
+ modules = self.env['ir.module.module']
+ if expected:
+ STATES = ('installed', 'to install', 'to upgrade')
+ modules = modules.search([('name', 'in', expected)])
+ modules = modules.filtered(lambda module: module.state not in STATES)
+
+ # other stuff
+ for team in self:
+ if team.use_rating:
+ for stage in team.stage_ids:
+ if stage.is_close and not stage.fold:
+ stage.template_id = self.env.ref('odex25_helpdesk.rating_ticket_request_email_template', raise_if_not_found= False)
+
+ if modules:
+ modules.button_immediate_install()
+
+ # just in case we want to do something if we install a module. (like a refresh ...)
+ return bool(modules)
+
+ # ------------------------------------------------------------
+ # Mail Alias Mixin
+ # ------------------------------------------------------------
+
+ def _alias_get_creation_values(self):
+ values = super(odex25_helpdeskTeam, self)._alias_get_creation_values()
+ values['alias_model_id'] = self.env['ir.model']._get('odex25_helpdesk.ticket').id
+ if self.id:
+ values['alias_defaults'] = defaults = ast.literal_eval(self.alias_defaults or "{}")
+ defaults['team_id'] = self.id
+ return values
+
+ # ------------------------------------------------------------
+ # Business Methods
+ # ------------------------------------------------------------
+
+ @api.model
+ def retrieve_dashboard(self):
+ domain = [('user_id', '=', self.env.uid)]
+ group_fields = ['priority', 'create_date', 'stage_id', 'close_hours']
+ list_fields = ['priority', 'create_date', 'stage_id', 'close_hours']
+ #TODO: remove SLA calculations if user_uses_sla is false.
+ user_uses_sla = self.user_has_groups('odex25_helpdesk.group_use_sla') and\
+ bool(self.env['odex25_helpdesk.team'].search([('use_sla', '=', True), '|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)]))
+
+ if user_uses_sla:
+ group_fields.insert(1, 'sla_deadline:year')
+ group_fields.insert(2, 'sla_deadline:hour')
+ group_fields.insert(3, 'sla_reached_late')
+ list_fields.insert(1, 'sla_deadline')
+ list_fields.insert(2, 'sla_reached_late')
+
+ odex25_helpdeskTicket = self.env['odex25_helpdesk.ticket']
+ tickets = odex25_helpdeskTicket.search_read(expression.AND([domain, [('stage_id.is_close', '=', False)]]), ['sla_deadline', 'open_hours', 'sla_reached_late', 'priority'])
+
+ result = {
+ 'odex25_helpdesk_target_closed': self.env.user.odex25_helpdesk_target_closed,
+ 'odex25_helpdesk_target_rating': self.env.user.odex25_helpdesk_target_rating,
+ 'odex25_helpdesk_target_success': self.env.user.odex25_helpdesk_target_success,
+ 'today': {'count': 0, 'rating': 0, 'success': 0},
+ '7days': {'count': 0, 'rating': 0, 'success': 0},
+ 'my_all': {'count': 0, 'hours': 0, 'failed': 0},
+ 'my_high': {'count': 0, 'hours': 0, 'failed': 0},
+ 'my_urgent': {'count': 0, 'hours': 0, 'failed': 0},
+ 'show_demo': not bool(odex25_helpdeskTicket.search([], limit=1)),
+ 'rating_enable': False,
+ 'success_rate_enable': user_uses_sla
+ }
+
+ def _is_sla_failed(data):
+ deadline = data.get('sla_deadline')
+ sla_deadline = fields.Datetime.now() > deadline if deadline else False
+ return sla_deadline or data.get('sla_reached_late')
+
+ def add_to(ticket, key="my_all"):
+ result[key]['count'] += 1
+ result[key]['hours'] += ticket['open_hours']
+ if _is_sla_failed(ticket):
+ result[key]['failed'] += 1
+
+ for ticket in tickets:
+ add_to(ticket, 'my_all')
+ if ticket['priority'] == '2':
+ add_to(ticket, 'my_high')
+ if ticket['priority'] == '3':
+ add_to(ticket, 'my_urgent')
+
+ dt = fields.Date.today()
+ tickets = odex25_helpdeskTicket.read_group(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)], list_fields, group_fields, lazy=False)
+ for ticket in tickets:
+ result['today']['count'] += ticket['__count']
+ if not _is_sla_failed(ticket):
+ result['today']['success'] += ticket['__count']
+
+ dt = fields.Datetime.to_string((datetime.date.today() - relativedelta.relativedelta(days=6)))
+ tickets = odex25_helpdeskTicket.read_group(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)], list_fields, group_fields, lazy=False)
+ for ticket in tickets:
+ result['7days']['count'] += ticket['__count']
+ if not _is_sla_failed(ticket):
+ result['7days']['success'] += ticket['__count']
+
+ result['today']['success'] = (result['today']['success'] * 100) / (result['today']['count'] or 1)
+ result['7days']['success'] = (result['7days']['success'] * 100) / (result['7days']['count'] or 1)
+ result['my_all']['hours'] = round(result['my_all']['hours'] / (result['my_all']['count'] or 1), 2)
+ result['my_high']['hours'] = round(result['my_high']['hours'] / (result['my_high']['count'] or 1), 2)
+ result['my_urgent']['hours'] = round(result['my_urgent']['hours'] / (result['my_urgent']['count'] or 1), 2)
+
+ if self.env['odex25_helpdesk.team'].search([('use_rating', '=', True), '|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)]):
+ result['rating_enable'] = True
+ # rating of today
+ domain = [('user_id', '=', self.env.uid)]
+ dt = fields.Date.today()
+ tickets = self.env['odex25_helpdesk.ticket'].search(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)])
+ activity = tickets.rating_get_grades()
+ total_rating = self._compute_activity_avg(activity)
+ total_activity_values = sum(activity.values())
+ team_satisfaction = round((total_rating / total_activity_values if total_activity_values else 0), 2) * 5
+ if team_satisfaction:
+ result['today']['rating'] = team_satisfaction
+
+ # rating of last 7 days (6 days + today)
+ dt = fields.Datetime.to_string((datetime.date.today() - relativedelta.relativedelta(days=6)))
+ tickets = self.env['odex25_helpdesk.ticket'].search(domain + [('stage_id.is_close', '=', True), ('close_date', '>=', dt)])
+ activity = tickets.rating_get_grades()
+ total_rating = self._compute_activity_avg(activity)
+ total_activity_values = sum(activity.values())
+ team_satisfaction_7days = round((total_rating / total_activity_values if total_activity_values else 0), 2) * 5
+ if team_satisfaction_7days:
+ result['7days']['rating'] = team_satisfaction_7days
+ return result
+
+ def _action_view_rating(self, period=False, only_my_closed=False):
+ """ return the action to see all the rating about the tickets of the Team
+ :param period: either 'today' or 'seven_days' to include (or not) the tickets closed in this period
+ :param only_my_closed: True will include only the ticket of the current user in a closed stage
+ """
+ domain = [('team_id', 'in', self.ids)]
+
+ if period == 'seven_days':
+ domain += [('close_date', '>=', fields.Datetime.to_string((datetime.date.today() - relativedelta.relativedelta(days=6))))]
+ elif period == 'today':
+ domain += [('close_date', '>=', fields.Datetime.to_string(datetime.date.today()))]
+
+ if only_my_closed:
+ domain += [('user_id', '=', self._uid), ('stage_id.is_close', '=', True)]
+
+ ticket_ids = self.env['odex25_helpdesk.ticket'].search(domain).ids
+ action = self.env["ir.actions.actions"]._for_xml_id("rating.rating_rating_view")
+ action['domain'] = [('res_id', 'in', ticket_ids), ('rating', '!=', -1), ('res_model', '=', 'odex25_helpdesk.ticket'), ('consumed', '=', True)]
+ action['help'] = '
No data yet !
Create tickets to get statistics.
'
+ return action
+
+ def action_view_ticket(self):
+ action = self.env["ir.actions.actions"]._for_xml_id("odex25_helpdesk.odex25_helpdesk_ticket_action_team")
+ action['display_name'] = self.name
+ return action
+
+ @api.model
+ def action_view_rating_today(self):
+ # call this method of on click "Customer Rating" button on dashbord for today rating of teams tickets
+ return self.search(['|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)])._action_view_rating(period='today', only_my_closed=True)
+
+ @api.model
+ def action_view_rating_7days(self):
+ # call this method of on click "Customer Rating" button on dashbord for last 7days rating of teams tickets
+ return self.search(['|', ('member_ids', 'in', self._uid), ('member_ids', '=', False)])._action_view_rating(period='seven_days', only_my_closed=True)
+
+ def action_view_all_rating(self):
+ """ return the action to see all the rating about the all sort of activity of the team (tickets) """
+ return self._action_view_rating()
+
+ @api.model
+ def _compute_activity_avg(self, activity):
+ # compute average base on all rating value
+ # like: 5 great, 3 okey, 1 bad
+ # great = 5, okey = 3, bad = 0
+ # (5*5) + (2*3) + (1*0) = 60 / 8 (nuber of activity for rating)
+ great = activity['great'] * 5.00
+ okey = activity['okay'] * 3.00
+ bad = activity['bad'] * 0.00
+ return great + okey + bad
+
+ def _determine_user_to_assign(self):
+ """ Get a dict with the user (per team) that should be assign to the nearly created ticket according to the team policy
+ :returns a mapping of team identifier with the "to assign" user (maybe an empty record).
+ :rtype : dict (key=team_id, value=record of res.users)
+ """
+ result = dict.fromkeys(self.ids, self.env['res.users'])
+ for team in self:
+ member_ids = sorted(team.member_ids.ids) if team.member_ids else sorted(team.visibility_member_ids.ids)
+ if member_ids:
+ if team.assign_method == 'randomly': # randomly means new tickets get uniformly distributed
+ last_assigned_user = self.env['odex25_helpdesk.ticket'].search([('team_id', '=', team.id)], order='create_date desc, id desc', limit=1).user_id
+ index = 0
+ if last_assigned_user and last_assigned_user.id in member_ids:
+ previous_index = member_ids.index(last_assigned_user.id)
+ index = (previous_index + 1) % len(member_ids)
+ result[team.id] = self.env['res.users'].browse(member_ids[index])
+ elif team.assign_method == 'balanced': # find the member with the least open ticket
+ ticket_count_data = self.env['odex25_helpdesk.ticket'].read_group([('stage_id.is_close', '=', False), ('user_id', 'in', member_ids), ('team_id', '=', team.id)], ['user_id'], ['user_id'])
+ open_ticket_per_user_map = dict.fromkeys(member_ids, 0) # dict: user_id -> open ticket count
+ open_ticket_per_user_map.update((item['user_id'][0], item['user_id_count']) for item in ticket_count_data)
+ result[team.id] = self.env['res.users'].browse(min(open_ticket_per_user_map, key=open_ticket_per_user_map.get))
+ return result
+
+ def _determine_stage(self):
+ """ Get a dict with the stage (per team) that should be set as first to a created ticket
+ :returns a mapping of team identifier with the stage (maybe an empty record).
+ :rtype : dict (key=team_id, value=record of odex25_helpdesk.stage)
+ """
+ result = dict.fromkeys(self.ids, self.env['odex25_helpdesk.stage'])
+ for team in self:
+ result[team.id] = self.env['odex25_helpdesk.stage'].search([('team_ids', 'in', team.id)], order='sequence', limit=1)
+ return result
+
+ def _get_closing_stage(self):
+ """
+ Return the first closing kanban stage or the last stage of the pipe if none
+ """
+ closed_stage = self.stage_ids.filtered(lambda stage: stage.is_close)
+ if not closed_stage:
+ closed_stage = self.stage_ids[-1]
+ return closed_stage
+
+
+
+class odex25_helpdeskStage(models.Model):
+ _name = 'odex25_helpdesk.stage'
+ _description = 'Helpdesk Stage'
+ _order = 'sequence, id'
+
+ def _default_team_ids(self):
+ team_id = self.env.context.get('default_team_id')
+ if team_id:
+ return [(4, team_id, 0)]
+
+ name = fields.Char('Stage Name', required=True, translate=True)
+ description = fields.Text(translate=True)
+ sequence = fields.Integer('Sequence', default=10)
+ is_close = fields.Boolean(
+ 'Closing Stage',
+ help='Tickets in this stage are considered as done. This is used notably when '
+ 'computing SLAs and KPIs on tickets.')
+ fold = fields.Boolean(
+ 'Folded in Kanban',
+ help='This stage is folded in the kanban view when there are no records in that stage to display.')
+ team_ids = fields.Many2many(
+ 'odex25_helpdesk.team', relation='team_stage_rel', string='Team',
+ default=_default_team_ids,
+ help='Specific team that uses this stage. Other teams will not be able to see or use this stage.')
+ template_id = fields.Many2one(
+ 'mail.template', 'Email Template',
+ domain="[('model', '=', 'odex25_helpdesk.ticket')]",
+ help="Automated email sent to the ticket's customer when the ticket reaches this stage.")
+ legend_blocked = fields.Char(
+ 'Red Kanban Label', default=lambda s: _('Blocked'), translate=True, required=True,
+ help='Override the default value displayed for the blocked state for kanban selection, when the task or issue is in that stage.')
+ legend_done = fields.Char(
+ 'Green Kanban Label', default=lambda s: _('Ready'), translate=True, required=True,
+ help='Override the default value displayed for the done state for kanban selection, when the task or issue is in that stage.')
+ legend_normal = fields.Char(
+ 'Grey Kanban Label', default=lambda s: _('In Progress'), translate=True, required=True,
+ help='Override the default value displayed for the normal state for kanban selection, when the task or issue is in that stage.')
+
+ def unlink(self):
+ stages = self
+ default_team_id = self.env.context.get('default_team_id')
+ if default_team_id:
+ shared_stages = self.filtered(lambda x: len(x.team_ids) > 1 and default_team_id in x.team_ids.ids)
+ tickets = self.env['odex25_helpdesk.ticket'].with_context(active_test=False).search([('team_id', '=', default_team_id), ('stage_id', 'in', self.ids)])
+ if shared_stages and not tickets:
+ shared_stages.write({'team_ids': [(3, default_team_id)]})
+ stages = self.filtered(lambda x: x not in shared_stages)
+ return super(odex25_helpdeskStage, stages).unlink()
+
+
+class odex25_helpdeskSLA(models.Model):
+ _name = "odex25_helpdesk.sla"
+ _order = "name"
+ _description = "Helpdesk SLA Policies"
+
+ name = fields.Char('SLA Policy Name', required=True, index=True)
+ description = fields.Text('SLA Policy Description')
+ active = fields.Boolean('Active', default=True)
+ team_id = fields.Many2one('odex25_helpdesk.team', 'Team', required=True)
+ target_type = fields.Selection([('stage', 'Reaching Stage'), ('assigning', 'Assigning Ticket')], default='stage', required=True)
+ ticket_type_id = fields.Many2one(
+ 'odex25_helpdesk.ticket.type', "Ticket Type",
+ help="Only apply the SLA to a specific ticket type. If left empty it will apply to all types.")
+ tag_ids = fields.Many2many(
+ 'odex25_helpdesk.tag', string='Tags',
+ help="Only apply the SLA to tickets with specific tags. If left empty it will apply to all tags.")
+ stage_id = fields.Many2one(
+ 'odex25_helpdesk.stage', 'Target Stage',
+ help='Minimum stage a ticket needs to reach in order to satisfy this SLA.')
+ exclude_stage_ids = fields.Many2many(
+ 'odex25_helpdesk.stage', string='Exclude Stages',
+ compute='_compute_exclude_stage_ids', store=True, readonly=False, copy=True,
+ domain="[('id', '!=', stage_id.id)]",
+ help='The amount of time the ticket spends in this stage will not be taken into account when evaluating the status of the SLA Policy.')
+ priority = fields.Selection(
+ TICKET_PRIORITY, string='Minimum Priority',
+ default='0', required=True,
+ help='Tickets under this priority will not be taken into account.')
+ company_id = fields.Many2one('res.company', 'Company', related='team_id.company_id', readonly=True, store=True)
+ time_days = fields.Integer(
+ 'Days', default=0, required=True,
+ help="Days to reach given stage based on ticket creation date")
+ time_hours = fields.Integer(
+ 'Hours', default=0, inverse='_inverse_time_hours', required=True,
+ help="Hours to reach given stage based on ticket creation date")
+ time_minutes = fields.Integer(
+ 'Minutes', default=0, inverse='_inverse_time_minutes', required=True,
+ help="Minutes to reach given stage based on ticket creation date")
+ category_id = fields.Many2one('service.category')
+ service_id = fields.Many2one('helpdesk.service')
+
+ @api.depends('target_type')
+ def _compute_exclude_stage_ids(self):
+ self.update({'exclude_stage_ids': False})
+
+ def _inverse_time_hours(self):
+ for sla in self:
+ sla.time_hours = max(0, sla.time_hours)
+ if sla.time_hours >= 24:
+ sla.time_days += sla.time_hours / 24
+ sla.time_hours = sla.time_hours % 24
+
+ def _inverse_time_minutes(self):
+ for sla in self:
+ sla.time_minutes = max(0, sla.time_minutes)
+ if sla.time_minutes >= 60:
+ sla.time_hours += sla.time_minutes / 60
+ sla.time_minutes = sla.time_minutes % 60
diff --git a/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk_ticket.py b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk_ticket.py
new file mode 100644
index 000000000..3ee17259a
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/models/odex25_helpdesk_ticket.py
@@ -0,0 +1,970 @@
+# -*- coding: utf-8 -*-
+
+from dateutil.relativedelta import relativedelta
+from datetime import timedelta
+from random import randint
+
+from odoo import api, fields, models, tools, _
+from odoo.osv import expression
+from odoo.exceptions import AccessError
+
+TICKET_PRIORITY = [
+ ('0', 'All'),
+ ('1', 'Low priority'),
+ ('2', 'High priority'),
+ ('3', 'Urgent'),
+]
+
+
+class odex25_helpdeskTag(models.Model):
+ _name = 'odex25_helpdesk.tag'
+ _description = 'Helpdesk Tags'
+ _order = 'name'
+
+ def _get_default_color(self):
+ return randint(1, 11)
+
+ name = fields.Char('Tag Name', required=True)
+ color = fields.Integer('Color', default=_get_default_color)
+
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', "Tag name already exists !"),
+ ]
+
+
+class odex25_helpdeskTicketType(models.Model):
+ _name = 'odex25_helpdesk.ticket.type'
+ _description = 'Helpdesk Ticket Type'
+ _order = 'sequence'
+
+ name = fields.Char('Type', required=True, translate=True)
+ sequence = fields.Integer(default=10)
+
+ _sql_constraints = [
+ ('name_uniq', 'unique (name)', "Type name already exists !"),
+ ]
+
+
+class odex25_helpdeskSLAStatus(models.Model):
+ _name = 'odex25_helpdesk.sla.status'
+ _description = "Ticket SLA Status"
+ _table = 'odex25_helpdesk_sla_status'
+ _order = 'deadline ASC, sla_stage_id'
+ _rec_name = 'sla_id'
+
+ ticket_id = fields.Many2one('odex25_helpdesk.ticket', string='Ticket', required=True, ondelete='cascade',
+ index=True)
+ sla_id = fields.Many2one('odex25_helpdesk.sla', required=True, ondelete='cascade')
+ sla_stage_id = fields.Many2one('odex25_helpdesk.stage', related='sla_id.stage_id',
+ store=True) # need to be stored for the search in `_sla_reach`
+ target_type = fields.Selection(related='sla_id.target_type', store=True)
+ deadline = fields.Datetime("Deadline", compute='_compute_deadline', compute_sudo=True, store=True)
+ reached_datetime = fields.Datetime("Reached Date",
+ help="Datetime at which the SLA stage was reached for the first time")
+ status = fields.Selection([('failed', 'Failed'), ('reached', 'Reached'), ('ongoing', 'Ongoing')], string="Status",
+ compute='_compute_status', compute_sudo=True, search='_search_status')
+ color = fields.Integer("Color Index", compute='_compute_color')
+ exceeded_days = fields.Float("Excedeed Working Days", compute='_compute_exceeded_days', compute_sudo=True,
+ store=True,
+ help="Working days exceeded for reached SLAs compared with deadline. Positive number means the SLA was eached after the deadline.")
+
+ @api.depends('ticket_id.create_date', 'sla_id', 'ticket_id.stage_id')
+ def _compute_deadline(self):
+ for status in self:
+ if (status.deadline and status.reached_datetime) or (
+ status.deadline and status.target_type == 'stage' and not status.sla_id.exclude_stage_ids) or (
+ status.status == 'failed'):
+ continue
+ if status.target_type == 'assigning' and status.sla_stage_id == status.ticket_id.stage_id:
+ deadline = fields.Datetime.now()
+ elif status.target_type == 'assigning' and status.sla_stage_id:
+ status.deadline = False
+ continue
+ else:
+ deadline = status.ticket_id.create_date
+ working_calendar = status.ticket_id.team_id.resource_calendar_id
+ if not working_calendar:
+ # Normally, having a working_calendar is mandatory
+ status.deadline = deadline
+ continue
+
+ if status.target_type == 'stage' and status.sla_id.exclude_stage_ids:
+ if status.ticket_id.stage_id in status.sla_id.exclude_stage_ids:
+ # We are in the freezed time stage: No deadline
+ status.deadline = False
+ continue
+
+ time_days = status.sla_id.time_days
+ if time_days and (
+ status.sla_id.target_type == 'stage' or status.sla_id.target_type == 'assigning' and not status.sla_id.stage_id):
+ deadline = working_calendar.plan_days(time_days + 1, deadline, compute_leaves=True)
+ # We should also depend on ticket creation time, otherwise for 1 day SLA, all tickets
+ # created on monday will have their deadline filled with tuesday 8:00
+ create_dt = status.ticket_id.create_date
+ deadline = deadline.replace(hour=create_dt.hour, minute=create_dt.minute, second=create_dt.second,
+ microsecond=create_dt.microsecond)
+ elif time_days and status.target_type == 'assigning' and status.sla_stage_id == status.ticket_id.stage_id:
+ deadline = working_calendar.plan_days(time_days + 1, deadline, compute_leaves=True)
+ reached_stage_dt = fields.Datetime.now()
+ deadline = deadline.replace(hour=reached_stage_dt.hour, minute=reached_stage_dt.minute,
+ second=reached_stage_dt.second, microsecond=reached_stage_dt.microsecond)
+
+ sla_hours = status.sla_id.time_hours + (status.sla_id.time_minutes / 60)
+
+ if status.target_type == 'stage' and status.sla_id.exclude_stage_ids:
+ sla_hours += status._get_freezed_hours(working_calendar)
+
+ # Except if ticket creation time is later than the end time of the working day
+ deadline_for_working_cal = working_calendar.plan_hours(0, deadline)
+ if deadline_for_working_cal and deadline.day < deadline_for_working_cal.day:
+ deadline = deadline.replace(hour=0, minute=0, second=0, microsecond=0)
+ # We should execute the function plan_hours in any case because, in a 1 day SLA environment,
+ # if I create a ticket knowing that I'm not working the day after at the same time, ticket
+ # deadline will be set at time I don't work (ticket creation time might not be in working calendar).
+ status.deadline = working_calendar.plan_hours(sla_hours, deadline, compute_leaves=True)
+
+ @api.depends('deadline', 'reached_datetime')
+ def _compute_status(self):
+ """ Note: this computed field depending on 'now()' is stored, but refreshed by a cron """
+ for status in self:
+ if status.reached_datetime and status.deadline: # if reached_datetime, SLA is finished: either failed or succeeded
+ status.status = 'reached' if status.reached_datetime < status.deadline else 'failed'
+ else: # if not finished, deadline should be compared to now()
+ status.status = 'ongoing' if not status.deadline or status.deadline > fields.Datetime.now() else 'failed'
+
+ @api.model
+ def _search_status(self, operator, value):
+ """ Supported operators: '=', 'in' and their negative form. """
+ # constants
+ datetime_now = fields.Datetime.now()
+ positive_domain = {
+ 'failed': ['|', '&', ('reached_datetime', '=', True), ('deadline', '<=', 'reached_datetime'), '&',
+ ('reached_datetime', '=', False), ('deadline', '<=', fields.Datetime.to_string(datetime_now))],
+ 'reached': ['&', ('reached_datetime', '=', True), ('reached_datetime', '<', 'deadline')],
+ 'ongoing': ['&', ('reached_datetime', '=', False),
+ ('deadline', '<=', fields.Datetime.to_string(datetime_now))]
+ }
+ # in/not in case: we treat value as a list of selection item
+ if not isinstance(value, list):
+ value = [value]
+ # transform domains
+ if operator in expression.NEGATIVE_TERM_OPERATORS:
+ # "('status', 'not in', [A, B])" tranformed into "('status', '=', C) OR ('status', '=', D)"
+ domains_to_keep = [dom for key, dom in positive_domain if key not in value]
+ return expression.OR(domains_to_keep)
+ else:
+ return expression.OR(positive_domain[value_item] for value_item in value)
+
+ @api.depends('status')
+ def _compute_color(self):
+ for status in self:
+ if status.status == 'failed':
+ status.color = 1
+ elif status.status == 'reached':
+ status.color = 10
+ else:
+ status.color = 0
+
+ @api.depends('deadline', 'reached_datetime')
+ def _compute_exceeded_days(self):
+ for status in self:
+ if status.reached_datetime and status.deadline and status.ticket_id.team_id.resource_calendar_id:
+ if status.reached_datetime <= status.deadline:
+ start_dt = status.reached_datetime
+ end_dt = status.deadline
+ factor = -1
+ else:
+ start_dt = status.deadline
+ end_dt = status.reached_datetime
+ factor = 1
+ duration_data = status.ticket_id.team_id.resource_calendar_id.get_work_duration_data(start_dt, end_dt,
+ compute_leaves=True)
+ status.exceeded_days = duration_data['days'] * factor
+ else:
+ status.exceeded_days = False
+
+ def _get_freezed_hours(self, working_calendar):
+ self.ensure_one()
+ hours_freezed = 0
+
+ field_stage = self.env['ir.model.fields']._get(self.ticket_id._name, "stage_id")
+ freeze_stages = self.sla_id.exclude_stage_ids.ids
+ tracking_lines = self.ticket_id.message_ids.tracking_value_ids.filtered(
+ lambda tv: tv.field == field_stage).sorted(key="create_date")
+
+ if not tracking_lines:
+ return 0
+
+ old_time = self.ticket_id.create_date
+ for tracking_line in tracking_lines:
+ if tracking_line.old_value_integer in freeze_stages:
+ # We must use get_work_hours_count to compute real waiting hours (as the deadline computation is also based on calendar)
+ hours_freezed += working_calendar.get_work_hours_count(old_time, tracking_line.create_date)
+ old_time = tracking_line.create_date
+ if tracking_lines[-1].new_value_integer in freeze_stages:
+ # the last tracking line is not yet created
+ hours_freezed += working_calendar.get_work_hours_count(old_time, fields.Datetime.now())
+ return hours_freezed
+
+
+class odex25_helpdeskTicket(models.Model):
+ _name = 'odex25_helpdesk.ticket'
+ _description = 'Helpdesk Ticket'
+ _order = 'priority desc, id desc'
+ _inherit = ['portal.mixin', 'mail.thread.cc', 'utm.mixin', 'rating.mixin', 'mail.activity.mixin']
+
+ @api.model
+ def default_get(self, fields):
+ result = super(odex25_helpdeskTicket, self).default_get(fields)
+ if result.get('team_id') and fields:
+ team = self.env['odex25_helpdesk.team'].browse(result['team_id'])
+ if 'user_id' in fields and 'user_id' not in result: # if no user given, deduce it from the team
+ result['user_id'] = team._determine_user_to_assign()[team.id].id
+ if 'stage_id' in fields and 'stage_id' not in result: # if no stage given, deduce it from the team
+ result['stage_id'] = team._determine_stage()[team.id].id
+ return result
+
+ @api.model
+ def _read_group_stage_ids(self, stages, domain, order):
+ # write the domain
+ # - ('id', 'in', stages.ids): add columns that should be present
+ # - OR ('team_ids', '=', team_id) if team_id: add team columns
+ search_domain = [('id', 'in', stages.ids)]
+ if self.env.context.get('default_team_id'):
+ search_domain = ['|', ('team_ids', 'in', self.env.context['default_team_id'])] + search_domain
+
+ return stages.search(search_domain, order=order)
+
+ name = fields.Char(string='Subject', required=True, index=True)
+ team_id = fields.Many2one('odex25_helpdesk.team', string='Helpdesk Team', index=True)
+ description = fields.Text(required=True, tracking=True)
+ active = fields.Boolean(default=True)
+ ticket_type_id = fields.Many2one('odex25_helpdesk.ticket.type', string="Ticket Type")
+ tag_ids = fields.Many2many('odex25_helpdesk.tag', string='Tags')
+ company_id = fields.Many2one(related='team_id.company_id', string='Company', store=True, readonly=True)
+ color = fields.Integer(string='Color Index')
+ kanban_state = fields.Selection([
+ ('normal', 'Grey'),
+ ('done', 'Green'),
+ ('blocked', 'Red')], string='Kanban State',
+ default='normal', required=True)
+ kanban_state_label = fields.Char(compute='_compute_kanban_state_label', string='Column Status', tracking=True)
+ legend_blocked = fields.Char(related='stage_id.legend_blocked', string='Kanban Blocked Explanation', readonly=True,
+ related_sudo=False)
+ legend_done = fields.Char(related='stage_id.legend_done', string='Kanban Valid Explanation', readonly=True,
+ related_sudo=False)
+ legend_normal = fields.Char(related='stage_id.legend_normal', string='Kanban Ongoing Explanation', readonly=True,
+ related_sudo=False)
+ domain_user_ids = fields.Many2many('res.users', compute='_compute_domain_user_ids')
+ user_id = fields.Many2one(
+ 'res.users', string='Assigned to', compute='_compute_user_and_stage_ids', store=True,
+ readonly=False, tracking=True,
+ domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)])
+ partner_id = fields.Many2one('res.partner', string='Customer')
+ partner_ticket_count = fields.Integer('Number of closed tickets from the same partner',
+ compute='_compute_partner_ticket_count')
+ attachment_number = fields.Integer(compute='_compute_attachment_number', string="Number of Attachments")
+ is_self_assigned = fields.Boolean("Am I assigned", compute='_compute_is_self_assigned')
+ # Used to submit tickets from a contact form
+ partner_name = fields.Char(string='Customer Name', compute='_compute_partner_info', store=True, readonly=False)
+ partner_email = fields.Char(string='Customer Email', compute='_compute_partner_info', store=True, readonly=False)
+ closed_by_partner = fields.Boolean('Closed by Partner', readonly=True,
+ help="If checked, this means the ticket was closed through the customer portal by the customer.")
+ # Used in message_get_default_recipients, so if no partner is created, email is sent anyway
+ email = fields.Char(related='partner_email', string='Email on Customer', readonly=False)
+ # priority = fields.Selection(related="service_id.priority", string='Priority', default='0')
+ priority = fields.Selection(TICKET_PRIORITY, string='Priority', default='0')
+ stage_id = fields.Many2one(
+ 'odex25_helpdesk.stage', string='Stage', compute='_compute_user_and_stage_ids', store=True,
+ readonly=False, ondelete='restrict', tracking=True, group_expand='_read_group_stage_ids',
+ copy=False, index=True, domain="[('team_ids', '=', team_id)]")
+ date_last_stage_update = fields.Datetime("Last Stage Update", copy=False, readonly=True)
+ # next 4 fields are computed in write (or create)
+ assign_date = fields.Datetime("First assignment date")
+ assign_hours = fields.Integer("Time to first assignment (hours)", compute='_compute_assign_hours', store=True,
+ help="This duration is based on the working calendar of the team")
+ close_date = fields.Datetime("Close date", copy=False)
+ close_hours = fields.Integer("Time to close (hours)", compute='_compute_close_hours', store=True,
+ help="This duration is based on the working calendar of the team")
+ open_hours = fields.Integer("Open Time (hours)", compute='_compute_open_hours', search='_search_open_hours',
+ help="This duration is not based on the working calendar of the team")
+ # SLA relative
+ sla_ids = fields.Many2many('odex25_helpdesk.sla', 'odex25_helpdesk_sla_status', 'ticket_id', 'sla_id',
+ string="SLAs", copy=False)
+ sla_status_ids = fields.One2many('odex25_helpdesk.sla.status', 'ticket_id', string="SLA Status")
+ sla_reached_late = fields.Boolean("Has SLA reached late", compute='_compute_sla_reached_late', compute_sudo=True,
+ store=True)
+ sla_deadline = fields.Datetime("SLA Deadline", compute='_compute_sla_deadline', compute_sudo=True, store=True,
+ help="The closest deadline of all SLA applied on this ticket")
+ sla_fail = fields.Boolean("Failed SLA Policy", compute='_compute_sla_fail', search='_search_sla_fail')
+ sla_success = fields.Boolean("Success SLA Policy", compute='_compute_sla_success', search='_search_sla_success')
+
+ use_credit_notes = fields.Boolean(related='team_id.use_credit_notes', string='Use Credit Notes')
+ use_coupons = fields.Boolean(related='team_id.use_coupons', string='Use Coupons')
+ use_product_returns = fields.Boolean(related='team_id.use_product_returns', string='Use Returns')
+ use_product_repairs = fields.Boolean(related='team_id.use_product_repairs', string='Use Repairs')
+
+ # customer portal: include comment and incoming emails in communication history
+ website_message_ids = fields.One2many(
+ domain=lambda self: [('model', '=', self._name), ('message_type', 'in', ['email', 'comment'])])
+
+ category_id = fields.Many2one('service.category')
+ service_id = fields.Many2one('helpdesk.service')
+ assistance_user_id = fields.Many2one('res.users', string="Assistance", domain=lambda self: [("groups_id", "=",
+ self.env.ref("odex25_helpdesk.group_odex25_helpdesk_user").id)])
+ # work_email = fields.Char(related='employee_id.work_email', string="Work Email")
+ department_id = fields.Many2one('hr.department', string="Administrative Structure")
+ phone_no = fields.Char(string="Phone Number")
+ on_behalf = fields.Many2one('hr.employee', string="On behalf of")
+ emp_req = fields.Boolean(default=False)
+ schedule_date = fields.Datetime("Scheduled Date")
+
+ @api.onchange('service_id')
+ def _onchange_invoice_date(self):
+ if self.service_id:
+ self.priority = self.service_id.priority
+
+ @api.onchange('partner_id')
+ def onchange_partner_id(self):
+ """
+ get the partner of the user
+ """
+ user = self.env['res.users'].search([('partner_id', '=', self.partner_id.id)], limit=1)
+ employee_id = self.env['hr.employee'].search([('user_id', '=', user.id)], limit=1)
+ self.department_id = employee_id.department_id
+
+ def activity_update(self):
+ for ticket in self.filtered(lambda request: request.schedule_date):
+ ticket.activity_schedule('odex25_helpdesk.mail_act_odex25_helpdesk_assistance',
+ fields.Datetime.from_string(ticket.schedule_date).date(),
+ note=ticket.name, user_id=ticket.assistance_user_id.id or self.env.uid)
+
+ @api.depends('stage_id', 'kanban_state')
+ def _compute_kanban_state_label(self):
+ for task in self:
+ if task.kanban_state == 'normal':
+ task.kanban_state_label = task.legend_normal
+ elif task.kanban_state == 'blocked':
+ task.kanban_state_label = task.legend_blocked
+ else:
+ task.kanban_state_label = task.legend_done
+
+ @api.depends('team_id')
+ def _compute_domain_user_ids(self):
+ for task in self:
+ if task.team_id and task.team_id.visibility_member_ids:
+ odex25_helpdesk_manager = self.env['res.users'].search(
+ [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager').id)])
+ task.domain_user_ids = [(6, 0, (odex25_helpdesk_manager + task.team_id.visibility_member_ids).ids)]
+ else:
+ odex25_helpdesk_users = self.env['res.users'].search(
+ [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)]).ids
+ task.domain_user_ids = [(6, 0, odex25_helpdesk_users)]
+
+ def _compute_access_url(self):
+ super(odex25_helpdeskTicket, self)._compute_access_url()
+ for ticket in self:
+ ticket.access_url = '/my/ticket/%s' % ticket.id
+
+ def _compute_attachment_number(self):
+ read_group_res = self.env['ir.attachment'].read_group(
+ [('res_model', '=', 'odex25_helpdesk.ticket'), ('res_id', 'in', self.ids)],
+ ['res_id'], ['res_id'])
+ attach_data = {res['res_id']: res['res_id_count'] for res in read_group_res}
+ for record in self:
+ record.attachment_number = attach_data.get(record.id, 0)
+
+ @api.depends('sla_status_ids.deadline', 'sla_status_ids.reached_datetime')
+ def _compute_sla_reached_late(self):
+ """ Required to do it in SQL since we need to compare 2 columns value """
+ mapping = {}
+ if self.ids:
+ self.env.cr.execute("""
+ SELECT ticket_id, COUNT(id) AS reached_late_count
+ FROM odex25_helpdesk_sla_status
+ WHERE ticket_id IN %s AND deadline < reached_datetime
+ GROUP BY ticket_id
+ """, (tuple(self.ids),))
+ mapping = dict(self.env.cr.fetchall())
+
+ for ticket in self:
+ ticket.sla_reached_late = mapping.get(ticket.id, 0) > 0
+
+ @api.depends('sla_status_ids.deadline', 'sla_status_ids.reached_datetime')
+ def _compute_sla_deadline(self):
+ """ Keep the deadline for the last stage (closed one), so a closed ticket can have a status failed.
+ Note: a ticket in a closed stage will probably have no deadline
+ """
+ for ticket in self:
+ deadline = False
+ status_not_reached = ticket.sla_status_ids.filtered(
+ lambda status: not status.reached_datetime and status.deadline)
+ ticket.sla_deadline = min(status_not_reached.mapped('deadline')) if status_not_reached else deadline
+
+ @api.depends('sla_deadline', 'sla_reached_late')
+ def _compute_sla_fail(self):
+ now = fields.Datetime.now()
+ for ticket in self:
+ if ticket.sla_deadline:
+ ticket.sla_fail = (ticket.sla_deadline < now) or ticket.sla_reached_late
+ else:
+ ticket.sla_fail = ticket.sla_reached_late
+
+ @api.model
+ def _search_sla_fail(self, operator, value):
+ datetime_now = fields.Datetime.now()
+ if (value and operator in expression.NEGATIVE_TERM_OPERATORS) or (
+ not value and operator not in expression.NEGATIVE_TERM_OPERATORS): # is not failed
+ return ['&', ('sla_reached_late', '=', False), '|', ('sla_deadline', '=', False),
+ ('sla_deadline', '>=', datetime_now)]
+ return ['|', ('sla_reached_late', '=', True), ('sla_deadline', '<', datetime_now)] # is failed
+
+ @api.depends('sla_deadline', 'sla_reached_late')
+ def _compute_sla_success(self):
+ now = fields.Datetime.now()
+ for ticket in self:
+ ticket.sla_success = (ticket.sla_deadline and ticket.sla_deadline > now)
+
+ @api.model
+ def _search_sla_success(self, operator, value):
+ datetime_now = fields.Datetime.now()
+ if (value and operator in expression.NEGATIVE_TERM_OPERATORS) or (
+ not value and operator not in expression.NEGATIVE_TERM_OPERATORS): # is failed
+ return [[('sla_status_ids.reached_datetime', '>', datetime_now), ('sla_reached_late', '!=', False)]]
+ return [('sla_status_ids.reached_datetime', '<', datetime_now), ('sla_reached_late', '=', False)] # is success
+
+ @api.depends('user_id')
+ def _compute_is_self_assigned(self):
+ for ticket in self:
+ ticket.is_self_assigned = self.env.user == ticket.user_id
+
+ @api.depends('team_id')
+ def _compute_user_and_stage_ids(self):
+ for ticket in self.filtered(lambda ticket: ticket.team_id):
+ if not ticket.user_id:
+ ticket.user_id = ticket.team_id._determine_user_to_assign()[ticket.team_id.id]
+ if not ticket.stage_id or ticket.stage_id not in ticket.team_id.stage_ids:
+ ticket.stage_id = ticket.team_id._determine_stage()[ticket.team_id.id]
+
+ @api.depends('partner_id')
+ def _compute_partner_info(self):
+ for ticket in self:
+ if ticket.partner_id:
+ ticket.partner_name = ticket.partner_id.name
+ ticket.partner_email = ticket.partner_id.email
+
+ @api.depends('partner_id')
+ def _compute_partner_ticket_count(self):
+ data = self.env['odex25_helpdesk.ticket'].read_group([
+ ('partner_id', 'in', self.mapped('partner_id').ids),
+ ('stage_id.is_close', '=', False)
+ ], ['partner_id'], ['partner_id'], lazy=False)
+ ticket_per_partner_map = dict((item['partner_id'][0], item['__count']) for item in data)
+ for ticket in self:
+ ticket.partner_ticket_count = ticket_per_partner_map.get(ticket.partner_id.id, 0)
+
+ @api.depends('assign_date')
+ def _compute_assign_hours(self):
+ for ticket in self:
+ create_date = fields.Datetime.from_string(ticket.create_date)
+ if create_date and ticket.assign_date and ticket.team_id.resource_calendar_id:
+ duration_data = ticket.team_id.resource_calendar_id.get_work_duration_data(create_date,
+ fields.Datetime.from_string(
+ ticket.assign_date),
+ compute_leaves=True)
+ ticket.assign_hours = duration_data['hours']
+ else:
+ ticket.assign_hours = False
+
+ @api.depends('create_date', 'close_date')
+ def _compute_close_hours(self):
+ for ticket in self:
+ create_date = fields.Datetime.from_string(ticket.create_date)
+ if create_date and ticket.close_date:
+ duration_data = ticket.team_id.resource_calendar_id.get_work_duration_data(create_date,
+ fields.Datetime.from_string(
+ ticket.close_date),
+ compute_leaves=True)
+ ticket.close_hours = duration_data['hours']
+ else:
+ ticket.close_hours = False
+
+ @api.depends('close_hours')
+ def _compute_open_hours(self):
+ for ticket in self:
+ if ticket.create_date: # fix from https://github.com/odoo/enterprise/commit/928fbd1a16e9837190e9c172fa50828fae2a44f7
+ if ticket.close_date:
+ time_difference = ticket.close_date - fields.Datetime.from_string(ticket.create_date)
+ else:
+ time_difference = fields.Datetime.now() - fields.Datetime.from_string(ticket.create_date)
+ ticket.open_hours = (time_difference.seconds) / 3600 + time_difference.days * 24
+ else:
+ ticket.open_hours = 0
+
+ @api.model
+ def _search_open_hours(self, operator, value):
+ dt = fields.Datetime.now() - relativedelta.relativedelta(hours=value)
+
+ d1, d2 = False, False
+ if operator in ['<', '<=', '>', '>=']:
+ d1 = ['&', ('close_date', '=', False), ('create_date', expression.TERM_OPERATORS_NEGATION[operator], dt)]
+ d2 = ['&', ('close_date', '!=', False), ('close_hours', operator, value)]
+ elif operator in ['=', '!=']:
+ subdomain = ['&', ('create_date', '>=', dt.replace(minute=0, second=0, microsecond=0)),
+ ('create_date', '<=', dt.replace(minute=59, second=59, microsecond=99))]
+ if operator in expression.NEGATIVE_TERM_OPERATORS:
+ subdomain = expression.distribute_not(subdomain)
+ d1 = expression.AND([[('close_date', '=', False)], subdomain])
+ d2 = ['&', ('close_date', '!=', False), ('close_hours', operator, value)]
+ return expression.OR([d1, d2])
+
+ # ------------------------------------------------------------
+ # ORM overrides
+ # ------------------------------------------------------------
+
+ def name_get(self):
+ result = []
+ for ticket in self:
+ result.append((ticket.id, "%s (#%d)" % (ticket.name, ticket._origin.id)))
+ return result
+
+ @api.model
+ def create_action(self, action_ref, title, search_view_ref):
+ action = self.env["ir.actions.actions"]._for_xml_id(action_ref)
+ if title:
+ action['display_name'] = title
+ if search_view_ref:
+ action['search_view_id'] = self.env.ref(search_view_ref).read()[0]
+ action['views'] = [(False, view) for view in action['view_mode'].split(",")]
+
+ return {'action': action}
+
+ @api.model_create_multi
+ def create(self, list_value):
+ now = fields.Datetime.now()
+ # determine user_id and stage_id if not given. Done in batch.
+ teams = self.env['odex25_helpdesk.team'].browse([vals['team_id'] for vals in list_value if vals.get('team_id')])
+ team_default_map = dict.fromkeys(teams.ids, dict())
+ for team in teams:
+ team_default_map[team.id] = {
+ 'stage_id': team._determine_stage()[team.id].id,
+ 'user_id': team._determine_user_to_assign()[team.id].id
+ }
+
+ # Manually create a partner now since 'generate_recipients' doesn't keep the name. This is
+ # to avoid intrusive changes in the 'mail' module
+ for vals in list_value:
+ partner_id = vals.get('partner_id', False)
+ partner_name = vals.get('partner_name', False)
+ partner_email = vals.get('partner_email', False)
+ if partner_name and partner_email and not partner_id:
+ try:
+ vals['partner_id'] = self.env['res.partner'].find_or_create(
+ tools.formataddr((partner_name, partner_email))
+ ).id
+ except UnicodeEncodeError:
+ # 'formataddr' doesn't support non-ascii characters in email. Therefore, we fall
+ # back on a simple partner creation.
+ vals['partner_id'] = self.env['res.partner'].create({
+ 'name': partner_name,
+ 'email': partner_email,
+ }).id
+
+ # determine partner email for ticket with partner but no email given
+ partners = self.env['res.partner'].browse([vals['partner_id'] for vals in list_value if
+ 'partner_id' in vals and vals.get(
+ 'partner_id') and 'partner_email' not in vals])
+ partner_email_map = {partner.id: partner.email for partner in partners}
+ partner_name_map = {partner.id: partner.name for partner in partners}
+
+ for vals in list_value:
+ if vals.get('team_id'):
+ team_default = team_default_map[vals['team_id']]
+ if 'stage_id' not in vals:
+ vals['stage_id'] = team_default['stage_id']
+ # Note: this will break the randomly distributed user assignment. Indeed, it will be too difficult to
+ # equally assigned user when creating ticket in batch, as it requires to search after the last assigned
+ # after every ticket creation, which is not very performant. We decided to not cover this user case.
+ if 'user_id' not in vals:
+ vals['user_id'] = team_default['user_id']
+ if vals.get(
+ 'user_id'): # if a user is finally assigned, force ticket assign_date and reset assign_hours
+ vals['assign_date'] = fields.Datetime.now()
+ vals['assign_hours'] = 0
+
+ # set partner email if in map of not given
+ if vals.get('partner_id') in partner_email_map:
+ vals['partner_email'] = partner_email_map.get(vals['partner_id'])
+ # set partner name if in map of not given
+ if vals.get('partner_id') in partner_name_map:
+ vals['partner_name'] = partner_name_map.get(vals['partner_id'])
+
+ if vals.get('stage_id'):
+ vals['date_last_stage_update'] = now
+
+ # context: no_log, because subtype already handle this
+ tickets = super(odex25_helpdeskTicket, self).create(list_value)
+ tickets.activity_update()
+ tickets.emp_req = True
+
+ # make customer follower
+ for ticket in tickets:
+ if ticket.partner_id:
+ ticket.message_subscribe(partner_ids=ticket.partner_id.ids)
+
+ ticket._portal_ensure_token()
+
+ # apply SLA
+ tickets.sudo()._sla_apply()
+
+ return tickets
+
+ def write(self, vals):
+ # we set the assignation date (assign_date) to now for tickets that are being assigned for the first time
+ # same thing for the closing date
+ assigned_tickets = closed_tickets = self.browse()
+ if vals.get('user_id'):
+ assigned_tickets = self.filtered(lambda ticket: not ticket.assign_date)
+
+ if vals.get('stage_id'):
+ if self.env['odex25_helpdesk.stage'].browse(vals.get('stage_id')).is_close:
+ closed_tickets = self.filtered(lambda ticket: not ticket.close_date)
+ else: # auto reset the 'closed_by_partner' flag
+ vals['closed_by_partner'] = False
+
+ now = fields.Datetime.now()
+
+ # update last stage date when changing stage
+ if 'stage_id' in vals:
+ vals['date_last_stage_update'] = now
+
+ res = super(odex25_helpdeskTicket, self - assigned_tickets - closed_tickets).write(vals)
+ res &= super(odex25_helpdeskTicket, assigned_tickets - closed_tickets).write(dict(vals, **{
+ 'assign_date': now,
+ }))
+ res &= super(odex25_helpdeskTicket, closed_tickets - assigned_tickets).write(dict(vals, **{
+ 'close_date': now,
+ }))
+ res &= super(odex25_helpdeskTicket, assigned_tickets & closed_tickets).write(dict(vals, **{
+ 'assign_date': now,
+ 'close_date': now,
+ }))
+
+ if vals.get('partner_id'):
+ self.message_subscribe([vals['partner_id']])
+ if vals.get('assistance_user_id'):
+ # need to change description of activity also so unlink old and create new activity
+ self.activity_unlink(['odex25_helpdesk.mail_act_odex25_helpdesk_assistance'])
+ self.activity_update()
+
+ # SLA business
+ sla_triggers = self._sla_reset_trigger()
+ if any(field_name in sla_triggers for field_name in vals.keys()):
+ self.sudo()._sla_apply(keep_reached=True)
+ if 'stage_id' in vals:
+ self.sudo()._sla_reach(vals['stage_id'])
+
+ if 'stage_id' in vals or 'user_id' in vals:
+ self.filtered(lambda ticket: ticket.user_id).sudo()._sla_assigning_reach()
+
+ return res
+
+ # ------------------------------------------------------------
+ # Actions and Business methods
+ # ------------------------------------------------------------
+
+ @api.model
+ def _sla_reset_trigger(self):
+ """ Get the list of field for which we have to reset the SLAs (regenerate) """
+ return ['team_id', 'priority', 'service_id', 'tag_ids']
+
+ def _sla_apply(self, keep_reached=False):
+ """ Apply SLA to current tickets: erase the current SLAs, then find and link the new SLAs to each ticket.
+ Note: transferring ticket to a team "not using SLA" (but with SLAs defined), SLA status of the ticket will be
+ erased but nothing will be recreated.
+ :returns recordset of new odex25_helpdesk.sla.status applied on current tickets
+ """
+ # get SLA to apply
+ sla_per_tickets = self._sla_find()
+
+ # generate values of new sla status
+ sla_status_value_list = []
+ for tickets, slas in sla_per_tickets.items():
+ sla_status_value_list += tickets._sla_generate_status_values(slas, keep_reached=keep_reached)
+
+ sla_status_to_remove = self.mapped('sla_status_ids')
+ if keep_reached: # keep only the reached one to avoid losing reached_date info
+ sla_status_to_remove = sla_status_to_remove.filtered(lambda status: not status.reached_datetime)
+
+ # if we are going to recreate many sla.status, then add norecompute to avoid 2 recomputation (unlink + recreate). Here,
+ # `norecompute` will not trigger recomputation. It will be done on the create multi (if value list is not empty).
+ if sla_status_value_list:
+ sla_status_to_remove.with_context(norecompute=True)
+
+ # unlink status and create the new ones in 2 operations (recomputation optimized)
+ sla_status_to_remove.unlink()
+ return self.env['odex25_helpdesk.sla.status'].create(sla_status_value_list)
+
+ def _sla_find(self):
+ """ Find the SLA to apply on the current tickets
+ :returns a map with the tickets linked to the SLA to apply on them
+ :rtype : dict {: }
+ """
+ tickets_map = {}
+ sla_domain_map = {}
+
+ def _generate_key(ticket):
+ """ Return a tuple identifying the combinaison of field determining the SLA to apply on the ticket """
+ fields_list = self._sla_reset_trigger()
+ key = list()
+ for field_name in fields_list:
+ if ticket._fields[field_name].type == 'many2one':
+ key.append(ticket[field_name].id)
+ else:
+ key.append(ticket[field_name])
+ return tuple(key)
+
+ for ticket in self:
+ if ticket.team_id.use_sla: # limit to the team using SLA
+ key = _generate_key(ticket)
+ # group the ticket per key
+ tickets_map.setdefault(key, self.env['odex25_helpdesk.ticket'])
+ tickets_map[key] |= ticket
+ # group the SLA to apply, by key
+ if key not in sla_domain_map:
+ sla_domain_map[key] = [
+ ('team_id', '=', ticket.team_id.id), ('priority', '<=', ticket.priority),
+ '|',
+ '&', ('stage_id.sequence', '>=', ticket.stage_id.sequence), ('target_type', '=', 'stage'),
+ ('target_type', '=', 'assigning'),
+ '|', ('service_id', '=', ticket.service_id.id), ('service_id', '=', False)]
+
+ result = {}
+ for key, tickets in tickets_map.items(): # only one search per ticket group
+ domain = sla_domain_map[key]
+ slas = self.env['odex25_helpdesk.sla'].search(domain)
+ result[tickets] = slas.filtered(lambda s: s.tag_ids <= tickets.tag_ids) # SLA to apply on ticket subset
+ return result
+
+ def _sla_generate_status_values(self, slas, keep_reached=False):
+ """ Return the list of values for given SLA to be applied on current ticket """
+ status_to_keep = dict.fromkeys(self.ids, list())
+
+ # generate the map of status to keep by ticket only if requested
+ if keep_reached:
+ for ticket in self:
+ for status in ticket.sla_status_ids:
+ if status.reached_datetime:
+ status_to_keep[ticket.id].append(status.sla_id.id)
+
+ # create the list of value, and maybe exclude the existing ones
+ result = []
+ for ticket in self:
+ for sla in slas:
+ if not (keep_reached and sla.id in status_to_keep[ticket.id]):
+ if sla.target_type == 'stage' and ticket.stage_id == sla.stage_id:
+ # in case of SLA of type stage and on first stage
+ reached_datetime = fields.Datetime.now()
+ elif sla.target_type == 'assigning' and (
+ not sla.stage_id or ticket.stage_id == sla.stage_id) and ticket.user_id:
+ # in case of SLA of type assigning and ticket is already assigned
+ reached_datetime = fields.Datetime.now()
+ else:
+ reached_datetime = False
+ result.append({
+ 'ticket_id': ticket.id,
+ 'sla_id': sla.id,
+ 'reached_datetime': reached_datetime
+ })
+
+ return result
+
+ def _sla_assigning_reach(self):
+ """ Flag the SLA status of current ticket for the given stage_id as reached, and even the unreached SLA applied
+ on stage having a sequence lower than the given one.
+ """
+ self.env['odex25_helpdesk.sla.status'].search([
+ ('ticket_id', 'in', self.ids),
+ ('reached_datetime', '=', False),
+ ('deadline', '!=', False),
+ ('target_type', '=', 'assigning')
+ ]).write({'reached_datetime': fields.Datetime.now()})
+
+ def _sla_reach(self, stage_id):
+ """ Flag the SLA status of current ticket for the given stage_id as reached, and even the unreached SLA applied
+ on stage having a sequence lower than the given one.
+ """
+ stage = self.env['odex25_helpdesk.stage'].browse(stage_id)
+ stages = self.env['odex25_helpdesk.stage'].search([('sequence', '<=', stage.sequence), (
+ 'team_ids', 'in', self.mapped('team_id').ids)]) # take previous stages
+ self.env['odex25_helpdesk.sla.status'].search([
+ ('ticket_id', 'in', self.ids),
+ ('sla_stage_id', 'in', stages.ids),
+ ('reached_datetime', '=', False),
+ ('target_type', '=', 'stage')
+ ]).write({'reached_datetime': fields.Datetime.now()})
+
+ # For all SLA of type assigning, we compute deadline if they are not succeded (is succeded = has a reach_datetime)
+ # and if they are linked to a specific stage.
+ self.env['odex25_helpdesk.sla.status'].search([
+ ('ticket_id', 'in', self.ids),
+ ('sla_stage_id', '!=', False),
+ ('reached_datetime', '=', False),
+ ('target_type', '=', 'assigning')
+ ])._compute_deadline()
+
+ def assign_ticket_to_self(self):
+ self.ensure_one()
+ self.user_id = self.env.user
+
+ def open_customer_tickets(self):
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': _('Customer Tickets'),
+ 'res_model': 'odex25_helpdesk.ticket',
+ 'view_mode': 'kanban,tree,form,pivot,graph',
+ 'context': {'search_default_is_open': True, 'search_default_partner_id': self.partner_id.id}
+ }
+
+ def action_get_attachment_tree_view(self):
+ attachment_action = self.env.ref('base.action_attachment')
+ action = attachment_action.read()[0]
+ action['domain'] = str(['&', ('res_model', '=', self._name), ('res_id', 'in', self.ids)])
+ return action
+
+ # ------------------------------------------------------------
+ # Messaging API
+ # ------------------------------------------------------------
+
+ # DVE FIXME: if partner gets created when sending the message it should be set as partner_id of the ticket.
+ def _message_get_suggested_recipients(self):
+ recipients = super(odex25_helpdeskTicket, self)._message_get_suggested_recipients()
+ try:
+ for ticket in self:
+ if ticket.partner_id and ticket.partner_id.email:
+ ticket._message_add_suggested_recipient(recipients, partner=ticket.partner_id, reason=_('Customer'))
+ elif ticket.partner_email:
+ ticket._message_add_suggested_recipient(recipients, email=ticket.partner_email,
+ reason=_('Customer Email'))
+ except AccessError: # no read access rights -> just ignore suggested recipients because this implies modifying followers
+ pass
+ return recipients
+
+ def _ticket_email_split(self, msg):
+ email_list = tools.email_split((msg.get('to') or '') + ',' + (msg.get('cc') or ''))
+ # check left-part is not already an alias
+ return [
+ x for x in email_list
+ if x.split('@')[0] not in self.mapped('team_id.alias_name')
+ ]
+
+ @api.model
+ def message_new(self, msg, custom_values=None):
+ values = dict(custom_values or {}, partner_email=msg.get('from'), partner_id=msg.get('author_id'))
+ ticket = super(odex25_helpdeskTicket, self.with_context(mail_notify_author=True)).message_new(msg,
+ custom_values=values)
+ partner_ids = [x.id for x in
+ self.env['mail.thread']._mail_find_partner_from_emails(self._ticket_email_split(msg),
+ records=ticket) if x]
+ customer_ids = [p.id for p in self.env['mail.thread']._mail_find_partner_from_emails(
+ tools.email_split(values['partner_email']), records=ticket) if p]
+ partner_ids += customer_ids
+ if customer_ids and not values.get('partner_id'):
+ ticket.partner_id = customer_ids[0]
+ if partner_ids:
+ ticket.message_subscribe(partner_ids)
+ return ticket
+
+ def message_update(self, msg, update_vals=None):
+ partner_ids = [x.id for x in
+ self.env['mail.thread']._mail_find_partner_from_emails(self._ticket_email_split(msg),
+ records=self) if x]
+ if partner_ids:
+ self.message_subscribe(partner_ids)
+ return super(odex25_helpdeskTicket, self).message_update(msg, update_vals=update_vals)
+
+ def _message_post_after_hook(self, message, msg_vals):
+ if self.partner_email and self.partner_id and not self.partner_id.email:
+ self.partner_id.email = self.partner_email
+
+ if self.partner_email and not self.partner_id:
+ # we consider that posting a message with a specified recipient (not a follower, a specific one)
+ # on a document without customer means that it was created through the chatter using
+ # suggested recipients. This heuristic allows to avoid ugly hacks in JS.
+ new_partner = message.partner_ids.filtered(lambda partner: partner.email == self.partner_email)
+ if new_partner:
+ self.search([
+ ('partner_id', '=', False),
+ ('partner_email', '=', new_partner.email),
+ ('stage_id.fold', '=', False)]).write({'partner_id': new_partner.id})
+ return super(odex25_helpdeskTicket, self)._message_post_after_hook(message, msg_vals)
+
+ def _track_template(self, changes):
+ res = super(odex25_helpdeskTicket, self)._track_template(changes)
+ ticket = self[0]
+ if 'stage_id' in changes and ticket.stage_id.template_id:
+ res['stage_id'] = (ticket.stage_id.template_id, {
+ 'auto_delete_message': True,
+ 'subtype_id': self.env['ir.model.data'].xmlid_to_res_id('mail.mt_note'),
+ 'email_layout_xmlid': 'mail.mail_notification_light'
+ }
+ )
+ return res
+
+ def _creation_subtype(self):
+ return self.env.ref('odex25_helpdesk.mt_ticket_new')
+
+ def _track_subtype(self, init_values):
+ self.ensure_one()
+ if 'stage_id' in init_values:
+ return self.env.ref('odex25_helpdesk.mt_ticket_stage')
+ return super(odex25_helpdeskTicket, self)._track_subtype(init_values)
+
+ def _notify_get_groups(self, msg_vals=None):
+ """ Handle Helpdesk users and managers recipients that can assign
+ tickets directly from notification emails. Also give access button
+ to portal and portal customers. If they are notified they should
+ probably have access to the document. """
+ groups = super(odex25_helpdeskTicket, self)._notify_get_groups()
+ msg_vals = msg_vals or {}
+
+ self.ensure_one()
+ for group_name, group_method, group_data in groups:
+ if group_name != 'customer':
+ group_data['has_button_access'] = True
+
+ if self.user_id:
+ return groups
+
+ take_action = self._notify_get_action_link('assign', **msg_vals)
+ odex25_helpdesk_actions = [{'url': take_action, 'title': _('Assign to me')}]
+ odex25_helpdesk_user_group_id = self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id
+ new_groups = [(
+ 'group_odex25_helpdesk_user',
+ lambda pdata: pdata['type'] == 'user' and odex25_helpdesk_user_group_id in pdata['groups'],
+ {'actions': odex25_helpdesk_actions}
+ )]
+ return new_groups + groups
+
+ def _notify_get_reply_to(self, default=None, records=None, company=None, doc_names=None):
+ """ Override to set alias of tickets to their team if any. """
+ aliases = self.mapped('team_id').sudo()._notify_get_reply_to(default=default, records=None, company=company,
+ doc_names=None)
+ res = {ticket.id: aliases.get(ticket.team_id.id) for ticket in self}
+ leftover = self.filtered(lambda rec: not rec.team_id)
+ if leftover:
+ res.update(super(odex25_helpdeskTicket, leftover)._notify_get_reply_to(default=default, records=None,
+ company=company,
+ doc_names=doc_names))
+ return res
+
+ # ------------------------------------------------------------
+ # Rating Mixin
+ # ------------------------------------------------------------
+
+ def rating_apply(self, rate, token=None, feedback=None, subtype_xmlid=None):
+ return super(odex25_helpdeskTicket, self).rating_apply(rate, token=token, feedback=feedback,
+ subtype_xmlid="odex25_helpdesk.mt_ticket_rated")
+
+ def _rating_get_parent_field_name(self):
+ return 'team_id'
diff --git a/odex25_helpdesk/odex25_helpdesk/models/res_partner.py b/odex25_helpdesk/odex25_helpdesk/models/res_partner.py
new file mode 100644
index 000000000..da829178f
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/models/res_partner.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models
+
+
+class ResPartner(models.Model):
+ _inherit = 'res.partner'
+
+ ticket_count = fields.Integer("Tickets", compute='_compute_ticket_count')
+
+ def _compute_ticket_count(self):
+ # retrieve all children partners and prefetch 'parent_id' on them
+ all_partners = self.with_context(active_test=False).search([('id', 'child_of', self.ids)])
+ all_partners.read(['parent_id'])
+
+ # group tickets by partner, and account for each partner in self
+ groups = self.env['odex25_helpdesk.ticket'].read_group(
+ [('partner_id', 'in', all_partners.ids)],
+ fields=['partner_id'], groupby=['partner_id'],
+ )
+ self.ticket_count = 0
+ for group in groups:
+ partner = self.browse(group['partner_id'][0])
+ while partner:
+ if partner in self:
+ partner.ticket_count += group['partner_id_count']
+ partner = partner.parent_id
+
+ def action_open_odex25_helpdesk_ticket(self):
+ action = self.env["ir.actions.actions"]._for_xml_id("odex25_helpdesk.odex25_helpdesk_ticket_action_main_tree")
+ action['context'] = {}
+ action['domain'] = [('partner_id', 'child_of', self.ids)]
+ return action
diff --git a/odex25_helpdesk/odex25_helpdesk/models/res_setting.py b/odex25_helpdesk/odex25_helpdesk/models/res_setting.py
new file mode 100644
index 000000000..0e27c1138
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/models/res_setting.py
@@ -0,0 +1,40 @@
+from odoo import models, fields, api, _
+from odoo.exceptions import ValidationError, UserError
+
+class odex25_helpdeskSLA(models.Model):
+ _inherit = "odex25_helpdesk.sla"
+
+ category_id = fields.Many2one('service.category')
+ service_id = fields.Many2one('helpdesk.service')
+
+
+
+class ServiceCategory(models.Model):
+ _name = 'service.category'
+ _description = 'Service Category'
+
+ name = fields.Char('Service Category', required=True)
+
+ @api.constrains('name')
+ def unique_service_category_constrains(self):
+ if self.name:
+ parties_party = self.env['service.category'].search([('name', '=', self.name), ('id', '!=', self.id)])
+ if parties_party:
+ raise ValidationError(_('The Service Category Must Be Unique'))
+
+
+class HelpdeskService(models.Model):
+ _name = 'helpdesk.service'
+ _description = 'Helpdesk Service'
+
+ name = fields.Char('Service', required=True)
+ category_id = fields.Many2one('service.category')
+ priority = fields.Selection([('0', 'All'), ('1', 'Low priority'), ('2', 'High priority'), ('3', 'Urgent')],
+ string='Priority', default='0')
+
+ @api.constrains('name')
+ def unique_helpdesk_service_constrains(self):
+ if self.name:
+ parties_party = self.env['helpdesk.service'].search([('name', '=', self.name), ('id', '!=', self.id)])
+ if parties_party:
+ raise ValidationError(_('The Helpdesk Service Must Be Unique'))
diff --git a/odex25_helpdesk/odex25_helpdesk/models/res_users.py b/odex25_helpdesk/odex25_helpdesk/models/res_users.py
new file mode 100644
index 000000000..41e366df9
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/models/res_users.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+from odoo import fields, models, api
+
+
+class ResUsers(models.Model):
+ _inherit = 'res.users'
+
+ odex25_helpdesk_target_closed = fields.Float(string='Target Tickets to Close', default=1)
+ odex25_helpdesk_target_rating = fields.Float(string='Target Customer Rating', default=100)
+ odex25_helpdesk_target_success = fields.Float(string='Target Success Rate', default=100)
+
+ _sql_constraints = [
+ ('target_closed_not_zero', 'CHECK(odex25_helpdesk_target_closed > 0)', 'You cannot have negative targets'),
+ ('target_rating_not_zero', 'CHECK(odex25_helpdesk_target_rating > 0)', 'You cannot have negative targets'),
+ ('target_success_not_zero', 'CHECK(odex25_helpdesk_target_success > 0)', 'You cannot have negative targets'),
+ ]
+
+ def __init__(self, pool, cr):
+ """ Override of __init__ to add access rights.
+ Access rights are disabled by default, but allowed
+ on some specific fields defined in self.SELF_{READ/WRITE}ABLE_FIELDS.
+ """
+ init_res = super(ResUsers, self).__init__(pool, cr)
+ odex25_helpdesk_fields = [
+ 'odex25_helpdesk_target_closed',
+ 'odex25_helpdesk_target_rating',
+ 'odex25_helpdesk_target_success',
+ ]
+ # duplicate list to avoid modifying the original reference
+ type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
+ type(self).SELF_WRITEABLE_FIELDS.extend(odex25_helpdesk_fields)
+ # duplicate list to avoid modifying the original reference
+ type(self).SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS)
+ type(self).SELF_READABLE_FIELDS.extend(odex25_helpdesk_fields)
+ return init_res
+
+
+class GroupsView(models.Model):
+ _inherit = 'res.groups'
+
+ def get_application_groups(self, domain):
+ group_helpdesk_user = self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user', raise_if_not_found=False)
+ group_helpdesk_admin = self.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager', raise_if_not_found=False)
+ if group_helpdesk_admin and group_helpdesk_user:
+ return super().get_application_groups(
+ domain + [('id', 'not in', (group_helpdesk_user.id, group_helpdesk_admin.id))])
+ return super(GroupsView, self).get_application_groups(domain)
diff --git a/odex25_helpdesk/odex25_helpdesk/report/__init__.py b/odex25_helpdesk/odex25_helpdesk/report/__init__.py
new file mode 100644
index 000000000..32d83819d
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/report/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import odex25_helpdesk_sla_report_analysis
diff --git a/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis.py b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis.py
new file mode 100644
index 000000000..e0b30f9c4
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis.py
@@ -0,0 +1,76 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models, tools
+from odoo.addons.odex25_helpdesk.models.odex25_helpdesk_ticket import TICKET_PRIORITY
+
+
+class odex25_helpdeskSLAReport(models.Model):
+ _name = 'odex25_helpdesk.sla.report.analysis'
+ _description = "SLA Status Analysis"
+ _auto = False
+ _order = 'create_date DESC'
+
+ ticket_id = fields.Many2one('odex25_helpdesk.ticket', string='Ticket', readonly=True)
+ create_date = fields.Date("Ticket Create Date", readonly=True)
+ priority = fields.Selection(TICKET_PRIORITY, string='Minimum Priority', readonly=True)
+ user_id = fields.Many2one('res.users', string="Assigned To", readonly=True)
+ partner_id = fields.Many2one('res.partner', string="Customer", readonly=True)
+ ticket_type_id = fields.Many2one('odex25_helpdesk.ticket.type', string="Ticket Type", readonly=True)
+ ticket_stage_id = fields.Many2one('odex25_helpdesk.stage', string="Ticket Stage", readonly=True)
+ ticket_deadline = fields.Datetime("Ticket Deadline", readonly=True)
+ ticket_failed = fields.Boolean("Ticket Failed", group_operator="bool_or", readonly=True)
+ ticket_closed = fields.Boolean("Ticket Closed", readonly=True)
+ ticket_close_hours = fields.Integer("Time to close (hours)", group_operator="avg", readonly=True)
+ ticket_open_hours = fields.Integer("Open Time (hours)", group_operator="avg", readonly=True)
+ ticket_assignation_hours = fields.Integer("Time to first assignment (hours)", group_operator="avg", readonly=True)
+
+ sla_id = fields.Many2one('odex25_helpdesk.sla', string="SLA", readonly=True)
+ sla_stage_id = fields.Many2one('odex25_helpdesk.stage', string="SLA Stage", readonly=True)
+ sla_deadline = fields.Datetime("SLA Deadline", group_operator='min', readonly=True)
+ sla_reached_datetime = fields.Datetime("SLA Reached Date", group_operator='min', readonly=True)
+ sla_status = fields.Selection([('failed', 'Failed'), ('reached', 'Reached'), ('ongoing', 'Ongoing')], string="Status", readonly=True)
+ sla_status_failed = fields.Boolean("SLA Status Failed", group_operator='bool_or', readonly=True)
+ sla_exceeded_days = fields.Integer("Day to reach SLA", group_operator='avg', readonly=True, help="Day to reach the stage of the SLA, without taking the working calendar into account")
+
+ team_id = fields.Many2one('odex25_helpdesk.team', string='Team', readonly=True)
+ company_id = fields.Many2one('res.company', string='Company', readonly=True)
+
+ def init(self):
+ tools.drop_view_if_exists(self.env.cr, 'odex25_helpdesk_sla_report_analysis')
+ self.env.cr.execute("""
+ CREATE VIEW odex25_helpdesk_sla_report_analysis AS (
+ SELECT
+ ST.id as id,
+ T.create_date AS create_date,
+ T.id AS ticket_id,
+ T.team_id,
+ T.stage_id AS ticket_stage_id,
+ T.ticket_type_id,
+ T.user_id,
+ T.partner_id,
+ T.company_id,
+ T.priority AS priority,
+ T.sla_reached_late OR T.sla_deadline < NOW() AT TIME ZONE 'UTC' AS ticket_failed,
+ T.sla_deadline AS ticket_deadline,
+ T.close_hours AS ticket_close_hours,
+ EXTRACT(HOUR FROM (COALESCE(T.assign_date, NOW()) - T.create_date)) AS ticket_open_hours,
+ T.assign_hours AS ticket_assignation_hours,
+ STA.is_close AS ticket_closed,
+ ST.sla_id,
+ SLA.stage_id AS sla_stage_id,
+ ST.deadline AS sla_deadline,
+ ST.reached_datetime AS sla_reached_datetime,
+ ST.exceeded_days AS sla_exceeded_days,
+ CASE
+ WHEN ST.reached_datetime IS NOT NULL AND ST.reached_datetime < ST.deadline THEN 'reached'
+ WHEN ST.reached_datetime IS NOT NULL AND ST.reached_datetime >= ST.deadline THEN 'failed'
+ WHEN ST.reached_datetime IS NULL AND ST.deadline > NOW() THEN 'ongoing'
+ ELSE 'failed'
+ END AS sla_status,
+ ST.reached_datetime >= ST.deadline OR (ST.reached_datetime IS NULL AND ST.deadline < NOW() AT TIME ZONE 'UTC') AS sla_status_failed
+ FROM odex25_helpdesk_ticket T
+ LEFT JOIN odex25_helpdesk_stage STA ON (T.stage_id = STA.id)
+ LEFT JOIN odex25_helpdesk_sla_status ST ON (T.id = ST.ticket_id)
+ LEFT JOIN odex25_helpdesk_sla SLA ON (ST.sla_id = SLA.id)
+ )
+ """)
diff --git a/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis_views.xml b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis_views.xml
new file mode 100644
index 000000000..fc226ad56
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk/report/odex25_helpdesk_sla_report_analysis_views.xml
@@ -0,0 +1,100 @@
+
+
+
+
+ odex25_helpdesk.sla.report.analysis.pivot
+ odex25_helpdesk.sla.report.analysis
+
+
+
+
+
+
+
+
+
+
+
+ odex25_helpdesk.sla.report.analysis.graph
+ odex25_helpdesk.sla.report.analysis
+
+
+
+
+
+
+
+
+
+ odex25_helpdesk.sla.report.analysis.search
+ odex25_helpdesk.sla.report.analysis
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SLA Status Analysis
+ odex25_helpdesk.sla.report.analysis
+ pivot,graph
+
+ {'search_default_last_7days': 1}
+
+
+
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/__init__.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/__init__.py
new file mode 100644
index 000000000..dc5e6b693
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/__manifest__.py
new file mode 100644
index 000000000..fcb67ec65
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/__manifest__.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'EXP Helpdesk Assignation methods',
+ 'summary': 'Ticket Assignation methods for team members considering ticket types',
+ 'author': "Expert Co Ltd",
+ 'website': "http://www.ex.com",
+ 'category': 'Odex25-Helpdesk/Odex25-Helpdesk',
+ 'depends': ['odex25_helpdesk_security'],
+ 'description': """
+ Ticket Assignation methods for team members considering ticket types
+ """,
+ 'auto_install': True,
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/helpdesk_views.xml',
+ ],
+ 'license': '',
+}
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/i18n/ar_001.po b/odex25_helpdesk/odex25_helpdesk_assignation_method/i18n/ar_001.po
new file mode 100644
index 000000000..7a5bf325c
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/i18n/ar_001.po
@@ -0,0 +1,132 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * odex25_helpdesk_assignation_method
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 14.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2022-09-21 11:37+0000\n"
+"PO-Revision-Date: 2022-09-21 11:37+0000\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__create_date
+msgid "Created on"
+msgstr ""
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_ticket__display_name
+msgid "Display Name"
+msgstr "الاسم المعروض"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model,name:odex25_helpdesk_assignation_method.model_odex25_helpdesk_team
+msgid "Helpdesk Team"
+msgstr "فريق مكتب المساعدة"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model,name:odex25_helpdesk_assignation_method.model_odex25_helpdesk_ticket
+msgid "Helpdesk Ticket"
+msgstr "تذكرة مكتب المساعدة"
+
+#. module: odex25_helpdesk_assignation_method
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method
+msgid "How to assign newly created tickets to the right person"
+msgstr "كيفية تعيين التذاكر التي تم إنشاؤها حديثًا إلى الشخص المناسب"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__id
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__id
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_ticket__id
+msgid "ID"
+msgstr "المُعرف"
+
+#. module: odex25_helpdesk_assignation_method
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method
+msgid "Keep empty for everyone to see this team"
+msgstr "ابق فارغًا حتى يرى الجميع هذا الفريق"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_ticket____last_update
+msgid "Last Modified on"
+msgstr "آخر تعديل في"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__member_id
+msgid "Member"
+msgstr "العضو"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__members_ids
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method
+msgid "Members"
+msgstr "الأعضاء"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__team_id
+msgid "Team"
+msgstr "فريق"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__team_leader_id
+msgid "Team Leader"
+msgstr "قائد الفريق"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_odex25_helpdesk_team__member_ids
+msgid "Team Members"
+msgstr "أعضاء الفريق"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__ticket_type_ids
+msgid "Ticket Type"
+msgstr "نوع التذكرة"
+
+#. module: odex25_helpdesk_assignation_method
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_assignation_method.helpdesk_team_view_form_assignation_method
+msgid ""
+"With random assignation, every user gets the same number of tickets. With "
+"balanced assignation, tickets are assigned to the user with the least amount"
+" of open tickets."
+msgstr "مع التخصيص العشوائي ، يحصل كل مستخدم على نفس عدد التذاكر. مع التوزيع المتوازن ، يتم تخصيص التذاكر للمستخدم بأقل عدد من التذاكر المفتوحة"
+
+#. module: odex25_helpdesk_assignation_method
+#: code:addons/odex25_helpdesk_assignation_method/models/helpdesk_team.py:0
+#, python-format
+msgid "You must have team members assigned to change the assignation method."
+msgstr "يجب أن يكون لديك أعضاء فريق معينين لتغيير طريقة التعيين"
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model,name:odex25_helpdesk_assignation_method.model_helpdesk_team_member
+msgid "helpdesk.team.member"
+msgstr ""
+
+#. module: odex25_helpdesk_assignation_method
+#: model:ir.model.fields,field_description:odex25_helpdesk_assignation_method.field_helpdesk_team_member__service_id
+msgid "Service"
+msgstr "الخدمة"
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/__init__.py
new file mode 100644
index 000000000..283c2fd2f
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import helpdesk_team
\ No newline at end of file
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/models/helpdesk_team.py b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/helpdesk_team.py
new file mode 100644
index 000000000..cb9f6ea11
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/models/helpdesk_team.py
@@ -0,0 +1,109 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError, ValidationError
+
+
+class HelpdeskTicket(models.Model):
+ _inherit = "odex25_helpdesk.ticket"
+
+ team_leader_id = fields.Many2one(related='team_id.team_leader_id')
+
+ @api.onchange('service_id')
+ def get_user_and_assign_it(self):
+ for rec in self:
+ result = rec._onchange_ticket_type_values(rec.team_id,rec.service_id)
+ rec.user_id = result['user_id']
+
+ def _onchange_ticket_type_values(self, team, ticket_type=None):
+ return {
+ 'user_id': team.get_new_user(ticket_type),
+ }
+
+
+ @api.model
+ def create(self, vals):
+ res = super(HelpdeskTicket, self).create(vals)
+ result = self._onchange_ticket_type_values(res.team_id,res.service_id)
+ res.user_id = result['user_id']
+ return res
+
+class HelpdeskTeam(models.Model):
+ _inherit = "odex25_helpdesk.team"
+
+ team_leader_id = fields.Many2one('res.users',domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_manager').id)])
+ members_ids = fields.One2many('helpdesk.team.member','team_id')
+ member_ids = fields.Many2many('res.users', string='Team Members', domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)])
+
+ @api.onchange('members_ids')
+ def _onchange_members_ids(self):
+ """
+ update the member_ids based on members_ids
+ """
+ for team in self:
+ for rec in team.member_ids:
+ rec = False
+ members = [member.member_id.id for member in self.members_ids]
+ addmember = [(6,0,members)]
+ team.update({
+ 'member_ids': addmember
+ })
+
+ @api.constrains('assign_method', 'members_ids')
+ def _check_members_assignation(self):
+ if not self.members_ids and self.assign_method != 'manual':
+ raise ValidationError(_("You must have team members assigned to change the assignation method."))
+
+ def get_new_user(self, ticket_type=None):
+ for rec in self:
+ rec.ensure_one()
+ new_user = self.env['res.users']
+ members_ids = []
+ if ticket_type:
+ for member in rec.members_ids:
+ if ticket_type in member.service_id:
+ members_ids.append(member.member_id.id)
+ members_ids = sorted(members_ids)
+ elif len(members_ids) == 0:
+ members_ids = sorted([member.member_id.id for member in rec.members_ids])
+ if members_ids:
+ if rec.assign_method == 'randomly':
+ # randomly means new ticketss get uniformly distributed
+ previous_assigned_user = self.env['odex25_helpdesk.ticket'].search([('team_id', '=', rec.id)], order='create_date desc', limit=1).user_id
+ # handle the case where the previous_assigned_user has left the team (or there is none).
+ if previous_assigned_user and previous_assigned_user.id in members_ids:
+ previous_index = members_ids.index(previous_assigned_user.id)
+ new_user = new_user.browse(members_ids[(previous_index + 1) % len(members_ids)])
+ else:
+ new_user = new_user.browse(members_ids[0])
+ elif rec.assign_method == 'balanced':
+ read_group_res = self.env['odex25_helpdesk.ticket'].read_group([('stage_id.is_close', '=', False), ('user_id', 'in', members_ids)], ['user_id'], ['user_id'])
+ # add all the members in case a member has no more open tickets (and thus doesn't appear in the previous read_group)
+ count_dict = dict((m_id, 0) for m_id in members_ids)
+ count_dict.update((data['user_id'][0], data['user_id_count']) for data in read_group_res)
+ new_user = new_user.browse(min(count_dict, key=count_dict.get))
+ return new_user
+
+
+class HelpdeskTeamMemebers(models.Model):
+ _name = "helpdesk.team.member"
+
+ team_id = fields.Many2one('odex25_helpdesk.team')
+ member_id = fields.Many2one('res.users',domain=lambda self: [('groups_id', 'in', self.env.ref('odex25_helpdesk.group_odex25_helpdesk_user').id)])
+ # ticket_type_ids = fields.Many2many('odex25_helpdesk.ticket.type')
+ service_id = fields.Many2many('helpdesk.service')
+
+#
+#
+# class InheritUser(models.Model):
+# _inherit = 'res.users'
+#
+# @api.model
+# def name_search(self, name='', args=None, operator='ilike', limit=100):
+# if self._context.get('members', []):
+# member_id = self.env['odex25_helpdesk.team'].new('members_ids',self._context.get('members', []))
+# args.append(('id', 'not in',
+# [isinstance(d['member_id'], tuple) and d['member_id'][0] or d['member_id']
+# for d in member_id]))
+# return super(InheritUser, self).name_search(name, args=args, operator=operator, limit=limit)
\ No newline at end of file
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/security/ir.model.access.csv b/odex25_helpdesk/odex25_helpdesk_assignation_method/security/ir.model.access.csv
new file mode 100644
index 000000000..82181a5a2
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_helpdesk_team_member,helpdesk.team.member,odex25_helpdesk_assignation_method.model_helpdesk_team_member,,1,1,1,1
\ No newline at end of file
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/icon.png b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/icon.png
new file mode 100644
index 000000000..a37b5967b
Binary files /dev/null and b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/icon.png differ
diff --git a/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/index.html b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/index.html
new file mode 100644
index 000000000..4bd6cf0ca
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_assignation_method/static/description/index.html
@@ -0,0 +1,83 @@
+
+
+
+ ONE OF ODEX MODULES
+
+ ODEX system is over than 200+ modules developed by love of Expert Company, based on ODOO system
+
+ .to effectively suite's Saudi and Arabic market needs.It is the first Arabic open source ERP and all-in-one
+ solution
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__init__.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__init__.py
new file mode 100644
index 000000000..dc5e6b693
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import models
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__manifest__.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__manifest__.py
new file mode 100644
index 000000000..bc2047c66
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/__manifest__.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+{
+ 'name': 'EXP Helpdesk SLA Escalation Reminder',
+ 'summary': 'Reminder in SLA Policy to reminde the team leader of the task depending on configuration',
+ 'author': "Expert Co Ltd",
+ 'website': "http://www.ex.com",
+ 'category': 'Odex25-Helpdesk/Odex25-Helpdesk',
+ 'depends': ['odex25_helpdesk'],
+ 'description': """
+ Reminder in SLA Policy to reminde the team leader of the task depending on configuration
+ """,
+ 'auto_install': True,
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'data/reminder_cron.xml',
+ 'data/reminder_templates.xml',
+ 'views/helpdesk_sla_views.xml',
+ ],
+ 'license': '',
+}
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_cron.xml b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_cron.xml
new file mode 100644
index 000000000..b497c4475
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_cron.xml
@@ -0,0 +1,13 @@
+
+
+
+ Helpdesk: reminder and escalation SLAs
+
+ code
+ model.reminder_and_escalation()
+ 30
+ minutes
+ -1
+
+
+
\ No newline at end of file
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_templates.xml b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_templates.xml
new file mode 100644
index 000000000..420ada2b6
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/data/reminder_templates.xml
@@ -0,0 +1,63 @@
+
+
+
+ Ticket Reminder
+
+ Ticket Reminder
+
+
+
+
+
+
+
+
Reminder!
+
Dear ${ctx['mail_to'|safe]},
+ your ticket
+
+ % if object.access_token:
+ ${object.name|safe}#${object.id|safe}
+
+ % endif
+
+ is about to seek the deadline
+ in ${ctx['time_untill']|safe}. Please follow up.
+
+
+ Kind regards,
+ ${object.team_id.name or 'Helpdesk'} Team.
+
\n"
+" "
+msgstr ""
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields.selection,name:odex25_helpdesk_sla_escalation_reminder.selection__helpdesk_sla_policy__type__after
+msgid "After"
+msgstr "بعد"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields.selection,name:odex25_helpdesk_sla_escalation_reminder.selection__helpdesk_sla_policy__type__before
+msgid "Before"
+msgstr "قبل"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__create_date
+msgid "Created on"
+msgstr ""
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#, python-format
+msgid "Days, "
+msgstr "الأيام"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__display_name
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket__display_name
+msgid "Display Name"
+msgstr "الاسم المعروض"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__escalation_ids
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy
+msgid "Escalation"
+msgstr "التصعيد"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model,name:odex25_helpdesk_sla_escalation_reminder.model_odex25_helpdesk_sla
+msgid "Helpdesk SLA Policies"
+msgstr "سياسات اتفاق مستوى الخدمة لمكتب المساعدة"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model,name:odex25_helpdesk_sla_escalation_reminder.model_odex25_helpdesk_ticket
+msgid "Helpdesk Ticket"
+msgstr "تذكرة مكتب المساعدة"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.actions.server,name:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_reminder_ir_actions_server
+#: model:ir.cron,cron_name:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_reminder
+#: model:ir.cron,name:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_reminder
+msgid "Helpdesk: reminder and escalation SLAs"
+msgstr "مكتب المساعدة: تذكير وتصعيد اتفاقيات مستوى الخدمة"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#, python-format
+msgid "Hours Can't be more than working hours, Kindly! try again"
+msgstr "لا يمكن أن تكون الساعات أكثر من ساعات العمل ، يرجى! حاول مرة أخرى"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__id
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__id
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket__id
+msgid "ID"
+msgstr "المُعرف"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#, python-format
+msgid "Kindly, make sure that time is not less than or equal zero"
+msgstr "يرجى التأكد من أن الوقت ليس أقل من أو يساوي الصفر"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#, python-format
+msgid "Kindly, make sure that time is not less than zero"
+msgstr "يرجى التأكد من أن الوقت لا يقل عن الصفر"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla____last_update
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket____last_update
+msgid "Last Modified on"
+msgstr "آخر تعديل في"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#: code:addons/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py:0
+#, python-format
+msgid "Madam, Sir"
+msgstr "سيدتي ، سيدي"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_sla__reminder_ids
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy
+msgid "Reminder"
+msgstr "تذكير"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy
+msgid "Reminder After-Days"
+msgstr ""
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy
+msgid "Reminder After-Hours"
+msgstr "تذكير بعد الأيام"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy
+msgid "Reminder Before-Days"
+msgstr "تذكير قبل الأيام"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model_terms:ir.ui.view,arch_db:odex25_helpdesk_sla_escalation_reminder.helpdesk_sla_view_form_reminder_policy
+msgid "Reminder Before-Hours"
+msgstr "تذكير قبل ساعات"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__reminder_days
+msgid "Reminder Days"
+msgstr "أيام التذكير"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__reminder_hours
+msgid "Reminder Hours"
+msgstr "ساعات التذكير"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_odex25_helpdesk_ticket__reminders_ids
+msgid "Reminders"
+msgstr "تذكير"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__sla_escalation_id
+msgid "Sla Escalation"
+msgstr "تصعيد اتفاقية مستوى الخدمة (SLA)"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__sla_reminder_id
+msgid "Sla Reminder"
+msgstr "تذكير اتفاقية مستوى الخدمة (SLA)"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__team_id
+msgid "Team"
+msgstr "الفريق"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:mail.template,subject:odex25_helpdesk_sla_escalation_reminder.ticket_sla_escalation
+msgid "Ticket Escalation"
+msgstr "تصعيد التذاكر"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:mail.template,subject:odex25_helpdesk_sla_escalation_reminder.ticket_sla_reminder
+msgid "Ticket Reminder"
+msgstr "تذكير التذكرة"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__type
+msgid "Type"
+msgstr "النوع"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model.fields,field_description:odex25_helpdesk_sla_escalation_reminder.field_helpdesk_sla_policy__user_id
+msgid "User"
+msgstr "المستخدم"
+
+#. module: odex25_helpdesk_sla_escalation_reminder
+#: model:ir.model,name:odex25_helpdesk_sla_escalation_reminder.model_helpdesk_sla_policy
+msgid "helpdesk.sla.policy"
+msgstr ""
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/__init__.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/__init__.py
new file mode 100644
index 000000000..a73b21d8e
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import helpdesk_reminder_policy
\ No newline at end of file
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py
new file mode 100644
index 000000000..081250132
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/models/helpdesk_reminder_policy.py
@@ -0,0 +1,204 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import api, fields, models, _
+from odoo.exceptions import UserError, ValidationError
+import datetime
+import logging
+_logger = logging.getLogger(__name__)
+
+
+class HelpdeskTicket(models.Model):
+ _inherit = "odex25_helpdesk.ticket"
+
+ reminders_ids = fields.Many2many('helpdesk.sla.policy')
+ # escalations_ids = fields.Many2many('helpdesk.sla.policy')
+
+
+class HelpdeskSLA(models.Model):
+ _inherit = "odex25_helpdesk.sla"
+
+ reminder_ids = fields.One2many('helpdesk.sla.policy','sla_reminder_id')
+ escalation_ids = fields.One2many('helpdesk.sla.policy','sla_escalation_id')
+
+ @api.constrains('time_hours')
+ def prevent_hours_morethan_workinghours(self):
+ working_calendar = self.env.user.company_id.resource_calendar_id
+ for rec in self:
+ if working_calendar.is_full_day:
+ if rec.time_hours > working_calendar.working_hours:
+ raise ValidationError(_("Hours Can't be more than working hours, Kindly! try again"))
+ else:
+ if rec.time_hours > working_calendar.shift_one_working_hours or rec.time_hours > working_calendar.shift_two_working_hours:
+ raise ValidationError(_("Hours Can't be more than working hours, Kindly! try again"))
+
+ @api.model
+ def reminder_and_escalation(self):
+ """
+ send reminder and escalation according to sla policy
+ """
+ policies = self.env['odex25_helpdesk.sla'].search([])
+ for sla in policies:
+ if sla.reminder_ids:
+ for reminder in sla.reminder_ids:
+ tickets = self.env['odex25_helpdesk.ticket'].search([('sla_id','=',sla.id),('reminders_ids','not in',reminder.id),('deadline','!=',None)])
+ _logger.error('length of tickets %s',len(tickets))
+ for ticket in tickets:
+ difference = fields.Datetime.from_string(ticket.deadline) - datetime.datetime.now()
+ difference = str(difference).split(',')
+ hour, minute = divmod(reminder.reminder_hours, 1)
+ minute *= 60
+ result = '{}:{}'.format(int(hour), int(minute))
+ result = result.split(':')
+ if len(difference) == 2:
+ days = int(difference[0].split(' ')[0])
+ hours = int(difference[1].split(':')[0])
+ minutes = int(difference[1].split(':')[1])
+ else:
+ days = 0
+ hours = int(difference[0].split(':')[0])
+ minutes = int(difference[0].split(':')[1])
+ if days <= reminder.reminder_days:
+ if hours < int(result[0]):
+ if ticket.sla_id.stage_id.id != ticket.stage_id.id:
+ template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_reminder')
+ template.email_to = ticket.user_id.partner_id.email
+ times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes')
+ if ticket.user_id:
+ to = ticket.user_id.name
+ else:
+ to = _("Madam, Sir")
+ template.sudo().with_context({
+ 'time_untill':times,
+ 'mail_to': to,
+ 'lang':self.env.user.lang,
+ }).send_mail(ticket.id, force_send=True, raise_exception=False)
+ ticket.write({'reminders_ids': [(4, reminder.id, 0)]})
+ elif hours == int(result[0]):
+ if minutes <= int(result[1]):
+ if ticket.sla_id.stage_id.id != ticket.stage_id.id:
+ template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_reminder')
+ template.email_to = ticket.user_id.partner_id.email
+ times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes')
+ if ticket.user_id:
+ to = ticket.user_id.name
+ else:
+ to = _("Madam, Sir")
+ template.sudo().with_context({
+ 'time_untill':times,
+ 'mail_to': to,
+ 'lang':self.env.user.lang,
+ }).send_mail(ticket.id, force_send=True, raise_exception=False)
+ ticket.write({'reminders_ids': [(4, reminder.id, 0)]})
+ elif days <= -1:
+ if ticket.sla_id.stage_id.id != ticket.stage_id.id:
+ template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_reminder')
+ template.email_to = ticket.user_id.partner_id.email
+ times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes')
+ if ticket.user_id:
+ to = ticket.user_id.name
+ else:
+ to = _("Madam, Sir")
+ template.sudo().with_context({
+ 'time_untill':times,
+ 'mail_to': to,
+ 'lang':self.env.user.lang,
+ }).send_mail(ticket.id, force_send=True, raise_exception=False)
+ ticket.write({'reminders_ids': [(4, reminder.id, 0)]})
+ else:
+ continue
+ if sla.escalation_ids:
+ for escalation in sla.escalation_ids:
+ tickets = self.env['odex25_helpdesk.ticket'].search([('sla_id','=',sla.id),('reminders_ids','not in',escalation.id),('deadline','!=',None)])
+ _logger.error('length of tickets %s',len(tickets))
+ for ticket in tickets:
+ difference = datetime.datetime.now() - fields.Datetime.from_string(ticket.deadline)
+ difference = str(difference).split(',')
+ hour, minute = divmod(escalation.reminder_hours, 1)
+ minute *= 60
+ result = '{}:{}'.format(int(hour), int(minute))
+ result = result.split(':')
+ if len(difference) == 2:
+ days = int(difference[0].split(' ')[0])
+ hours = int(difference[1].split(':')[0])
+ minutes = int(difference[1].split(':')[1])
+ else:
+ days = 0
+ hours = int(difference[0].split(':')[0])
+ minutes = int(difference[0].split(':')[1])
+ if days >= escalation.reminder_days:
+ if hours > int(result[0]):
+ if ticket.sla_id.stage_id.id != ticket.stage_id.id:
+ template = self.env.ref('odex25_helpdesk_sla_escalation_reminder.ticket_sla_escalation')
+ template.email_to = escalation.user_id.partner_id.email
+ times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes')
+ if escalation.user_id:
+ to = escalation.user_id.name
+ else:
+ to = _("Madam, Sir")
+ template.sudo().with_context({
+ 'time_untill':times,
+ 'mail_to': to,
+ 'lang':self.env.user.lang,
+ }).send_mail(ticket.id, force_send=True, raise_exception=False)
+ ticket.write({'reminders_ids': [(4, escalation.id, 0)]})
+ elif hours == int(result[0]):
+ if minutes >= int(result[1]):
+ if ticket.sla_id.stage_id.id != ticket.stage_id.id:
+ template = self.env.ref('helpdesk_sla_escalation_reminder.ticket_sla_escalation')
+ template.email_to = escalation.user_id.partner_id.email
+ times = str(days) + _('Days, ') + str(hours) + _(' Hours and ') + str(minutes) + _(' Minutes')
+ if escalation.user_id:
+ to = escalation.user_id.name
+ else:
+ to = _("Madam, Sir")
+ template.sudo().with_context({
+ 'time_untill':times,
+ 'mail_to': to,
+ 'lang':self.env.user.lang,
+ }).send_mail(ticket.id, force_send=True, raise_exception=False)
+ ticket.write({'reminders_ids': [(4, escalation.id, 0)]})
+ else:
+ continue
+
+
+class HelpdeskSLAReminderPolicy(models.Model):
+ _name = 'helpdesk.sla.policy'
+
+ sla_reminder_id = fields.Many2one('odex25_helpdesk.sla')
+ sla_escalation_id = fields.Many2one('odex25_helpdesk.sla')
+ team_id = fields.Many2one(related="sla_reminder_id.team_id",store=True)
+
+ type = fields.Selection([
+ ('after','After'),
+ ('before','Before'),
+ ])
+ reminder_hours = fields.Float()
+ reminder_days = fields.Integer()
+ user_id = fields.Many2one('res.users')
+
+ @api.constrains('reminder_hours','reminder_days')
+ def _prevent_zero(self):
+ """
+ Prevent the time to be zero if the days are zero
+ """
+ if self.reminder_days < 0.0 or self.reminder_hours < 0.0:
+ raise ValidationError(_("Kindly, make sure that time is not less than zero"))
+ if self.reminder_days == 0.0 and self.reminder_hours <= 0.0:
+ raise ValidationError(_("Kindly, make sure that time is not less than or equal zero"))
+
+ @api.onchange('team_id')
+ def _domain_user_to_sla(self):
+ """
+ return domain in users
+ """
+ users = []
+ if self.sla_reminder_id:
+ users = self.sla_reminder_id.team_id.members_ids.mapped('member_id').ids
+ users.append(self.sla_reminder_id.team_id.team_leader_id.id)
+ if self.sla_escalation_id:
+ users = self.sla_escalation_id.team_id.members_ids.mapped('member_id').ids
+ users.append(self.sla_escalation_id.team_id.team_leader_id.id)
+ return{
+ 'domain':{'user_id':[('id','in',users)]}
+ }
\ No newline at end of file
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/security/ir.model.access.csv b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/security/ir.model.access.csv
new file mode 100644
index 000000000..eb5c019fd
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_helpdesk_sla_policy,odex25_helpdesk_sla_escalation_reminder.sla.policy,odex25_helpdesk_sla_escalation_reminder.model_helpdesk_sla_policy,,1,1,1,1
\ No newline at end of file
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/icon.png b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/icon.png
new file mode 100644
index 000000000..a37b5967b
Binary files /dev/null and b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/icon.png differ
diff --git a/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/index.html b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/index.html
new file mode 100644
index 000000000..4bd6cf0ca
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_sla_escalation_reminder/static/description/index.html
@@ -0,0 +1,83 @@
+
+
+
+ ONE OF ODEX MODULES
+
+ ODEX system is over than 200+ modules developed by love of Expert Company, based on ODOO system
+
+ .to effectively suite's Saudi and Arabic market needs.It is the first Arabic open source ERP and all-in-one
+ solution
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/__init__.py b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/__init__.py
new file mode 100644
index 000000000..2ba46a4a2
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import odex25_helpdesk_ticket_create_timesheet
diff --git a/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet.py b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet.py
new file mode 100644
index 000000000..aff87ba79
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+from odoo import api, fields, models
+
+
+class odex25_helpdeskTicketCreateTimesheet(models.TransientModel):
+ _name = 'odex25_helpdesk.ticket.create.timesheet'
+ _description = "Create Timesheet from ticket"
+
+ _sql_constraints = [('time_positive', 'CHECK(time_spent > 0)', "The timesheet's time must be positive" )]
+
+ time_spent = fields.Float('Time', digits=(16, 2))
+ description = fields.Char('Description')
+ ticket_id = fields.Many2one(
+ 'odex25_helpdesk.ticket', "Ticket", required=True,
+ default=lambda self: self.env.context.get('active_id', None),
+ help="Ticket for which we are creating a sales order",
+ )
+
+ def action_generate_timesheet(self):
+ values = {
+ 'task_id': self.ticket_id.task_id.id,
+ 'project_id': self.ticket_id.project_id.id,
+ 'date': fields.Datetime.now(),
+ 'name': self.description,
+ 'user_id': self.env.uid,
+ 'unit_amount': self.time_spent,
+ }
+
+ timesheet = self.env['account.analytic.line'].create(values)
+
+ self.ticket_id.write({
+ 'timer_start': False,
+ 'timer_pause': False
+ })
+ self.ticket_id.timesheet_ids = [(4, timesheet.id, None)]
+ self.ticket_id.user_timer_id.unlink()
+ return timesheet
diff --git a/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet_views.xml b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet_views.xml
new file mode 100644
index 000000000..c4738a0a7
--- /dev/null
+++ b/odex25_helpdesk/odex25_helpdesk_timesheet/wizard/odex25_helpdesk_ticket_create_timesheet_views.xml
@@ -0,0 +1,22 @@
+
+
+
+
+ odex25_helpdesk.ticket.create.timesheet.wizard.form
+ odex25_helpdesk.ticket.create.timesheet
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_hr_gantt/__init__.py b/odex25_helpdesk/odex25_hr_gantt/__init__.py
new file mode 100644
index 000000000..40a96afc6
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/odex25_helpdesk/odex25_hr_gantt/__manifest__.py b/odex25_helpdesk/odex25_hr_gantt/__manifest__.py
new file mode 100644
index 000000000..d91bde236
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/__manifest__.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+{
+ 'name': 'Employees in Gantt',
+ 'category': 'Hidden',
+ 'summary': 'Employees in Gantt',
+ 'version': '1.0',
+ 'description': """ """,
+ 'depends': ['hr', 'odex25_web_gantt'],
+ 'data': ['views/assets.xml'],
+ 'qweb': ['static/src/xml/odex25_hr_gantt.xml'],
+ 'auto_install': True,
+}
diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_renderer.js b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_renderer.js
new file mode 100644
index 000000000..179f41b8a
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_renderer.js
@@ -0,0 +1,14 @@
+odoo.define('odex25_hr_gantt.GanttRenderer', function (require) {
+ 'use strict';
+
+ const GanttRenderer = require('odex25_web_gantt.GanttRenderer');
+ const HrGanttRow = require('odex25_hr_gantt.GanttRow');
+
+ const HrGanttRenderer = GanttRenderer.extend({
+ config: Object.assign({}, GanttRenderer.prototype.config, {
+ GanttRow: HrGanttRow
+ }),
+ });
+
+ return HrGanttRenderer;
+});
diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_row.js b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_row.js
new file mode 100644
index 000000000..155acecf8
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_row.js
@@ -0,0 +1,59 @@
+odoo.define('odex25_hr_gantt.GanttRow', function (require) {
+ 'use strict';
+
+ const GanttRow = require('odex25_web_gantt.GanttRow');
+ const StandaloneM2OAvatarEmployee = require('hr.StandaloneM2OAvatarEmployee');
+
+ const HrGanttRow = GanttRow.extend({
+ template: 'HrGanttView.Row',
+
+ /**
+ * @override
+ */
+ init(parent, pillsInfo, viewInfo, options) {
+ this._super(...arguments);
+ const isGroupedByEmployee = pillsInfo.groupedByField === 'employee_id';
+ const isEmptyGroup = pillsInfo.groupId === 'empty';
+ this.showEmployeeAvatar = (isGroupedByEmployee && !isEmptyGroup && !!pillsInfo.resId);
+ },
+
+ /**
+ * @override
+ */
+ willStart() {
+ const defs = [this._super(...arguments)];
+ if (this.showEmployeeAvatar) {
+ defs.push(this._preloadAvatarWidget());
+ }
+ return Promise.all(defs);
+ },
+
+ /**
+ * @override
+ */
+ start() {
+ if (this.showEmployeeAvatar) {
+ this.avatarWidget.$el.appendTo(this.$('.o_gantt_row_employee_avatar'));
+ }
+ return this._super(...arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Initialize the avatar widget in virtual DOM.
+ *
+ * @private
+ * @returns {Promise}
+ */
+ async _preloadAvatarWidget() {
+ const employee = [this.resId, this.name];
+ this.avatarWidget = new StandaloneM2OAvatarEmployee(this, employee);
+ return this.avatarWidget.appendTo(document.createDocumentFragment());
+ },
+ });
+
+ return HrGanttRow;
+});
diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_view.js b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_view.js
new file mode 100644
index 000000000..a825ef4c7
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/static/src/js/odex25_hr_gantt_view.js
@@ -0,0 +1,16 @@
+odoo.define('odex25_hr_gantt.GanttView', function (require) {
+ 'use strict';
+
+ const viewRegistry = require('web.view_registry');
+ const GanttView = require('odex25_web_gantt.GanttView');
+ const HrGanttRenderer = require('odex25_hr_gantt.GanttRenderer');
+
+ const HrGanttView = GanttView.extend({
+ config: Object.assign({}, GanttView.prototype.config, {
+ Renderer: HrGanttRenderer,
+ }),
+ });
+
+ viewRegistry.add('odex25_hr_gantt', HrGanttView);
+ return HrGanttView;
+});
diff --git a/odex25_helpdesk/odex25_hr_gantt/static/src/xml/odex25_hr_gantt.xml b/odex25_helpdesk/odex25_hr_gantt/static/src/xml/odex25_hr_gantt.xml
new file mode 100644
index 000000000..6ab662c02
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/static/src/xml/odex25_hr_gantt.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_hr_gantt/static/tests/odex25_hr_gantt_tests.js b/odex25_helpdesk/odex25_hr_gantt/static/tests/odex25_hr_gantt_tests.js
new file mode 100644
index 000000000..034f05694
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/static/tests/odex25_hr_gantt_tests.js
@@ -0,0 +1,123 @@
+odoo.define('odex25_hr_gantt.Tests', function (require) {
+ 'use strict';
+
+ const { createView } = require('web.test_utils');
+ const HrGanttView = require('odex25_hr_gantt.GanttView');
+
+ let initialDate = new Date(2018, 11, 20, 8, 0, 0);
+ initialDate = new Date(initialDate.getTime() - initialDate.getTimezoneOffset() * 60 * 1000);
+
+ QUnit.module('odex25_hr_gantt', {}, function () {
+ QUnit.module('GanttView', {
+ beforeEach: function () {
+ this.data = {
+ tasks: {
+ fields: {
+ name: {string: 'Name', type: 'char'},
+ start: {string: 'Start Date', type: 'datetime'},
+ stop: {string: 'Stop Date', type: 'datetime'},
+ employee_id: {string: "Employee", type: 'many2one', relation: 'hr.employee'},
+ foo: {string: "Foo", type: 'char'},
+ },
+ records: [
+ {id: 1, name: 'Task 1', start: '2018-11-30 18:30:00', stop: '2018-12-31 18:29:59', employee_id: 11, foo: 'Foo 1'},
+ {id: 2, name: 'Task 2', start: '2018-12-17 11:30:00', stop: '2018-12-22 06:29:59', employee_id: 7, foo: 'Foo 2'},
+ {id: 3, name: 'Task 3', start: '2018-12-27 06:30:00', stop: '2019-01-03 06:29:59', employee_id: 23, foo: 'Foo 1'},
+ {id: 4, name: 'Task 4', start: '2018-12-19 18:30:00', stop: '2018-12-20 06:29:59', employee_id: 11, foo: 'Foo 3'}
+ ],
+ },
+ 'hr.employee': {
+ fields: {},
+ records: [
+ {id: 11, name: "Mario"},
+ {id: 7, name: "Luigi"},
+ {id: 23, name: "Yoshi"},
+ ],
+ },
+ };
+ },
+ });
+
+ QUnit.test('hr gantt view not grouped', async function (assert) {
+ assert.expect(1);
+
+ const gantt = await createView({
+ View: HrGanttView,
+ model: 'tasks',
+ data: this.data,
+ arch: '',
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ });
+
+ assert.containsNone(gantt, '.o_standalone_avatar_employee',
+ 'should have 0 employee avatars');
+
+ gantt.destroy();
+ });
+
+ QUnit.test('hr gantt view grouped by employee only', async function (assert) {
+ assert.expect(1);
+
+ const gantt = await createView({
+ View: HrGanttView,
+ model: 'tasks',
+ data: this.data,
+ arch: '',
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ groupBy: ['employee_id'],
+ });
+
+ assert.containsN(gantt,
+ '.o_gantt_row .o_gantt_row_sidebar .o_gantt_row_title .o_gantt_row_employee_avatar .o_standalone_avatar_employee',
+ 3, 'should have 3 employee avatars');
+
+ gantt.destroy();
+ });
+
+ QUnit.test('hr gantt view grouped by employee > foo', async function (assert) {
+ assert.expect(1);
+
+ const gantt = await createView({
+ View: HrGanttView,
+ model: 'tasks',
+ data: this.data,
+ arch: '',
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ groupBy: ['employee_id', 'foo'],
+ });
+
+ assert.containsN(gantt,
+ '.o_gantt_row_group .o_gantt_row_sidebar .o_gantt_row_title .o_gantt_row_employee_avatar .o_standalone_avatar_employee',
+ 3, 'should have 3 employee avatars');
+
+ gantt.destroy();
+ });
+
+ QUnit.test('hr gantt view grouped by foo > employee', async function (assert) {
+ assert.expect(1);
+
+ const gantt = await createView({
+ View: HrGanttView,
+ model: 'tasks',
+ data: this.data,
+ arch: '',
+ viewOptions: {
+ initialDate: initialDate,
+ },
+ groupBy: ['foo', 'employee_id'],
+ });
+
+ assert.containsN(gantt,
+ '.o_gantt_row_nogroup .o_gantt_row_sidebar .o_gantt_row_title .o_gantt_row_employee_avatar .o_standalone_avatar_employee',
+ 4, 'should have 4 employee avatars');
+
+ gantt.destroy();
+ });
+ });
+});
diff --git a/odex25_helpdesk/odex25_hr_gantt/views/assets.xml b/odex25_helpdesk/odex25_hr_gantt/views/assets.xml
new file mode 100644
index 000000000..dfee8558c
--- /dev/null
+++ b/odex25_helpdesk/odex25_hr_gantt/views/assets.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_timer/__init__.py b/odex25_helpdesk/odex25_timer/__init__.py
new file mode 100644
index 000000000..cde864bae
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import models
diff --git a/odex25_helpdesk/odex25_timer/__manifest__.py b/odex25_helpdesk/odex25_timer/__manifest__.py
new file mode 100644
index 000000000..b1a0072bf
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/__manifest__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+
+{
+ 'name': 'Timer',
+ 'version': '1.0',
+ 'sequence': 24,
+ 'summary': 'Record time',
+ 'category': 'Odex25 Services/Timesheets',
+ 'author': "Expert Co. Ltd.",
+ 'website': "http://www.exp-sa.com",
+ 'description': """
+This module implements a timer.
+==========================================
+
+It adds a timer to a view for time recording purpose
+ """,
+ 'depends': ['web', 'mail'],
+ 'data': [
+ 'security/timer_security.xml',
+ 'security/ir.model.access.csv',
+ 'views/assets.xml',
+ ],
+ 'demo': [],
+ 'installable': True,
+ 'application': False,
+ 'auto_install': False,
+}
diff --git a/odex25_helpdesk/odex25_timer/i18n/ar.po b/odex25_helpdesk/odex25_timer/i18n/ar.po
new file mode 100644
index 000000000..36914121e
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/i18n/ar.po
@@ -0,0 +1,158 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * timer
+#
+# Translators:
+# Sherif Abd Ekmoniem , 2020
+# Mustafa Rawi , 2020
+# Akram Alfusayal , 2020
+# amrnegm , 2020
+# Martin Trigaux, 2020
+# Osama Ahmaro , 2020
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server saas~13.5+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-01 07:39+0000\n"
+"PO-Revision-Date: 2020-09-07 08:24+0000\n"
+"Last-Translator: Osama Ahmaro , 2020\n"
+"Language-Team: Arabic (https://www.transifex.com/odoo/teams/41243/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__create_uid
+msgid "Created by"
+msgstr "أنشئ بواسطة"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__create_date
+msgid "Created on"
+msgstr "أنشئ في"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__display_name
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__display_name
+msgid "Display Name"
+msgstr "الاسم المعروض"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__display_timer_pause
+msgid "Display Timer Pause"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__display_timer_resume
+msgid "Display Timer Resume"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__display_timer_start_primary
+msgid "Display Timer Start Primary"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__display_timer_stop
+msgid "Display Timer Stop"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__id
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__id
+msgid "ID"
+msgstr "المُعرف"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__is_timer_running
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__is_timer_running
+msgid "Is Timer Running"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin____last_update
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer____last_update
+msgid "Last Modified on"
+msgstr "آخر تعديل في"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__write_uid
+msgid "Last Updated by"
+msgstr "آخر تحديث بواسطة"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__write_date
+msgid "Last Updated on"
+msgstr "آخر تحديث في"
+
+#. module: odex25_timer
+#: model:ir.model.constraint,message:odex25_timer.constraint_timer_timer_unique_timer
+msgid "Only one timer occurrence by model, record and user"
+msgstr ""
+
+#. module: odex25_timer
+#. openerp-web
+#: code:addons/odex25_timer/static/src/js/timer_toggle_button.js:0
+#, python-format
+msgid "Play"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__res_id
+msgid "Res"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__res_model
+msgid "Res Model"
+msgstr "كائن Res"
+
+#. module: odex25_timer
+#. openerp-web
+#: code:addons/odex25_timer/static/src/js/timer_toggle_button.js:0
+#, python-format
+msgid "Start"
+msgstr "بدء"
+
+#. module: odex25_timer
+#. openerp-web
+#: code:addons/odex25_timer/static/src/js/timer_toggle_button.js:0
+#: code:addons/odex25_timer/static/src/js/timer_toggle_button.js:0
+#, python-format
+msgid "Stop"
+msgstr "إيقاف"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__timer_pause
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__timer_pause
+msgid "Timer Last Pause"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model,name:odex25_timer.model_timer_mixin
+msgid "Timer Mixin"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model,name:odex25_timer.model_timer_timer
+msgid "Timer Module"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__timer_start
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__timer_start
+msgid "Timer Start"
+msgstr ""
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_timer__user_id
+msgid "User"
+msgstr "المستخدم"
+
+#. module: odex25_timer
+#: model:ir.model.fields,field_description:odex25_timer.field_timer_mixin__user_timer_id
+msgid "User Timer"
+msgstr ""
diff --git a/odex25_helpdesk/odex25_timer/models/__init__.py b/odex25_helpdesk/odex25_timer/models/__init__.py
new file mode 100644
index 000000000..d704acefd
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/models/__init__.py
@@ -0,0 +1,4 @@
+# -*- coding: utf-8 -*-
+
+from . import timer
+from . import timer_mixin
diff --git a/odex25_helpdesk/odex25_timer/models/timer.py b/odex25_helpdesk/odex25_timer/models/timer.py
new file mode 100644
index 000000000..fd6de7bd2
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/models/timer.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+from odoo.exceptions import ValidationError
+
+class TimerTimer(models.Model):
+ _name = 'timer.timer'
+ _description = 'Timer Module'
+
+ timer_start = fields.Datetime("Timer Start")
+ timer_pause = fields.Datetime("Timer Last Pause")
+ is_timer_running = fields.Boolean(compute="_compute_is_timer_running")
+ res_model = fields.Char(required=True)
+ res_id = fields.Integer(required=True)
+ user_id = fields.Many2one('res.users')
+
+ _sql_constraints = [(
+ 'unique_timer', 'UNIQUE(res_model, res_id, user_id)',
+ 'Only one timer occurrence by model, record and user')]
+
+ @api.depends('timer_start', 'timer_pause')
+ def _compute_is_timer_running(self):
+ for record in self:
+ record.is_timer_running = record.timer_start and not record.timer_pause
+
+ @api.model
+ def create(self, vals):
+ # Reset the user_timer_id to force the recomputation
+ self.env[vals['res_model']].invalidate_cache(fnames=['user_timer_id'], ids=[vals['res_id']])
+ return super().create(vals)
+
+ def action_timer_start(self):
+ if not self.timer_start:
+ self.write({'timer_start': fields.Datetime.now()})
+
+ def action_timer_stop(self):
+ """ Stop the timer and return the spent minutes since it started
+ :return minutes_spent if the timer is started,
+ otherwise return False
+ """
+ if not self.timer_start:
+ return False
+ minutes_spent = self._get_minutes_spent()
+ self.write({'timer_start': False, 'timer_pause': False})
+ return minutes_spent
+
+ def _get_minutes_spent(self):
+ start_time = self.timer_start
+ stop_time = fields.Datetime.now()
+ # timer was either running or paused
+ if self.timer_pause:
+ start_time += (stop_time - self.timer_pause)
+ return (stop_time - start_time).total_seconds() / 60
+
+ def action_timer_pause(self):
+ self.write({'timer_pause': fields.Datetime.now()})
+
+ def action_timer_resume(self):
+ new_start = self.timer_start + (fields.Datetime.now() - self.timer_pause)
+ self.write({'timer_start': new_start, 'timer_pause': False})
+
+ @api.model
+ def get_server_time(self):
+ """ Returns the server time.
+ The timer widget needs the server time instead of the client time
+ to avoid time desynchronization issues like the timer beginning at 0:00
+ and not 23:59 and so on.
+ """
+ return fields.Datetime.now()
diff --git a/odex25_helpdesk/odex25_timer/models/timer_mixin.py b/odex25_helpdesk/odex25_timer/models/timer_mixin.py
new file mode 100644
index 000000000..85695db34
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/models/timer_mixin.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+
+from odoo import models, fields, api
+from math import ceil
+
+class TimerMixin(models.AbstractModel):
+ _name = 'timer.mixin'
+ _description = 'Timer Mixin'
+
+ timer_start = fields.Datetime(related='user_timer_id.timer_start')
+ timer_pause = fields.Datetime(related='user_timer_id.timer_pause')
+ is_timer_running = fields.Boolean(related='user_timer_id.is_timer_running')
+ user_timer_id = fields.One2many('timer.timer', compute='_compute_user_timer_id', search='_search_user_timer_id')
+
+ display_timer_start_primary = fields.Boolean(compute='_compute_display_timer_buttons')
+ display_timer_stop = fields.Boolean(compute='_compute_display_timer_buttons')
+ display_timer_pause = fields.Boolean(compute='_compute_display_timer_buttons')
+ display_timer_resume = fields.Boolean(compute='_compute_display_timer_buttons')
+
+ def _search_user_timer_id(self, operator, value):
+ timers = self.env['timer.timer'].search([
+ ('id', operator, value),
+ ('user_id', '=', self.env.user.id),
+ ])
+ return [('id', 'in', timers.mapped('res_id'))]
+
+ @api.depends_context('uid')
+ def _compute_user_timer_id(self):
+ """ Get the timers according these conditions
+ :user_id is is the current user
+ :res_id is the current record
+ :res_model is the current model
+ limit=1 by security but the search should never have more than one record
+ """
+ for record in self:
+ record.user_timer_id = self.env['timer.timer'].search([
+ ('user_id', '=', record.env.user.id),
+ ('res_id', '=', record.id),
+ ('res_model', '=', record._name)
+ ], limit=1)
+
+ @api.model
+ def _get_user_timers(self):
+ # Return user's timers. Can have multiple timers if some are in pause
+ return self.env['timer.timer'].search([('user_id', '=', self.env.user.id)])
+
+ def action_timer_start(self):
+ """ Start the timer of the current record
+ First, if a timer is running, stop or pause it
+ If there isn't a timer for the current record, create one then start it
+ Otherwise, resume or start it
+ """
+ self.ensure_one()
+ self._stop_timer_in_progress()
+ timer = self.user_timer_id
+ if not timer:
+ timer = self.env['timer.timer'].create({
+ 'timer_start': False,
+ 'timer_pause': False,
+ 'is_timer_running': False,
+ 'res_model': self._name,
+ 'res_id': self.id,
+ 'user_id': self.env.user.id,
+ })
+ timer.action_timer_start()
+ else:
+ # Check if it is in pause then resume it or start it
+ if timer.timer_pause:
+ timer.action_timer_resume()
+ else:
+ timer.action_timer_start()
+
+ def action_timer_stop(self):
+ """ Stop the timer of the current record
+ Unlink the timer, it's useless to keep the stopped timer.
+ A new timer can be create if needed
+ Return the amount of minutes spent
+ """
+ self.ensure_one()
+ timer = self.user_timer_id
+ minutes_spent = timer.action_timer_stop()
+ timer.unlink()
+ return minutes_spent
+
+ def action_timer_pause(self):
+ self.ensure_one()
+ timer = self.user_timer_id
+ timer.action_timer_pause()
+
+ def action_timer_resume(self):
+ self.ensure_one()
+ self._stop_timer_in_progress()
+ timer = self.user_timer_id
+ timer.action_timer_resume()
+
+ def _action_interrupt_user_timers(self):
+ # Interruption is the action called when the timer is stoped by the start of another one
+ self.action_timer_pause()
+
+ def _stop_timer_in_progress(self):
+ """
+ Cancel the timer in progress if there is one
+ Each model can interrupt the running timer in a specific way
+ By setting it in pause or stop by example
+ """
+ timer = self._get_user_timers().filtered(lambda t: t.is_timer_running)
+ if timer:
+ model = self.env[timer.res_model].browse(timer.res_id)
+ model._action_interrupt_user_timers()
+
+ @api.depends('timer_start', 'timer_pause')
+ def _compute_display_timer_buttons(self):
+ for record in self:
+ start_p, stop, pause, resume = True, True, True, True
+ if record.timer_start:
+ start_p = False
+ if record.timer_pause:
+ pause = False
+ else:
+ resume = False
+ record.update({
+ 'display_timer_start_primary': start_p,
+ 'display_timer_stop': stop,
+ 'display_timer_pause': pause,
+ 'display_timer_resume': resume,
+ })
+
+ @api.model
+ def _timer_rounding(self, minutes_spent, minimum, rounding):
+ minutes_spent = max(minimum, minutes_spent)
+ if rounding and ceil(minutes_spent % rounding) != 0:
+ minutes_spent = ceil(minutes_spent / rounding) * rounding
+ return minutes_spent
diff --git a/odex25_helpdesk/odex25_timer/security/ir.model.access.csv b/odex25_helpdesk/odex25_timer/security/ir.model.access.csv
new file mode 100644
index 000000000..becd37aa2
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_timer_user,odex25_timer.odex25_timer.user,odex25_timer.model_timer_timer,base.group_user,1,1,1,1
diff --git a/odex25_helpdesk/odex25_timer/security/timer_security.xml b/odex25_helpdesk/odex25_timer/security/timer_security.xml
new file mode 100644
index 000000000..a81cff853
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/security/timer_security.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ timer.user.rule.create-write-delete
+
+ [
+ ('user_id', '=', user.id)
+ ]
+
+
+
+
+
+
+
+
+ timer.not.user.rule.create-write-delete
+
+ [
+ ('user_id', '!=', user.id)
+ ]
+
+
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_timer/static/description/icon.png b/odex25_helpdesk/odex25_timer/static/description/icon.png
new file mode 100644
index 000000000..4141f52da
Binary files /dev/null and b/odex25_helpdesk/odex25_timer/static/description/icon.png differ
diff --git a/odex25_helpdesk/odex25_timer/static/src/js/timer.js b/odex25_helpdesk/odex25_timer/static/src/js/timer.js
new file mode 100644
index 000000000..0042ebfae
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/static/src/js/timer.js
@@ -0,0 +1,63 @@
+odoo.define('timer.timer', function (require) {
+"use strict";
+
+var fieldRegistry = require('web.field_registry');
+var AbstractField = require('web.AbstractField');
+var Timer = require('timer.Timer');
+
+var TimerFieldWidget = AbstractField.extend({
+
+ /**
+ * @override
+ * @private
+ */
+ isSet: function () {
+ return true;
+ },
+ /**
+ * @override
+ * @private
+ */
+ _render: function () {
+ this._super.apply(this, arguments);
+ this._startTimeCounter();
+ },
+ /**
+ * @override
+ */
+ destroy: function () {
+ this._super.apply(this, arguments);
+ clearInterval(this.timer);
+ },
+ /**
+ * @private
+ */
+ _startTimeCounter: async function () {
+ if (this.record.data.timer_start) {
+ const serverTime = this.record.data.timer_pause || await this._getServerTime();
+ this.time = Timer.createTimer(0, this.record.data.timer_start, serverTime);
+ this.$el.text(this.time.toString());
+ this.timer = setInterval(() => {
+ if (this.record.data.timer_pause) {
+ clearInterval(this.timer);
+ } else {
+ this.time.addSecond();
+ this.$el.text(this.time.toString());
+ }
+ }, 1000);
+ } else if (!this.record.data.timer_pause){
+ clearInterval(this.timer);
+ }
+ },
+ _getServerTime: function () {
+ return this._rpc({
+ model: 'timer.timer',
+ method: 'get_server_time',
+ args: []
+ });
+ }
+});
+
+fieldRegistry.add('timer_timer', TimerFieldWidget);
+
+});
diff --git a/odex25_helpdesk/odex25_timer/static/src/js/timer_mixin.js b/odex25_helpdesk/odex25_timer/static/src/js/timer_mixin.js
new file mode 100644
index 000000000..c338e336e
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/static/src/js/timer_mixin.js
@@ -0,0 +1,119 @@
+odoo.define('timer.Timer', function (require) {
+"use strict";
+
+ /**
+ * This class improves the timer display using the
+ * unit_amount field on account.analytic.line.
+ */
+ class Timer {
+ constructor(hours, minutes, seconds) {
+ this.hours = hours;
+ this.minutes = minutes;
+ this.seconds = seconds;
+ }
+
+ addHours(hours) {
+ this.hours += hours;
+ }
+
+ addMinutes(minutes) {
+ minutes += this.minutes;
+
+ this.minutes = minutes % 60;
+ this.addHours(Math.floor(minutes / 60));
+ }
+
+ addSeconds(seconds) {
+ seconds += this.seconds;
+ this.seconds = seconds % 60;
+ this.addMinutes(Math.floor(seconds / 60));
+ }
+
+ display2digits(number) {
+ return number > 9 ? "" + number : "0" + number;
+ }
+
+ addSecond() {
+ this.seconds += 1;
+ if (this.seconds === 60) {
+ this.minutes += 1;
+ this.seconds = 0;
+
+ if (this.minutes === 60) {
+ this.hours += 1;
+ this.minutes = 0;
+ }
+ }
+ }
+
+ addTime(time) {
+ if (typeof time == 'string' && time.indexOf(':') !== -1) {
+ let [hour, minute, second] = time.split(':');
+
+ hour = parseInt(hour);
+ minute = parseInt(minute);
+ second = (second === undefined) ? 0 : parseInt(second);
+
+ this.addSeconds(second);
+ this.addMinutes(minute);
+ this.addHours(hour);
+ }
+ }
+
+ convertToFloat() {
+ return (this.hours * 60 + this.minutes) * 60 / 3600;
+ }
+
+ convertToSeconds() {
+ return (this.hours * 60 + this.minutes) * 60 + this.seconds;
+ }
+
+ toString() {
+ const time = {
+ hours: this.display2digits(this.hours),
+ minutes: this.display2digits(this.minutes),
+ seconds: this.display2digits(this.seconds)
+ };
+
+ return `${time.hours}:${time.minutes}:${time.seconds}`;
+ }
+ }
+
+ /**
+ * Convert float to time
+ * @param {number} float
+ */
+ Timer.convertFloatToTime = function (float) {
+ if (float === 0) {
+ return new Timer(0, 0, 0);
+ }
+
+ let minutes = float % 1;
+ const hours = float - minutes;
+ minutes *= 60;
+
+ return new Timer(hours, Math.round(minutes), 0);
+ };
+
+ /**
+ * Create timer
+ * @param {number} unit_amount
+ * @param {String} timer_start
+ * @param {String} serverTime
+ */
+ Timer.createTimer = function (unit_amount, timer_start, serverTime) {
+ const timer = this.convertFloatToTime(unit_amount);
+
+ timer.addTime(
+ moment.utc(
+ Math.abs(moment.utc(serverTime)
+ .diff(moment.utc(timer_start))
+ )).format("HH:mm:ss")
+ );
+
+ return timer;
+ };
+
+ return Timer;
+
+});
diff --git a/odex25_helpdesk/odex25_timer/static/src/js/timer_toggle_button.js b/odex25_helpdesk/odex25_timer/static/src/js/timer_toggle_button.js
new file mode 100644
index 000000000..d189d9965
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/static/src/js/timer_toggle_button.js
@@ -0,0 +1,79 @@
+odoo.define('timer.timer_toggle_button', function (require) {
+"use strict";
+
+const fieldRegistry = require('web.field_registry');
+const { FieldToggleBoolean } = require("web.basic_fields");
+const { _lt } = require('web.core');
+
+/**
+ * The TimerToggleButton is used to display correctly the button
+ * to start or stop a timer for a timesheet in kanban, list and grid
+ * views.
+ */
+const TimerToggleButton = FieldToggleBoolean.extend({
+ /**
+ * @override
+ * @private
+ */
+ _render: function () {
+ // When the is_timer_running field is false, then the button is used to start the timer
+ const title = this.value ? _lt('Stop') : _lt('Play');
+ const name = this.value ? 'action_timer_stop' : 'action_timer_start';
+ const label = this.value ? _lt('Stop') : _lt('Start');
+
+ this.$('i')
+ .addClass('fa')
+ .toggleClass('fa-stop-circle o-timer-stop-button', this.value)
+ .toggleClass('fa-play-circle o-timer-play-button', !this.value)
+ .attr('title', title);
+
+ this.$el.addClass('o-timer-button');
+ this.$el.attr('title', title);
+ this.$el.attr('name', name);
+ this.$el.attr('aria-label', label);
+ this.$el.attr('aria-pressed', this.value);
+ this.$el.attr('type', 'button');
+ this.$el.attr('role', 'button');
+ },
+ /**
+ * Toggle the button
+ *
+ * When the user click on this button,
+ * - the action "action_timer_start" is called
+ * into the account.analytic.line model,
+ * if the value of is_timer_running field is set on false.
+ * - the action "action_timer_stop" is called
+ * into the account.analytic.line model,
+ * if the value of is_timer_running field is set on true.
+ * Then we change the value of the is_timer_running.
+ * @override
+ * @private
+ * @param {MouseEvent} event
+ */
+ _onToggleButton: async function (event) {
+ const context = this.record.getContext();
+ event.stopPropagation();
+ const result = await this._rpc({
+ model: this.model,
+ method: this._getActionButton(),
+ context: context,
+ args: [this.res_id]
+ });
+
+ this.trigger_up('timer_changed', {
+ id: this.res_id,
+ is_timer_running: !this.value
+ });
+
+ this._setValue(!this.value);
+ },
+ _getActionButton: function () {
+ return this.value ? 'action_timer_stop' : 'action_timer_start';
+ }
+});
+
+fieldRegistry.add('timer_toggle_button', TimerToggleButton);
+
+return TimerToggleButton;
+
+});
diff --git a/odex25_helpdesk/odex25_timer/static/src/scss/timer_button.scss b/odex25_helpdesk/odex25_timer/static/src/scss/timer_button.scss
new file mode 100644
index 000000000..8b269fa8b
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/static/src/scss/timer_button.scss
@@ -0,0 +1,26 @@
+.o-timer-button {
+ &:focus {
+ border: none !important;
+ outline: 0 !important;
+ }
+
+ & > i {
+ transition: 0.3s;
+
+ &.o-timer-play-button {
+ color: $link-color !important;
+ }
+
+ &.o-timer-stop-button {
+ color: $red !important;
+ }
+ }
+
+ &:hover > i.o-timer-play-button {
+ color: $link-hover-color !important;
+ }
+
+ &:hover > i.o-timer-stop-button {
+ color: darken($red, 15%) !important;
+ }
+}
diff --git a/odex25_helpdesk/odex25_timer/views/assets.xml b/odex25_helpdesk/odex25_timer/views/assets.xml
new file mode 100644
index 000000000..c47e9b749
--- /dev/null
+++ b/odex25_helpdesk/odex25_timer/views/assets.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex25_timesheet_grid/__init__.py b/odex25_helpdesk/odex25_timesheet_grid/__init__.py
new file mode 100644
index 000000000..2ba729757
--- /dev/null
+++ b/odex25_helpdesk/odex25_timesheet_grid/__init__.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+
+from . import models
+from . import wizard
+
+from odoo import api, SUPERUSER_ID
+
+
+def pre_init_hook(cr):
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ root_menu = env.ref('hr_timesheet.timesheet_menu_root', raise_if_not_found=False)
+ if root_menu and not root_menu.active:
+ root_menu.write({'active': True})
+
+
+def uninstall_hook(cr, registry):
+ """
+ Unfortunately, the grid view is defined in enterprise, and the
+ timesheet actions (community) are inherited in enterprise to
+ add the grid view in the view_modes.
+ As they override view_mode directly instead of creating
+ ir.actions.act_window.view that would be unlinked properly
+ when uninstalling odex25_timesheet_grid, here we clean the view_mode
+ manually.
+
+ YTI TODO: But in master, define ir.actions.act_window.view instead,
+ so that they are removed with the module installation.
+ """
+ env = api.Environment(cr, SUPERUSER_ID, {})
+ root_menu = env.ref('hr_timesheet.timesheet_menu_root', raise_if_not_found=False)
+ if root_menu and root_menu.active:
+ root_menu.write({'active': False})
+
+ actions = env['ir.actions.act_window'].search([
+ ('res_model', '=', 'account.analytic.line')
+ ]).filtered(
+ lambda action: action.xml_id.startswith('hr_timesheet.') and 'grid' in action.view_mode)
+ for action in actions:
+ action.view_mode = ','.join(view_mode for view_mode in action.view_mode.split(',') if view_mode != 'grid')
+
diff --git a/odex25_helpdesk/odex25_timesheet_grid/__manifest__.py b/odex25_helpdesk/odex25_timesheet_grid/__manifest__.py
new file mode 100644
index 000000000..492b9d521
--- /dev/null
+++ b/odex25_helpdesk/odex25_timesheet_grid/__manifest__.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+{
+ 'name': "Timesheets",
+ 'summary': "Track employee time on tasks",
+ 'description': """
+* Timesheet submission and validation
+* Activate grid view for timesheets
+ """,
+ 'version': '1.0',
+ 'depends': ['odex25_web_grid', 'hr_timesheet', 'odex25_timer'],
+ 'category': 'Services/Timesheets',
+ 'sequence': 65,
+ 'data': [
+ 'data/mail_data.xml',
+ 'security/timesheet_security.xml',
+ 'security/ir.model.access.csv',
+ 'views/hr_timesheet_views.xml',
+ 'views/res_config_settings_views.xml',
+ 'views/assets.xml',
+ 'wizard/timesheet_merge_wizard_views.xml',
+ ],
+ 'demo': [
+ 'data/odex25_timesheet_grid_demo.xml',
+ ],
+ 'qweb': [
+ 'static/src/xml/odex25_timesheet_grid.xml',
+ 'static/src/xml/timer_m2o.xml',
+ ],
+ 'author': "Expert Co. Ltd.",
+ 'website': "http://www.exp-sa.com",
+ 'auto_install': ['odex25_web_grid', 'hr_timesheet'],
+ 'application': True,
+ 'pre_init_hook': 'pre_init_hook',
+ 'uninstall_hook': 'uninstall_hook',
+}
diff --git a/odex25_helpdesk/odex25_timesheet_grid/data/mail_data.xml b/odex25_helpdesk/odex25_timesheet_grid/data/mail_data.xml
new file mode 100644
index 000000000..5d5a4b304
--- /dev/null
+++ b/odex25_helpdesk/odex25_timesheet_grid/data/mail_data.xml
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+ Timesheet: Employees Email Reminder
+
+ code
+ model._cron_timesheet_reminder_employee()
+
+ 1
+ days
+ -1
+
+
+
+
+
+ Timesheet: Managers Email Reminder
+
+ code
+ model._cron_timesheet_reminder_manager()
+
+ 1
+ days
+ -1
+
+
+
+
+
+
+ Timesheet: Employees Reminder
+
+ Reminder to log your timesheets
+ ${(object.user_id.company_id.partner_id.email_formatted or user.email_formatted) | safe}
+ ${object.work_email | safe}
+
+
+
+
+
+
+
+
+
+ Your Timesheets
+
+ ${object.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hello ${object.name},
+ This is a friendly reminder to log your hours for the period : ${ctx.get('date_start')} - ${ctx.get('date_stop')}.
+ For the time being, you only logged ${ctx.get('timesheet_hours')} hours on the ${ctx.get('working_hours')} requested.
+ % if ctx.get('action_url'):
+
+
+ ${object.user_id.lang}
+
+
+
+
+ Timesheet: Manager Reminder
+
+ Reminder to validate your team timesheets
+ ${(object.user_id.company_id.partner_id.email_formatted or user.email_formatted) | safe}
+ ${object.user_id.email_formatted | safe}
+
+
+
+
+
+
+
+
+
+ Your Timesheets
+
+ ${object.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hello ${object.name},
+ This is a friendly reminder to approved your team's hours for the period : ${ctx.get('date_start')} - ${ctx.get('date_stop')}.
+ % if ctx.get('action_url'):
+
\n"
+" This is a friendly reminder to approved your team's hours for the period : ${ctx.get('date_start')} - ${ctx.get('date_stop')}. \n"
+" % if ctx.get('action_url'):\n"
+"
\n"
+" هذه رسالة تذكير ودية لتذكيركم باعتماد ساعات عمل فريقك للفترة: ${ctx.get('date_start')} - ${ctx.get('date_stop')}. \n"
+" % if ctx.get('action_url'):\n"
+"
\n"
+" This is a friendly reminder to log your hours for the period : ${ctx.get('date_start')} - ${ctx.get('date_stop')}.\n"
+" For the time being, you only logged ${ctx.get('timesheet_hours')} hours on the ${ctx.get('working_hours')} requested. \n"
+" % if ctx.get('action_url'):\n"
+"
\n"
+" هذه رسالة تذكير ودية لتذكيركم بتسجيل ساعات عملكم للفترة: ${ctx.get('date_start')} - ${ctx.get('date_stop')}.\n"
+" حتى الآن، لم تسجل سوى ${ctx.get('timesheet_hours')} ساعة للساعات ${ctx.get('working_hours')} المطلوبة. \n"
+" % if ctx.get('action_url'):\n"
+"
+ You can register and track your workings hours by project every
+ day. Every time spent on a project will become a cost and can be re-invoiced to
+ customers if required.
+
+
Download our web/mobile apps to track your time from anywhere, even offline.
+
+
+
+
diff --git a/odex25_helpdesk/odex25_website_helpdesk_livechat/__init__.py b/odex25_helpdesk/odex25_website_helpdesk_livechat/__init__.py
new file mode 100644
index 000000000..cde864bae
--- /dev/null
+++ b/odex25_helpdesk/odex25_website_helpdesk_livechat/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import models
diff --git a/odex25_helpdesk/odex25_website_helpdesk_livechat/__manifest__.py b/odex25_helpdesk/odex25_website_helpdesk_livechat/__manifest__.py
new file mode 100644
index 000000000..f2200f1cd
--- /dev/null
+++ b/odex25_helpdesk/odex25_website_helpdesk_livechat/__manifest__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+{
+ 'name': 'Website IM Livechat Helpdesk',
+ 'sequence': 58,
+ 'summary': 'Ticketing, Support, Livechat',
+ 'author': "Expert Co Ltd",
+ 'website': "http://www.ex.com",
+ 'category': 'Odex25-Helpdesk/Odex25-Helpdesk',
+ 'depends': [
+ 'odex25_website_helpdesk',
+ 'website_livechat',
+ ],
+ 'description': """
+Website IM Livechat integration for the helpdesk module
+=======================================================
+
+Features:
+
+ - Have a team-related livechat channel to answer your customer's questions.
+ - Create new tickets with ease using commands in the channel.
+
+ """,
+ 'data': [
+ 'views/helpdesk_view.xml',
+ ],
+ 'auto_install': True,
+}
diff --git a/odex25_helpdesk/odex25_website_helpdesk_livechat/i18n/ar.po b/odex25_helpdesk/odex25_website_helpdesk_livechat/i18n/ar.po
new file mode 100644
index 000000000..3bc29f942
--- /dev/null
+++ b/odex25_helpdesk/odex25_website_helpdesk_livechat/i18n/ar.po
@@ -0,0 +1,192 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * odex25_website_helpdesk_livechat
+#
+# Translators:
+# Mustafa Rawi , 2020
+# Osama Ahmaro , 2020
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server saas~13.5+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-09-01 07:39+0000\n"
+"PO-Revision-Date: 2020-09-07 08:26+0000\n"
+"Last-Translator: Osama Ahmaro , 2020\n"
+"Language-Team: Arabic (https://www.transifex.com/odoo/teams/41243/ar/)\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Language: ar\n"
+"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid " This channel is private. People must be invited to join it."
+msgstr ""
+" هذه القناة خاصة. يجب الحصول على دعوة ليتمكن الأشخاص من الانضمام إليها."
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid ""
+"
\n"
+" You can create a new ticket by typing /helpdesk \"ticket title\". \n"
+" You can search ticket by typing /helpdesk_search \"Keywords1 Keywords2 etc\" \n"
+" "
+msgstr ""
+"
\n"
+" يمكنك إنشاء تذكرة جديدة عن طريق كتابة /helpdesk \"عنوان التذكرة\". \n"
+" يمكنك البحث عن تذكرة عن طريق كتابة /helpdesk_search \"الكلمة المفتاحية 1، الكلمة المفتاحية 2، إلخ\" \n"
+" "
+
+#. module: odex25_website_helpdesk_livechat
+#: model_terms:ir.ui.view,arch_db:odex25_website_helpdesk_livechat.helpdesk_team_view_form_inherit_odex25_website_helpdesk_livechat
+msgid "Canned Responses"
+msgstr "الردود الجاهزة"
+
+#. module: odex25_website_helpdesk_livechat
+#: model_terms:ir.ui.view,arch_db:odex25_website_helpdesk_livechat.helpdesk_team_view_form_inherit_odex25_website_helpdesk_livechat
+msgid "Configure Canned Responses"
+msgstr "إعداد الردود الجاهزة"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid "Create a new helpdesk ticket"
+msgstr "إنشاء تذكرة جديدة من مكتب المساعدة"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid "Created a new ticket and request: %s"
+msgstr "أنشأ تذكرة وطلب جديدين: %s"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model,name:odex25_website_helpdesk_livechat.model_mail_channel
+msgid "Discussion Channel"
+msgstr "قناة المناقشة"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_helpdesk_team__display_name
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_mail_channel__display_name
+msgid "Display Name"
+msgstr "الاسم المعروض"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model,name:odex25_website_helpdesk_livechat.model_odex25_helpdesk_team
+msgid "Helpdesk Team"
+msgstr "فريق مكتب المساعدة"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_helpdesk_team__id
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_mail_channel__id
+msgid "ID"
+msgstr "المُعرف"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_helpdesk_team__is_canned_response
+msgid "Is Canned Response"
+msgstr "رد جاهز"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_helpdesk_team____last_update
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_mail_channel____last_update
+msgid "Last Modified on"
+msgstr "آخر تعديل في"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_helpdesk_team__feature_livechat_channel_id
+msgid "Live Chat Channel"
+msgstr "قناة المحادثة"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model.fields,field_description:odex25_website_helpdesk_livechat.field_helpdesk_team__feature_livechat_web_page
+msgid "Live Chat Test Page"
+msgstr "صفحة اختبار الدردشة الحية"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid ""
+"No tickets found related to the search query. make sure to use the "
+"right format: (/helpdesk_search Keyword1 Keyword2 etc...)"
+msgstr ""
+"لم يُعثر على أي تذاكر مرتبطة بعملية البحث. تأكد من استخدام لصيغة البحث "
+"الصحيحة: (/helpdesk_search \"الكلمة المفتاحية 1، الكلمة المفتاحية 2، إلخ...)"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid "Search for a helpdesk ticket"
+msgstr "البحث عن تذكرة مكتب المساعدة"
+
+#. module: odex25_website_helpdesk_livechat
+#: model_terms:ir.ui.view,arch_db:odex25_website_helpdesk_livechat.helpdesk_team_view_form_inherit_odex25_website_helpdesk_livechat
+msgid ""
+"Set shortcuts predefined answers. This works in both the mail composer and "
+"the live website chatter. Simply ype ':' followed by the shortcut label to "
+"get selectable canned reponses."
+msgstr ""
+"قم بتعيين إجابات مختصرة محددة مسبقًا. يمكنك استخدامهم في تطبيق كتابة الرسائل"
+" وكذلك في تطبيق المحادثة الحية على الموقع. ببساطة يمكنك أن تكتب ':' متبوعة "
+"بالاسم المختصر لتحصل على ردود جاهزة للاختيار من بينها."
+
+#. module: odex25_website_helpdesk_livechat
+#: model_terms:ir.ui.view,arch_db:odex25_website_helpdesk_livechat.helpdesk_team_view_form_inherit_odex25_website_helpdesk_livechat
+msgid "Setup the"
+msgstr "إعداد"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid "Something is missing or wrong in command"
+msgstr "هناك خطأ ما أو كلمة ناقصة في الأمر"
+
+#. module: odex25_website_helpdesk_livechat
+#: model:ir.model.fields,help:odex25_website_helpdesk_livechat.field_helpdesk_team__feature_livechat_web_page
+msgid ""
+"URL to a static page where you client can discuss with the operator of the "
+"channel."
+msgstr "رابط صفحة استاتيكية يمكن لعميلك فيها التحدث مع موظفي الدعم بقناتك."
+
+#. module: odex25_website_helpdesk_livechat
+#: model_terms:ir.ui.view,arch_db:odex25_website_helpdesk_livechat.helpdesk_team_view_form_inherit_odex25_website_helpdesk_livechat
+msgid "Use predefined answers in the mail composer"
+msgstr "استخدم الإجابات المحددة مسبقًا في تطبيق كتابة الرسائل"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid "We found some matched ticket(s) related to the search query: %s"
+msgstr "عثرنا على بعض التذاكر المطابقة المرتبطة بعملية البحث: %s"
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid "You are in a private conversation with @%s."
+msgstr "أنت في محادثة خاصة مع @%s."
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid "You are in channel #%s."
+msgstr "أنت في القناة #%s."
+
+#. module: odex25_website_helpdesk_livechat
+#: code:addons/odex25_website_helpdesk_livechat/models/helpdesk.py:0
+#, python-format
+msgid ""
+"You can search ticket by typing /helpdesk_search \"Keywords1 Keywords2 "
+"etc\" "
+msgstr ""
+"يمكنك البحث عن تذكرة عن طريق كتابة /helpdesk_search \"الكلمة المفتاحية 1،"
+" الكلمة المفتاحية 2، إلخ\" "
+
+#. module: odex25_website_helpdesk_livechat
+#: model_terms:ir.ui.view,arch_db:odex25_website_helpdesk_livechat.helpdesk_team_view_form_inherit_odex25_website_helpdesk_livechat
+msgid "channel to define auto-popup rules and geolocation filters"
+msgstr ""
+"قناة لتعريف قواعد نوافذ الانبثاق التلقائي وفلاتر تحديد الموقع الجغرافي"
diff --git a/odex25_helpdesk/odex25_website_helpdesk_livechat/models/__init__.py b/odex25_helpdesk/odex25_website_helpdesk_livechat/models/__init__.py
new file mode 100644
index 000000000..b0541b13e
--- /dev/null
+++ b/odex25_helpdesk/odex25_website_helpdesk_livechat/models/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import helpdesk
diff --git a/odex25_helpdesk/odex25_website_helpdesk_livechat/models/helpdesk.py b/odex25_helpdesk/odex25_website_helpdesk_livechat/models/helpdesk.py
new file mode 100644
index 000000000..2ec02153d
--- /dev/null
+++ b/odex25_helpdesk/odex25_website_helpdesk_livechat/models/helpdesk.py
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+
+import re
+from odoo import api, fields, models, _
+
+
+class HelpdeskTeam(models.Model):
+ _inherit = ['odex25_helpdesk.team']
+
+ feature_livechat_channel_id = fields.Many2one('im_livechat.channel', string='Live Chat Channel', compute='_get_livechat_channel', store=True)
+ feature_livechat_web_page = fields.Char(related='feature_livechat_channel_id.web_page', string='Live Chat Test Page', readonly=True)
+ is_canned_response = fields.Boolean()
+
+ @api.depends('use_website_helpdesk_livechat')
+ def _get_livechat_channel(self):
+ LiveChat = self.env['im_livechat.channel']
+ for team in self:
+ if team.name and team.use_website_helpdesk_livechat:
+ channel = LiveChat.search([('name', '=', team.name)])
+ if not channel:
+ if team.member_ids:
+ channel = LiveChat.create({'name': team.name, 'user_ids': [(6, _, team.member_ids.ids)]})
+ else:
+ channel = LiveChat.create({'name': team.name})
+ team.feature_livechat_channel_id = channel
+ else:
+ team.feature_livechat_channel_id = False
+
+
+class MailChannel(models.Model):
+ _inherit = 'mail.channel'
+
+ # ------------------------------------------------------
+ # Commands
+ # ------------------------------------------------------
+
+ def _define_command_helpdesk(self):
+ return {'help': _("Create a new helpdesk ticket")}
+
+ def _execute_command_helpdesk(self, **kwargs):
+ key = kwargs.get('body').split()
+ partner = self.env.user.partner_id
+ msg = _('Something is missing or wrong in command')
+ channel_partners = self.env['mail.channel.partner'].search([('partner_id', '!=', partner.id), ('channel_id', '=', self.id)], limit=1)
+ if key[0].lower() == '/helpdesk':
+ if len(key) == 1:
+ if self.channel_type == 'channel':
+ msg = _("You are in channel #%s.", self.name)
+ if self.public == 'private':
+ msg += _(" This channel is private. People must be invited to join it.")
+ else:
+ msg = _("You are in a private conversation with @%s.", channel_partners.partner_id.name)
+ msg += _("""
+ You can create a new ticket by typing /helpdesk "ticket title".
+ You can search ticket by typing /helpdesk_search "Keywords1 Keywords2 etc"
+ """)
+ else:
+ list_value = key[1:]
+ description = ''
+ for message in self.channel_message_ids.sorted(key=lambda r: r.id):
+ name = message.author_id.name or 'Anonymous'
+ description += '%s: ' % name + '%s\n' % re.sub('<[^>]*>', '', message.body)
+ team = self.env['odex25_helpdesk.team'].search([('member_ids', 'in', self._uid)], order='sequence', limit=1)
+ team_id = team.id if team else False
+ helpdesk_ticket = self.env['odex25_helpdesk.ticket'].create({
+ 'name': ' '.join(list_value),
+ 'user_id': self.env.user.id,
+ 'description': description,
+ 'partner_id': channel_partners.partner_id.id,
+ 'team_id': team_id,
+ })
+ link_ticket = ''+helpdesk_ticket.name+''
+ msg = _("Created a new ticket and request: %s", link_ticket)
+ return self._send_transient_message(partner, msg)
+
+ def _define_command_helpdesk_search(self):
+ return {'help': _("Search for a helpdesk ticket")}
+
+ def _execute_command_helpdesk_search(self, **kwargs):
+ key = kwargs.get('body').split()
+ partner = self.env.user.partner_id
+ msg = _('Something is missing or wrong in command')
+ if key[0].lower() == '/helpdesk_search':
+ if len(key) == 1:
+ msg = _('You can search ticket by typing /helpdesk_search "Keywords1 Keywords2 etc" ')
+ else:
+ list_value = key[1:]
+ Keywords = re.findall('\w+', ' '.join(list_value))
+ HelpdeskTag = self.env['odex25_helpdesk.tag']
+ for Keyword in Keywords:
+ HelpdeskTag |= HelpdeskTag.search([('name', 'ilike', Keyword)])
+ tickets = self.env['odex25_helpdesk.ticket'].search([('tag_ids', 'in', HelpdeskTag.ids)], limit=10)
+ if not tickets:
+ for Keyword in Keywords:
+ tickets |= self.env['odex25_helpdesk.ticket'].search([('name', 'ilike', Keyword)], order="id desc", limit=10)
+ if len(tickets) > 10:
+ break
+ if tickets:
+ link_tickets = [' #'+ticket.name+'' for ticket in tickets]
+ msg = _('We found some matched ticket(s) related to the search query: %s') % ''.join(link_tickets)
+ else:
+ msg = _('No tickets found related to the search query. make sure to use the right format: (/helpdesk_search Keyword1 Keyword2 etc...)')
+ return self._send_transient_message(partner, msg)
diff --git a/odex25_helpdesk/odex25_website_helpdesk_livechat/static/description/icon.png b/odex25_helpdesk/odex25_website_helpdesk_livechat/static/description/icon.png
new file mode 100644
index 000000000..4141f52da
Binary files /dev/null and b/odex25_helpdesk/odex25_website_helpdesk_livechat/static/description/icon.png differ
diff --git a/odex25_helpdesk/odex25_website_helpdesk_livechat/views/helpdesk_view.xml b/odex25_helpdesk/odex25_website_helpdesk_livechat/views/helpdesk_view.xml
new file mode 100644
index 000000000..710a013d7
--- /dev/null
+++ b/odex25_helpdesk/odex25_website_helpdesk_livechat/views/helpdesk_view.xml
@@ -0,0 +1,42 @@
+
+
+
+ odex25_helpdesk.team.form.inherit.website.livechat.helpdesk
+ odex25_helpdesk.team
+
+
+
+
+
+
+
+
+
+
+ Setup the
+
+
+
+ channel to define auto-popup rules and geolocation filters
+
'.format(message),
+ 'email_to': record.partner_id.email,
+ 'email_cc': record.env.user.company_id.email,
+ }
+ record.env['mail.mail'].create(main_content).send()
+
+
+ # @api.multi
+ def set_open(self):
+ """ Set the subscription status to 'open' """
+ if len(self.recurring_invoice_line_ids) ==0:
+ raise UserError(
+ _("You must add subscription lines!"))
+
+ sub = self
+ sub.check_close_date()
+ if sub.state != 'close':
+ subject = _('Subscription Progress') + ' {} '.format(sub.display_name)
+ message = _('Hello,') + '{}'.format(sub.partner_id.name) + _(', your subscription is in progress') + ': {} '.format(sub.display_name)
+ sub.partner_id.send_notification_message(subject=subject, body=message)
+ # Send Email
+ main_content = {
+ 'subject': subject,
+ 'author_id': False,
+ 'body_html': '
{}
'.format(message),
+ 'email_to': sub.partner_id.email,
+ 'email_cc': sub.env.user.company_id.email,
+ }
+ sub.env['mail.mail'].create(main_content).send()
+
+ today = fields.Date.today()
+ next_date = fields.Date.from_string(sub.recurring_next_date)
+ if next_date == today:
+ sub._recurring_create_invoice(automatic=True)
+
+ return sub.write({'state': 'open', 'close_date': None})
+
+ # @api.multi
+ def set_pending(self):
+ """ Set the subscription status to 'pending' """
+ return self.write({'state': 'pending'})
+
+ # @api.multi
+ def set_cancel(self):
+ """ Set the subscription status to 'cancel' """
+ return self.write({'state': 'cancel'})
+
+ # @api.multi
+ def set_close(self):
+ """ Set the subscription status to 'close' """
+ # Send Notifications
+ subject = _('Close Subscription') + ' {} '.format(self.display_name)
+ message = _('Hello,') + '{}'.format(self.partner_id.name) + _(', your subscription is closed') + ': {} '.format(self.display_name)
+ self.partner_id.send_notification_message(subject=subject, body=message)
+ # Send Email
+ main_content = {
+ 'subject': subject,
+ 'author_id': False,
+ 'body_html': '
{}
'.format(message),
+ 'email_to': self.partner_id.email,
+ 'email_cc': self.env.user.company_id.email,
+ }
+ self.env['mail.mail'].create(main_content).send()
+ return self.write({'state': 'close', 'date_end': fields.Date.from_string(fields.Date.today()),
+ 'close_date': fields.Date.from_string(fields.Date.today())
+ })
+
+ # @api.multi
+ def _prepare_invoice_data(self):
+ """ Prepare the data of the invoice """
+ self.ensure_one()
+
+ if not self.partner_id:
+ raise UserError(
+ _("You must first select a Customer for Subscription %s!") % self.name)
+
+ """ Get the fiscal position of the company """
+ fpos_id = self.env['account.fiscal.position'].with_context(
+ company_id=self.company_id.id).get_fiscal_position(self.partner_id.id)
+ """ Get the subscription journal of the company """
+ journal = self.env['account.journal'].search(
+ [('type', '=', self.type), ('company_id', '=', self.company_id.id)], limit=1)
+ if not journal:
+ raise UserError(_('Please define a %s journal for the company "%s".') % (self.type, self.company_id.name or '', ))
+
+ next_date = fields.Date.from_string(self.recurring_next_date)
+ periods = {'daily': 'days', 'weekly': 'weeks',
+ 'monthly': 'months', 'yearly': 'years'}
+ new_date = next_date + \
+ relativedelta(
+ **{periods[self.recurrency]: self.recurring_interval})
+
+ return {
+ # 'account_id': self.partner_id.property_account_payable_id.id,
+ 'move_type': INV_TYPE[self.type],
+ 'invoice_date': self.recurring_next_date,
+ # 'origin': self.code,
+ # 'date_invoice': self.recurring_next_date,
+
+ 'partner_id': self.partner_id.id,
+ 'journal_id': journal.id,
+ 'date': self.recurring_next_date,
+ 'payment_reference': self.code or '',
+ 'fiscal_position_id': fpos_id,
+ 'currency_id': self.currency_id and self.currency_id.id or False,
+ 'invoice_payment_term_id': self.payment_term_id and self.payment_term_id.id
+ or self.partner_id.property_supplier_payment_term_id.id,
+ 'company_id': self.company_id.id,
+ 'narration': _("This invoice covers the following period: %s - %s") % (next_date, new_date),
+ }
+
+ # @api.multi
+ def _prepare_invoice_line(self, line, fiscal_position):
+ """ Prepare the invoice line """
+ account_id = line.product_id.property_account_expense_id.id
+ if not account_id:
+ account_id = line.product_id.categ_id.property_account_expense_categ_id.id
+ account_id = fiscal_position.map_account(account_id)
+
+ tax = self.env['account.tax']
+ if 'purchase' == line.subscription_serv_id.type:
+ tax = line.product_id.supplier_taxes_id.filtered(lambda r: r.company_id == line.subscription_serv_id.company_id)
+ elif 'sale' == line.subscription_serv_id.type:
+ tax = line.product_id.taxes_id.filtered(lambda r: r.company_id == line.subscription_serv_id.company_id)
+ tax = fiscal_position.map_tax(tax)
+ return {
+ 'name': line.name,
+ 'account_id': account_id,
+ 'analytic_account_id': line.analytic_account_id.id,
+ 'analytic_tag_ids': [(6, 0, line.analytic_tag_ids.ids)],
+ 'price_unit': line.unit_price or 0.0,
+ 'discount': line.discount,
+ 'quantity': line.qty or 1,
+ 'product_uom_id': line.uom_id.id,
+ 'product_id': line.product_id.id,
+ 'tax_ids': [(6, 0, tax.ids)],
+ }
+
+ # @api.multi
+ def _prepare_invoice_lines(self, fiscal_position):
+ """ Prepare the invoice lines """
+ self.ensure_one()
+ fiscal_position = self.env[
+ 'account.fiscal.position'].browse(fiscal_position)
+ return [(0, 0, self._prepare_invoice_line(line, fiscal_position)) for line in self.recurring_invoice_line_ids]
+
+ # @api.multi
+ def _prepare_invoice(self):
+ """ Prepare the invoice """
+ invoice = self._prepare_invoice_data()
+ invoice['invoice_line_ids'] = self._prepare_invoice_lines(
+ invoice['fiscal_position_id'])
+ return invoice
+
+ # @api.multi
+ def recurring_invoice(self):
+ """ Reccuring the invoice """
+ invoice_ids = self._recurring_create_invoice()
+ # return False
+ # return self.action_subscription_invoice()
+
+ # @api.multi
+ def action_subscription_invoice(self):
+ """ Show the invoices views """
+ # views = []
+ # if 'purchase' == self.type:
+ # views = [[self.env.ref('account.move_supplier_tree').id, "tree"],
+ # [self.env.ref('account.move_supplier_form').id, "form"]]
+ # elif 'sale' == self.type:
+ # views = [[self.env.ref('account.move_tree').id, "tree"],
+ # [self.env.ref('account.move_form').id, "form"]]
+
+ # views = [[self.env.ref('account.move_form').id, "form"]]
+ return {
+ 'view_type': 'form',
+ 'view_mode': 'form',
+ "res_model": "account.move",
+ "context": {"create": False},
+ "name": _("Invoices"),
+ 'type': 'ir.actions.act_window',
+ 'target': 'current',
+ "domain": [["id", "in", self.invoice_ids.ids]],
+ }
+
+
+ # @api.returns('account.move')
+ def _recurring_create_invoice(self, automatic=False):
+ invoices = self.env['account.move']
+ AccountInvoice = self.env['account.move']
+ current_date = fields.Date.today()
+ periods = {'daily': 'days', 'weekly': 'weeks',
+ 'monthly': 'months', 'yearly': 'years'}
+ domain = [('id', 'in', self.ids)] if self.ids else [
+ ('recurring_next_date', '<=', current_date), ('state', '=', 'open')]
+ sub_data = self.search_read(fields=['id', 'company_id'], domain=domain)
+ for company_id in set(data['company_id'][0] for data in sub_data):
+ sub_ids = map(lambda s: s['id'], filter(
+ lambda s: s['company_id'][0] == company_id, sub_data))
+ subs = self.with_context(company_id=company_id).browse(sub_ids)
+ for sub in subs:
+ try:
+ invoices |= AccountInvoice.create(sub._prepare_invoice())
+ sub.invoice_ids = [(4, invoices[-1].id, _)]
+ # invoices[-1].compute_taxes()
+ if sub.payment_term_id:
+ invoices[-1].write({'invoice_payment_term_id': sub.payment_term_id.id})
+ next_date = fields.Date.from_string(
+ sub.recurring_next_date or current_date)
+ rule, interval = sub.recurrency, sub.recurring_interval
+ new_date = next_date + \
+ relativedelta(**{periods[rule]: interval})
+ sub.write({'recurring_next_date': new_date})
+ if automatic:
+ self.env.cr.commit()
+ body = _('A new invoice has been created for subscription') + ': {}'.format(sub.display_name)
+ sub.message_post(body=body, message_type='comment',**{'subtype_id': 1})
+ except Exception:
+ if automatic:
+ self.env.cr.rollback()
+ _logger.exception(
+ 'Fail to create recurring invoice for subscription %s', sub.code)
+ else:
+ raise
+
+ # invoices.action_post()
+ return invoices
+
+ # @api.multi
+ def increment_period(self):
+ """ Get the date of the next occurrence """
+ for account in self:
+ current_date = account.recurring_next_date or self.default_get(
+ ['recurring_next_date'])['recurring_next_date']
+ periods = {'daily': 'days', 'weekly': 'weeks',
+ 'monthly': 'months', 'yearly': 'years'}
+ new_date = fields.Date.from_string(current_date) + relativedelta(
+ **{periods[account.recurrency]: account.recurring_interval})
+ account.write({'recurring_next_date': new_date})
+
+
+
+
+class SubscriptionServiceLine(models.Model):
+ _name = 'subscription.service.line'
+ _description = 'Subscription Service Line'
+
+ @api.onchange('product_id')
+ def _compute_unit_price(self):
+ partner_id = self.subscription_serv_id.partner_id
+ pricelist = partner_id.property_product_pricelist if partner_id.property_product_pricelist else False
+ item_price = 0.0
+ if 'sale' == self.sub_type:
+ item_price = self.product_id.list_price
+ elif 'purchase' == self.sub_type:
+ item_price = self.product_id.standard_price
+ if pricelist and self.product_id and 'sale' == self.sub_type:
+ item_price = pricelist.get_products_price(self.product_id,[self.qty],partner_id)[self.product_id.id]
+ elif 'purchase' == self.sub_type and partner_id in self.product_id.seller_ids.mapped('name'):
+ item_price = list(filter(lambda x: x.name == partner_id, self.product_id.seller_ids))[0].price
+ self.unit_price = item_price
+ if 'sale' == self.sub_type:
+ self.sub_line_tax_ids |= self.product_id.taxes_id
+ elif 'purchase' == self.sub_type:
+ self.sub_line_tax_ids |= self.product_id.supplier_taxes_id
+
+ @api.depends('unit_price', 'qty', 'discount','sub_line_tax_ids')
+ def _compute_subtotal(self):
+ """ Compute the subtotal price """
+ for line in self:
+ subtotal = line.qty * line.unit_price * (100.0 - line.discount) / 100.0
+ taxes = line.sub_line_tax_ids.compute_all(subtotal, line.subscription_serv_id.currency_id, line.qty, product=line.product_id, partner=line.subscription_serv_id.partner_id)
+ line.subtotal = subtotal
+ line.price_tax = sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])) if line.sub_type == 'sale' else 0.0
+
+ subscription_serv_id = fields.Many2one('subscription.service', string="Subscription", required=True)
+ name = fields.Text(string='Description', required=True)
+ sub_type = fields.Selection(related='subscription_serv_id.type')
+ product_id = fields.Many2one('product.product', string='Product',required=True)
+ qty = fields.Float(string='Quantity', default=1, required=True)
+ uom_id = fields.Many2one('uom.uom',string='Unit of Measure', related='product_id.uom_id')
+ unit_price = fields.Float(string='Unit Price', required=True)
+ subtotal = fields.Float(string='Subtotal', compute='_compute_subtotal' )
+ discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'))
+ analytic_account_id = fields.Many2one('account.analytic.account', string="Analytic account")
+ analytic_tag_ids = fields.Many2many('account.analytic.tag', string='Analytic Tags')
+ sub_line_tax_ids = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
+ price_tax = fields.Float(compute='_compute_subtotal', string='Total Tax', readonly=True, store=True)
+
+ @api.onchange('product_id')
+ def set_product_domain(self):
+ product_ids = []
+ sub_type = self._context.get('type')
+ if sub_type:
+ product_ids = self.env['product.product'].search([(PRODUCT_DOMAIN[sub_type],'=', True)]).mapped('id')
+ return {'domain': {'product_id': [('id', 'in', product_ids)],},}
diff --git a/odex25_helpdesk/odex_subscription_service/security/ir.model.access.csv b/odex25_helpdesk/odex_subscription_service/security/ir.model.access.csv
new file mode 100644
index 000000000..16487e955
--- /dev/null
+++ b/odex25_helpdesk/odex_subscription_service/security/ir.model.access.csv
@@ -0,0 +1,9 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_odex_subscription_service_user,subscription.service,model_subscription_service,base.group_user,1,0,0,0
+access_odex_po_subscription_service,subscription.service,model_subscription_service,purchase.group_purchase_manager,1,1,1,1
+access_odex_so_subscription_service,subscription.service,model_subscription_service,sales_team.group_sale_manager,1,1,1,1
+access_subscription_service_line_user,subscription.service.line,model_subscription_service_line,base.group_user,1,0,0,0
+access_po_subscription_service_line,subscription.service.line,model_subscription_service_line,purchase.group_purchase_manager,1,1,1,1
+access_so_subscription_service_line,subscription.service.line,model_subscription_service_line,sales_team.group_sale_manager,1,1,1,1
+access_odex_subscription_service_portal,access_odex_subscription_service_portal,odex_subscription_service.model_subscription_service,base.group_portal,1,1,0,0
+access_subscription_line_portal,access_subscription_line_portal,odex_subscription_service.model_subscription_service_line,base.group_portal,1,0,0,0
diff --git a/odex25_helpdesk/odex_subscription_service/security/sale_subscription_security.xml b/odex25_helpdesk/odex_subscription_service/security/sale_subscription_security.xml
new file mode 100644
index 000000000..833ab99db
--- /dev/null
+++ b/odex25_helpdesk/odex_subscription_service/security/sale_subscription_security.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+ Subscription portal access
+
+ [('partner_id','in',[user.partner_id.id,user.commercial_partner_id.id])]
+
+
+
+
+
+
+
+ Analytic Account portal access
+
+ [('partner_id','in',[user.partner_id.id,user.commercial_partner_id.id])]
+
+
+
+
+
+
+
+ Invoice line portal access
+
+ [('analytic_account_id.partner_id','in',[user.partner_id.id,user.commercial_partner_id.id])]
+
+
+
+
+
+
+
+
+
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/Subscription_invoice.png b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_invoice.png
new file mode 100644
index 000000000..6a79ea7e1
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_invoice.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/Subscription_list.png b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_list.png
new file mode 100644
index 000000000..6f752feac
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_list.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal1.png b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal1.png
new file mode 100644
index 000000000..8b52939ce
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal1.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal2.png b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal2.png
new file mode 100644
index 000000000..0047568de
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_portal2.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/Subscription_subscription.png b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_subscription.png
new file mode 100644
index 000000000..ef38128a3
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/Subscription_subscription.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/banner.png b/odex25_helpdesk/odex_subscription_service/static/description/banner.png
new file mode 100644
index 000000000..6cfd1f54c
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/banner.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/icon.png b/odex25_helpdesk/odex_subscription_service/static/description/icon.png
new file mode 100644
index 000000000..0842eeccb
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/icon.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/icon2.png b/odex25_helpdesk/odex_subscription_service/static/description/icon2.png
new file mode 100644
index 000000000..e822fe3c5
Binary files /dev/null and b/odex25_helpdesk/odex_subscription_service/static/description/icon2.png differ
diff --git a/odex25_helpdesk/odex_subscription_service/static/description/index.html b/odex25_helpdesk/odex_subscription_service/static/description/index.html
new file mode 100644
index 000000000..2632b133f
--- /dev/null
+++ b/odex25_helpdesk/odex_subscription_service/static/description/index.html
@@ -0,0 +1,85 @@
+
+
+
Subscription Services
+
An easy way to manage your Subscription Services.
+
Notice: This module is designed to work with Odoo Community Edition
+
+
+
+
+
+
+
+
+
+
+
Manage subscriptions
+
+
+
+
+ Create a recurring payment such as rent, phone bill and internet subscription or create a customer subscription for a recurring service or monthly payment or anything that requires regular invoices.
+
+
+ Define how often you need to create invoices and the duration of the subscription.
+
+
+
+ Create, Start, Cancel or Stop your subscription whenever you want.
+
+
+
+
+
+
+
+
+
+
+
Customer Portal
+
+
+
+
+
+
+
+ A customer portal allows your customers to view, communicate, track any open invoices and cancel their subscription
+
+
+
+
+
+
+
+
+
+
+
Generate your invoices
+
+
+ Generate your invoices with ease:
+ Invoices are automatically generated and already defined on the right period of time,
+ depending on the previous invoice or the start date if it's the first invoice.
+
+ Use contracts to follow tasks, issues, timesheets or invoicing based on
+ work done, expenses and/or purchase orders. Odoo will automatically manage
+ the alerts for the renewal of the contracts to the right salesperson.
+
+ You will find here the contracts to be renewed because the
+ end date is passed or the working effort is higher than the
+ maximum authorized one.
+
+ Odoo automatically sets contracts to be renewed in a pending
+ state. After the negociation, the salesman should close or renew
+ pending contracts.
+