mirror of
https://github.com/bringout/oca-ocb-vertical-industry.git
synced 2026-04-21 04:32:03 +02:00
19.0 vanilla
This commit is contained in:
parent
4607ccbd2e
commit
825ff6514e
487 changed files with 184979 additions and 195262 deletions
|
|
@ -5,6 +5,8 @@ import logging
|
|||
from datetime import date
|
||||
|
||||
from odoo import api, fields, models, _, exceptions
|
||||
from odoo.tools import SQL
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -98,24 +100,24 @@ class GamificationBadge(models.Model):
|
|||
return
|
||||
|
||||
Users = self.env["res.users"]
|
||||
query = Users._where_calc([])
|
||||
Users._apply_ir_rules(query)
|
||||
query = Users._search([])
|
||||
badge_alias = query.join("res_users", "id", "gamification_badge_user", "user_id", "badges")
|
||||
|
||||
tables, where_clauses, where_params = query.get_sql()
|
||||
|
||||
self.env.cr.execute(
|
||||
f"""
|
||||
SELECT {badge_alias}.badge_id, count(res_users.id) as stat_count,
|
||||
rows = self.env.execute_query(SQL(
|
||||
"""
|
||||
SELECT %(badge_alias)s.badge_id, count(res_users.id) as stat_count,
|
||||
count(distinct(res_users.id)) as stat_count_distinct,
|
||||
array_agg(distinct(res_users.id)) as unique_owner_ids
|
||||
FROM {tables}
|
||||
WHERE {where_clauses}
|
||||
AND {badge_alias}.badge_id IN %s
|
||||
GROUP BY {badge_alias}.badge_id
|
||||
FROM %(from_clause)s
|
||||
WHERE %(where_clause)s
|
||||
AND %(badge_alias)s.badge_id IN %(ids)s
|
||||
GROUP BY %(badge_alias)s.badge_id
|
||||
""",
|
||||
[*where_params, tuple(self.ids)]
|
||||
)
|
||||
from_clause=query.from_clause,
|
||||
where_clause=query.where_clause or SQL("TRUE"),
|
||||
badge_alias=SQL.identifier(badge_alias),
|
||||
ids=tuple(self.ids),
|
||||
))
|
||||
|
||||
mapping = {
|
||||
badge_id: {
|
||||
|
|
@ -123,7 +125,7 @@ class GamificationBadge(models.Model):
|
|||
'granted_users_count': distinct_count,
|
||||
'unique_owner_ids': owner_ids,
|
||||
}
|
||||
for (badge_id, count, distinct_count, owner_ids) in self.env.cr._obj
|
||||
for (badge_id, count, distinct_count, owner_ids) in rows
|
||||
}
|
||||
for badge in self:
|
||||
badge.update(mapping.get(badge.id, defaults))
|
||||
|
|
@ -195,8 +197,6 @@ class GamificationBadge(models.Model):
|
|||
def _can_grant_badge(self):
|
||||
"""Check if a user can grant a badge to another user
|
||||
|
||||
:param uid: the id of the res.users trying to send the badge
|
||||
:param badge_id: the granted badge id
|
||||
:return: integer representing the permission.
|
||||
"""
|
||||
if self.env.is_admin():
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class BadgeUser(models.Model):
|
||||
class GamificationBadgeUser(models.Model):
|
||||
"""User having received a badge"""
|
||||
|
||||
_name = 'gamification.badge.user'
|
||||
_description = 'Gamification User Badge'
|
||||
_inherit = ["mail.thread"]
|
||||
_order = "create_date desc"
|
||||
_rec_name = "badge_name"
|
||||
|
||||
user_id = fields.Many2one('res.users', string="User", required=True, ondelete="cascade", index=True)
|
||||
user_partner_id = fields.Many2one('res.partner', related='user_id.partner_id')
|
||||
sender_id = fields.Many2one('res.users', string="Sender")
|
||||
badge_id = fields.Many2one('gamification.badge', string='Badge', required=True, ondelete="cascade", index=True)
|
||||
challenge_id = fields.Many2one('gamification.challenge', string='Challenge')
|
||||
|
|
@ -29,22 +30,33 @@ class BadgeUser(models.Model):
|
|||
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',
|
||||
raise_if_not_found=False
|
||||
)
|
||||
if not template:
|
||||
return
|
||||
|
||||
body_html = self.env.ref('gamification.email_template_badge_received')._render_field('body_html', self.ids)[self.id]
|
||||
for badge_user in self:
|
||||
template.send_mail(
|
||||
badge_user.id,
|
||||
badge_user.message_notify(
|
||||
model=badge_user._name,
|
||||
res_id=badge_user.id,
|
||||
body=body_html,
|
||||
partner_ids=[badge_user.user_partner_id.id],
|
||||
subject=_("🎉 You've earned the %(badge)s badge!", badge=badge_user.badge_name),
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
email_layout_xmlid='mail.mail_notification_layout',
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _notify_get_recipients_groups(self, message, model_description, msg_vals=False):
|
||||
groups = super()._notify_get_recipients_groups(message, model_description, msg_vals)
|
||||
self.ensure_one()
|
||||
for group in groups:
|
||||
if group[0] == 'user':
|
||||
group[2]['has_button_access'] = False
|
||||
return groups
|
||||
|
||||
@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)
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['user_partner_id']
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
from odoo import models, fields
|
||||
|
||||
|
||||
class ChallengeLine(models.Model):
|
||||
class GamificationChallengeLine(models.Model):
|
||||
"""Gamification challenge line
|
||||
|
||||
Predefined goal for 'gamification_challenge'
|
||||
|
|
@ -15,7 +15,7 @@ class ChallengeLine(models.Model):
|
|||
_description = 'Gamification generic goal for challenge'
|
||||
_order = "sequence, id"
|
||||
|
||||
challenge_id = fields.Many2one('gamification.challenge', string='Challenge', required=True, ondelete="cascade")
|
||||
challenge_id = fields.Many2one('gamification.challenge', string='Challenge', required=True, index=True, ondelete="cascade")
|
||||
definition_id = fields.Many2one('gamification.goal.definition', string='Goal Definition', required=True, ondelete="cascade")
|
||||
|
||||
sequence = fields.Integer('Sequence', default=1)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from odoo.tools.safe_eval import safe_eval, time
|
|||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Goal(models.Model):
|
||||
class GamificationGoal(models.Model):
|
||||
"""Goal instance for a user
|
||||
|
||||
An individual goal for a user on a specified time period"""
|
||||
|
|
@ -22,7 +22,8 @@ class Goal(models.Model):
|
|||
_order = 'start_date desc, end_date desc, definition_id, id'
|
||||
|
||||
definition_id = fields.Many2one('gamification.goal.definition', string="Goal Definition", required=True, ondelete="cascade")
|
||||
user_id = fields.Many2one('res.users', string="User", required=True, auto_join=True, ondelete="cascade")
|
||||
user_id = fields.Many2one('res.users', string="User", required=True, bypass_search_access=True, index=True, ondelete="cascade")
|
||||
user_partner_id = fields.Many2one('res.partner', related='user_id.partner_id')
|
||||
line_id = fields.Many2one('gamification.challenge.line', string="Challenge Line", ondelete="cascade")
|
||||
challenge_id = fields.Many2one(
|
||||
related='line_id.challenge_id', store=True, readonly=True, index=True,
|
||||
|
|
@ -39,12 +40,13 @@ class Goal(models.Model):
|
|||
('inprogress', "In progress"),
|
||||
('reached', "Reached"),
|
||||
('failed', "Failed"),
|
||||
('canceled', "Canceled"),
|
||||
('canceled', "Cancelled"),
|
||||
], default='draft', string='State', required=True)
|
||||
to_update = fields.Boolean('To update')
|
||||
closed = fields.Boolean('Closed goal')
|
||||
|
||||
computation_mode = fields.Selection(related='definition_id.computation_mode', readonly=False)
|
||||
color = fields.Integer("Color Index", compute='_compute_color')
|
||||
remind_update_delay = fields.Integer(
|
||||
"Remind delay", help="The number of days after which the user "
|
||||
"assigned to a manual goal will be reminded. "
|
||||
|
|
@ -60,6 +62,17 @@ class Goal(models.Model):
|
|||
definition_suffix = fields.Char("Suffix", related='definition_id.full_suffix', readonly=True)
|
||||
definition_display = fields.Selection(string="Display Mode", related='definition_id.display_mode', readonly=True)
|
||||
|
||||
@api.depends('end_date', 'last_update', 'state')
|
||||
def _compute_color(self):
|
||||
"""Set the color based on the goal's state and completion"""
|
||||
for goal in self:
|
||||
goal.color = 0
|
||||
if (goal.end_date and goal.last_update):
|
||||
if (goal.end_date < goal.last_update) and (goal.state == 'failed'):
|
||||
goal.color = 2
|
||||
elif (goal.end_date < goal.last_update) and (goal.state == 'reached'):
|
||||
goal.color = 5
|
||||
|
||||
@api.depends('current', 'target_goal', 'definition_id.condition')
|
||||
def _get_completion(self):
|
||||
"""Return the percentage of completeness of the goal, between 0 and 100"""
|
||||
|
|
@ -150,7 +163,7 @@ class Goal(models.Model):
|
|||
'time': time,
|
||||
}
|
||||
code = definition.compute_code.strip()
|
||||
safe_eval(code, cxt, mode="exec", nocopy=True)
|
||||
safe_eval(code, cxt, mode="exec")
|
||||
# the result of the evaluated codeis put in the 'result' local variable, propagated to the context
|
||||
result = cxt.get('result')
|
||||
if isinstance(result, (float, int)):
|
||||
|
|
@ -185,32 +198,23 @@ class Goal(models.Model):
|
|||
subquery_domain.append((field_date_name, '<=', end_date))
|
||||
|
||||
if definition.computation_mode == 'count':
|
||||
value_field_name = field_name + '_count'
|
||||
if field_name == 'id':
|
||||
# grouping on id does not work and is similar to search anyway
|
||||
users = Obj.search(subquery_domain)
|
||||
user_values = [{'id': user.id, value_field_name: 1} for user in users]
|
||||
else:
|
||||
user_values = Obj.read_group(subquery_domain, fields=[field_name], groupby=[field_name])
|
||||
user_values = Obj._read_group(subquery_domain, groupby=[field_name], aggregates=['__count'])
|
||||
|
||||
else: # sum
|
||||
value_field_name = definition.field_id.name
|
||||
if field_name == 'id':
|
||||
user_values = Obj.search_read(subquery_domain, fields=['id', value_field_name])
|
||||
else:
|
||||
user_values = Obj.read_group(subquery_domain, fields=[field_name, "%s:sum" % value_field_name], groupby=[field_name])
|
||||
user_values = Obj._read_group(subquery_domain, groupby=[field_name], aggregates=[f'{value_field_name}:sum'])
|
||||
|
||||
# user_values has format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...]
|
||||
# user_values has format of _read_group: [(<partner>, <aggregate>), ...]
|
||||
for goal in [g for g in goals if g.id in query_goals]:
|
||||
for user_value in user_values:
|
||||
queried_value = field_name in user_value and user_value[field_name] or False
|
||||
if isinstance(queried_value, tuple) and len(queried_value) == 2 and isinstance(queried_value[0], int):
|
||||
queried_value = queried_value[0]
|
||||
for field_value, aggregate in user_values:
|
||||
queried_value = field_value.id if isinstance(field_value, models.Model) else field_value
|
||||
if queried_value == query_goals[goal.id]:
|
||||
new_value = user_value.get(value_field_name, goal.current)
|
||||
goals_to_write.update(goal._get_write_values(new_value))
|
||||
goals_to_write.update(goal._get_write_values(aggregate))
|
||||
|
||||
else:
|
||||
field_name = definition.field_id.name
|
||||
field = Obj._fields.get(field_name)
|
||||
sum_supported = bool(field) and field.type in {'integer', 'float', 'monetary'}
|
||||
for goal in goals:
|
||||
# eval the domain with user replaced by goal user object
|
||||
domain = safe_eval(definition.domain, {'user': goal.user_id})
|
||||
|
|
@ -221,10 +225,9 @@ class Goal(models.Model):
|
|||
if goal.end_date and field_date_name:
|
||||
domain.append((field_date_name, '<=', goal.end_date))
|
||||
|
||||
if definition.computation_mode == 'sum':
|
||||
field_name = definition.field_id.name
|
||||
res = Obj.read_group(domain, [field_name], [])
|
||||
new_value = res and res[0][field_name] or 0.0
|
||||
if definition.computation_mode == 'sum' and sum_supported:
|
||||
res = Obj._read_group(domain, [], [f'{field_name}:{definition.computation_mode}'])
|
||||
new_value = res[0][0] or 0.0
|
||||
|
||||
else: # computation mode = count
|
||||
new_value = Obj.search_count(domain)
|
||||
|
|
@ -274,7 +277,7 @@ class Goal(models.Model):
|
|||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
return super(Goal, self.with_context(no_remind_goal=True)).create(vals_list)
|
||||
return super(GamificationGoal, self.with_context(no_remind_goal=True)).create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
"""Overwrite the write method to update the last_update field to today
|
||||
|
|
@ -283,7 +286,7 @@ class Goal(models.Model):
|
|||
change, a report is generated
|
||||
"""
|
||||
vals['last_update'] = fields.Date.context_today(self)
|
||||
result = super(Goal, self).write(vals)
|
||||
result = super().write(vals)
|
||||
for goal in self:
|
||||
if goal.state != "draft" and ('definition_id' in vals or 'user_id' in vals):
|
||||
# avoid drag&drop in kanban view
|
||||
|
|
@ -332,3 +335,6 @@ class Goal(models.Model):
|
|||
return action
|
||||
|
||||
return False
|
||||
|
||||
def _mail_get_partner_fields(self, introspect_fields=False):
|
||||
return ['user_partner_id']
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ 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):
|
||||
class GamificationGoalDefinition(models.Model):
|
||||
"""Goal definition
|
||||
|
||||
A goal definition contains the way to evaluate an objective
|
||||
|
|
@ -91,7 +91,11 @@ class GoalDefinition(models.Model):
|
|||
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))
|
||||
raise exceptions.UserError(_(
|
||||
"The domain for the definition %(definition)s seems incorrect, please check it.\n\n%(error_message)s",
|
||||
definition=definition.name,
|
||||
error_message=msg,
|
||||
))
|
||||
return True
|
||||
|
||||
def _check_model_validity(self):
|
||||
|
|
@ -118,7 +122,7 @@ class GoalDefinition(models.Model):
|
|||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
definitions = super(GoalDefinition, self).create(vals_list)
|
||||
definitions = super().create(vals_list)
|
||||
definitions.filtered_domain([
|
||||
('computation_mode', 'in', ['count', 'sum']),
|
||||
])._check_domain_validity()
|
||||
|
|
@ -128,7 +132,7 @@ class GoalDefinition(models.Model):
|
|||
return definitions
|
||||
|
||||
def write(self, vals):
|
||||
res = super(GoalDefinition, self).write(vals)
|
||||
res = super().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'):
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ from odoo import api, fields, models
|
|||
from odoo.tools.translate import html_translate
|
||||
|
||||
|
||||
class KarmaRank(models.Model):
|
||||
class GamificationKarmaRank(models.Model):
|
||||
_name = 'gamification.karma.rank'
|
||||
_description = 'Rank based on karma'
|
||||
_inherit = 'image.mixin'
|
||||
_inherit = ['image.mixin']
|
||||
_order = 'karma_min'
|
||||
|
||||
name = fields.Text(string='Rank Name', translate=True, required=True)
|
||||
|
|
@ -20,20 +20,21 @@ class KarmaRank(models.Model):
|
|||
user_ids = fields.One2many('res.users', 'rank_id', string='Users')
|
||||
rank_users_count = fields.Integer("# Users", compute="_compute_rank_users_count")
|
||||
|
||||
_sql_constraints = [
|
||||
('karma_min_check', "CHECK( karma_min > 0 )", 'The required karma has to be above 0.')
|
||||
]
|
||||
_karma_min_check = models.Constraint(
|
||||
'CHECK( karma_min > 0 )',
|
||||
'The required karma has to be above 0.',
|
||||
)
|
||||
|
||||
@api.depends('user_ids')
|
||||
def _compute_rank_users_count(self):
|
||||
requests_data = self.env['res.users']._read_group([('rank_id', '!=', False)], ['rank_id'], ['rank_id'])
|
||||
requests_mapped_data = dict((data['rank_id'][0], data['rank_id_count']) for data in requests_data)
|
||||
requests_data = self.env['res.users']._read_group([('rank_id', '!=', False)], ['rank_id'], ['__count'])
|
||||
requests_mapped_data = {rank.id: count for rank, count in requests_data}
|
||||
for rank in self:
|
||||
rank.rank_users_count = requests_mapped_data.get(rank.id, 0)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values_list):
|
||||
res = super(KarmaRank, self).create(values_list)
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
if any(res.mapped('karma_min')) > 0:
|
||||
users = self.env['res.users'].sudo().search([('karma', '>=', max(min(res.mapped('karma_min')), 1))])
|
||||
if users:
|
||||
|
|
@ -46,7 +47,7 @@ class KarmaRank(models.Model):
|
|||
low = min(vals['karma_min'], min(self.mapped('karma_min')))
|
||||
high = max(vals['karma_min'], max(self.mapped('karma_min')))
|
||||
|
||||
res = super(KarmaRank, self).write(vals)
|
||||
res = super().write(vals)
|
||||
|
||||
if 'karma_min' in vals:
|
||||
after_ranks = self.env['gamification.karma.rank'].search([], order="karma_min DESC").ids
|
||||
|
|
|
|||
|
|
@ -1,69 +1,139 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import calendar
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.tools import date_utils
|
||||
|
||||
|
||||
class KarmaTracking(models.Model):
|
||||
class GamificationKarmaTracking(models.Model):
|
||||
_name = 'gamification.karma.tracking'
|
||||
_description = 'Track Karma Changes'
|
||||
_rec_name = 'user_id'
|
||||
_order = 'tracking_date DESC'
|
||||
_order = 'tracking_date desc, id desc'
|
||||
|
||||
user_id = fields.Many2one('res.users', 'User', index=True, readonly=True, required=True, ondelete='cascade')
|
||||
old_value = fields.Integer('Old Karma Value', required=True, readonly=True)
|
||||
new_value = fields.Integer('New Karma Value', required=True, readonly=True)
|
||||
def _get_origin_selection_values(self):
|
||||
return [('res.users', _('User'))]
|
||||
|
||||
user_id = fields.Many2one('res.users', 'User', index=True, required=True, ondelete='cascade')
|
||||
old_value = fields.Integer('Old Karma Value', readonly=True)
|
||||
new_value = fields.Integer('New Karma Value', required=True)
|
||||
gain = fields.Integer('Gain', compute='_compute_gain', readonly=False)
|
||||
consolidated = fields.Boolean('Consolidated')
|
||||
tracking_date = fields.Date(default=fields.Date.context_today)
|
||||
|
||||
tracking_date = fields.Datetime(default=fields.Datetime.now, readonly=True, index=True)
|
||||
reason = fields.Text(default=lambda self: _('Add Manually'), string='Description')
|
||||
origin_ref = fields.Reference(
|
||||
string='Source',
|
||||
selection=lambda self: self._get_origin_selection_values(),
|
||||
default=lambda self: f'res.users,{self.env.user.id}',
|
||||
)
|
||||
origin_ref_model_name = fields.Selection(
|
||||
string='Source Type', selection=lambda self: self._get_origin_selection_values(),
|
||||
compute='_compute_origin_ref_model_name', store=True)
|
||||
|
||||
@api.depends('old_value', 'new_value')
|
||||
def _compute_gain(self):
|
||||
for karma in self:
|
||||
karma.gain = karma.new_value - (karma.old_value or 0)
|
||||
|
||||
@api.depends('origin_ref')
|
||||
def _compute_origin_ref_model_name(self):
|
||||
for karma in self:
|
||||
if not karma.origin_ref:
|
||||
karma.origin_ref_model_name = False
|
||||
continue
|
||||
|
||||
karma.origin_ref_model_name = karma.origin_ref._name
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
# fill missing old value with current user karma
|
||||
users = self.env['res.users'].browse([
|
||||
values['user_id']
|
||||
for values in vals_list
|
||||
if 'old_value' not in values and values.get('user_id')
|
||||
])
|
||||
karma_per_users = {user.id: user.karma for user in users}
|
||||
|
||||
for values in vals_list:
|
||||
if 'old_value' not in values and values.get('user_id'):
|
||||
values['old_value'] = karma_per_users[values['user_id']]
|
||||
|
||||
if 'gain' in values and 'old_value' in values:
|
||||
values['new_value'] = values['old_value'] + values['gain']
|
||||
del values['gain']
|
||||
|
||||
return super().create(vals_list)
|
||||
|
||||
@api.model
|
||||
def _consolidate_last_month(self):
|
||||
""" Consolidate last month. Used by a cron to cleanup tracking records. """
|
||||
previous_month_start = fields.Date.today() + relativedelta(months=-1, day=1)
|
||||
return self._process_consolidate(previous_month_start)
|
||||
def _consolidate_cron(self):
|
||||
"""Consolidate the trackings 2 months ago. Used by a cron to cleanup tracking records."""
|
||||
from_date = date_utils.start_of(fields.Datetime.today(), 'month') - relativedelta(months=2)
|
||||
return self._process_consolidate(from_date)
|
||||
|
||||
def _process_consolidate(self, from_date, end_date=None):
|
||||
"""Consolidate the karma trackings.
|
||||
|
||||
The consolidation keeps, for each user, the oldest "old_value" and the most recent
|
||||
"new_value", creates a new karma tracking with those values and removes all karma
|
||||
trackings between those dates. The origin / reason is changed on the consolidated
|
||||
records, so this information is lost in the process.
|
||||
"""
|
||||
self.env['gamification.karma.tracking'].flush_model()
|
||||
|
||||
if not end_date:
|
||||
end_date = date_utils.end_of(date_utils.end_of(from_date, 'month'), 'day')
|
||||
|
||||
def _process_consolidate(self, from_date):
|
||||
""" Consolidate trackings into a single record for a given month, starting
|
||||
at a from_date (included). End date is set to last day of current month
|
||||
using a smart calendar.monthrange construction. """
|
||||
end_date = from_date + relativedelta(day=calendar.monthrange(from_date.year, from_date.month)[1])
|
||||
select_query = """
|
||||
SELECT user_id,
|
||||
(
|
||||
SELECT old_value from gamification_karma_tracking old_tracking
|
||||
WHERE old_tracking.user_id = gamification_karma_tracking.user_id
|
||||
AND tracking_date::timestamp BETWEEN %(from_date)s AND %(to_date)s
|
||||
AND consolidated IS NOT TRUE
|
||||
ORDER BY tracking_date ASC LIMIT 1
|
||||
), (
|
||||
SELECT new_value from gamification_karma_tracking new_tracking
|
||||
WHERE new_tracking.user_id = gamification_karma_tracking.user_id
|
||||
AND tracking_date::timestamp BETWEEN %(from_date)s AND %(to_date)s
|
||||
AND consolidated IS NOT TRUE
|
||||
ORDER BY tracking_date DESC LIMIT 1
|
||||
)
|
||||
FROM gamification_karma_tracking
|
||||
WHERE tracking_date::timestamp BETWEEN %(from_date)s AND %(to_date)s
|
||||
AND consolidated IS NOT TRUE
|
||||
GROUP BY user_id """
|
||||
WITH old_tracking AS (
|
||||
SELECT DISTINCT ON (user_id) user_id, old_value, tracking_date
|
||||
FROM gamification_karma_tracking
|
||||
WHERE tracking_date BETWEEN %(from_date)s
|
||||
AND %(end_date)s
|
||||
AND consolidated IS NOT TRUE
|
||||
ORDER BY user_id, tracking_date ASC, id ASC
|
||||
)
|
||||
INSERT INTO gamification_karma_tracking (
|
||||
user_id,
|
||||
old_value,
|
||||
new_value,
|
||||
tracking_date,
|
||||
origin_ref,
|
||||
consolidated,
|
||||
reason)
|
||||
SELECT DISTINCT ON (nt.user_id)
|
||||
nt.user_id,
|
||||
ot.old_value AS old_value,
|
||||
nt.new_value AS new_value,
|
||||
ot.tracking_date AS from_tracking_date,
|
||||
%(origin_ref)s AS origin_ref,
|
||||
TRUE,
|
||||
%(reason)s
|
||||
FROM gamification_karma_tracking AS nt
|
||||
JOIN old_tracking AS ot
|
||||
ON ot.user_id = nt.user_id
|
||||
WHERE nt.tracking_date BETWEEN %(from_date)s
|
||||
AND %(end_date)s
|
||||
AND nt.consolidated IS NOT TRUE
|
||||
ORDER BY nt.user_id, nt.tracking_date DESC, id DESC
|
||||
"""
|
||||
|
||||
self.env.cr.execute(select_query, {
|
||||
'from_date': from_date,
|
||||
'to_date': end_date,
|
||||
'end_date': end_date,
|
||||
'origin_ref': f'res.users,{self.env.user.id}',
|
||||
'reason': _('Consolidation from %(from_date)s to %(end_date)s', from_date=from_date.date(), end_date=end_date.date()),
|
||||
})
|
||||
results = self.env.cr.dictfetchall()
|
||||
if results:
|
||||
for result in results:
|
||||
result['consolidated'] = True
|
||||
result['tracking_date'] = fields.Date.to_string(from_date)
|
||||
self.create(results)
|
||||
|
||||
self.search([
|
||||
('tracking_date', '>=', from_date),
|
||||
('tracking_date', '<=', end_date),
|
||||
('consolidated', '!=', True)]
|
||||
).unlink()
|
||||
trackings = self.search([
|
||||
('tracking_date', '>=', from_date),
|
||||
('tracking_date', '<=', end_date),
|
||||
('consolidated', '!=', True)]
|
||||
)
|
||||
# HACK: the unlink() AND the flush_all() must have that key in their context!
|
||||
trackings = trackings.with_context(skip_karma_computation=True)
|
||||
trackings.unlink()
|
||||
trackings.env.flush_all()
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,21 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.tools import SQL
|
||||
|
||||
|
||||
class Users(models.Model):
|
||||
class ResUsers(models.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
karma = fields.Integer('Karma', default=0, copy=False)
|
||||
karma = fields.Integer('Karma', compute='_compute_karma', store=True, readonly=False)
|
||||
karma_tracking_ids = fields.One2many('gamification.karma.tracking', 'user_id', string='Karma Changes', groups="base.group_system")
|
||||
badge_ids = fields.One2many('gamification.badge.user', 'user_id', string='Badges', copy=False)
|
||||
gold_badge = fields.Integer('Gold badges count', compute="_get_user_badge_level")
|
||||
silver_badge = fields.Integer('Silver badges count', compute="_get_user_badge_level")
|
||||
bronze_badge = fields.Integer('Bronze badges count', compute="_get_user_badge_level")
|
||||
rank_id = fields.Many2one('gamification.karma.rank', 'Rank')
|
||||
rank_id = fields.Many2one('gamification.karma.rank', 'Rank', index='btree_not_null')
|
||||
next_rank_id = fields.Many2one('gamification.karma.rank', 'Next Rank')
|
||||
|
||||
@api.depends('karma_tracking_ids.new_value')
|
||||
def _compute_karma(self):
|
||||
if self.env.context.get('skip_karma_computation'):
|
||||
# do not need to update the user karma
|
||||
# e.g. during the tracking consolidation
|
||||
return
|
||||
|
||||
self.env['gamification.karma.tracking'].flush_model()
|
||||
|
||||
select_query = """
|
||||
SELECT DISTINCT ON (user_id) user_id, new_value
|
||||
FROM gamification_karma_tracking
|
||||
WHERE user_id = ANY(%(user_ids)s)
|
||||
ORDER BY user_id, tracking_date DESC, id DESC
|
||||
"""
|
||||
self.env.cr.execute(select_query, {'user_ids': self.ids})
|
||||
|
||||
user_karma_map = {
|
||||
values['user_id']: values['new_value']
|
||||
for values in self.env.cr.dictfetchall()
|
||||
}
|
||||
|
||||
for user in self:
|
||||
user.karma = user_karma_map.get(user.id, 0)
|
||||
|
||||
self.sudo()._recompute_rank()
|
||||
|
||||
@api.depends('badge_ids')
|
||||
def _get_user_badge_level(self):
|
||||
""" Return total badge per level of users
|
||||
|
|
@ -40,37 +68,59 @@ class Users(models.Model):
|
|||
self.browse(user_id)['{}_badge'.format(level)] = count
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values_list):
|
||||
res = super(Users, self).create(values_list)
|
||||
def create(self, vals_list):
|
||||
res = super().create(vals_list)
|
||||
|
||||
karma_trackings = []
|
||||
for user in res:
|
||||
if user.karma:
|
||||
karma_trackings.append({'user_id': user.id, 'old_value': 0, 'new_value': user.karma})
|
||||
if karma_trackings:
|
||||
self.env['gamification.karma.tracking'].sudo().create(karma_trackings)
|
||||
self._add_karma_batch({
|
||||
user: {
|
||||
'gain': int(vals['karma']),
|
||||
'old_value': 0,
|
||||
'origin_ref': f'res.users,{self.env.uid}',
|
||||
'reason': _('User Creation'),
|
||||
}
|
||||
for user, vals in zip(res, vals_list)
|
||||
if vals.get('karma')
|
||||
})
|
||||
|
||||
res._recompute_rank()
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
karma_trackings = []
|
||||
if 'karma' in vals:
|
||||
for user in self:
|
||||
if user.karma != vals['karma']:
|
||||
karma_trackings.append({'user_id': user.id, 'old_value': user.karma, 'new_value': vals['karma']})
|
||||
self._add_karma_batch({
|
||||
user: {
|
||||
'gain': int(vals['karma']) - user.karma,
|
||||
'origin_ref': f'res.users,{self.env.uid}',
|
||||
}
|
||||
for user in self
|
||||
if int(vals['karma']) != user.karma
|
||||
})
|
||||
return super().write(vals)
|
||||
|
||||
result = super(Users, self).write(vals)
|
||||
def _add_karma(self, gain, source=None, reason=None):
|
||||
self.ensure_one()
|
||||
values = {'gain': gain, 'source': source, 'reason': reason}
|
||||
return self._add_karma_batch({self: values})
|
||||
|
||||
if karma_trackings:
|
||||
self.env['gamification.karma.tracking'].sudo().create(karma_trackings)
|
||||
if 'karma' in vals:
|
||||
self._recompute_rank()
|
||||
return result
|
||||
def _add_karma_batch(self, values_per_user):
|
||||
if not values_per_user:
|
||||
return
|
||||
|
||||
def add_karma(self, karma):
|
||||
for user in self:
|
||||
user.karma += karma
|
||||
create_values = []
|
||||
for user, values in values_per_user.items():
|
||||
origin = values.get('source') or self.env.user
|
||||
reason = values.get('reason') or _('Add Manually')
|
||||
origin_description = f'{origin.display_name} #{origin.id}'
|
||||
old_value = values.get('old_value', user.karma)
|
||||
|
||||
create_values.append({
|
||||
'new_value': old_value + values['gain'],
|
||||
'old_value': old_value,
|
||||
'origin_ref': f'{origin._name},{origin.id}',
|
||||
'reason': f'{reason} ({origin_description})',
|
||||
'user_id': user.id,
|
||||
})
|
||||
|
||||
self.env['gamification.karma.tracking'].sudo().create(create_values)
|
||||
return True
|
||||
|
||||
def _get_tracking_karma_gain_position(self, user_domain, from_date=None, to_date=None):
|
||||
|
|
@ -90,49 +140,46 @@ class Users(models.Model):
|
|||
:param to_date: compute karma gained before this date (included) or until
|
||||
end of time;
|
||||
|
||||
:return list: [{
|
||||
'user_id': user_id (belonging to current record set),
|
||||
'karma_gain_total': integer, karma gained in the given timeframe,
|
||||
'karma_position': integer, ranking position
|
||||
}, {..}] ordered by karma_position desc
|
||||
:rtype: list[dict]
|
||||
:return:
|
||||
::
|
||||
|
||||
[{
|
||||
'user_id': user_id (belonging to current record set),
|
||||
'karma_gain_total': integer, karma gained in the given timeframe,
|
||||
'karma_position': integer, ranking position
|
||||
}, {..}]
|
||||
|
||||
ordered by descending karma position
|
||||
"""
|
||||
if not self:
|
||||
return []
|
||||
|
||||
where_query = self.env['res.users']._where_calc(user_domain)
|
||||
user_from_clause, user_where_clause, where_clause_params = where_query.get_sql()
|
||||
where_query = self.env['res.users']._search(user_domain, bypass_access=True)
|
||||
|
||||
params = []
|
||||
if from_date:
|
||||
date_from_condition = 'AND tracking.tracking_date::timestamp >= timestamp %s'
|
||||
params.append(from_date)
|
||||
if to_date:
|
||||
date_to_condition = 'AND tracking.tracking_date::timestamp <= timestamp %s'
|
||||
params.append(to_date)
|
||||
params.append(tuple(self.ids))
|
||||
|
||||
query = """
|
||||
sql = SQL("""
|
||||
SELECT final.user_id, final.karma_gain_total, final.karma_position
|
||||
FROM (
|
||||
SELECT intermediate.user_id, intermediate.karma_gain_total, row_number() OVER (ORDER BY intermediate.karma_gain_total DESC) AS karma_position
|
||||
FROM (
|
||||
SELECT "res_users".id as user_id, COALESCE(SUM("tracking".new_value - "tracking".old_value), 0) as karma_gain_total
|
||||
FROM %(user_from_clause)s
|
||||
FROM %s
|
||||
LEFT JOIN "gamification_karma_tracking" as "tracking"
|
||||
ON "res_users".id = "tracking".user_id AND "res_users"."active" = TRUE
|
||||
WHERE %(user_where_clause)s %(date_from_condition)s %(date_to_condition)s
|
||||
ON "res_users".id = "tracking".user_id AND "res_users"."active" IS TRUE
|
||||
WHERE %s %s %s
|
||||
GROUP BY "res_users".id
|
||||
ORDER BY karma_gain_total DESC
|
||||
) intermediate
|
||||
) final
|
||||
WHERE final.user_id IN %%s""" % {
|
||||
'user_from_clause': user_from_clause,
|
||||
'user_where_clause': user_where_clause or (not from_date and not to_date and 'TRUE') or '',
|
||||
'date_from_condition': date_from_condition if from_date else '',
|
||||
'date_to_condition': date_to_condition if to_date else ''
|
||||
}
|
||||
WHERE final.user_id IN %s""",
|
||||
where_query.from_clause,
|
||||
where_query.where_clause or SQL("TRUE"),
|
||||
SQL("AND tracking.tracking_date::DATE >= %s::DATE", from_date) if from_date else SQL(),
|
||||
SQL("AND tracking.tracking_date::DATE <= %s::DATE", to_date) if to_date else SQL(),
|
||||
tuple(self.ids),
|
||||
)
|
||||
|
||||
self.env.cr.execute(query, tuple(where_clause_params + params))
|
||||
self.env.cr.execute(sql)
|
||||
return self.env.cr.dictfetchall()
|
||||
|
||||
def _get_karma_position(self, user_domain):
|
||||
|
|
@ -148,32 +195,36 @@ WHERE final.user_id IN %%s""" % {
|
|||
:param user_domain: general domain (i.e. active, karma > 1, website, ...)
|
||||
to compute the absolute position of the current record set
|
||||
|
||||
:return list: [{
|
||||
'user_id': user_id (belonging to current record set),
|
||||
'karma_position': integer, ranking position
|
||||
}, {..}] ordered by karma_position desc
|
||||
:rtype: list[dict]
|
||||
:return:
|
||||
|
||||
::
|
||||
|
||||
[{
|
||||
'user_id': user_id (belonging to current record set),
|
||||
'karma_position': integer, ranking position
|
||||
}, {..}] ordered by karma_position desc
|
||||
"""
|
||||
if not self:
|
||||
return {}
|
||||
|
||||
where_query = self.env['res.users']._where_calc(user_domain)
|
||||
user_from_clause, user_where_clause, where_clause_params = where_query.get_sql()
|
||||
where_query = self.env['res.users']._search(user_domain, bypass_access=True)
|
||||
|
||||
# we search on every user in the DB to get the real positioning (not the one inside the subset)
|
||||
# then, we filter to get only the subset.
|
||||
query = """
|
||||
sql = SQL("""
|
||||
SELECT sub.user_id, sub.karma_position
|
||||
FROM (
|
||||
SELECT "res_users"."id" as user_id, row_number() OVER (ORDER BY res_users.karma DESC) AS karma_position
|
||||
FROM %(user_from_clause)s
|
||||
WHERE %(user_where_clause)s
|
||||
FROM %s
|
||||
WHERE %s
|
||||
) sub
|
||||
WHERE sub.user_id IN %%s""" % {
|
||||
'user_from_clause': user_from_clause,
|
||||
'user_where_clause': user_where_clause or 'TRUE',
|
||||
}
|
||||
|
||||
self.env.cr.execute(query, tuple(where_clause_params + [tuple(self.ids)]))
|
||||
WHERE sub.user_id IN %s""",
|
||||
where_query.from_clause,
|
||||
where_query.where_clause or SQL("TRUE"),
|
||||
tuple(self.ids),
|
||||
)
|
||||
self.env.cr.execute(sql)
|
||||
return self.env.cr.dictfetchall()
|
||||
|
||||
def _rank_changed(self):
|
||||
|
|
@ -290,10 +341,9 @@ WHERE sub.user_id IN %%s""" % {
|
|||
|
||||
if self.next_rank_id:
|
||||
return self.next_rank_id
|
||||
elif not self.rank_id:
|
||||
return self.env['gamification.karma.rank'].search([], order="karma_min ASC", limit=1)
|
||||
else:
|
||||
return self.env['gamification.karma.rank']
|
||||
domain = [('karma_min', '>', self.rank_id.karma_min)] if self.rank_id else []
|
||||
return self.env['gamification.karma.rank'].search(domain, order="karma_min ASC", limit=1)
|
||||
|
||||
def get_gamification_redirection_data(self):
|
||||
"""
|
||||
|
|
@ -303,3 +353,18 @@ WHERE sub.user_id IN %%s""" % {
|
|||
"""
|
||||
self.ensure_one()
|
||||
return []
|
||||
|
||||
def action_karma_report(self):
|
||||
self.ensure_one()
|
||||
|
||||
return {
|
||||
'name': _('Karma Updates'),
|
||||
'res_model': 'gamification.karma.tracking',
|
||||
'target': 'current',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_mode': 'list',
|
||||
'context': {
|
||||
'default_user_id': self.id,
|
||||
'search_default_user_id': self.id,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue