helpdesk_ticket.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. from odoo import _, api, fields, models
  4. class HelpdeskTicket(models.Model):
  5. _inherit = 'helpdesk.ticket'
  6. request_type_id = fields.Many2one(
  7. 'helpdesk.request.type',
  8. string=_('Request Type'),
  9. required=True,
  10. tracking=True,
  11. help=_("Type of ticket (e.g., Incident, Improvement)"),
  12. default=lambda self: self._default_request_type_id()
  13. )
  14. request_type_code = fields.Char(
  15. related='request_type_id.code',
  16. string=_('Request Type Code'),
  17. store=True,
  18. readonly=True,
  19. help=_("Code of the request type for conditional logic")
  20. )
  21. affected_module_id = fields.Many2one(
  22. 'helpdesk.affected.module',
  23. string=_('Affected Module'),
  24. required=False,
  25. domain=[('active', '=', True)],
  26. help=_("Odoo module where the issue or improvement occurs")
  27. )
  28. affected_user_email = fields.Char(
  29. string=_('Affected User Email'),
  30. tracking=True,
  31. help=_("Email address of the affected user (from another Odoo instance or external)")
  32. )
  33. business_impact = fields.Selection(
  34. [
  35. ('0', _('Critical')),
  36. ('1', _('High')),
  37. ('2', _('Normal')),
  38. ],
  39. string=_('Business Impact'),
  40. default='2',
  41. tracking=True,
  42. help=_("Urgency reported by the client")
  43. )
  44. reproduce_steps = fields.Html(
  45. string=_('Steps to Reproduce'),
  46. help=_("Detailed steps to reproduce the issue (only for Incidents)")
  47. )
  48. business_goal = fields.Html(
  49. string=_('Business Goal'),
  50. help=_("Business objective for this improvement (only for Improvements)")
  51. )
  52. client_authorization = fields.Boolean(
  53. string=_('Client Authorization'),
  54. default=False,
  55. help=_("Checkbox from web form indicating client authorization")
  56. )
  57. estimated_hours = fields.Float(
  58. string=_('Estimated Hours'),
  59. help=_("Hours quoted after analysis")
  60. )
  61. approval_status = fields.Selection(
  62. [
  63. ('draft', _('N/A')),
  64. ('waiting', _('Waiting for Approval')),
  65. ('approved', _('Approved')),
  66. ('rejected', _('Rejected')),
  67. ],
  68. string=_('Approval Status'),
  69. default='draft',
  70. tracking=True,
  71. help=_("Status of the approval workflow")
  72. )
  73. has_template = fields.Boolean(
  74. string=_('Has Template'),
  75. compute='_compute_has_template',
  76. help=_("Indicates if the team has a template assigned")
  77. )
  78. attachment_ids = fields.One2many(
  79. 'ir.attachment',
  80. 'res_id',
  81. string=_('Attachments'),
  82. domain=[('res_model', '=', 'helpdesk.ticket')],
  83. help=_("Files attached to this ticket")
  84. )
  85. @api.depends('team_id.template_id')
  86. def _compute_has_template(self):
  87. """Compute if team has a template"""
  88. for ticket in self:
  89. ticket.has_template = bool(ticket.team_id and ticket.team_id.template_id)
  90. @api.model
  91. def _default_request_type_id(self):
  92. """Default to 'Incident' type if available"""
  93. incident_type = self.env.ref(
  94. 'helpdesk_extras.type_incident',
  95. raise_if_not_found=False
  96. )
  97. return incident_type.id if incident_type else False
  98. def _get_template_fields(self):
  99. """Get template fields for this ticket's team"""
  100. self.ensure_one()
  101. if not self.team_id or not self.team_id.template_id:
  102. return self.env['helpdesk.template.field']
  103. return self.team_id.template_id.field_ids.sorted('sequence')
  104. @api.onchange('request_type_id', 'business_impact')
  105. def _onchange_compute_priority(self):
  106. """
  107. Auto-calculate priority based on request type and business impact.
  108. Only applies when both fields have values.
  109. Mapping:
  110. - Incident + Critical = 3 (Urgent)
  111. - Incident + High = 2 (High)
  112. - Incident + Normal = 1 (Normal)
  113. - Improvement + Critical = 2 (High)
  114. - Improvement + High = 1 (Normal)
  115. - Improvement + Normal = 0 (Low)
  116. """
  117. priority = self._compute_priority_from_impact()
  118. if priority is not None:
  119. self.priority = priority
  120. def _compute_priority_from_impact(self, request_type_id=None, business_impact=None):
  121. """
  122. Helper method to compute priority from request type and business impact.
  123. Can be used by onchange, create, and write methods.
  124. Returns the priority string or None if not applicable.
  125. """
  126. # Use provided values or instance values
  127. if request_type_id is None:
  128. request_type = self.request_type_id
  129. else:
  130. request_type = self.env['helpdesk.request.type'].browse(request_type_id) if isinstance(request_type_id, int) else request_type_id
  131. if business_impact is None:
  132. business_impact = self.business_impact
  133. if not request_type or not business_impact:
  134. return None
  135. type_code = request_type.code if hasattr(request_type, 'code') else ''
  136. if not type_code:
  137. return None
  138. # Priority mapping based on business_impact
  139. # business_impact: '0' = Critical, '1' = High, '2' = Normal
  140. # priority: '0' = Low, '1' = Normal, '2' = High, '3' = Urgent
  141. priority_map = {
  142. # Incidents get +1 priority boost
  143. ('incident', '0'): '3', # Incident + Critical = Urgent
  144. ('incident', '1'): '2', # Incident + High = High
  145. ('incident', '2'): '1', # Incident + Normal = Normal
  146. # Improvements have standard mapping
  147. ('improvement', '0'): '2', # Improvement + Critical = High
  148. ('improvement', '1'): '1', # Improvement + High = Normal
  149. ('improvement', '2'): '0', # Improvement + Normal = Low
  150. }
  151. key = (type_code, business_impact)
  152. return priority_map.get(key)
  153. @api.model_create_multi
  154. def create(self, vals_list):
  155. """Override create to auto-calculate priority for website form submissions."""
  156. for vals in vals_list:
  157. # Only calculate if priority not explicitly set and we have both fields
  158. if 'priority' not in vals or vals.get('priority') == '0':
  159. request_type_id = vals.get('request_type_id')
  160. business_impact = vals.get('business_impact')
  161. if request_type_id and business_impact:
  162. priority = self._compute_priority_from_impact(request_type_id, business_impact)
  163. if priority is not None:
  164. vals['priority'] = priority
  165. return super().create(vals_list)
  166. def write(self, vals):
  167. """Override write to recalculate priority when request_type or business_impact changes."""
  168. result = super().write(vals)
  169. # If either field was updated, recalculate priority for affected records
  170. if 'request_type_id' in vals or 'business_impact' in vals:
  171. for record in self:
  172. priority = record._compute_priority_from_impact()
  173. if priority is not None and record.priority != priority:
  174. # Use super().write to avoid recursion
  175. super(HelpdeskTicket, record).write({'priority': priority})
  176. return result