helpdesk_ticket.py 7.6 KB

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