helpdesk_template.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704
  1. # -*- coding: utf-8 -*-
  2. # Part of Odoo. See LICENSE file for full copyright and licensing details.
  3. import logging
  4. from odoo import api, fields, models, _
  5. from odoo.exceptions import UserError
  6. _logger = logging.getLogger(__name__)
  7. class HelpdeskTemplate(models.Model):
  8. _name = 'helpdesk.template'
  9. _description = 'Helpdesk Template'
  10. _order = 'name'
  11. name = fields.Char(
  12. string='Name',
  13. required=True,
  14. translate=True,
  15. help="Name of the template"
  16. )
  17. description = fields.Text(
  18. string='Description',
  19. translate=True,
  20. help="Description of the template"
  21. )
  22. active = fields.Boolean(
  23. string='Active',
  24. default=True,
  25. help="If unchecked, this template will be hidden and won't be available"
  26. )
  27. field_ids = fields.One2many(
  28. 'helpdesk.template.field',
  29. 'template_id',
  30. string='Fields',
  31. copy=True,
  32. help="Fields included in this template"
  33. )
  34. @api.model
  35. def default_get(self, fields_list):
  36. """Set default required fields when creating a new template"""
  37. res = super().default_get(fields_list)
  38. # Only set defaults if creating a new record (not editing existing)
  39. if 'field_ids' in fields_list and not self.env.context.get('default_field_ids'):
  40. # Get the required fields from form builder (same as website_helpdesk_form_editor.js)
  41. required_field_names = ['partner_name', 'partner_email', 'name', 'description']
  42. # Get field records
  43. ticket_model = self.env['ir.model'].search([('model', '=', 'helpdesk.ticket')], limit=1)
  44. if ticket_model:
  45. # Find the field records
  46. required_fields = self.env['ir.model.fields'].search([
  47. ('model_id', '=', ticket_model.id),
  48. ('name', 'in', required_field_names),
  49. ('website_form_blacklisted', '=', False)
  50. ])
  51. # Create field mapping
  52. field_map = {f.name: f for f in required_fields}
  53. # Prepare default field_ids
  54. field_ids_commands = []
  55. sequence = 10
  56. # Add partner_name (required: true, sequence 10)
  57. if 'partner_name' in field_map:
  58. field_ids_commands.append((0, 0, {
  59. 'field_id': field_map['partner_name'].id,
  60. 'required': True,
  61. 'sequence': sequence
  62. }))
  63. sequence += 10
  64. # Add partner_email (required: true, sequence 20)
  65. if 'partner_email' in field_map:
  66. field_ids_commands.append((0, 0, {
  67. 'field_id': field_map['partner_email'].id,
  68. 'required': True,
  69. 'sequence': sequence
  70. }))
  71. sequence += 10
  72. # Add name (modelRequired: true, sequence 30) - required by model
  73. # Note: model_required will be set automatically in create() based on field.required
  74. if 'name' in field_map:
  75. name_field = field_map['name']
  76. field_ids_commands.append((0, 0, {
  77. 'field_id': name_field.id,
  78. 'required': True, # Mark as required since it's modelRequired
  79. 'model_required': name_field.required, # Auto-detect from field definition
  80. 'sequence': sequence
  81. }))
  82. sequence += 10
  83. # Add description (required: true, sequence 40)
  84. if 'description' in field_map:
  85. field_ids_commands.append((0, 0, {
  86. 'field_id': field_map['description'].id,
  87. 'required': True,
  88. 'sequence': sequence
  89. }))
  90. sequence += 10
  91. if field_ids_commands:
  92. res['field_ids'] = field_ids_commands
  93. _logger.info(f"Setting default required fields for new template: {[cmd[2]['field_id'] for cmd in field_ids_commands]}")
  94. return res
  95. @api.model_create_multi
  96. def create(self, vals_list):
  97. """Override create to automatically add required fields from form builder"""
  98. # Get the required fields from form builder (same as website_helpdesk_form_editor.js)
  99. # These are the fields that are always required in the form builder:
  100. # - partner_name (required: true)
  101. # - partner_email (required: true)
  102. # - name (modelRequired: true) - required by the model
  103. # - description (required: true)
  104. required_field_names = ['partner_name', 'partner_email', 'name', 'description']
  105. # Get field records
  106. ticket_model = self.env['ir.model'].search([('model', '=', 'helpdesk.ticket')], limit=1)
  107. if not ticket_model:
  108. return super().create(vals_list)
  109. # Find the field records
  110. required_fields = self.env['ir.model.fields'].search([
  111. ('model_id', '=', ticket_model.id),
  112. ('name', 'in', required_field_names),
  113. ('website_form_blacklisted', '=', False)
  114. ])
  115. # Create field mapping
  116. field_map = {f.name: f for f in required_fields}
  117. # Prepare default field_ids for each template
  118. for vals in vals_list:
  119. if 'field_ids' not in vals or not vals.get('field_ids'):
  120. # Only add default fields if no fields are provided
  121. field_ids_commands = []
  122. sequence = 10
  123. # Add partner_name (required: true, sequence 10)
  124. if 'partner_name' in field_map:
  125. field_ids_commands.append((0, 0, {
  126. 'field_id': field_map['partner_name'].id,
  127. 'required': True,
  128. 'sequence': sequence
  129. }))
  130. sequence += 10
  131. # Add partner_email (required: true, sequence 20)
  132. if 'partner_email' in field_map:
  133. field_ids_commands.append((0, 0, {
  134. 'field_id': field_map['partner_email'].id,
  135. 'required': True,
  136. 'sequence': sequence
  137. }))
  138. sequence += 10
  139. # Add name (modelRequired: true, sequence 30) - required by model
  140. # Note: model_required will be set automatically in create() based on field.required
  141. if 'name' in field_map:
  142. name_field = field_map['name']
  143. field_ids_commands.append((0, 0, {
  144. 'field_id': name_field.id,
  145. 'required': True, # Mark as required since it's modelRequired
  146. 'model_required': name_field.required, # Auto-detect from field definition
  147. 'sequence': sequence
  148. }))
  149. sequence += 10
  150. # Add description (required: true, sequence 40)
  151. if 'description' in field_map:
  152. field_ids_commands.append((0, 0, {
  153. 'field_id': field_map['description'].id,
  154. 'required': True,
  155. 'sequence': sequence
  156. }))
  157. sequence += 10
  158. if field_ids_commands:
  159. vals['field_ids'] = field_ids_commands
  160. _logger.info(f"Adding default required fields to new template: {[cmd[2]['field_id'] for cmd in field_ids_commands]}")
  161. return super().create(vals_list)
  162. def write(self, vals):
  163. """Override write to regenerate forms in all teams using this template"""
  164. result = super().write(vals)
  165. # If template fields or active status changed, regenerate forms in all teams using this template
  166. # Note: field_ids changes are handled by helpdesk.template.field create/write/unlink methods
  167. # but we also check here in case field_ids is directly modified
  168. if 'field_ids' in vals or 'active' in vals:
  169. # Find all teams using this template
  170. teams = self.env['helpdesk.team'].search([
  171. ('template_id', 'in', self.ids),
  172. ('use_website_helpdesk_form', '=', True)
  173. ])
  174. # Regenerate form XML for each team
  175. for team in teams:
  176. # Ensure view exists before regenerating
  177. if not team.website_form_view_id:
  178. team._ensure_submit_form_view()
  179. # Regenerate or restore form based on template status
  180. if team.website_form_view_id:
  181. try:
  182. if team.template_id.active:
  183. team._regenerate_form_from_template()
  184. _logger.info(f"Regenerated form for team {team.id} after template {team.template_id.id} change")
  185. else:
  186. # If template is deactivated, restore default form
  187. team._restore_default_form()
  188. _logger.info(f"Restored default form for team {team.id} after template {team.template_id.id} deactivation")
  189. except Exception as e:
  190. _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
  191. return result
  192. class HelpdeskTemplateField(models.Model):
  193. _name = 'helpdesk.template.field'
  194. _description = 'Helpdesk Template Field'
  195. _order = 'sequence, id'
  196. template_id = fields.Many2one(
  197. 'helpdesk.template',
  198. string='Template',
  199. required=True,
  200. ondelete='cascade',
  201. index=True
  202. )
  203. field_id = fields.Many2one(
  204. 'ir.model.fields',
  205. string='Field',
  206. required=True,
  207. domain="[('model', '=', 'helpdesk.ticket'), ('website_form_blacklisted', '=', False)]",
  208. ondelete='cascade',
  209. help="Field from helpdesk.ticket model"
  210. )
  211. field_name = fields.Char(
  212. related='field_id.name',
  213. string='Field Name',
  214. store=True,
  215. readonly=True
  216. )
  217. field_type = fields.Selection(
  218. related='field_id.ttype',
  219. string='Field Type',
  220. readonly=True
  221. )
  222. label_custom = fields.Char(
  223. string='Custom Label',
  224. help="Custom label for the field in the form. If empty, uses the field's default label."
  225. )
  226. placeholder = fields.Text(
  227. string='Placeholder',
  228. help="Placeholder text shown when field is empty"
  229. )
  230. default_value = fields.Char(
  231. string='Default Value',
  232. help="Default value for the field"
  233. )
  234. help_text = fields.Html(
  235. string='Help Text',
  236. help="Help text/description shown below the field (supports HTML formatting)"
  237. )
  238. widget = fields.Selection(
  239. [
  240. ('default', 'Default'),
  241. ('radio', 'Radio Buttons'),
  242. ('checkbox', 'Checkboxes'),
  243. ],
  244. string='Widget',
  245. default='default',
  246. help="Widget to use for selection/many2one fields. Default uses dropdown select."
  247. )
  248. selection_options = fields.Text(
  249. string='Selection Options',
  250. help="For selection fields (not relations): JSON array of [value, label] pairs. Example: [['option1', 'Option 1'], ['option2', 'Option 2']]"
  251. )
  252. sequence = fields.Integer(
  253. string='Sequence',
  254. default=10,
  255. help="Order in which fields are displayed"
  256. )
  257. required = fields.Boolean(
  258. string='Required',
  259. default=False,
  260. help="Make this field required in addition to its base configuration"
  261. )
  262. model_required = fields.Boolean(
  263. string='Model Required',
  264. default=False,
  265. readonly=True,
  266. help="This field is mandatory for the model and cannot be removed"
  267. )
  268. # Visibility conditions
  269. visibility_dependency = fields.Many2one(
  270. 'ir.model.fields',
  271. string='Visibility Dependency',
  272. domain="[('model', '=', 'helpdesk.ticket'), ('website_form_blacklisted', '=', False)]",
  273. help="Field on which visibility depends"
  274. )
  275. visibility_condition = fields.Char(
  276. string='Visibility Condition Value',
  277. help="Value to compare against the dependency field (for text, number, date, etc.)"
  278. )
  279. visibility_comparator = fields.Selection(
  280. [
  281. # Basic comparators
  282. ('equal', 'Is equal to'),
  283. ('!equal', 'Is not equal to'),
  284. ('contains', 'Contains'),
  285. ('!contains', "Doesn't contain"),
  286. ('set', 'Is set'),
  287. ('!set', 'Is not set'),
  288. # Numeric comparators
  289. ('greater', 'Is greater than'),
  290. ('less', 'Is less than'),
  291. ('greater or equal', 'Is greater than or equal to'),
  292. ('less or equal', 'Is less than or equal to'),
  293. # Date/Datetime comparators
  294. ('dateEqual', 'Is equal to (date)'),
  295. ('date!equal', 'Is not equal to (date)'),
  296. ('after', 'Is after'),
  297. ('before', 'Is before'),
  298. ('equal or after', 'Is after or equal to'),
  299. ('equal or before', 'Is before or equal to'),
  300. ('between', 'Is between (included)'),
  301. ('!between', 'Is not between (excluded)'),
  302. # Selection/Many2one comparators
  303. ('selected', 'Is equal to (selected)'),
  304. ('!selected', 'Is not equal to (not selected)'),
  305. # File comparators
  306. ('fileSet', 'Is set (file)'),
  307. ('!fileSet', 'Is not set (file)'),
  308. ],
  309. string='Visibility Comparator',
  310. default='equal',
  311. help="Comparison operator for visibility condition"
  312. )
  313. # Computed field to determine dependency field type
  314. visibility_dependency_type = fields.Char(
  315. string='Dependency Field Type',
  316. compute='_compute_visibility_dependency_type',
  317. store=False,
  318. help="Type of the visibility dependency field"
  319. )
  320. # Field for many2one dependency - store ID as Integer (not Many2one to avoid model validation)
  321. # The widget will handle the dynamic model change and display
  322. visibility_condition_m2o_id = fields.Integer(
  323. string='Visibility Condition (Many2one ID)',
  324. help="ID of the selected record when dependency is a many2one field (model stored separately)"
  325. )
  326. visibility_condition_m2o_model = fields.Char(
  327. string='M2O Model',
  328. related='visibility_dependency.relation',
  329. store=False,
  330. readonly=True,
  331. help="Model name for the many2one condition"
  332. )
  333. # Field for selection dependency - computed selection options
  334. visibility_condition_selection = fields.Selection(
  335. selection='_get_visibility_condition_selection_options',
  336. string='Visibility Condition (Selection)',
  337. help="Selected value when dependency is a selection field"
  338. )
  339. # Field for range conditions (between/!between) - second value for date/datetime ranges
  340. visibility_between = fields.Char(
  341. string='Visibility Between (End Value)',
  342. help="Second value for 'between' and '!between' comparators (for date/datetime ranges)"
  343. )
  344. def _get_visibility_condition_selection_options(self):
  345. """Return selection options based on visibility_dependency field"""
  346. # Handle empty recordset (when called from fields_get)
  347. if not self:
  348. return []
  349. # Handle multiple records (shouldn't happen, but be safe)
  350. if len(self) > 1:
  351. return []
  352. record = self[0] if self else None
  353. if not record or not record.visibility_dependency or record.visibility_dependency.ttype != 'selection':
  354. return []
  355. # Get selection options from ir.model.fields.selection
  356. selection_records = self.env['ir.model.fields.selection'].search([
  357. ('field_id', '=', record.visibility_dependency.id)
  358. ], order='sequence, id')
  359. if selection_records:
  360. return [(sel.value, sel.name) for sel in selection_records]
  361. # Fallback: try to get from field definition (for old-style selection)
  362. try:
  363. model = self.env[record.visibility_dependency.model]
  364. field = model._fields.get(record.visibility_dependency.name)
  365. if field and hasattr(field, 'selection') and field.selection:
  366. if callable(field.selection):
  367. return field.selection(model)
  368. return field.selection
  369. except:
  370. pass
  371. return []
  372. @api.depends('visibility_dependency')
  373. def _compute_visibility_dependency_type(self):
  374. """Compute the type of the visibility dependency field"""
  375. for record in self:
  376. if record.visibility_dependency:
  377. record.visibility_dependency_type = record.visibility_dependency.ttype
  378. else:
  379. record.visibility_dependency_type = False
  380. @api.onchange('visibility_condition_m2o_id', 'visibility_dependency')
  381. def _onchange_visibility_condition_m2o_id(self):
  382. """Sync many2one ID to visibility_condition"""
  383. if self.visibility_dependency and self.visibility_dependency.ttype == 'many2one':
  384. if self.visibility_condition_m2o_id:
  385. self.visibility_condition = str(self.visibility_condition_m2o_id)
  386. else:
  387. self.visibility_condition = False
  388. @api.onchange('visibility_condition_selection')
  389. def _onchange_visibility_condition_selection(self):
  390. """Sync selection value to visibility_condition"""
  391. if self.visibility_condition_selection:
  392. self.visibility_condition = self.visibility_condition_selection
  393. @api.onchange('visibility_dependency')
  394. def _onchange_visibility_dependency(self):
  395. """Clear condition values when dependency changes"""
  396. if not self.visibility_dependency:
  397. self.visibility_condition = False
  398. self.visibility_condition_m2o_id = False
  399. self.visibility_condition_selection = False
  400. elif self.visibility_dependency.ttype not in ['many2one', 'selection']:
  401. self.visibility_condition_m2o_id = False
  402. self.visibility_condition_selection = False
  403. elif self.visibility_dependency.ttype == 'many2one':
  404. # Load current value into m2o_id if exists
  405. if self.visibility_condition and self.visibility_condition.isdigit():
  406. try:
  407. model_name = self.visibility_dependency.relation
  408. if model_name:
  409. model = self.env[model_name]
  410. record = model.browse(int(self.visibility_condition))
  411. if record.exists():
  412. # Store the ID - the widget will handle the model change
  413. self.visibility_condition_m2o_id = int(self.visibility_condition)
  414. else:
  415. self.visibility_condition_m2o_id = False
  416. else:
  417. self.visibility_condition_m2o_id = False
  418. except:
  419. self.visibility_condition_m2o_id = False
  420. elif self.visibility_dependency.ttype == 'selection':
  421. # Load current value into selection if exists
  422. if self.visibility_condition:
  423. self.visibility_condition_selection = self.visibility_condition
  424. @api.onchange('visibility_comparator')
  425. def _onchange_visibility_comparator(self):
  426. """Clear visibility_between when comparator changes away from between/!between"""
  427. if self.visibility_comparator not in ['between', '!between']:
  428. self.visibility_between = False
  429. _sql_constraints = [
  430. ('unique_template_field', 'unique(template_id, field_id)',
  431. 'A field can only be added once to a template')
  432. ]
  433. @api.model
  434. def _register_hook(self):
  435. """Register label_custom field in ir.model.fields if it doesn't exist"""
  436. super()._register_hook()
  437. try:
  438. model = self.env['ir.model'].search([('model', '=', 'helpdesk.template.field')], limit=1)
  439. if model:
  440. field_model = self.env['ir.model.fields']
  441. existing_field = field_model.search([
  442. ('model_id', '=', model.id),
  443. ('name', '=', 'label_custom')
  444. ], limit=1)
  445. if not existing_field:
  446. field_model.create({
  447. 'model_id': model.id,
  448. 'name': 'label_custom',
  449. 'field_description': 'Custom Label',
  450. 'ttype': 'char',
  451. 'state': 'manual',
  452. 'required': False,
  453. 'readonly': False,
  454. 'store': True,
  455. })
  456. _logger.info("Campo label_custom registrado en _register_hook")
  457. except Exception as e:
  458. _logger.error(f"Error registrando label_custom en _register_hook: {str(e)}", exc_info=True)
  459. @api.model
  460. def _migrate_label_custom_field(self):
  461. """
  462. Migration method to ensure label_custom field exists in database.
  463. This method should be called after module update to fix any missing field issues.
  464. """
  465. try:
  466. # Check if column exists in database
  467. self.env.cr.execute("""
  468. SELECT column_name
  469. FROM information_schema.columns
  470. WHERE table_name = 'helpdesk_template_field'
  471. AND column_name = 'label_custom'
  472. """)
  473. column_exists = self.env.cr.fetchone()
  474. if not column_exists:
  475. _logger.warning("Column 'label_custom' does not exist. Adding it...")
  476. # Add column manually if it doesn't exist
  477. self.env.cr.execute("""
  478. ALTER TABLE helpdesk_template_field
  479. ADD COLUMN label_custom VARCHAR
  480. """)
  481. self.env.cr.commit()
  482. _logger.info("Column 'label_custom' added successfully")
  483. else:
  484. _logger.info("Column 'label_custom' already exists")
  485. # Update ir.model.fields to ensure field is registered
  486. field_model = self.env['ir.model.fields']
  487. model_id = self.env['ir.model'].search([('model', '=', 'helpdesk.template.field')], limit=1)
  488. if model_id:
  489. existing_field = field_model.search([
  490. ('model_id', '=', model_id.id),
  491. ('name', '=', 'label_custom')
  492. ], limit=1)
  493. if not existing_field:
  494. _logger.warning("Field 'label_custom' not found in ir.model.fields. Creating it...")
  495. field_model.create({
  496. 'model_id': model_id.id,
  497. 'name': 'label_custom',
  498. 'field_description': 'Custom Label',
  499. 'ttype': 'char',
  500. 'state': 'manual',
  501. })
  502. _logger.info("Field 'label_custom' registered in ir.model.fields")
  503. else:
  504. _logger.info("Field 'label_custom' already registered in ir.model.fields")
  505. # Clear cache to ensure changes are reflected
  506. self.env.registry.clear_cache()
  507. except Exception as e:
  508. _logger.error(f"Error in _migrate_label_custom_field: {str(e)}", exc_info=True)
  509. # Don't raise to avoid breaking module update
  510. @api.model_create_multi
  511. def create(self, vals_list):
  512. """Override create to mark model required fields and regenerate forms when template field is added"""
  513. # Mark model required fields automatically based on field definition
  514. for vals in vals_list:
  515. if 'field_id' in vals and vals['field_id']:
  516. field = self.env['ir.model.fields'].browse(vals['field_id'])
  517. # Check if field is required at model level (not just in form)
  518. # A field is model required if:
  519. # 1. It's in the helpdesk.ticket model
  520. # 2. It has required=True in ir.model.fields (mandatory at model level)
  521. # 3. It's not blacklisted for website forms
  522. if (field.model == 'helpdesk.ticket' and
  523. field.required and
  524. not field.website_form_blacklisted):
  525. vals['model_required'] = True
  526. _logger.info(f"Auto-marked field {field.name} as model_required (required at model level)")
  527. fields_created = super().create(vals_list)
  528. # Get unique templates that were modified
  529. templates = fields_created.mapped('template_id')
  530. # Regenerate forms in all teams using these templates
  531. for template in templates:
  532. if not template:
  533. continue
  534. teams = self.env['helpdesk.team'].search([
  535. ('template_id', '=', template.id),
  536. ('use_website_helpdesk_form', '=', True)
  537. ])
  538. for team in teams:
  539. # Ensure view exists before regenerating
  540. if not team.website_form_view_id:
  541. team._ensure_submit_form_view()
  542. # Regenerate form if view exists
  543. if team.website_form_view_id:
  544. try:
  545. team._regenerate_form_from_template()
  546. _logger.info(f"Regenerated form for team {team.id} after adding field to template {template.id}")
  547. except Exception as e:
  548. _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
  549. return fields_created
  550. def write(self, vals):
  551. """Override write to mark model required fields and regenerate forms when template field is modified"""
  552. # Mark/unmark model_required automatically based on field definition
  553. if 'field_id' in vals and vals['field_id']:
  554. field = self.env['ir.model.fields'].browse(vals['field_id'])
  555. # Check if field is required at model level
  556. if (field.model == 'helpdesk.ticket' and
  557. field.required and
  558. not field.website_form_blacklisted):
  559. vals['model_required'] = True
  560. _logger.info(f"Auto-marked field {field.name} as model_required (required at model level)")
  561. else:
  562. # Field is not model required, unmark it
  563. vals['model_required'] = False
  564. elif 'field_id' in vals and not vals['field_id']:
  565. # Field_id is being cleared, unmark model_required
  566. vals['model_required'] = False
  567. result = super().write(vals)
  568. # If any field configuration changed, regenerate forms
  569. if any(key in vals for key in ['field_id', 'sequence', 'required', 'visibility_dependency',
  570. 'visibility_condition', 'visibility_comparator', 'label_custom',
  571. 'model_required', 'placeholder', 'default_value', 'help_text',
  572. 'widget', 'selection_options']):
  573. # Get unique templates that were modified
  574. templates = self.mapped('template_id')
  575. # Regenerate forms in all teams using these templates
  576. for template in templates:
  577. if not template:
  578. continue
  579. teams = self.env['helpdesk.team'].search([
  580. ('template_id', '=', template.id),
  581. ('use_website_helpdesk_form', '=', True)
  582. ])
  583. for team in teams:
  584. # Ensure view exists before regenerating
  585. if not team.website_form_view_id:
  586. team._ensure_submit_form_view()
  587. # Regenerate form if view exists
  588. if team.website_form_view_id:
  589. try:
  590. team._regenerate_form_from_template()
  591. _logger.info(f"Regenerated form for team {team.id} after modifying field in template {template.id}")
  592. except Exception as e:
  593. _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
  594. return result
  595. def unlink(self):
  596. """Override unlink to prevent deletion of model required fields and regenerate forms"""
  597. # Prevent deletion of model required fields
  598. model_required_fields = self.filtered('model_required')
  599. if model_required_fields:
  600. field_names = [f.field_id.name if f.field_id else 'Unknown' for f in model_required_fields]
  601. raise UserError(
  602. _("Cannot delete model required field(s): %s. This field is mandatory for the model and cannot be removed. "
  603. "Try hiding it with the 'Visibility' option instead and add it a default value.")
  604. % ', '.join(field_names)
  605. )
  606. # Get templates before deletion
  607. templates = self.mapped('template_id')
  608. result = super().unlink()
  609. # Regenerate forms in all teams using these templates
  610. for template in templates:
  611. if not template:
  612. continue
  613. teams = self.env['helpdesk.team'].search([
  614. ('template_id', '=', template.id),
  615. ('use_website_helpdesk_form', '=', True)
  616. ])
  617. for team in teams:
  618. # Ensure view exists before regenerating
  619. if not team.website_form_view_id:
  620. team._ensure_submit_form_view()
  621. # Regenerate form if view exists
  622. if team.website_form_view_id:
  623. try:
  624. team._regenerate_form_from_template()
  625. _logger.info(f"Regenerated form for team {team.id} after removing field from template {template.id}")
  626. except Exception as e:
  627. _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
  628. return result