|
@@ -1,729 +0,0 @@
|
|
|
-# -*- coding: utf-8 -*-
|
|
|
|
|
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
|
-
|
|
|
|
|
-import logging
|
|
|
|
|
-
|
|
|
|
|
-from odoo import api, fields, models, _
|
|
|
|
|
-from odoo.exceptions import UserError
|
|
|
|
|
-
|
|
|
|
|
-_logger = logging.getLogger(__name__)
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-class HelpdeskTemplate(models.Model):
|
|
|
|
|
- _name = 'helpdesk.template'
|
|
|
|
|
- _description = 'Helpdesk Template'
|
|
|
|
|
- _order = 'name'
|
|
|
|
|
-
|
|
|
|
|
- name = fields.Char(
|
|
|
|
|
- string='Name',
|
|
|
|
|
- required=True,
|
|
|
|
|
- translate=True,
|
|
|
|
|
- help="Name of the template"
|
|
|
|
|
- )
|
|
|
|
|
- description = fields.Text(
|
|
|
|
|
- string='Description',
|
|
|
|
|
- translate=True,
|
|
|
|
|
- help="Description of the template"
|
|
|
|
|
- )
|
|
|
|
|
- active = fields.Boolean(
|
|
|
|
|
- string='Active',
|
|
|
|
|
- default=True,
|
|
|
|
|
- help="If unchecked, this template will be hidden and won't be available"
|
|
|
|
|
- )
|
|
|
|
|
- field_ids = fields.One2many(
|
|
|
|
|
- 'helpdesk.template.field',
|
|
|
|
|
- 'template_id',
|
|
|
|
|
- string='Fields',
|
|
|
|
|
- copy=True,
|
|
|
|
|
- help="Fields included in this template"
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- @api.model
|
|
|
|
|
- def default_get(self, fields_list):
|
|
|
|
|
- """Set default required fields when creating a new template"""
|
|
|
|
|
- res = super().default_get(fields_list)
|
|
|
|
|
-
|
|
|
|
|
- # Only set defaults if creating a new record (not editing existing)
|
|
|
|
|
- if 'field_ids' in fields_list and not self.env.context.get('default_field_ids'):
|
|
|
|
|
- # Get the required fields from form builder (same as website_helpdesk_form_editor.js)
|
|
|
|
|
- required_field_names = ['partner_name', 'partner_email', 'name', 'description']
|
|
|
|
|
-
|
|
|
|
|
- # Get field records
|
|
|
|
|
- ticket_model = self.env['ir.model'].search([('model', '=', 'helpdesk.ticket')], limit=1)
|
|
|
|
|
- if ticket_model:
|
|
|
|
|
- # Find the field records
|
|
|
|
|
- required_fields = self.env['ir.model.fields'].search([
|
|
|
|
|
- ('model_id', '=', ticket_model.id),
|
|
|
|
|
- ('name', 'in', required_field_names),
|
|
|
|
|
- ('website_form_blacklisted', '=', False)
|
|
|
|
|
- ])
|
|
|
|
|
-
|
|
|
|
|
- # Create field mapping
|
|
|
|
|
- field_map = {f.name: f for f in required_fields}
|
|
|
|
|
-
|
|
|
|
|
- # Prepare default field_ids
|
|
|
|
|
- field_ids_commands = []
|
|
|
|
|
- sequence = 10
|
|
|
|
|
-
|
|
|
|
|
- # Add partner_name (required: true, sequence 10)
|
|
|
|
|
- if 'partner_name' in field_map:
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': field_map['partner_name'].id,
|
|
|
|
|
- 'required': True,
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- # Add partner_email (required: true, sequence 20)
|
|
|
|
|
- if 'partner_email' in field_map:
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': field_map['partner_email'].id,
|
|
|
|
|
- 'required': True,
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- # Add name (modelRequired: true, sequence 30) - required by model
|
|
|
|
|
- # Note: model_required will be set automatically in create() based on field.required
|
|
|
|
|
- if 'name' in field_map:
|
|
|
|
|
- name_field = field_map['name']
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': name_field.id,
|
|
|
|
|
- 'required': True, # Mark as required since it's modelRequired
|
|
|
|
|
- 'model_required': name_field.required, # Auto-detect from field definition
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- # Add description (required: true, sequence 40)
|
|
|
|
|
- if 'description' in field_map:
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': field_map['description'].id,
|
|
|
|
|
- 'required': True,
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- if field_ids_commands:
|
|
|
|
|
- res['field_ids'] = field_ids_commands
|
|
|
|
|
- _logger.info(f"Setting default required fields for new template: {[cmd[2]['field_id'] for cmd in field_ids_commands]}")
|
|
|
|
|
-
|
|
|
|
|
- return res
|
|
|
|
|
-
|
|
|
|
|
- @api.model_create_multi
|
|
|
|
|
- def create(self, vals_list):
|
|
|
|
|
- """Override create to automatically add required fields from form builder"""
|
|
|
|
|
- # Get the required fields from form builder (same as website_helpdesk_form_editor.js)
|
|
|
|
|
- # These are the fields that are always required in the form builder:
|
|
|
|
|
- # - partner_name (required: true)
|
|
|
|
|
- # - partner_email (required: true)
|
|
|
|
|
- # - name (modelRequired: true) - required by the model
|
|
|
|
|
- # - description (required: true)
|
|
|
|
|
- required_field_names = ['partner_name', 'partner_email', 'name', 'description']
|
|
|
|
|
-
|
|
|
|
|
- # Get field records
|
|
|
|
|
- ticket_model = self.env['ir.model'].search([('model', '=', 'helpdesk.ticket')], limit=1)
|
|
|
|
|
- if not ticket_model:
|
|
|
|
|
- return super().create(vals_list)
|
|
|
|
|
-
|
|
|
|
|
- # Find the field records
|
|
|
|
|
- required_fields = self.env['ir.model.fields'].search([
|
|
|
|
|
- ('model_id', '=', ticket_model.id),
|
|
|
|
|
- ('name', 'in', required_field_names),
|
|
|
|
|
- ('website_form_blacklisted', '=', False)
|
|
|
|
|
- ])
|
|
|
|
|
-
|
|
|
|
|
- # Create field mapping
|
|
|
|
|
- field_map = {f.name: f for f in required_fields}
|
|
|
|
|
-
|
|
|
|
|
- # Prepare default field_ids for each template
|
|
|
|
|
- for vals in vals_list:
|
|
|
|
|
- if 'field_ids' not in vals or not vals.get('field_ids'):
|
|
|
|
|
- # Only add default fields if no fields are provided
|
|
|
|
|
- field_ids_commands = []
|
|
|
|
|
- sequence = 10
|
|
|
|
|
-
|
|
|
|
|
- # Add partner_name (required: true, sequence 10)
|
|
|
|
|
- if 'partner_name' in field_map:
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': field_map['partner_name'].id,
|
|
|
|
|
- 'required': True,
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- # Add partner_email (required: true, sequence 20)
|
|
|
|
|
- if 'partner_email' in field_map:
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': field_map['partner_email'].id,
|
|
|
|
|
- 'required': True,
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- # Add name (modelRequired: true, sequence 30) - required by model
|
|
|
|
|
- # Note: model_required will be set automatically in create() based on field.required
|
|
|
|
|
- if 'name' in field_map:
|
|
|
|
|
- name_field = field_map['name']
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': name_field.id,
|
|
|
|
|
- 'required': True, # Mark as required since it's modelRequired
|
|
|
|
|
- 'model_required': name_field.required, # Auto-detect from field definition
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- # Add description (required: true, sequence 40)
|
|
|
|
|
- if 'description' in field_map:
|
|
|
|
|
- field_ids_commands.append((0, 0, {
|
|
|
|
|
- 'field_id': field_map['description'].id,
|
|
|
|
|
- 'required': True,
|
|
|
|
|
- 'sequence': sequence
|
|
|
|
|
- }))
|
|
|
|
|
- sequence += 10
|
|
|
|
|
-
|
|
|
|
|
- if field_ids_commands:
|
|
|
|
|
- vals['field_ids'] = field_ids_commands
|
|
|
|
|
- _logger.info(f"Adding default required fields to new template: {[cmd[2]['field_id'] for cmd in field_ids_commands]}")
|
|
|
|
|
-
|
|
|
|
|
- return super().create(vals_list)
|
|
|
|
|
-
|
|
|
|
|
- def write(self, vals):
|
|
|
|
|
- """Override write to regenerate forms in all teams using this template"""
|
|
|
|
|
- result = super().write(vals)
|
|
|
|
|
-
|
|
|
|
|
- # If template fields or active status changed, regenerate forms in all teams using this template
|
|
|
|
|
- # Note: field_ids changes are handled by helpdesk.template.field create/write/unlink methods
|
|
|
|
|
- # but we also check here in case field_ids is directly modified
|
|
|
|
|
- if 'field_ids' in vals or 'active' in vals:
|
|
|
|
|
- # Find all teams using this template
|
|
|
|
|
- teams = self.env['helpdesk.team'].search([
|
|
|
|
|
- ('template_id', 'in', self.ids),
|
|
|
|
|
- ('use_website_helpdesk_form', '=', True)
|
|
|
|
|
- ])
|
|
|
|
|
-
|
|
|
|
|
- # Regenerate form XML for each team
|
|
|
|
|
- for team in teams:
|
|
|
|
|
- # Ensure view exists before regenerating
|
|
|
|
|
- if not team.website_form_view_id:
|
|
|
|
|
- team._ensure_submit_form_view()
|
|
|
|
|
- # Regenerate or restore form based on template status
|
|
|
|
|
- if team.website_form_view_id:
|
|
|
|
|
- try:
|
|
|
|
|
- if team.template_id.active:
|
|
|
|
|
- team._regenerate_form_from_template()
|
|
|
|
|
- _logger.info(f"Regenerated form for team {team.id} after template {team.template_id.id} change")
|
|
|
|
|
- else:
|
|
|
|
|
- # If template is deactivated, restore default form
|
|
|
|
|
- team._restore_default_form()
|
|
|
|
|
- _logger.info(f"Restored default form for team {team.id} after template {team.template_id.id} deactivation")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
|
|
|
|
|
-
|
|
|
|
|
- return result
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
-class HelpdeskTemplateField(models.Model):
|
|
|
|
|
- _name = 'helpdesk.template.field'
|
|
|
|
|
- _description = 'Helpdesk Template Field'
|
|
|
|
|
- _order = 'sequence, id'
|
|
|
|
|
-
|
|
|
|
|
- template_id = fields.Many2one(
|
|
|
|
|
- 'helpdesk.template',
|
|
|
|
|
- string='Template',
|
|
|
|
|
- required=True,
|
|
|
|
|
- ondelete='cascade',
|
|
|
|
|
- index=True
|
|
|
|
|
- )
|
|
|
|
|
- field_id = fields.Many2one(
|
|
|
|
|
- 'ir.model.fields',
|
|
|
|
|
- string='Field',
|
|
|
|
|
- required=True,
|
|
|
|
|
- domain="[('model', '=', 'helpdesk.ticket'), ('website_form_blacklisted', '=', False)]",
|
|
|
|
|
- ondelete='cascade',
|
|
|
|
|
- help="Field from helpdesk.ticket model"
|
|
|
|
|
- )
|
|
|
|
|
- field_name = fields.Char(
|
|
|
|
|
- related='field_id.name',
|
|
|
|
|
- string='Field Name',
|
|
|
|
|
- store=True,
|
|
|
|
|
- readonly=True
|
|
|
|
|
- )
|
|
|
|
|
- field_type = fields.Selection(
|
|
|
|
|
- related='field_id.ttype',
|
|
|
|
|
- string='Field Type',
|
|
|
|
|
- readonly=True
|
|
|
|
|
- )
|
|
|
|
|
- label_custom = fields.Char(
|
|
|
|
|
- string='Custom Label',
|
|
|
|
|
- help="Custom label for the field in the form. If empty, uses the field's default label."
|
|
|
|
|
- )
|
|
|
|
|
- placeholder = fields.Text(
|
|
|
|
|
- string='Placeholder',
|
|
|
|
|
- help="Placeholder text shown when field is empty"
|
|
|
|
|
- )
|
|
|
|
|
- default_value = fields.Char(
|
|
|
|
|
- string='Default Value',
|
|
|
|
|
- help="Default value for the field"
|
|
|
|
|
- )
|
|
|
|
|
- help_text = fields.Html(
|
|
|
|
|
- string='Help Text',
|
|
|
|
|
- help="Help text/description shown below the field (supports HTML formatting)"
|
|
|
|
|
- )
|
|
|
|
|
- widget = fields.Selection(
|
|
|
|
|
- [
|
|
|
|
|
- ('default', 'Default'),
|
|
|
|
|
- ('radio', 'Radio Buttons'),
|
|
|
|
|
- ('checkbox', 'Checkboxes'),
|
|
|
|
|
- ],
|
|
|
|
|
- string='Widget',
|
|
|
|
|
- default='default',
|
|
|
|
|
- help="Widget to use for selection/many2one fields. Default uses dropdown select."
|
|
|
|
|
- )
|
|
|
|
|
- selection_type = fields.Selection(
|
|
|
|
|
- [
|
|
|
|
|
- ('dropdown', 'Dropdown List'),
|
|
|
|
|
- ('radio', 'Radio'),
|
|
|
|
|
- ],
|
|
|
|
|
- string='Selection Type',
|
|
|
|
|
- default='dropdown',
|
|
|
|
|
- help="Display type for selection and many2one fields. Dropdown List shows a select dropdown, Radio shows radio buttons. Same as Odoo formbuilder."
|
|
|
|
|
- )
|
|
|
|
|
- selection_options = fields.Text(
|
|
|
|
|
- string='Selection Options',
|
|
|
|
|
- help="For selection fields (not relations): JSON array of [value, label] pairs. Example: [['option1', 'Option 1'], ['option2', 'Option 2']]"
|
|
|
|
|
- )
|
|
|
|
|
- rows = fields.Integer(
|
|
|
|
|
- string='Height (Rows)',
|
|
|
|
|
- default=3,
|
|
|
|
|
- help="Number of rows for textarea fields. Default is 3."
|
|
|
|
|
- )
|
|
|
|
|
- input_type = fields.Selection(
|
|
|
|
|
- [
|
|
|
|
|
- ('text', 'Text'),
|
|
|
|
|
- ('email', 'Email'),
|
|
|
|
|
- ('tel', 'Telephone'),
|
|
|
|
|
- ('url', 'Url'),
|
|
|
|
|
- ],
|
|
|
|
|
- string='Input Type',
|
|
|
|
|
- default='text',
|
|
|
|
|
- help="Input type for text fields. Determines the HTML input type attribute."
|
|
|
|
|
- )
|
|
|
|
|
- sequence = fields.Integer(
|
|
|
|
|
- string='Sequence',
|
|
|
|
|
- default=10,
|
|
|
|
|
- help="Order in which fields are displayed"
|
|
|
|
|
- )
|
|
|
|
|
- required = fields.Boolean(
|
|
|
|
|
- string='Required',
|
|
|
|
|
- default=False,
|
|
|
|
|
- help="Make this field required in addition to its base configuration"
|
|
|
|
|
- )
|
|
|
|
|
- model_required = fields.Boolean(
|
|
|
|
|
- string='Model Required',
|
|
|
|
|
- default=False,
|
|
|
|
|
- readonly=True,
|
|
|
|
|
- help="This field is mandatory for the model and cannot be removed"
|
|
|
|
|
- )
|
|
|
|
|
- # Visibility conditions
|
|
|
|
|
- visibility_dependency = fields.Many2one(
|
|
|
|
|
- 'ir.model.fields',
|
|
|
|
|
- string='Visibility Dependency',
|
|
|
|
|
- domain="[('model', '=', 'helpdesk.ticket'), ('website_form_blacklisted', '=', False)]",
|
|
|
|
|
- help="Field on which visibility depends"
|
|
|
|
|
- )
|
|
|
|
|
- visibility_condition = fields.Char(
|
|
|
|
|
- string='Visibility Condition Value',
|
|
|
|
|
- help="Value to compare against the dependency field (for text, number, date, etc.)"
|
|
|
|
|
- )
|
|
|
|
|
- visibility_comparator = fields.Selection(
|
|
|
|
|
- [
|
|
|
|
|
- # Basic comparators
|
|
|
|
|
- ('equal', 'Is equal to'),
|
|
|
|
|
- ('!equal', 'Is not equal to'),
|
|
|
|
|
- ('contains', 'Contains'),
|
|
|
|
|
- ('!contains', "Doesn't contain"),
|
|
|
|
|
- ('set', 'Is set'),
|
|
|
|
|
- ('!set', 'Is not set'),
|
|
|
|
|
- # Numeric comparators
|
|
|
|
|
- ('greater', 'Is greater than'),
|
|
|
|
|
- ('less', 'Is less than'),
|
|
|
|
|
- ('greater or equal', 'Is greater than or equal to'),
|
|
|
|
|
- ('less or equal', 'Is less than or equal to'),
|
|
|
|
|
- # Date/Datetime comparators
|
|
|
|
|
- ('dateEqual', 'Is equal to (date)'),
|
|
|
|
|
- ('date!equal', 'Is not equal to (date)'),
|
|
|
|
|
- ('after', 'Is after'),
|
|
|
|
|
- ('before', 'Is before'),
|
|
|
|
|
- ('equal or after', 'Is after or equal to'),
|
|
|
|
|
- ('equal or before', 'Is before or equal to'),
|
|
|
|
|
- ('between', 'Is between (included)'),
|
|
|
|
|
- ('!between', 'Is not between (excluded)'),
|
|
|
|
|
- # Selection/Many2one comparators
|
|
|
|
|
- ('selected', 'Is equal to (selected)'),
|
|
|
|
|
- ('!selected', 'Is not equal to (not selected)'),
|
|
|
|
|
- # File comparators
|
|
|
|
|
- ('fileSet', 'Is set (file)'),
|
|
|
|
|
- ('!fileSet', 'Is not set (file)'),
|
|
|
|
|
- ],
|
|
|
|
|
- string='Visibility Comparator',
|
|
|
|
|
- default='equal',
|
|
|
|
|
- help="Comparison operator for visibility condition"
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # Computed field to determine dependency field type
|
|
|
|
|
- visibility_dependency_type = fields.Char(
|
|
|
|
|
- string='Dependency Field Type',
|
|
|
|
|
- compute='_compute_visibility_dependency_type',
|
|
|
|
|
- store=False,
|
|
|
|
|
- help="Type of the visibility dependency field"
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # Field for many2one dependency - store ID as Integer (not Many2one to avoid model validation)
|
|
|
|
|
- # The widget will handle the dynamic model change and display
|
|
|
|
|
- visibility_condition_m2o_id = fields.Integer(
|
|
|
|
|
- string='Visibility Condition (Many2one ID)',
|
|
|
|
|
- help="ID of the selected record when dependency is a many2one field (model stored separately)"
|
|
|
|
|
- )
|
|
|
|
|
- visibility_condition_m2o_model = fields.Char(
|
|
|
|
|
- string='M2O Model',
|
|
|
|
|
- related='visibility_dependency.relation',
|
|
|
|
|
- store=False,
|
|
|
|
|
- readonly=True,
|
|
|
|
|
- help="Model name for the many2one condition"
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # Field for selection dependency - computed selection options
|
|
|
|
|
- visibility_condition_selection = fields.Selection(
|
|
|
|
|
- selection='_get_visibility_condition_selection_options',
|
|
|
|
|
- string='Visibility Condition (Selection)',
|
|
|
|
|
- help="Selected value when dependency is a selection field"
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # Field for range conditions (between/!between) - second value for date/datetime ranges
|
|
|
|
|
- visibility_between = fields.Char(
|
|
|
|
|
- string='Visibility Between (End Value)',
|
|
|
|
|
- help="Second value for 'between' and '!between' comparators (for date/datetime ranges)"
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- def _get_visibility_condition_selection_options(self):
|
|
|
|
|
- """Return selection options based on visibility_dependency field"""
|
|
|
|
|
- # Handle empty recordset (when called from fields_get)
|
|
|
|
|
- if not self:
|
|
|
|
|
- return []
|
|
|
|
|
-
|
|
|
|
|
- # Handle multiple records (shouldn't happen, but be safe)
|
|
|
|
|
- if len(self) > 1:
|
|
|
|
|
- return []
|
|
|
|
|
-
|
|
|
|
|
- record = self[0] if self else None
|
|
|
|
|
- if not record or not record.visibility_dependency or record.visibility_dependency.ttype != 'selection':
|
|
|
|
|
- return []
|
|
|
|
|
-
|
|
|
|
|
- # Get selection options from ir.model.fields.selection
|
|
|
|
|
- selection_records = self.env['ir.model.fields.selection'].search([
|
|
|
|
|
- ('field_id', '=', record.visibility_dependency.id)
|
|
|
|
|
- ], order='sequence, id')
|
|
|
|
|
-
|
|
|
|
|
- if selection_records:
|
|
|
|
|
- return [(sel.value, sel.name) for sel in selection_records]
|
|
|
|
|
-
|
|
|
|
|
- # Fallback: try to get from field definition (for old-style selection)
|
|
|
|
|
- try:
|
|
|
|
|
- model = self.env[record.visibility_dependency.model]
|
|
|
|
|
- field = model._fields.get(record.visibility_dependency.name)
|
|
|
|
|
- if field and hasattr(field, 'selection') and field.selection:
|
|
|
|
|
- if callable(field.selection):
|
|
|
|
|
- return field.selection(model)
|
|
|
|
|
- return field.selection
|
|
|
|
|
- except:
|
|
|
|
|
- pass
|
|
|
|
|
-
|
|
|
|
|
- return []
|
|
|
|
|
-
|
|
|
|
|
- @api.depends('visibility_dependency')
|
|
|
|
|
- def _compute_visibility_dependency_type(self):
|
|
|
|
|
- """Compute the type of the visibility dependency field"""
|
|
|
|
|
- for record in self:
|
|
|
|
|
- if record.visibility_dependency:
|
|
|
|
|
- record.visibility_dependency_type = record.visibility_dependency.ttype
|
|
|
|
|
- else:
|
|
|
|
|
- record.visibility_dependency_type = False
|
|
|
|
|
-
|
|
|
|
|
- @api.onchange('visibility_condition_m2o_id', 'visibility_dependency')
|
|
|
|
|
- def _onchange_visibility_condition_m2o_id(self):
|
|
|
|
|
- """Sync many2one ID to visibility_condition"""
|
|
|
|
|
- if self.visibility_dependency and self.visibility_dependency.ttype == 'many2one':
|
|
|
|
|
- if self.visibility_condition_m2o_id:
|
|
|
|
|
- self.visibility_condition = str(self.visibility_condition_m2o_id)
|
|
|
|
|
- else:
|
|
|
|
|
- self.visibility_condition = False
|
|
|
|
|
-
|
|
|
|
|
- @api.onchange('visibility_condition_selection')
|
|
|
|
|
- def _onchange_visibility_condition_selection(self):
|
|
|
|
|
- """Sync selection value to visibility_condition"""
|
|
|
|
|
- if self.visibility_condition_selection:
|
|
|
|
|
- self.visibility_condition = self.visibility_condition_selection
|
|
|
|
|
-
|
|
|
|
|
- @api.onchange('visibility_dependency')
|
|
|
|
|
- def _onchange_visibility_dependency(self):
|
|
|
|
|
- """Clear condition values when dependency changes"""
|
|
|
|
|
- if not self.visibility_dependency:
|
|
|
|
|
- self.visibility_condition = False
|
|
|
|
|
- self.visibility_condition_m2o_id = False
|
|
|
|
|
- self.visibility_condition_selection = False
|
|
|
|
|
- elif self.visibility_dependency.ttype not in ['many2one', 'selection']:
|
|
|
|
|
- self.visibility_condition_m2o_id = False
|
|
|
|
|
- self.visibility_condition_selection = False
|
|
|
|
|
- elif self.visibility_dependency.ttype == 'many2one':
|
|
|
|
|
- # Load current value into m2o_id if exists
|
|
|
|
|
- if self.visibility_condition and self.visibility_condition.isdigit():
|
|
|
|
|
- try:
|
|
|
|
|
- model_name = self.visibility_dependency.relation
|
|
|
|
|
- if model_name:
|
|
|
|
|
- model = self.env[model_name]
|
|
|
|
|
- record = model.browse(int(self.visibility_condition))
|
|
|
|
|
- if record.exists():
|
|
|
|
|
- # Store the ID - the widget will handle the model change
|
|
|
|
|
- self.visibility_condition_m2o_id = int(self.visibility_condition)
|
|
|
|
|
- else:
|
|
|
|
|
- self.visibility_condition_m2o_id = False
|
|
|
|
|
- else:
|
|
|
|
|
- self.visibility_condition_m2o_id = False
|
|
|
|
|
- except:
|
|
|
|
|
- self.visibility_condition_m2o_id = False
|
|
|
|
|
- elif self.visibility_dependency.ttype == 'selection':
|
|
|
|
|
- # Load current value into selection if exists
|
|
|
|
|
- if self.visibility_condition:
|
|
|
|
|
- self.visibility_condition_selection = self.visibility_condition
|
|
|
|
|
-
|
|
|
|
|
- @api.onchange('visibility_comparator')
|
|
|
|
|
- def _onchange_visibility_comparator(self):
|
|
|
|
|
- """Clear visibility_between when comparator changes away from between/!between"""
|
|
|
|
|
- if self.visibility_comparator not in ['between', '!between']:
|
|
|
|
|
- self.visibility_between = False
|
|
|
|
|
-
|
|
|
|
|
- _sql_constraints = [
|
|
|
|
|
- ('unique_template_field', 'unique(template_id, field_id)',
|
|
|
|
|
- 'A field can only be added once to a template')
|
|
|
|
|
- ]
|
|
|
|
|
-
|
|
|
|
|
- @api.model
|
|
|
|
|
- def _register_hook(self):
|
|
|
|
|
- """Register label_custom field in ir.model.fields if it doesn't exist"""
|
|
|
|
|
- super()._register_hook()
|
|
|
|
|
- try:
|
|
|
|
|
- model = self.env['ir.model'].search([('model', '=', 'helpdesk.template.field')], limit=1)
|
|
|
|
|
- if model:
|
|
|
|
|
- field_model = self.env['ir.model.fields']
|
|
|
|
|
- existing_field = field_model.search([
|
|
|
|
|
- ('model_id', '=', model.id),
|
|
|
|
|
- ('name', '=', 'label_custom')
|
|
|
|
|
- ], limit=1)
|
|
|
|
|
-
|
|
|
|
|
- if not existing_field:
|
|
|
|
|
- field_model.create({
|
|
|
|
|
- 'model_id': model.id,
|
|
|
|
|
- 'name': 'label_custom',
|
|
|
|
|
- 'field_description': 'Custom Label',
|
|
|
|
|
- 'ttype': 'char',
|
|
|
|
|
- 'state': 'manual',
|
|
|
|
|
- 'required': False,
|
|
|
|
|
- 'readonly': False,
|
|
|
|
|
- 'store': True,
|
|
|
|
|
- })
|
|
|
|
|
- _logger.info("Campo label_custom registrado en _register_hook")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- _logger.error(f"Error registrando label_custom en _register_hook: {str(e)}", exc_info=True)
|
|
|
|
|
-
|
|
|
|
|
- @api.model
|
|
|
|
|
- def _migrate_label_custom_field(self):
|
|
|
|
|
- """
|
|
|
|
|
- Migration method to ensure label_custom field exists in database.
|
|
|
|
|
- This method should be called after module update to fix any missing field issues.
|
|
|
|
|
- """
|
|
|
|
|
- try:
|
|
|
|
|
- # Check if column exists in database
|
|
|
|
|
- self.env.cr.execute("""
|
|
|
|
|
- SELECT column_name
|
|
|
|
|
- FROM information_schema.columns
|
|
|
|
|
- WHERE table_name = 'helpdesk_template_field'
|
|
|
|
|
- AND column_name = 'label_custom'
|
|
|
|
|
- """)
|
|
|
|
|
- column_exists = self.env.cr.fetchone()
|
|
|
|
|
-
|
|
|
|
|
- if not column_exists:
|
|
|
|
|
- _logger.warning("Column 'label_custom' does not exist. Adding it...")
|
|
|
|
|
- # Add column manually if it doesn't exist
|
|
|
|
|
- self.env.cr.execute("""
|
|
|
|
|
- ALTER TABLE helpdesk_template_field
|
|
|
|
|
- ADD COLUMN label_custom VARCHAR
|
|
|
|
|
- """)
|
|
|
|
|
- self.env.cr.commit()
|
|
|
|
|
- _logger.info("Column 'label_custom' added successfully")
|
|
|
|
|
- else:
|
|
|
|
|
- _logger.info("Column 'label_custom' already exists")
|
|
|
|
|
-
|
|
|
|
|
- # Update ir.model.fields to ensure field is registered
|
|
|
|
|
- field_model = self.env['ir.model.fields']
|
|
|
|
|
- model_id = self.env['ir.model'].search([('model', '=', 'helpdesk.template.field')], limit=1)
|
|
|
|
|
-
|
|
|
|
|
- if model_id:
|
|
|
|
|
- existing_field = field_model.search([
|
|
|
|
|
- ('model_id', '=', model_id.id),
|
|
|
|
|
- ('name', '=', 'label_custom')
|
|
|
|
|
- ], limit=1)
|
|
|
|
|
-
|
|
|
|
|
- if not existing_field:
|
|
|
|
|
- _logger.warning("Field 'label_custom' not found in ir.model.fields. Creating it...")
|
|
|
|
|
- field_model.create({
|
|
|
|
|
- 'model_id': model_id.id,
|
|
|
|
|
- 'name': 'label_custom',
|
|
|
|
|
- 'field_description': 'Custom Label',
|
|
|
|
|
- 'ttype': 'char',
|
|
|
|
|
- 'state': 'manual',
|
|
|
|
|
- })
|
|
|
|
|
- _logger.info("Field 'label_custom' registered in ir.model.fields")
|
|
|
|
|
- else:
|
|
|
|
|
- _logger.info("Field 'label_custom' already registered in ir.model.fields")
|
|
|
|
|
-
|
|
|
|
|
- # Clear cache to ensure changes are reflected
|
|
|
|
|
- self.env.registry.clear_cache()
|
|
|
|
|
-
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- _logger.error(f"Error in _migrate_label_custom_field: {str(e)}", exc_info=True)
|
|
|
|
|
- # Don't raise to avoid breaking module update
|
|
|
|
|
-
|
|
|
|
|
- @api.model_create_multi
|
|
|
|
|
- def create(self, vals_list):
|
|
|
|
|
- """Override create to mark model required fields and regenerate forms when template field is added"""
|
|
|
|
|
- # Mark model required fields automatically based on field definition
|
|
|
|
|
- for vals in vals_list:
|
|
|
|
|
- if 'field_id' in vals and vals['field_id']:
|
|
|
|
|
- field = self.env['ir.model.fields'].browse(vals['field_id'])
|
|
|
|
|
- # Check if field is required at model level (not just in form)
|
|
|
|
|
- # A field is model required if:
|
|
|
|
|
- # 1. It's in the helpdesk.ticket model
|
|
|
|
|
- # 2. It has required=True in ir.model.fields (mandatory at model level)
|
|
|
|
|
- # 3. It's not blacklisted for website forms
|
|
|
|
|
- if (field.model == 'helpdesk.ticket' and
|
|
|
|
|
- field.required and
|
|
|
|
|
- not field.website_form_blacklisted):
|
|
|
|
|
- vals['model_required'] = True
|
|
|
|
|
- _logger.info(f"Auto-marked field {field.name} as model_required (required at model level)")
|
|
|
|
|
-
|
|
|
|
|
- fields_created = super().create(vals_list)
|
|
|
|
|
-
|
|
|
|
|
- # Get unique templates that were modified
|
|
|
|
|
- templates = fields_created.mapped('template_id')
|
|
|
|
|
-
|
|
|
|
|
- # Regenerate forms in all teams using these templates
|
|
|
|
|
- for template in templates:
|
|
|
|
|
- if not template:
|
|
|
|
|
- continue
|
|
|
|
|
- teams = self.env['helpdesk.team'].search([
|
|
|
|
|
- ('template_id', '=', template.id),
|
|
|
|
|
- ('use_website_helpdesk_form', '=', True)
|
|
|
|
|
- ])
|
|
|
|
|
- for team in teams:
|
|
|
|
|
- # Ensure view exists before regenerating
|
|
|
|
|
- if not team.website_form_view_id:
|
|
|
|
|
- team._ensure_submit_form_view()
|
|
|
|
|
- # Regenerate form if view exists
|
|
|
|
|
- if team.website_form_view_id:
|
|
|
|
|
- try:
|
|
|
|
|
- team._regenerate_form_from_template()
|
|
|
|
|
- _logger.info(f"Regenerated form for team {team.id} after adding field to template {template.id}")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
|
|
|
|
|
-
|
|
|
|
|
- return fields_created
|
|
|
|
|
-
|
|
|
|
|
- def write(self, vals):
|
|
|
|
|
- """Override write to mark model required fields and regenerate forms when template field is modified"""
|
|
|
|
|
- # Mark/unmark model_required automatically based on field definition
|
|
|
|
|
- if 'field_id' in vals and vals['field_id']:
|
|
|
|
|
- field = self.env['ir.model.fields'].browse(vals['field_id'])
|
|
|
|
|
- # Check if field is required at model level
|
|
|
|
|
- if (field.model == 'helpdesk.ticket' and
|
|
|
|
|
- field.required and
|
|
|
|
|
- not field.website_form_blacklisted):
|
|
|
|
|
- vals['model_required'] = True
|
|
|
|
|
- _logger.info(f"Auto-marked field {field.name} as model_required (required at model level)")
|
|
|
|
|
- else:
|
|
|
|
|
- # Field is not model required, unmark it
|
|
|
|
|
- vals['model_required'] = False
|
|
|
|
|
- elif 'field_id' in vals and not vals['field_id']:
|
|
|
|
|
- # Field_id is being cleared, unmark model_required
|
|
|
|
|
- vals['model_required'] = False
|
|
|
|
|
-
|
|
|
|
|
- result = super().write(vals)
|
|
|
|
|
-
|
|
|
|
|
- # If any field configuration changed, regenerate forms
|
|
|
|
|
- if any(key in vals for key in ['field_id', 'sequence', 'required', 'visibility_dependency',
|
|
|
|
|
- 'visibility_condition', 'visibility_comparator', 'label_custom',
|
|
|
|
|
- 'model_required', 'placeholder', 'default_value', 'help_text',
|
|
|
|
|
- 'widget', 'selection_options', 'rows', 'input_type', 'selection_type']):
|
|
|
|
|
- # Get unique templates that were modified
|
|
|
|
|
- templates = self.mapped('template_id')
|
|
|
|
|
-
|
|
|
|
|
- # Regenerate forms in all teams using these templates
|
|
|
|
|
- for template in templates:
|
|
|
|
|
- if not template:
|
|
|
|
|
- continue
|
|
|
|
|
- teams = self.env['helpdesk.team'].search([
|
|
|
|
|
- ('template_id', '=', template.id),
|
|
|
|
|
- ('use_website_helpdesk_form', '=', True)
|
|
|
|
|
- ])
|
|
|
|
|
- for team in teams:
|
|
|
|
|
- # Ensure view exists before regenerating
|
|
|
|
|
- if not team.website_form_view_id:
|
|
|
|
|
- team._ensure_submit_form_view()
|
|
|
|
|
- # Regenerate form if view exists
|
|
|
|
|
- if team.website_form_view_id:
|
|
|
|
|
- try:
|
|
|
|
|
- team._regenerate_form_from_template()
|
|
|
|
|
- _logger.info(f"Regenerated form for team {team.id} after modifying field in template {template.id}")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
|
|
|
|
|
-
|
|
|
|
|
- return result
|
|
|
|
|
-
|
|
|
|
|
- def unlink(self):
|
|
|
|
|
- """Override unlink to prevent deletion of model required fields and regenerate forms"""
|
|
|
|
|
- # Prevent deletion of model required fields
|
|
|
|
|
- model_required_fields = self.filtered('model_required')
|
|
|
|
|
- if model_required_fields:
|
|
|
|
|
- field_names = [f.field_id.name if f.field_id else 'Unknown' for f in model_required_fields]
|
|
|
|
|
- raise UserError(
|
|
|
|
|
- _("Cannot delete model required field(s): %s. This field is mandatory for the model and cannot be removed. "
|
|
|
|
|
- "Try hiding it with the 'Visibility' option instead and add it a default value.")
|
|
|
|
|
- % ', '.join(field_names)
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- # Get templates before deletion
|
|
|
|
|
- templates = self.mapped('template_id')
|
|
|
|
|
-
|
|
|
|
|
- result = super().unlink()
|
|
|
|
|
-
|
|
|
|
|
- # Regenerate forms in all teams using these templates
|
|
|
|
|
- for template in templates:
|
|
|
|
|
- if not template:
|
|
|
|
|
- continue
|
|
|
|
|
- teams = self.env['helpdesk.team'].search([
|
|
|
|
|
- ('template_id', '=', template.id),
|
|
|
|
|
- ('use_website_helpdesk_form', '=', True)
|
|
|
|
|
- ])
|
|
|
|
|
- for team in teams:
|
|
|
|
|
- # Ensure view exists before regenerating
|
|
|
|
|
- if not team.website_form_view_id:
|
|
|
|
|
- team._ensure_submit_form_view()
|
|
|
|
|
- # Regenerate form if view exists
|
|
|
|
|
- if team.website_form_view_id:
|
|
|
|
|
- try:
|
|
|
|
|
- team._regenerate_form_from_template()
|
|
|
|
|
- _logger.info(f"Regenerated form for team {team.id} after removing field from template {template.id}")
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)
|
|
|
|
|
-
|
|
|
|
|
- return result
|
|
|