helpdesk_ticket.py 7.2 KB

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