hr_efficiency_dynamic_field.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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. ('monetary', 'Monetary'),
  24. ], string='Widget', default='percentage', help='How to display the field')
  25. # Styling
  26. decoration_success = fields.Float('Success Threshold', help='Value above which to show green')
  27. decoration_warning = fields.Float('Warning Threshold', help='Value above which to show yellow')
  28. decoration_danger = fields.Float('Danger Threshold', help='Value below which to show red')
  29. # Computed fields
  30. field_technical_name = fields.Char('Technical Field Name', compute='_compute_technical_name', store=True)
  31. @api.depends('name')
  32. def _compute_technical_name(self):
  33. for record in self:
  34. if record.name:
  35. # Convert to valid field name
  36. field_name = record.name.lower()
  37. field_name = field_name.replace(' ', '_').replace('-', '_').replace('(', '').replace(')', '')
  38. field_name = field_name.replace('í', 'i').replace('á', 'a').replace('é', 'e').replace('ó', 'o').replace('ú', 'u')
  39. field_name = field_name.replace('ñ', 'n')
  40. # Ensure it starts with a letter
  41. if not field_name[0].isalpha():
  42. field_name = 'indicator_' + field_name
  43. record.field_technical_name = field_name
  44. @api.constrains('name')
  45. def _check_name_unique(self):
  46. for record in self:
  47. if self.search_count([('name', '=', record.name), ('id', '!=', record.id)]) > 0:
  48. raise ValidationError(_('Field name must be unique'))
  49. @api.model_create_multi
  50. def create(self, vals_list):
  51. """Create dynamic fields and update model"""
  52. records = super().create(vals_list)
  53. self._update_model_fields()
  54. return records
  55. def write(self, vals):
  56. """Update dynamic field and update model"""
  57. result = super().write(vals)
  58. self._update_model_fields()
  59. return result
  60. def unlink(self):
  61. """Delete dynamic fields and update model"""
  62. result = super().unlink()
  63. self._update_model_fields()
  64. return result
  65. @api.model
  66. def _update_model_fields(self):
  67. """Update the hr.efficiency model with dynamic fields"""
  68. efficiency_model = self.env['hr.efficiency']
  69. # Get all active dynamic fields
  70. dynamic_fields = self.search([('active', '=', True)], order='sequence')
  71. # Clear existing dynamic fields from model (only those starting with 'indicator_')
  72. for field_name in list(efficiency_model._fields.keys()):
  73. if field_name.startswith('indicator_'):
  74. if hasattr(efficiency_model.__class__, field_name):
  75. try:
  76. delattr(efficiency_model.__class__, field_name)
  77. except AttributeError:
  78. pass # Field might not exist, ignore
  79. # Add new dynamic fields
  80. for dynamic_field in dynamic_fields:
  81. field_name = dynamic_field.field_technical_name
  82. # Create field with appropriate widget
  83. field_kwargs = {
  84. 'string': dynamic_field.label,
  85. 'compute': '_compute_indicators',
  86. 'store': True,
  87. 'help': f'Dynamic indicator: {dynamic_field.indicator_id.name}',
  88. }
  89. # Add widget if specified
  90. if dynamic_field.widget:
  91. field_kwargs['widget'] = dynamic_field.widget
  92. # Create the field
  93. field = fields.Float(**field_kwargs)
  94. setattr(efficiency_model.__class__, field_name, field)
  95. def action_toggle_visibility(self):
  96. """Toggle field visibility"""
  97. for record in self:
  98. record.active = not record.active
  99. self._update_model_fields()
  100. def action_move_up(self):
  101. """Move field up in sequence"""
  102. for record in self:
  103. prev_record = self.search([
  104. ('sequence', '<', record.sequence),
  105. ('active', '=', True)
  106. ], order='sequence desc', limit=1)
  107. if prev_record:
  108. record.sequence, prev_record.sequence = prev_record.sequence, record.sequence
  109. def action_move_down(self):
  110. """Move field down in sequence"""
  111. for record in self:
  112. next_record = self.search([
  113. ('sequence', '>', record.sequence),
  114. ('active', '=', True)
  115. ], order='sequence', limit=1)
  116. if next_record:
  117. record.sequence, next_record.sequence = next_record.sequence, record.sequence