hr_efficiency_dynamic_field.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from odoo import models, fields, api, _
  2. from odoo.exceptions import ValidationError
  3. class HrEfficiencyDynamicField(models.Model):
  4. _name = 'hr.efficiency.dynamic.field'
  5. _description = 'Dynamic Field Configuration'
  6. _order = 'sequence, name'
  7. name = fields.Char('Field Name', required=True, help='Technical field name (e.g., planning_efficiency)')
  8. label = fields.Char('Field Label', required=True, help='Display name (e.g., Planning Efficiency)')
  9. indicator_id = fields.Many2one('hr.efficiency.indicator', 'Related Indicator', required=True, ondelete='cascade')
  10. # Display settings
  11. sequence = fields.Integer('Sequence', default=10, help='Order in which fields appear')
  12. active = fields.Boolean('Active', default=True, help='Whether this field is visible')
  13. # View settings
  14. show_in_list = fields.Boolean('Show in List View', default=True)
  15. show_in_form = fields.Boolean('Show in Form View', default=True)
  16. show_in_search = fields.Boolean('Show in Search View', default=False)
  17. # Formatting
  18. widget = fields.Selection([
  19. ('percentage', 'Percentage'),
  20. ('float_time', 'Float Time'),
  21. ('number', 'Number'),
  22. ('text', 'Text'),
  23. ], string='Widget', default='percentage', help='How to display the field')
  24. # Styling
  25. decoration_success = fields.Float('Success Threshold', help='Value above which to show green')
  26. decoration_warning = fields.Float('Warning Threshold', help='Value above which to show yellow')
  27. decoration_danger = fields.Float('Danger Threshold', help='Value below which to show red')
  28. # Computed fields
  29. field_technical_name = fields.Char('Technical Field Name', compute='_compute_technical_name', store=True)
  30. @api.depends('name')
  31. def _compute_technical_name(self):
  32. for record in self:
  33. if record.name:
  34. # Convert to valid field name
  35. field_name = record.name.lower()
  36. field_name = field_name.replace(' ', '_').replace('-', '_').replace('(', '').replace(')', '')
  37. field_name = field_name.replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ó', 'o').replace('ú', 'u')
  38. field_name = field_name.replace('ñ', 'n')
  39. # Ensure it starts with a letter
  40. if not field_name[0].isalpha():
  41. field_name = 'indicator_' + field_name
  42. record.field_technical_name = field_name
  43. @api.constrains('name')
  44. def _check_name_unique(self):
  45. for record in self:
  46. if self.search_count([('name', '=', record.name), ('id', '!=', record.id)]) > 0:
  47. raise ValidationError(_('Field name must be unique'))
  48. @api.model_create_multi
  49. def create(self, vals_list):
  50. """Create dynamic fields and update model"""
  51. records = super().create(vals_list)
  52. self._update_model_fields()
  53. return records
  54. def write(self, vals):
  55. """Update dynamic field and update model"""
  56. result = super().write(vals)
  57. self._update_model_fields()
  58. return result
  59. def unlink(self):
  60. """Delete dynamic fields and update model"""
  61. result = super().unlink()
  62. self._update_model_fields()
  63. return result
  64. @api.model
  65. def _update_model_fields(self):
  66. """Update the hr.efficiency model with dynamic fields"""
  67. efficiency_model = self.env['hr.efficiency']
  68. # Get all active dynamic fields
  69. dynamic_fields = self.search([('active', '=', True)], order='sequence')
  70. # Clear existing dynamic fields from model (only those starting with 'indicator_')
  71. for field_name in list(efficiency_model._fields.keys()):
  72. if field_name.startswith('indicator_'):
  73. if hasattr(efficiency_model.__class__, field_name):
  74. try:
  75. delattr(efficiency_model.__class__, field_name)
  76. except AttributeError:
  77. pass # Field might not exist, ignore
  78. # Add new dynamic fields
  79. for dynamic_field in dynamic_fields:
  80. field_name = dynamic_field.field_technical_name
  81. # Create field with appropriate widget
  82. field_kwargs = {
  83. 'string': dynamic_field.label,
  84. 'compute': '_compute_indicators',
  85. 'store': True,
  86. 'help': f'Dynamic indicator: {dynamic_field.indicator_id.name}',
  87. }
  88. # Add widget if specified
  89. if dynamic_field.widget:
  90. field_kwargs['widget'] = dynamic_field.widget
  91. # Create the field
  92. field = fields.Float(**field_kwargs)
  93. setattr(efficiency_model.__class__, field_name, field)
  94. def action_toggle_visibility(self):
  95. """Toggle field visibility"""
  96. for record in self:
  97. record.active = not record.active
  98. self._update_model_fields()
  99. def action_move_up(self):
  100. """Move field up in sequence"""
  101. for record in self:
  102. prev_record = self.search([
  103. ('sequence', '<', record.sequence),
  104. ('active', '=', True)
  105. ], order='sequence desc', limit=1)
  106. if prev_record:
  107. record.sequence, prev_record.sequence = prev_record.sequence, record.sequence
  108. def action_move_down(self):
  109. """Move field down in sequence"""
  110. for record in self:
  111. next_record = self.search([
  112. ('sequence', '>', record.sequence),
  113. ('active', '=', True)
  114. ], order='sequence', limit=1)
  115. if next_record:
  116. record.sequence, next_record.sequence = next_record.sequence, record.sequence