helpdesk_ticket.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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', 'team_id.workflow_template_id')
  86. def _compute_has_template(self):
  87. """Compute if team has a template (supports both legacy template_id and workflow_template_id)"""
  88. for ticket in self:
  89. has_legacy_template = bool(ticket.team_id and ticket.team_id.template_id)
  90. has_workflow_template = bool(ticket.team_id and ticket.team_id.workflow_template_id and ticket.team_id.workflow_template_id.field_ids)
  91. ticket.has_template = has_legacy_template or has_workflow_template
  92. @api.model
  93. def _default_request_type_id(self):
  94. """Default to 'Incident' type if available"""
  95. incident_type = self.env.ref(
  96. 'helpdesk_extras.type_incident',
  97. raise_if_not_found=False
  98. )
  99. return incident_type.id if incident_type else False
  100. def _get_template_fields(self):
  101. """Get template fields for this ticket's team (supports both legacy template_id and workflow_template_id)"""
  102. self.ensure_one()
  103. if not self.team_id:
  104. return self.env['helpdesk.template.field']
  105. # Priority: workflow_template_id over template_id (legacy)
  106. if self.team_id.workflow_template_id and self.team_id.workflow_template_id.field_ids:
  107. return self.team_id.workflow_template_id.field_ids.sorted('sequence')
  108. elif self.team_id.template_id and self.team_id.template_id.field_ids:
  109. return self.team_id.template_id.field_ids.sorted('sequence')
  110. return self.env['helpdesk.template.field']
  111. @api.onchange('request_type_id', 'business_impact')
  112. def _onchange_compute_priority(self):
  113. """
  114. Auto-calculate priority based on request type and business impact.
  115. Only applies when both fields have values.
  116. Mapping:
  117. - Incident + Critical = 3 (Urgent)
  118. - Incident + High = 2 (High)
  119. - Incident + Normal = 1 (Normal)
  120. - Improvement + Critical = 2 (High)
  121. - Improvement + High = 1 (Normal)
  122. - Improvement + Normal = 0 (Low)
  123. """
  124. priority = self._compute_priority_from_impact()
  125. if priority is not None:
  126. self.priority = priority
  127. def _compute_priority_from_impact(self, request_type_id=None, business_impact=None):
  128. """
  129. Helper method to compute priority from request type and business impact.
  130. Can be used by onchange, create, and write methods.
  131. Returns the priority string or None if not applicable.
  132. """
  133. # Use provided values or instance values
  134. if request_type_id is None:
  135. request_type = self.request_type_id
  136. else:
  137. request_type = self.env['helpdesk.request.type'].browse(request_type_id) if isinstance(request_type_id, int) else request_type_id
  138. if business_impact is None:
  139. business_impact = self.business_impact
  140. if not request_type or not business_impact:
  141. return None
  142. type_code = request_type.code if hasattr(request_type, 'code') else ''
  143. if not type_code:
  144. return None
  145. # Priority mapping based on business_impact
  146. # business_impact: '0' = Critical, '1' = High, '2' = Normal
  147. # priority: '0' = Low, '1' = Normal, '2' = High, '3' = Urgent
  148. priority_map = {
  149. # Incidents get +1 priority boost
  150. ('incident', '0'): '3', # Incident + Critical = Urgent
  151. ('incident', '1'): '2', # Incident + High = High
  152. ('incident', '2'): '1', # Incident + Normal = Normal
  153. # Improvements have standard mapping
  154. ('improvement', '0'): '2', # Improvement + Critical = High
  155. ('improvement', '1'): '1', # Improvement + High = Normal
  156. ('improvement', '2'): '0', # Improvement + Normal = Low
  157. }
  158. key = (type_code, business_impact)
  159. return priority_map.get(key)
  160. @api.model_create_multi
  161. def create(self, vals_list):
  162. """Override create to auto-calculate priority for website form submissions."""
  163. for vals in vals_list:
  164. # Only calculate if priority not explicitly set and we have both fields
  165. if 'priority' not in vals or vals.get('priority') == '0':
  166. request_type_id = vals.get('request_type_id')
  167. business_impact = vals.get('business_impact')
  168. if request_type_id and business_impact:
  169. priority = self._compute_priority_from_impact(request_type_id, business_impact)
  170. if priority is not None:
  171. vals['priority'] = priority
  172. return super().create(vals_list)
  173. def write(self, vals):
  174. """Override write to recalculate priority when request_type or business_impact changes."""
  175. result = super().write(vals)
  176. # If either field was updated, recalculate priority for affected records
  177. if 'request_type_id' in vals or 'business_impact' in vals:
  178. for record in self:
  179. priority = record._compute_priority_from_impact()
  180. if priority is not None and record.priority != priority:
  181. # Use super().write to avoid recursion
  182. super(HelpdeskTicket, record).write({'priority': priority})
  183. return result