19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:43 +01:00
parent 4607ccbd2e
commit 825ff6514e
487 changed files with 184979 additions and 195262 deletions

View file

@ -6,9 +6,10 @@ import logging
from datetime import date, timedelta
from dateutil.relativedelta import relativedelta, MO
from markupsafe import Markup
from odoo import api, models, fields, _, exceptions
from odoo.tools import ustr
from odoo import _, api, exceptions, fields, models
from odoo.http import SESSION_LIFETIME
_logger = logging.getLogger(__name__)
@ -44,7 +45,8 @@ def start_end_date_for_period(period, default_start_date=False, default_end_date
return fields.Datetime.to_string(start_date), fields.Datetime.to_string(end_date)
class Challenge(models.Model):
class GamificationChallenge(models.Model):
"""Gamification challenge
Set of predifined objectives assigned to people with rules for recurrence and
@ -57,15 +59,15 @@ class Challenge(models.Model):
_name = 'gamification.challenge'
_description = 'Gamification Challenge'
_inherit = 'mail.thread'
_inherit = ['mail.thread']
_order = 'end_date, start_date, name, id'
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
if 'user_domain' in fields_list and 'user_domain' not in res:
def default_get(self, fields):
res = super().default_get(fields)
if 'user_domain' in fields and 'user_domain' not in res:
user_group_id = self.env.ref('base.group_user')
res['user_domain'] = f'["&", ("groups_id", "=", "{user_group_id.name}"), ("active", "=", True)]'
res['user_domain'] = f'["&", ("all_group_ids", "in", [{user_group_id.id}]), ("active", "=", True)]'
return res
# description
@ -105,7 +107,7 @@ class Challenge(models.Model):
help="List of goals that will be set",
required=True, copy=True)
reward_id = fields.Many2one('gamification.badge', string="For Every Succeeding User")
reward_id = fields.Many2one('gamification.badge', string="For Every Succeeding User", index='btree_not_null')
reward_first_id = fields.Many2one('gamification.badge', string="For 1st user")
reward_second_id = fields.Many2one('gamification.badge', string="For 2nd user")
reward_third_id = fields.Many2one('gamification.badge', string="For 3rd user")
@ -127,7 +129,7 @@ class Challenge(models.Model):
('yearly', "Yearly")
], default='never',
string="Report Frequency", required=True)
report_message_group_id = fields.Many2one('mail.channel', string="Send a copy to", help="Group that will receive a copy of the report in addition to the user")
report_message_group_id = fields.Many2one('discuss.channel', string="Send a copy to", help="Group that will receive a copy of the report in addition to the user")
report_template_id = fields.Many2one('mail.template', default=lambda self: self._get_report_template(), string="Report Template", required=True)
remind_update_delay = fields.Integer("Non-updated manual goals will be reminded after", help="Never reminded if no value or zero is specified.")
last_report_date = fields.Date("Last Report Date", default=fields.Date.today)
@ -188,8 +190,8 @@ class Challenge(models.Model):
def create(self, vals_list):
"""Overwrite the create method to add the user of groups"""
for vals in vals_list:
if vals.get('user_domain'):
users = self._get_challenger_users(ustr(vals.get('user_domain')))
if user_domain := vals.get('user_domain'):
users = self._get_challenger_users(str(user_domain))
if not vals.get('user_ids'):
vals['user_ids'] = []
@ -198,19 +200,14 @@ class Challenge(models.Model):
return super().create(vals_list)
def write(self, vals):
if vals.get('user_domain'):
users = self._get_challenger_users(ustr(vals.get('user_domain')))
if user_domain := vals.get('user_domain'):
users = self._get_challenger_users(str(user_domain))
if not vals.get('user_ids'):
vals['user_ids'] = []
vals['user_ids'].extend((4, user.id) for user in users)
write_res = super(Challenge, self).write(vals)
if vals.get('report_message_frequency', 'never') != 'never':
# _recompute_challenge_users do not set users for challenges with no reports, subscribing them now
for challenge in self:
challenge.message_subscribe([user.partner_id.id for user in challenge.user_ids])
write_res = super().write(vals)
if vals.get('state') == 'inprogress':
self._recompute_challenge_users()
@ -221,7 +218,7 @@ class Challenge(models.Model):
elif vals.get('state') == 'draft':
# resetting progress
if self.env['gamification.goal'].search([('challenge_id', 'in', self.ids), ('state', '=', 'inprogress')], limit=1):
if self.env['gamification.goal'].search_count([('challenge_id', 'in', self.ids), ('state', '=', 'inprogress')], limit=1):
raise exceptions.UserError(_("You can not reset a challenge with unfinished goals."))
return write_res
@ -267,22 +264,28 @@ class Challenge(models.Model):
return True
Goals = self.env['gamification.goal']
self.flush_recordset()
self.user_ids.presence_ids.flush_recordset()
# include yesterday goals to update the goals that just ended
# exclude goals for portal users that did not connect since the last update
# exclude goals for users that have not interacted with the
# webclient since the last update or whose session is no longer
# valid.
yesterday = fields.Date.to_string(date.today() - timedelta(days=1))
self.env.cr.execute("""SELECT gg.id
FROM gamification_goal as gg
JOIN res_users_log as log ON gg.user_id = log.create_uid
JOIN res_users ru on log.create_uid = ru.id
WHERE (gg.write_date < log.create_date OR ru.share IS NOT TRUE)
AND ru.active IS TRUE
JOIN mail_presence as mp ON mp.user_id = gg.user_id
WHERE gg.write_date <= mp.last_presence
AND mp.last_presence >= now() AT TIME ZONE 'UTC' - interval '%(session_lifetime)s seconds'
AND gg.closed IS NOT TRUE
AND gg.challenge_id IN %s
AND gg.challenge_id IN %(challenge_ids)s
AND (gg.state = 'inprogress'
OR (gg.state = 'reached' AND gg.end_date >= %s))
OR (gg.state = 'reached' AND gg.end_date >= %(yesterday)s))
GROUP BY gg.id
""", [tuple(self.ids), yesterday])
""", {
'session_lifetime': SESSION_LIFETIME,
'challenge_ids': tuple(self.ids),
'yesterday': yesterday
})
Goals.browse(goal_id for [goal_id] in self.env.cr.fetchall()).update_goal()
@ -520,24 +523,27 @@ class Challenge(models.Model):
domain.append(('user_id', '=', user.id))
goal = Goals.search(domain, limit=1)
goal = Goals.search_fetch(domain, ['current', 'completeness', 'state'], limit=1)
if not goal:
continue
if goal.state != 'reached':
return []
line_data.update(goal.read(['id', 'current', 'completeness', 'state'])[0])
line_data.update({
fname: goal[fname]
for fname in ['id', 'current', 'completeness', 'state']
})
res_lines.append(line_data)
continue
line_data['own_goal_id'] = False,
line_data['goals'] = []
if line.condition=='higher':
goals = Goals.search(domain, order="completeness desc, current desc")
else:
goals = Goals.search(domain, order="completeness desc, current asc")
goals = Goals.search(domain, order='id')
if not goals:
continue
goals = goals.sorted(key=lambda goal: (
-goal.completeness, -goal.current if line.condition == 'higher' else goal.current
))
for ranking, goal in enumerate(goals):
if user and goal.user_id == user:
@ -608,7 +614,9 @@ class Challenge(models.Model):
lines = challenge._get_serialized_challenge_lines(user, restrict_goals=subset_goals)
if not lines:
continue
# Avoid error if 'full_suffix' is missing in the line
for line in lines:
line.setdefault('full_suffix', '')
body_html = challenge.report_template_id.with_user(user).with_context(challenge_lines=lines)._render_field('body_html', challenge.ids)[challenge.id]
# notify message only to users, do not post on the challenge
@ -661,15 +669,14 @@ class Challenge(models.Model):
challenge_ended = force or end_date == fields.Date.to_string(yesterday)
if challenge.reward_id and (challenge_ended or challenge.reward_realtime):
# not using start_date as intemportal goals have a start date but no end_date
reached_goals = self.env['gamification.goal'].read_group([
reached_goals = self.env['gamification.goal']._read_group([
('challenge_id', '=', challenge.id),
('end_date', '=', end_date),
('state', '=', 'reached')
], fields=['user_id'], groupby=['user_id'])
for reach_goals_user in reached_goals:
if reach_goals_user['user_id_count'] == len(challenge.line_ids):
], groupby=['user_id'], aggregates=['__count'])
for user, count in reached_goals:
if count == len(challenge.line_ids):
# the user has succeeded every assigned goal
user = self.env['res.users'].browse(reach_goals_user['user_id'][0])
if challenge.reward_realtime:
badges = self.env['gamification.badge.user'].search_count([
('challenge_id', '=', challenge.id),
@ -689,22 +696,21 @@ class Challenge(models.Model):
message_body = _("The challenge %s is finished.", challenge.name)
if rewarded_users:
user_names = rewarded_users.name_get()
message_body += _(
"<br/>Reward (badge %(badge_name)s) for every succeeding user was sent to %(users)s.",
message_body += Markup("<br/>") + _(
"Reward (badge %(badge_name)s) for every succeeding user was sent to %(users)s.",
badge_name=challenge.reward_id.name,
users=", ".join(name for (user_id, name) in user_names)
users=", ".join(rewarded_users.mapped('display_name'))
)
else:
message_body += _("<br/>Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.")
message_body += Markup("<br/>") + _("Nobody has succeeded to reach every goal, no badge is rewarded for this challenge.")
# reward bests
reward_message = _("<br/> %(rank)d. %(user_name)s - %(reward_name)s")
reward_message = Markup("<br/> %(rank)d. %(user_name)s - %(reward_name)s")
if challenge.reward_first_id:
(first_user, second_user, third_user) = challenge._get_topN_users(MAX_VISIBILITY_RANKING)
if first_user:
challenge._reward_user(first_user, challenge.reward_first_id)
message_body += _("<br/>Special rewards were sent to the top competing users. The ranking for this challenge is :")
message_body += Markup("<br/>") + _("Special rewards were sent to the top competing users. The ranking for this challenge is:")
message_body += reward_message % {
'rank': 1,
'user_name': first_user.name,