diff --git a/addons/gamification/__manifest__.py b/addons/gamification/__manifest__.py index 724fa844fe1..9b99964392a 100644 --- a/addons/gamification/__manifest__.py +++ b/addons/gamification/__manifest__.py @@ -22,19 +22,23 @@ Both goals and badges are flexibles and can be adapted to a large range of modul 'data': [ 'wizard/update_goal.xml', 'wizard/grant_badge.xml', - 'views/badge.xml', - 'views/challenge.xml', - 'views/goal.xml', - 'data/cron.xml', - 'security/gamification_security.xml', - 'security/ir.model.access.csv', - 'data/mail_template_data.xml', - 'data/goal_base.xml', - 'data/badge.xml', - 'data/gamification_karma_rank_data.xml', + 'views/res_users_views.xml', 'views/gamification_karma_rank_views.xml', 'views/gamification_karma_tracking_views.xml', - 'views/res_users_views.xml', + 'views/gamification_badge_views.xml', + 'views/gamification_badge_user_views.xml', + 'views/gamification_goal_views.xml', + 'views/gamification_goal_definition_views.xml', + 'views/gamification_challenge_views.xml', + 'views/gamification_challenge_line_views.xml', + 'views/gamification_menus.xml', + 'security/gamification_security.xml', + 'security/ir.model.access.csv', + 'data/ir_cron_data.xml', + 'data/mail_template_data.xml', # keep before to populate challenge reports + 'data/gamification_badge_data.xml', + 'data/gamification_challenge_data.xml', + 'data/gamification_karma_rank_data.xml', ], 'demo': [ 'data/gamification_karma_rank_demo.xml', diff --git a/addons/gamification/data/badge.xml b/addons/gamification/data/gamification_badge_data.xml similarity index 100% rename from addons/gamification/data/badge.xml rename to addons/gamification/data/gamification_badge_data.xml diff --git a/addons/gamification/data/goal_base.xml b/addons/gamification/data/gamification_challenge_data.xml similarity index 87% rename from addons/gamification/data/goal_base.xml rename to addons/gamification/data/gamification_challenge_data.xml index 06ba1f0d936..90be06389e6 100644 --- a/addons/gamification/data/goal_base.xml +++ b/addons/gamification/data/gamification_challenge_data.xml @@ -38,16 +38,6 @@ user.company_id.id - - Create User - ir.actions.act_window - res.users - current - - {} - Create and manage users that will connect to the system. Users can be deactivated should there be a period of time during which they will/should not connect to the system. You can assign them groups in order to give them specific access to the applications they need to use in the system. - - Invite new Users Create at least another user diff --git a/addons/gamification/data/cron.xml b/addons/gamification/data/ir_cron_data.xml similarity index 100% rename from addons/gamification/data/cron.xml rename to addons/gamification/data/ir_cron_data.xml diff --git a/addons/gamification/models/__init__.py b/addons/gamification/models/__init__.py index 659fcff70a1..1bd2662a772 100644 --- a/addons/gamification/models/__init__.py +++ b/addons/gamification/models/__init__.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import goal -from . import challenge -from . import badge +from . import gamification_badge +from . import gamification_badge_user +from . import gamification_challenge +from . import gamification_challenge_line +from . import gamification_goal +from . import gamification_goal_definition from . import gamification_karma_rank from . import gamification_karma_tracking from . import res_users diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/gamification_badge.py similarity index 78% rename from addons/gamification/models/badge.py rename to addons/gamification/models/gamification_badge.py index 8517582f66d..954bc1f2c9a 100644 --- a/addons/gamification/models/badge.py +++ b/addons/gamification/models/gamification_badge.py @@ -9,56 +9,6 @@ from odoo import api, fields, models, _, exceptions _logger = logging.getLogger(__name__) -class BadgeUser(models.Model): - """User having received a badge""" - - _name = 'gamification.badge.user' - _description = 'Gamification User Badge' - _order = "create_date desc" - _rec_name = "badge_name" - - user_id = fields.Many2one('res.users', string="User", required=True, ondelete="cascade", index=True) - sender_id = fields.Many2one('res.users', string="Sender", help="The user who has send the badge") - badge_id = fields.Many2one('gamification.badge', string='Badge', required=True, ondelete="cascade", index=True) - challenge_id = fields.Many2one('gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge") - comment = fields.Text('Comment') - badge_name = fields.Char(related='badge_id.name', string="Badge Name", readonly=False) - level = fields.Selection( - string='Badge Level', related="badge_id.level", store=True, readonly=True) - - def _send_badge(self): - """Send a notification to a user for receiving a badge - - Does not verify constrains on badge granting. - The users are added to the owner_ids (create badge_user if needed) - The stats counters are incremented - :param ids: list(int) of badge users that will receive the badge - """ - template = self.env.ref('gamification.email_template_badge_received') - - for badge_user in self: - self.env['mail.thread'].message_post_with_template( - template.id, - model=badge_user._name, - res_id=badge_user.id, - composition_mode='mass_mail', - # `website_forum` triggers `_cron_update` which triggers this method for template `Received Badge` - # for which `badge_user.user_id.partner_id.ids` equals `[8]`, which is then passed to `self.env['mail.compose.message'].create(...)` - # which expects a command list and not a list of ids. In master, this wasn't doing anything, at the end composer.partner_ids was [] and not [8] - # I believe this line is useless, it will take the partners to which the template must be send from the template itself (`partner_to`) - # The below line was therefore pointless. - # partner_ids=badge_user.user_id.partner_id.ids, - ) - - return True - - @api.model_create_multi - def create(self, vals_list): - for vals in vals_list: - self.env['gamification.badge'].browse(vals['badge_id']).check_granting() - return super().create(vals_list) - - class GamificationBadge(models.Model): """Badge object that users can send and receive""" diff --git a/addons/gamification/models/gamification_badge_user.py b/addons/gamification/models/gamification_badge_user.py new file mode 100644 index 00000000000..c46b9e86085 --- /dev/null +++ b/addons/gamification/models/gamification_badge_user.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models + + +class BadgeUser(models.Model): + """User having received a badge""" + + _name = 'gamification.badge.user' + _description = 'Gamification User Badge' + _order = "create_date desc" + _rec_name = "badge_name" + + user_id = fields.Many2one('res.users', string="User", required=True, ondelete="cascade", index=True) + sender_id = fields.Many2one('res.users', string="Sender", help="The user who has send the badge") + badge_id = fields.Many2one('gamification.badge', string='Badge', required=True, ondelete="cascade", index=True) + challenge_id = fields.Many2one('gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge") + comment = fields.Text('Comment') + badge_name = fields.Char(related='badge_id.name', string="Badge Name", readonly=False) + level = fields.Selection( + string='Badge Level', related="badge_id.level", store=True, readonly=True) + + def _send_badge(self): + """Send a notification to a user for receiving a badge + + Does not verify constrains on badge granting. + The users are added to the owner_ids (create badge_user if needed) + The stats counters are incremented + :param ids: list(int) of badge users that will receive the badge + """ + template = self.env.ref('gamification.email_template_badge_received') + + for badge_user in self: + self.env['mail.thread'].message_post_with_template( + template.id, + model=badge_user._name, + res_id=badge_user.id, + composition_mode='mass_mail', + # `website_forum` triggers `_cron_update` which triggers this method for template `Received Badge` + # for which `badge_user.user_id.partner_id.ids` equals `[8]`, which is then passed to `self.env['mail.compose.message'].create(...)` + # which expects a command list and not a list of ids. In master, this wasn't doing anything, at the end composer.partner_ids was [] and not [8] + # I believe this line is useless, it will take the partners to which the template must be send from the template itself (`partner_to`) + # The below line was therefore pointless. + # partner_ids=badge_user.user_id.partner_id.ids, + ) + + return True + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + self.env['gamification.badge'].browse(vals['badge_id']).check_granting() + return super().create(vals_list) diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/gamification_challenge.py similarity index 96% rename from addons/gamification/models/challenge.py rename to addons/gamification/models/gamification_challenge.py index 36cad9077f4..d6314683e31 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/gamification_challenge.py @@ -60,6 +60,7 @@ class Challenge(models.Model): _inherit = 'mail.thread' _order = 'end_date, start_date, name, id' + # description name = fields.Char("Challenge Name", required=True, translate=True) description = fields.Text("Description", translate=True) state = fields.Selection([ @@ -71,10 +72,11 @@ class Challenge(models.Model): manager_id = fields.Many2one( 'res.users', default=lambda self: self.env.uid, string="Responsible", help="The user responsible for the challenge.",) - + # members user_ids = fields.Many2many('res.users', 'gamification_challenge_users_rel', string="Users", help="List of users participating to the challenge") user_domain = fields.Char("User domain", help="Alternative to a list of users") - + user_count = fields.Integer('# Users', compute='_compute_user_count') + # periodicity period = fields.Selection([ ('once', "Non recurring"), ('daily', "Daily"), @@ -129,6 +131,26 @@ class Challenge(models.Model): ], string="Appears in", required=True, default='hr', help="Define the visibility of the challenge through menus") + @api.depends('user_ids') + def _compute_user_count(self): + mapped_data = {} + if self.ids: + query = """ + SELECT gamification_challenge_id, count(res_users_id) + FROM gamification_challenge_users_rel rel + LEFT JOIN res_users users + ON users.id=rel.res_users_id AND users.active = TRUE + WHERE gamification_challenge_id IN %s + GROUP BY gamification_challenge_id + """ + self.env.cr.execute(query, [tuple(self.ids)]) + mapped_data = dict( + (challenge_id, user_count) + for challenge_id, user_count in self.env.cr.fetchall() + ) + for challenge in self: + challenge.user_count = mapped_data.get(challenge.id, 0) + REPORT_OFFSETS = { 'daily': timedelta(days=1), 'weekly': timedelta(days=7), @@ -315,6 +337,12 @@ class Challenge(models.Model): challenge.report_progress() return True + def action_view_users(self): + """ Redirect to the participants (users) list. """ + action = self.env["ir.actions.actions"]._for_xml_id("base.action_res_users") + action['domain'] = [('id', 'in', self.user_ids.ids)] + return action + ##### Automatic actions ##### def _generate_goals_from_challenge(self): @@ -769,27 +797,3 @@ class Challenge(models.Model): 'badge_id': badge.id, 'challenge_id': self.id })._send_badge() - - -class ChallengeLine(models.Model): - """Gamification challenge line - - Predefined goal for 'gamification_challenge' - These are generic list of goals with only the target goal defined - Should only be created for the gamification.challenge object - """ - _name = 'gamification.challenge.line' - _description = 'Gamification generic goal for challenge' - _order = "sequence, id" - - challenge_id = fields.Many2one('gamification.challenge', string='Challenge', required=True, ondelete="cascade") - definition_id = fields.Many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade") - - sequence = fields.Integer('Sequence', help='Sequence number for ordering', default=1) - target_goal = fields.Float('Target Value to Reach', required=True) - - name = fields.Char("Name", related='definition_id.name', readonly=False) - condition = fields.Selection(string="Condition", related='definition_id.condition', readonly=True) - definition_suffix = fields.Char("Unit", related='definition_id.suffix', readonly=True) - definition_monetary = fields.Boolean("Monetary", related='definition_id.monetary', readonly=True) - definition_full_suffix = fields.Char("Suffix", related='definition_id.full_suffix', readonly=True) diff --git a/addons/gamification/models/gamification_challenge_line.py b/addons/gamification/models/gamification_challenge_line.py new file mode 100644 index 00000000000..279275d7a7b --- /dev/null +++ b/addons/gamification/models/gamification_challenge_line.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + + +class ChallengeLine(models.Model): + """Gamification challenge line + + Predefined goal for 'gamification_challenge' + These are generic list of goals with only the target goal defined + Should only be created for the gamification.challenge object + """ + _name = 'gamification.challenge.line' + _description = 'Gamification generic goal for challenge' + _order = "sequence, id" + + challenge_id = fields.Many2one('gamification.challenge', string='Challenge', required=True, ondelete="cascade") + definition_id = fields.Many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade") + + sequence = fields.Integer('Sequence', help='Sequence number for ordering', default=1) + target_goal = fields.Float('Target Value to Reach', required=True) + + name = fields.Char("Name", related='definition_id.name', readonly=False) + condition = fields.Selection(string="Condition", related='definition_id.condition', readonly=True) + definition_suffix = fields.Char("Unit", related='definition_id.suffix', readonly=True) + definition_monetary = fields.Boolean("Monetary", related='definition_id.monetary', readonly=True) + definition_full_suffix = fields.Char("Suffix", related='definition_id.full_suffix', readonly=True) diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/gamification_goal.py similarity index 69% rename from addons/gamification/models/goal.py rename to addons/gamification/models/gamification_goal.py index ef44e7f4c41..cbfcc2526b3 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/gamification_goal.py @@ -6,141 +6,11 @@ import logging from datetime import date, datetime, timedelta from odoo import api, fields, models, _, exceptions -from odoo.osv import expression from odoo.tools.safe_eval import safe_eval, time _logger = logging.getLogger(__name__) -DOMAIN_TEMPLATE = "[('store', '=', True), '|', ('model_id', '=', model_id), ('model_id', 'in', model_inherited_ids)%s]" -class GoalDefinition(models.Model): - """Goal definition - - A goal definition contains the way to evaluate an objective - Each module wanting to be able to set goals to the users needs to create - a new gamification_goal_definition - """ - _name = 'gamification.goal.definition' - _description = 'Gamification Goal Definition' - - name = fields.Char("Goal Definition", required=True, translate=True) - description = fields.Text("Goal Description") - monetary = fields.Boolean("Monetary Value", default=False, help="The target and current value are defined in the company currency.") - suffix = fields.Char("Suffix", help="The unit of the target and current values", translate=True) - full_suffix = fields.Char("Full Suffix", compute='_compute_full_suffix', help="The currency and suffix field") - computation_mode = fields.Selection([ - ('manually', "Recorded manually"), - ('count', "Automatic: number of records"), - ('sum', "Automatic: sum on a field"), - ('python', "Automatic: execute a specific Python code"), - ], default='manually', string="Computation Mode", required=True, - help="Define how the goals will be computed. The result of the operation will be stored in the field 'Current'.") - display_mode = fields.Selection([ - ('progress', "Progressive (using numerical values)"), - ('boolean', "Exclusive (done or not-done)"), - ], default='progress', string="Displayed as", required=True) - model_id = fields.Many2one('ir.model', string='Model', help='The model object for the field to evaluate') - model_inherited_ids = fields.Many2many('ir.model', related='model_id.inherited_model_ids') - field_id = fields.Many2one( - 'ir.model.fields', string='Field to Sum', help='The field containing the value to evaluate', - domain=DOMAIN_TEMPLATE % '' - ) - field_date_id = fields.Many2one( - 'ir.model.fields', string='Date Field', help='The date to use for the time period evaluated', - domain=DOMAIN_TEMPLATE % ", ('ttype', 'in', ('date', 'datetime'))" - ) - domain = fields.Char( - "Filter Domain", required=True, default="[]", - help="Domain for filtering records. General rule, not user depending," - " e.g. [('state', '=', 'done')]. The expression can contain" - " reference to 'user' which is a browse record of the current" - " user if not in batch mode.") - - batch_mode = fields.Boolean("Batch Mode", help="Evaluate the expression in batch instead of once for each user") - batch_distinctive_field = fields.Many2one('ir.model.fields', string="Distinctive field for batch user", help="In batch mode, this indicates which field distinguishes one user from the other, e.g. user_id, partner_id...") - batch_user_expression = fields.Char("Evaluated expression for batch mode", help="The value to compare with the distinctive field. The expression can contain reference to 'user' which is a browse record of the current user, e.g. user.id, user.partner_id.id...") - compute_code = fields.Text("Python Code", help="Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id.") - condition = fields.Selection([ - ('higher', "The higher the better"), - ('lower', "The lower the better") - ], default='higher', required=True, string="Goal Performance", - help="A goal is considered as completed when the current value is compared to the value to reach") - action_id = fields.Many2one('ir.actions.act_window', string="Action", help="The action that will be called to update the goal value.") - res_id_field = fields.Char("ID Field of user", help="The field name on the user profile (res.users) containing the value for res_id for action.") - - @api.depends('suffix', 'monetary') # also depends of user... - def _compute_full_suffix(self): - for goal in self: - items = [] - - if goal.monetary: - items.append(self.env.company.currency_id.symbol or u'¤') - if goal.suffix: - items.append(goal.suffix) - - goal.full_suffix = u' '.join(items) - - def _check_domain_validity(self): - # take admin as should always be present - for definition in self: - if definition.computation_mode not in ('count', 'sum'): - continue - - Obj = self.env[definition.model_id.model] - try: - domain = safe_eval(definition.domain, { - 'user': self.env.user.with_user(self.env.user) - }) - # dummy search to make sure the domain is valid - Obj.search_count(domain) - except (ValueError, SyntaxError) as e: - msg = e - if isinstance(e, SyntaxError): - msg = (e.msg + '\n' + e.text) - raise exceptions.UserError(_("The domain for the definition %s seems incorrect, please check it.\n\n%s") % (definition.name, msg)) - return True - - def _check_model_validity(self): - """ make sure the selected field and model are usable""" - for definition in self: - try: - if not (definition.model_id and definition.field_id): - continue - - Model = self.env[definition.model_id.model] - field = Model._fields.get(definition.field_id.name) - if not (field and field.store): - raise exceptions.UserError(_( - "The model configuration for the definition %(name)s seems incorrect, please check it.\n\n%(field_name)s not stored", - name=definition.name, - field_name=definition.field_id.name - )) - except KeyError as e: - raise exceptions.UserError(_( - "The model configuration for the definition %(name)s seems incorrect, please check it.\n\n%(error)s not found", - name=definition.name, - error=e - )) - - @api.model_create_multi - def create(self, vals_list): - definitions = super(GoalDefinition, self).create(vals_list) - definitions.filtered_domain([ - ('computation_mode', 'in', ['count', 'sum']), - ])._check_domain_validity() - definitions.filtered_domain([ - ('field_id', '=', 'True'), - ])._check_model_validity() - return definitions - - def write(self, vals): - res = super(GoalDefinition, self).write(vals) - if vals.get('computation_mode', 'count') in ('count', 'sum') and (vals.get('domain') or vals.get('model_id')): - self._check_domain_validity() - if vals.get('field_id') or vals.get('model_id') or vals.get('batch_mode'): - self._check_model_validity() - return res - class Goal(models.Model): """Goal instance for a user diff --git a/addons/gamification/models/gamification_goal_definition.py b/addons/gamification/models/gamification_goal_definition.py new file mode 100644 index 00000000000..e9305edc261 --- /dev/null +++ b/addons/gamification/models/gamification_goal_definition.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, fields, models, _, exceptions +from odoo.tools.safe_eval import safe_eval + +DOMAIN_TEMPLATE = "[('store', '=', True), '|', ('model_id', '=', model_id), ('model_id', 'in', model_inherited_ids)%s]" + + +class GoalDefinition(models.Model): + """Goal definition + + A goal definition contains the way to evaluate an objective + Each module wanting to be able to set goals to the users needs to create + a new gamification_goal_definition + """ + _name = 'gamification.goal.definition' + _description = 'Gamification Goal Definition' + + name = fields.Char("Goal Definition", required=True, translate=True) + description = fields.Text("Goal Description") + monetary = fields.Boolean("Monetary Value", default=False, help="The target and current value are defined in the company currency.") + suffix = fields.Char("Suffix", help="The unit of the target and current values", translate=True) + full_suffix = fields.Char("Full Suffix", compute='_compute_full_suffix', help="The currency and suffix field") + computation_mode = fields.Selection([ + ('manually', "Recorded manually"), + ('count', "Automatic: number of records"), + ('sum', "Automatic: sum on a field"), + ('python', "Automatic: execute a specific Python code"), + ], default='manually', string="Computation Mode", required=True, + help="Define how the goals will be computed. The result of the operation will be stored in the field 'Current'.") + display_mode = fields.Selection([ + ('progress', "Progressive (using numerical values)"), + ('boolean', "Exclusive (done or not-done)"), + ], default='progress', string="Displayed as", required=True) + model_id = fields.Many2one('ir.model', string='Model', help='The model object for the field to evaluate') + model_inherited_ids = fields.Many2many('ir.model', related='model_id.inherited_model_ids') + field_id = fields.Many2one( + 'ir.model.fields', string='Field to Sum', help='The field containing the value to evaluate', + domain=DOMAIN_TEMPLATE % '' + ) + field_date_id = fields.Many2one( + 'ir.model.fields', string='Date Field', help='The date to use for the time period evaluated', + domain=DOMAIN_TEMPLATE % ", ('ttype', 'in', ('date', 'datetime'))" + ) + domain = fields.Char( + "Filter Domain", required=True, default="[]", + help="Domain for filtering records. General rule, not user depending," + " e.g. [('state', '=', 'done')]. The expression can contain" + " reference to 'user' which is a browse record of the current" + " user if not in batch mode.") + + batch_mode = fields.Boolean("Batch Mode", help="Evaluate the expression in batch instead of once for each user") + batch_distinctive_field = fields.Many2one('ir.model.fields', string="Distinctive field for batch user", help="In batch mode, this indicates which field distinguishes one user from the other, e.g. user_id, partner_id...") + batch_user_expression = fields.Char("Evaluated expression for batch mode", help="The value to compare with the distinctive field. The expression can contain reference to 'user' which is a browse record of the current user, e.g. user.id, user.partner_id.id...") + compute_code = fields.Text("Python Code", help="Python code to be executed for each user. 'result' should contains the new current value. Evaluated user can be access through object.user_id.") + condition = fields.Selection([ + ('higher', "The higher the better"), + ('lower', "The lower the better") + ], default='higher', required=True, string="Goal Performance", + help="A goal is considered as completed when the current value is compared to the value to reach") + action_id = fields.Many2one('ir.actions.act_window', string="Action", help="The action that will be called to update the goal value.") + res_id_field = fields.Char("ID Field of user", help="The field name on the user profile (res.users) containing the value for res_id for action.") + + @api.depends('suffix', 'monetary') # also depends of user... + def _compute_full_suffix(self): + for goal in self: + items = [] + + if goal.monetary: + items.append(self.env.company.currency_id.symbol or u'¤') + if goal.suffix: + items.append(goal.suffix) + + goal.full_suffix = u' '.join(items) + + def _check_domain_validity(self): + # take admin as should always be present + for definition in self: + if definition.computation_mode not in ('count', 'sum'): + continue + + Obj = self.env[definition.model_id.model] + try: + domain = safe_eval(definition.domain, { + 'user': self.env.user.with_user(self.env.user) + }) + # dummy search to make sure the domain is valid + Obj.search_count(domain) + except (ValueError, SyntaxError) as e: + msg = e + if isinstance(e, SyntaxError): + msg = (e.msg + '\n' + e.text) + raise exceptions.UserError(_("The domain for the definition %s seems incorrect, please check it.\n\n%s") % (definition.name, msg)) + return True + + def _check_model_validity(self): + """ make sure the selected field and model are usable""" + for definition in self: + try: + if not (definition.model_id and definition.field_id): + continue + + Model = self.env[definition.model_id.model] + field = Model._fields.get(definition.field_id.name) + if not (field and field.store): + raise exceptions.UserError(_( + "The model configuration for the definition %(name)s seems incorrect, please check it.\n\n%(field_name)s not stored", + name=definition.name, + field_name=definition.field_id.name + )) + except KeyError as e: + raise exceptions.UserError(_( + "The model configuration for the definition %(name)s seems incorrect, please check it.\n\n%(error)s not found", + name=definition.name, + error=e + )) + + @api.model_create_multi + def create(self, vals_list): + definitions = super(GoalDefinition, self).create(vals_list) + definitions.filtered_domain([ + ('computation_mode', 'in', ['count', 'sum']), + ])._check_domain_validity() + definitions.filtered_domain([ + ('field_id', '=', 'True'), + ])._check_model_validity() + return definitions + + def write(self, vals): + res = super(GoalDefinition, self).write(vals) + if vals.get('computation_mode', 'count') in ('count', 'sum') and (vals.get('domain') or vals.get('model_id')): + self._check_domain_validity() + if vals.get('field_id') or vals.get('model_id') or vals.get('batch_mode'): + self._check_model_validity() + return res diff --git a/addons/gamification/views/gamification_badge_user_views.xml b/addons/gamification/views/gamification_badge_user_views.xml new file mode 100644 index 00000000000..d09d7f69e6e --- /dev/null +++ b/addons/gamification/views/gamification_badge_user_views.xml @@ -0,0 +1,38 @@ + + + + + Badge User Kanban View + gamification.badge.user + + + + + + + + + +
+
+
+ +
+
+

+ +

+ +

+
+

Granted by the

+
+
+
+
+
+
+
+
+ +
diff --git a/addons/gamification/views/badge.xml b/addons/gamification/views/gamification_badge_views.xml similarity index 82% rename from addons/gamification/views/badge.xml rename to addons/gamification/views/gamification_badge_views.xml index 539e5254a7e..127b49533e0 100644 --- a/addons/gamification/views/badge.xml +++ b/addons/gamification/views/gamification_badge_views.xml @@ -1,3 +1,4 @@ + @@ -162,41 +163,4 @@ - - - - - - Badge User Kanban View - gamification.badge.user - - - - - - - - - -
-
-
- -
-
-

- -

- -

-
-

Granted by the

-
-
-
-
-
-
-
-
diff --git a/addons/gamification/views/gamification_challenge_line_views.xml b/addons/gamification/views/gamification_challenge_line_views.xml new file mode 100644 index 00000000000..e73b1c6502b --- /dev/null +++ b/addons/gamification/views/gamification_challenge_line_views.xml @@ -0,0 +1,15 @@ + + + + + Challenge line list + gamification.challenge.line + + + + + + + + + diff --git a/addons/gamification/views/challenge.xml b/addons/gamification/views/gamification_challenge_views.xml similarity index 79% rename from addons/gamification/views/challenge.xml rename to addons/gamification/views/gamification_challenge_views.xml index b028ccb3391..7cc7ce2a654 100644 --- a/addons/gamification/views/challenge.xml +++ b/addons/gamification/views/gamification_challenge_views.xml @@ -1,9 +1,10 @@ + Challenges List gamification.challenge - + @@ -12,26 +13,11 @@ - - gamification.goal - Related Goals - kanban,tree,form - {'search_default_group_by_definition': True, 'search_default_inprogress': True, 'search_default_challenge_id': active_id, 'default_challenge_id': active_id} - -

- No goal found -

- There is no goal associated to this challenge matching your search. - Make sure that your challenge is active and assigned to at least one user. -

-
-
- Challenge Form gamification.challenge -
+