Kaynağa Gözat

feat(helpdesk_extras): add form fields to workflow templates

- Created helpdesk.workflow.template.field model with 30 fields
- Added field_ids One2many to helpdesk.workflow.template
- Added Form Fields tab in workflow template view
- Updated Team to support form fields from workflow_template_id
- Added _has_form_fields() and _get_form_fields() helper methods
- Backwards compatible: legacy template_id still works
- Version bump to 18.0.1.0.12
odoo 2 ay önce
ebeveyn
işleme
df93a3e5ff

+ 1 - 1
helpdesk_extras/__manifest__.py

@@ -1,6 +1,6 @@
 {
     "name": "Helpdesk Extras",
-    "version": "18.0.1.0.11",
+    "version": "18.0.1.0.12",
     "category": "Services/Helpdesk",
     "summary": "Funcionalidades extras para Helpdesk - Compartir equipos y widget de horas",
     "description": """

+ 2 - 0
helpdesk_extras/models/__init__.py

@@ -8,4 +8,6 @@ from . import helpdesk_template
 from . import helpdesk_workflow_template
 from . import helpdesk_workflow_template_stage
 from . import helpdesk_workflow_template_sla
+from . import helpdesk_workflow_template_field
 from . import helpdesk_affected_module
+

+ 38 - 29
helpdesk_extras/models/helpdesk_team.py

@@ -34,46 +34,56 @@ class HelpdeskTeamExtras(models.Model):
 
     @api.model_create_multi
     def create(self, vals_list):
-        """Override create to regenerate form XML if template is set"""
+        """Override create to regenerate form XML if template/workflow fields are set"""
         teams = super().create(vals_list)
-        # After create, if template is set and form view exists, regenerate
-        # This handles the case when team is created with template_id already set
-        for team in teams.filtered(lambda t: t.use_website_helpdesk_form and t.template_id and t.website_form_view_id):
+        # After create, if form fields are available (from template_id OR workflow_template_id), regenerate
+        for team in teams.filtered(lambda t: t.use_website_helpdesk_form and t._has_form_fields() and t.website_form_view_id):
             team._regenerate_form_from_template()
         return teams
 
+    def _has_form_fields(self):
+        """Check if team has form fields configured (from template_id or workflow_template_id)"""
+        self.ensure_one()
+        # Check workflow_template_id.field_ids first (new), then template_id (legacy)
+        if self.workflow_template_id and self.workflow_template_id.field_ids:
+            return True
+        if self.template_id and self.template_id.field_ids:
+            return True
+        return False
+
+    def _get_form_fields(self):
+        """Get form fields from workflow_template_id (preferred) or template_id (legacy)"""
+        self.ensure_one()
+        # Prefer workflow_template_id.field_ids (new), fallback to template_id (legacy)
+        if self.workflow_template_id and self.workflow_template_id.field_ids:
+            return self.workflow_template_id.field_ids.sorted('sequence')
+        if self.template_id and self.template_id.field_ids:
+            return self.template_id.field_ids.sorted('sequence')
+        return self.env['helpdesk.workflow.template.field']
+
     def _ensure_submit_form_view(self):
         """Override to regenerate form from template after creating view"""
         result = super()._ensure_submit_form_view()
-        # After view is created, if template is set, regenerate form
-        # Note: super() may have created views, so we need to refresh to get updated website_form_view_id
-        for team in self.filtered(lambda t: t.use_website_helpdesk_form and t.template_id):
-            # Refresh to get updated website_form_view_id after super() created it
+        # After view is created, if form fields are set, regenerate form
+        for team in self.filtered(lambda t: t.use_website_helpdesk_form and t._has_form_fields()):
             team.invalidate_recordset(['website_form_view_id'])
             if team.website_form_view_id:
                 team._regenerate_form_from_template()
         return result
 
     def write(self, vals):
-        """Override write to regenerate form XML when template changes"""
+        """Override write to regenerate form XML when template or workflow changes"""
         result = super().write(vals)
-        if 'template_id' in vals:
-            # Regenerate form XML when template is assigned/changed
-            # After super().write(), refresh teams to get updated values
+        # Regenerate form when template_id OR workflow_template_id changes
+        if 'template_id' in vals or 'workflow_template_id' in vals:
             teams_to_process = self.browse(self.ids).filtered('use_website_helpdesk_form')
             for team in teams_to_process:
-                # Ensure website_form_view_id exists before regenerating
-                # This handles the case when template is assigned but view doesn't exist yet
                 if not team.website_form_view_id:
-                    # Call _ensure_submit_form_view which will create the view if needed
-                    # This method already handles template regeneration if template_id is set
                     team._ensure_submit_form_view()
                 else:
-                    # View exists, regenerate or restore form based on template
-                    if team.template_id:
+                    if team._has_form_fields():
                         team._regenerate_form_from_template()
                     else:
-                        # If template is removed, restore default form
                         team._restore_default_form()
         return result
 
@@ -393,24 +403,21 @@ class HelpdeskTeamExtras(models.Model):
         return result
 
     def _regenerate_form_from_template(self):
-        """Regenerate the website form XML based on the template"""
+        """Regenerate the website form XML based on form fields (from workflow or template)"""
         self.ensure_one()
-        if not self.template_id or not self.website_form_view_id:
+        if not self._has_form_fields() or not self.website_form_view_id:
             return
 
         # Get base form structure (from default template)
-        # We use the default template arch to ensure we start with a clean base
         default_form = self.env.ref('website_helpdesk.ticket_submit_form', raise_if_not_found=False)
         if not default_form:
             return
 
         # Get website language for translations
-        # Try to get current website, fallback to default website or system language
         website = self.env['website'].get_current_website()
         if website:
             lang = website.default_lang_id.code if website.default_lang_id else 'en_US'
         else:
-            # Fallback: try to get default website
             try:
                 default_website = self.env.ref('website.default_website', raise_if_not_found=False)
                 if default_website and default_website.default_lang_id:
@@ -420,14 +427,16 @@ class HelpdeskTeamExtras(models.Model):
             except Exception:
                 lang = self.env.context.get('lang', 'en_US')
         
-        # Create environment with website language for translations
         env_lang = self.env(context=dict(self.env.context, lang=lang))
 
-        # Get template fields sorted by sequence
-        template_fields = self.template_id.field_ids.sorted('sequence')
+        # Get form fields sorted by sequence (from workflow_template or legacy template)
+        template_fields = self._get_form_fields()
+        
+        # Determine source for logging
+        source = "workflow_template" if (self.workflow_template_id and self.workflow_template_id.field_ids) else "template"
+        source_id = self.workflow_template_id.id if source == "workflow_template" else (self.template_id.id if self.template_id else None)
         
-        # Log template fields for debugging
-        _logger.info(f"Regenerating form for team {self.id}, template {self.template_id.id} with {len(template_fields)} fields")
+        _logger.info(f"Regenerating form for team {self.id}, {source} {source_id} with {len(template_fields)} fields")
         for tf in template_fields:
             _logger.info(f"  - Field: {tf.field_id.name if tf.field_id else 'None'} (type: {tf.field_id.ttype if tf.field_id else 'None'})")
         

+ 14 - 1
helpdesk_extras/models/helpdesk_workflow_template.py

@@ -41,6 +41,13 @@ class HelpdeskWorkflowTemplate(models.Model):
         string='SLA Policies',
         help='SLA policies included in this workflow template'
     )
+    field_ids = fields.One2many(
+        'helpdesk.workflow.template.field',
+        'workflow_template_id',
+        string='Form Fields',
+        copy=True,
+        help='Form fields for website ticket submission'
+    )
     stage_count = fields.Integer(
         string='Stages Count',
         compute='_compute_counts',
@@ -51,6 +58,11 @@ class HelpdeskWorkflowTemplate(models.Model):
         compute='_compute_counts',
         store=False
     )
+    field_count = fields.Integer(
+        string='Form Fields Count',
+        compute='_compute_counts',
+        store=False
+    )
     team_ids = fields.One2many(
         'helpdesk.team',
         'workflow_template_id',
@@ -63,11 +75,12 @@ class HelpdeskWorkflowTemplate(models.Model):
         store=False
     )
 
-    @api.depends('stage_template_ids', 'sla_template_ids', 'team_ids')
+    @api.depends('stage_template_ids', 'sla_template_ids', 'field_ids', 'team_ids')
     def _compute_counts(self):
         for template in self:
             template.stage_count = len(template.stage_template_ids)
             template.sla_count = len(template.sla_template_ids)
+            template.field_count = len(template.field_ids)
             template.team_count = len(template.team_ids)
 
     def action_view_teams(self):

+ 378 - 0
helpdesk_extras/models/helpdesk_workflow_template_field.py

@@ -0,0 +1,378 @@
+# -*- 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 HelpdeskWorkflowTemplateField(models.Model):
+    """
+    Form field configuration for workflow templates.
+    Migrated from helpdesk.template.field to consolidate templates and workflows.
+    """
+    _name = 'helpdesk.workflow.template.field'
+    _description = 'Workflow Template Form Field'
+    _order = 'sequence, id'
+
+    workflow_template_id = fields.Many2one(
+        'helpdesk.workflow.template',
+        string='Workflow 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."
+    )
+    selection_options = fields.Text(
+        string='Selection Options',
+        help="For selection fields (not relations): JSON array of [value, label] pairs."
+    )
+    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"
+    )
+    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 for 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
+    visibility_condition_m2o_id = fields.Integer(
+        string='Visibility Condition (Many2one ID)',
+        help="ID of the selected record when dependency is a many2one field"
+    )
+    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
+    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
+    visibility_between = fields.Char(
+        string='Visibility Between (End Value)',
+        help="Second value for 'between' and '!between' comparators"
+    )
+    
+    def _get_visibility_condition_selection_options(self):
+        """Return selection options based on visibility_dependency field"""
+        if not self:
+            return []
+        
+        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
+        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':
+            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():
+                            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':
+            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_workflow_template_field', 'unique(workflow_template_id, field_id)',
+         'A field can only be added once to a workflow template')
+    ]
+
+    @api.model_create_multi
+    def create(self, vals_list):
+        """Override create to mark model required fields and regenerate forms"""
+        for vals in vals_list:
+            if 'field_id' in vals and vals['field_id']:
+                field = self.env['ir.model.fields'].browse(vals['field_id'])
+                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")
+        
+        fields_created = super().create(vals_list)
+        
+        # Regenerate forms in all teams using these workflow templates
+        workflow_templates = fields_created.mapped('workflow_template_id')
+        self._regenerate_team_forms(workflow_templates)
+        
+        return fields_created
+
+    def write(self, vals):
+        """Override write to mark model required fields and regenerate forms"""
+        if 'field_id' in vals and vals['field_id']:
+            field = self.env['ir.model.fields'].browse(vals['field_id'])
+            if (field.model == 'helpdesk.ticket' and 
+                field.required and 
+                not field.website_form_blacklisted):
+                vals['model_required'] = True
+            else:
+                vals['model_required'] = False
+        elif 'field_id' in vals and not vals['field_id']:
+            vals['model_required'] = False
+        
+        result = super().write(vals)
+        
+        # If any field configuration changed, regenerate forms
+        regenerate_keys = ['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']
+        if any(key in vals for key in regenerate_keys):
+            workflow_templates = self.mapped('workflow_template_id')
+            self._regenerate_team_forms(workflow_templates)
+        
+        return result
+
+    def unlink(self):
+        """Prevent deletion of model required fields and regenerate forms"""
+        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)
+            )
+        
+        workflow_templates = self.mapped('workflow_template_id')
+        
+        result = super().unlink()
+        
+        self._regenerate_team_forms(workflow_templates)
+        
+        return result
+    
+    def _regenerate_team_forms(self, workflow_templates):
+        """Helper to regenerate forms in teams using these workflow templates"""
+        for wf_template in workflow_templates:
+            if not wf_template:
+                continue
+            teams = self.env['helpdesk.team'].search([
+                ('workflow_template_id', '=', wf_template.id),
+                ('use_website_helpdesk_form', '=', True)
+            ])
+            for team in teams:
+                if not team.website_form_view_id:
+                    team._ensure_submit_form_view()
+                if team.website_form_view_id:
+                    try:
+                        team._regenerate_form_from_template()
+                        _logger.info(f"Regenerated form for team {team.id} after workflow template field change")
+                    except Exception as e:
+                        _logger.error(f"Error regenerating form for team {team.id}: {e}", exc_info=True)

+ 2 - 0
helpdesk_extras/security/ir.model.access.csv

@@ -21,6 +21,8 @@ access_helpdesk_workflow_template_stage_internal,helpdesk.workflow.template.stag
 access_helpdesk_workflow_template_stage_manager,helpdesk.workflow.template.stage.manager,helpdesk_extras.model_helpdesk_workflow_template_stage,helpdesk.group_helpdesk_manager,1,1,1,1
 access_helpdesk_workflow_template_sla_internal,helpdesk.workflow.template.sla.internal,helpdesk_extras.model_helpdesk_workflow_template_sla,base.group_user,1,0,0,0
 access_helpdesk_workflow_template_sla_manager,helpdesk.workflow.template.sla.manager,helpdesk_extras.model_helpdesk_workflow_template_sla,helpdesk.group_helpdesk_manager,1,1,1,1
+access_helpdesk_workflow_template_field_internal,helpdesk.workflow.template.field.internal,helpdesk_extras.model_helpdesk_workflow_template_field,base.group_user,1,0,0,0
+access_helpdesk_workflow_template_field_manager,helpdesk.workflow.template.field.manager,helpdesk_extras.model_helpdesk_workflow_template_field,helpdesk.group_helpdesk_manager,1,1,1,1
 access_helpdesk_workflow_template_apply_wizard_user,helpdesk.workflow.template.apply.wizard.user,helpdesk_extras.model_helpdesk_workflow_template_apply_wizard,helpdesk.group_helpdesk_user,1,1,1,1
 access_helpdesk_workflow_template_apply_wizard_manager,helpdesk.workflow.template.apply.wizard.manager,helpdesk_extras.model_helpdesk_workflow_template_apply_wizard,helpdesk.group_helpdesk_manager,1,1,1,1
 access_helpdesk_affected_module_user,helpdesk.affected.module.user,helpdesk_extras.model_helpdesk_affected_module,helpdesk.group_helpdesk_user,1,0,0,0

+ 52 - 0
helpdesk_extras/views/helpdesk_workflow_template_views.xml

@@ -128,6 +128,58 @@
                                 </form>
                             </field>
                         </page>
+                        <page string="Form Fields" name="form_fields">
+                            <field name="field_ids" nolabel="1">
+                                <list string="Form Fields" editable="bottom" default_order="sequence">
+                                    <field name="sequence" widget="handle"/>
+                                    <field name="field_id" required="1" 
+                                           domain="[('model', '=', 'helpdesk.ticket'), ('website_form_blacklisted', '=', False)]"/>
+                                    <field name="label_custom" optional="show"/>
+                                    <field name="required" widget="boolean_toggle"/>
+                                    <field name="model_required" invisible="1"/>
+                                    <field name="placeholder" optional="hide"/>
+                                    <field name="default_value" optional="hide"/>
+                                    <field name="selection_type" optional="hide"/>
+                                    <field name="rows" optional="hide"/>
+                                    <field name="input_type" optional="hide"/>
+                                    <field name="visibility_dependency" optional="hide"/>
+                                    <field name="visibility_comparator" optional="hide" 
+                                           invisible="not visibility_dependency"/>
+                                    <field name="visibility_condition" optional="hide" 
+                                           invisible="not visibility_dependency"/>
+                                </list>
+                                <form string="Form Field">
+                                    <sheet>
+                                        <group>
+                                            <group string="Field Configuration">
+                                                <field name="field_id" required="1"/>
+                                                <field name="sequence"/>
+                                                <field name="label_custom"/>
+                                                <field name="required"/>
+                                                <field name="model_required" readonly="1"/>
+                                            </group>
+                                            <group string="Display Options">
+                                                <field name="placeholder"/>
+                                                <field name="default_value"/>
+                                                <field name="selection_type"/>
+                                                <field name="rows"/>
+                                                <field name="input_type"/>
+                                                <field name="help_text"/>
+                                            </group>
+                                        </group>
+                                        <group string="Visibility Conditions" col="4">
+                                            <field name="visibility_dependency"/>
+                                            <field name="visibility_comparator" 
+                                                   invisible="not visibility_dependency"/>
+                                            <field name="visibility_condition" 
+                                                   invisible="not visibility_dependency"/>
+                                            <field name="visibility_between" 
+                                                   invisible="visibility_comparator not in ['between', '!between']"/>
+                                        </group>
+                                    </sheet>
+                                </form>
+                            </field>
+                        </page>
                     </notebook>
                 </sheet>
             </form>