[MIG] gamification: Migrate to new API
Includes compatibility-change to website_forum: a selection(callable) converted to a Selection(list) since that can easily be extended in the new API.
This commit is contained in:
committed by
Yannick Tivisse
parent
c7a818caee
commit
0c649644db
@@ -33,6 +33,4 @@ Both goals and badges are flexibles and can be adapted to a large range of modul
|
||||
'data/badge.xml',
|
||||
'views/gamification.xml',
|
||||
],
|
||||
'application': False,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="badge_good_job" model="gamification.badge">
|
||||
<field name="name">Good Job</field>
|
||||
@@ -58,4 +57,4 @@
|
||||
]]></field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record forcecreate="True" id="ir_cron_check_challenge"
|
||||
model="ir.cron">
|
||||
@@ -13,4 +12,4 @@
|
||||
<field name="args">()</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<!-- Mail template is done in a NOUPDATE block
|
||||
so users can freely customize/delete them -->
|
||||
<data noupdate="1">
|
||||
@@ -163,7 +162,7 @@
|
||||
<field name="period">once</field>
|
||||
<field name="visibility_mode">personal</field>
|
||||
<field name="report_message_frequency">never</field>
|
||||
<field name="user_domain" eval="[('groups_id', '=', ref('base.group_user'))]" />
|
||||
<field name="user_domain" eval="str([('groups_id', '=', ref('base.group_user'))])" />
|
||||
<field name="state">inprogress</field>
|
||||
<field name="category">other</field>
|
||||
</record>
|
||||
@@ -173,7 +172,7 @@
|
||||
<field name="period">once</field>
|
||||
<field name="visibility_mode">personal</field>
|
||||
<field name="report_message_frequency">never</field>
|
||||
<field name="user_domain" eval="[('groups_id', '=', ref('base.group_erp_manager'))]" />
|
||||
<field name="user_domain" eval="str([('groups_id', '=', ref('base.group_erp_manager'))])" />
|
||||
<field name="state">inprogress</field>
|
||||
<field name="category">other</field>
|
||||
</record>
|
||||
@@ -202,4 +201,4 @@
|
||||
</record>
|
||||
</data>
|
||||
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
+154
-183
@@ -1,18 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||
from openerp.tools.translate import _
|
||||
|
||||
from datetime import date
|
||||
import logging
|
||||
from openerp.exceptions import UserError
|
||||
from datetime import date
|
||||
|
||||
from odoo import api, fields, models, _, exceptions
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class gamification_badge_user(osv.Model):
|
||||
class BadgeUser(models.Model):
|
||||
"""User having received a badge"""
|
||||
|
||||
_name = 'gamification.badge.user'
|
||||
@@ -20,19 +16,16 @@ class gamification_badge_user(osv.Model):
|
||||
_order = "create_date desc"
|
||||
_rec_name = "badge_name"
|
||||
|
||||
_columns = {
|
||||
'user_id': fields.many2one('res.users', string="User", required=True, ondelete="cascade", select=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", select=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.related('badge_id', 'name', type="char", string="Badge Name"),
|
||||
'create_date': fields.datetime('Created', readonly=True),
|
||||
'create_uid': fields.many2one('res.users', string='Creator', readonly=True),
|
||||
}
|
||||
user_id = fields.Many2one('res.users', string="User", required=True, ondelete="cascade", select=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", select=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")
|
||||
create_date = fields.Datetime('Created', readonly=True)
|
||||
create_uid = fields.Many2one('res.users', string='Creator', readonly=True)
|
||||
|
||||
|
||||
def _send_badge(self, cr, uid, ids, context=None):
|
||||
def _send_badge(self):
|
||||
"""Send a notification to a user for receiving a badge
|
||||
|
||||
Does not verify constrains on badge granting.
|
||||
@@ -40,27 +33,29 @@ class gamification_badge_user(osv.Model):
|
||||
The stats counters are incremented
|
||||
:param ids: list(int) of badge users that will receive the badge
|
||||
"""
|
||||
res = True
|
||||
temp_obj = self.pool.get('mail.template')
|
||||
user_obj = self.pool.get('res.users')
|
||||
template_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'gamification', 'email_template_badge_received')[1]
|
||||
for badge_user in self.browse(cr, uid, ids, context=context):
|
||||
template = temp_obj.get_email_template(cr, uid, template_id, badge_user.id, context=context)
|
||||
body_html = temp_obj.render_template(cr, uid, template.body_html, 'gamification.badge.user', badge_user.id, context=template._context)
|
||||
res = user_obj.message_post(
|
||||
cr, uid, badge_user.user_id.id,
|
||||
Template = self.env['mail.template']
|
||||
template_id = self.env.ref('gamification.email_template_badge_received')
|
||||
|
||||
for badge_user in self:
|
||||
# .ids would trigger the "multi mode" which returns a mapping of
|
||||
# res_id to templates
|
||||
template = template_id.get_email_template(badge_user.id)
|
||||
body_html = Template.with_context(template._context).render_template(template.body_html, 'gamification.badge.user', badge_user.ids)
|
||||
badge_user.user_id.message_post(
|
||||
body=body_html,
|
||||
subtype='gamification.mt_badge_granted',
|
||||
partner_ids=[badge_user.user_id.partner_id.id],
|
||||
context=context)
|
||||
return res
|
||||
partner_ids=badge_user.user_id.partner_id.ids
|
||||
)
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
self.pool.get('gamification.badge').check_granting(cr, uid, badge_id=vals.get('badge_id'), context=context)
|
||||
return super(gamification_badge_user, self).create(cr, uid, vals, context=context)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
self.env['gamification.badge'].browse(vals['badge_id']).check_granting()
|
||||
return super(BadgeUser, self).create(vals)
|
||||
|
||||
|
||||
class gamification_badge(osv.Model):
|
||||
class GamificationBadge(models.Model):
|
||||
"""Badge object that users can send and receive"""
|
||||
|
||||
CAN_GRANT = 1
|
||||
@@ -73,204 +68,180 @@ class gamification_badge(osv.Model):
|
||||
_description = 'Gamification badge'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
def _get_owners_info(self, cr, uid, ids, name, args, context=None):
|
||||
name = fields.Char('Badge', required=True, translate=True)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
description = fields.Text('Description', translate=True)
|
||||
image = fields.Binary("Image", attachment=True, help="This field holds the image used for the badge, limited to 256x256")
|
||||
|
||||
rule_auth = fields.Selection([
|
||||
('everyone', 'Everyone'),
|
||||
('users', 'A selected list of users'),
|
||||
('having', 'People having some badges'),
|
||||
('nobody', 'No one, assigned through challenges'),
|
||||
], default='everyone',
|
||||
string="Allowance to Grant", help="Who can grant this badge", required=True)
|
||||
rule_auth_user_ids = fields.Many2many(
|
||||
'res.users', 'rel_badge_auth_users',
|
||||
string='Authorized Users',
|
||||
help="Only these people can give this badge")
|
||||
rule_auth_badge_ids = fields.Many2many(
|
||||
'gamification.badge', 'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id',
|
||||
string='Required Badges',
|
||||
help="Only the people having these badges can give this badge")
|
||||
|
||||
rule_max = fields.Boolean('Monthly Limited Sending', help="Check to set a monthly limit per person of sending this badge")
|
||||
rule_max_number = fields.Integer('Limitation Number', help="The maximum number of time this badge can be sent per month per person.")
|
||||
challenge_ids = fields.One2many('gamification.challenge', 'reward_id', string="Reward of Challenges")
|
||||
|
||||
goal_definition_ids = fields.Many2many(
|
||||
'gamification.goal.definition', 'badge_unlocked_definition_rel',
|
||||
string='Rewarded by', help="The users that have succeeded theses goals will receive automatically the badge.")
|
||||
|
||||
owner_ids = fields.One2many(
|
||||
'gamification.badge.user', 'badge_id',
|
||||
string='Owners', help='The list of instances of this badge granted to users')
|
||||
|
||||
stat_count = fields.Integer("Total", compute='_get_owners_info', help="The number of time this badge has been received.")
|
||||
stat_count_distinct = fields.Integer("Number of users", compute='_get_owners_info', help="The number of time this badge has been received by unique users.")
|
||||
unique_owner_ids = fields.Many2many(
|
||||
'res.users', string="Unique Owners", compute='_get_owners_info',
|
||||
help="The list of unique users having received this badge.")
|
||||
|
||||
stat_this_month = fields.Integer(
|
||||
"Monthly total", compute='_get_badge_user_stats',
|
||||
help="The number of time this badge has been received this month.")
|
||||
stat_my = fields.Integer(
|
||||
"My Total", compute='_get_badge_user_stats',
|
||||
help="The number of time the current user has received this badge.")
|
||||
stat_my_this_month = fields.Integer(
|
||||
"My Monthly Total", compute='_get_badge_user_stats',
|
||||
help="The number of time the current user has received this badge this month.")
|
||||
stat_my_monthly_sending = fields.Integer(
|
||||
'My Monthly Sending Total',
|
||||
compute='_get_badge_user_stats',
|
||||
help="The number of time the current user has sent this badge this month.")
|
||||
|
||||
remaining_sending = fields.Integer(
|
||||
"Remaining Sending Allowed", compute='_remaining_sending_calc',
|
||||
help="If a maximum is set")
|
||||
|
||||
@api.depends('owner_ids')
|
||||
def _get_owners_info(self):
|
||||
"""Return:
|
||||
the list of unique res.users ids having received this badge
|
||||
the total number of time this badge was granted
|
||||
the total number of users this badge was granted to
|
||||
"""
|
||||
result = dict((res_id, {'stat_count': 0, 'stat_count_distinct': 0, 'unique_owner_ids': []}) for res_id in ids)
|
||||
|
||||
cr.execute("""
|
||||
self.env.cr.execute("""
|
||||
SELECT badge_id, count(user_id) as stat_count,
|
||||
count(distinct(user_id)) as stat_count_distinct,
|
||||
array_agg(distinct(user_id)) as unique_owner_ids
|
||||
FROM gamification_badge_user
|
||||
WHERE badge_id in %s
|
||||
GROUP BY badge_id
|
||||
""", (tuple(ids),))
|
||||
for (badge_id, stat_count, stat_count_distinct, unique_owner_ids) in cr.fetchall():
|
||||
result[badge_id] = {
|
||||
'stat_count': stat_count,
|
||||
'stat_count_distinct': stat_count_distinct,
|
||||
'unique_owner_ids': unique_owner_ids,
|
||||
}
|
||||
return result
|
||||
""", [tuple(self.ids)])
|
||||
|
||||
def _get_badge_user_stats(self, cr, uid, ids, name, args, context=None):
|
||||
defaults = {
|
||||
'stat_count': 0,
|
||||
'stat_count_distinct': 0,
|
||||
'unique_owner_ids': [],
|
||||
}
|
||||
mapping = {
|
||||
badge_id: {
|
||||
'stat_count': count,
|
||||
'stat_count_distinct': distinct_count,
|
||||
'unique_owner_ids': owner_ids,
|
||||
}
|
||||
for (badge_id, count, distinct_count, owner_ids) in self.env.cr._obj
|
||||
}
|
||||
for badge in self:
|
||||
badge.update(mapping.get(badge.id, defaults))
|
||||
|
||||
@api.depends('owner_ids.badge_id', 'owner_ids.create_date', 'owner_ids.user_id')
|
||||
def _get_badge_user_stats(self):
|
||||
"""Return stats related to badge users"""
|
||||
result = dict.fromkeys(ids, False)
|
||||
badge_user_obj = self.pool.get('gamification.badge.user')
|
||||
first_month_day = date.today().replace(day=1).strftime(DF)
|
||||
for bid in ids:
|
||||
result[bid] = {
|
||||
'stat_my': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid)], context=context, count=True),
|
||||
'stat_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_date', '>=', first_month_day)], context=context, count=True),
|
||||
'stat_my_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True),
|
||||
'stat_my_monthly_sending': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_uid', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True)
|
||||
}
|
||||
return result
|
||||
first_month_day = fields.Date.to_string(date.today().replace(day=1))
|
||||
|
||||
def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
|
||||
for badge in self:
|
||||
owners = badge.owner_ids
|
||||
badge.stats_my = sum(o.user_id == self.env.user for o in owners)
|
||||
badge.stats_this_month = sum(o.create_date >= first_month_day for o in owners)
|
||||
badge.stats_my_this_month = sum(
|
||||
o.user_id == self.env.user and o.create_date >= first_month_day
|
||||
for o in owners
|
||||
)
|
||||
badge.stats_my_monthly_sending = sum(
|
||||
o.create_uid == self.env.user and o.create_date >= first_month_day
|
||||
for o in owners
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'rule_auth',
|
||||
'rule_auth_user_ids',
|
||||
'rule_auth_badge_ids',
|
||||
'rule_max',
|
||||
'rule_max_number',
|
||||
'stat_my_monthly_sending',
|
||||
)
|
||||
def _remaining_sending_calc(self):
|
||||
"""Computes the number of badges remaining the user can send
|
||||
|
||||
0 if not allowed or no remaining
|
||||
integer if limited sending
|
||||
-1 if infinite (should not be displayed)
|
||||
"""
|
||||
result = dict.fromkeys(ids, False)
|
||||
for badge in self.browse(cr, uid, ids, context=context):
|
||||
if self._can_grant_badge(cr, uid, badge.id, context) != 1:
|
||||
for badge in self:
|
||||
if badge._can_grant_badge() != self.CAN_GRANT:
|
||||
# if the user cannot grant this badge at all, result is 0
|
||||
result[badge.id] = 0
|
||||
badge.remaining_sending = 0
|
||||
elif not badge.rule_max:
|
||||
# if there is no limitation, -1 is returned which means 'infinite'
|
||||
result[badge.id] = -1
|
||||
badge.remaining_sending = -1
|
||||
else:
|
||||
result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
|
||||
return result
|
||||
badge.remaining_sending = badge.rule_max_number - badge.stat_my_monthly_sending
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Badge', required=True, translate=True),
|
||||
'description': fields.text('Description', translate=True),
|
||||
'image': fields.binary("Image", attachment=True,
|
||||
help="This field holds the image used for the badge, limited to 256x256"),
|
||||
'rule_auth': fields.selection([
|
||||
('everyone', 'Everyone'),
|
||||
('users', 'A selected list of users'),
|
||||
('having', 'People having some badges'),
|
||||
('nobody', 'No one, assigned through challenges'),
|
||||
],
|
||||
string="Allowance to Grant",
|
||||
help="Who can grant this badge",
|
||||
required=True),
|
||||
'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users',
|
||||
string='Authorized Users',
|
||||
help="Only these people can give this badge"),
|
||||
'rule_auth_badge_ids': fields.many2many('gamification.badge',
|
||||
'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id',
|
||||
string='Required Badges',
|
||||
help="Only the people having these badges can give this badge"),
|
||||
|
||||
'rule_max': fields.boolean('Monthly Limited Sending',
|
||||
help="Check to set a monthly limit per person of sending this badge"),
|
||||
'rule_max_number': fields.integer('Limitation Number',
|
||||
help="The maximum number of time this badge can be sent per month per person."),
|
||||
'stat_my_monthly_sending': fields.function(_get_badge_user_stats,
|
||||
type="integer",
|
||||
string='My Monthly Sending Total',
|
||||
multi='badge_users',
|
||||
help="The number of time the current user has sent this badge this month."),
|
||||
'remaining_sending': fields.function(_remaining_sending_calc, type='integer',
|
||||
string='Remaining Sending Allowed', help="If a maxium is set"),
|
||||
|
||||
'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
|
||||
string="Reward of Challenges"),
|
||||
|
||||
'goal_definition_ids': fields.many2many('gamification.goal.definition', 'badge_unlocked_definition_rel',
|
||||
string='Rewarded by',
|
||||
help="The users that have succeeded theses goals will receive automatically the badge."),
|
||||
|
||||
'owner_ids': fields.one2many('gamification.badge.user', 'badge_id',
|
||||
string='Owners', help='The list of instances of this badge granted to users'),
|
||||
'active': fields.boolean('Active'),
|
||||
'unique_owner_ids': fields.function(_get_owners_info,
|
||||
string='Unique Owners',
|
||||
help="The list of unique users having received this badge.",
|
||||
multi='unique_users',
|
||||
type="many2many", relation="res.users"),
|
||||
|
||||
'stat_count': fields.function(_get_owners_info, string='Total',
|
||||
type="integer",
|
||||
multi='stat_users',
|
||||
help="The number of time this badge has been received."),
|
||||
'stat_count_distinct': fields.function(_get_owners_info,
|
||||
type="integer",
|
||||
string='Number of users',
|
||||
multi='stat_users',
|
||||
help="The number of time this badge has been received by unique users."),
|
||||
'stat_this_month': fields.function(_get_badge_user_stats,
|
||||
type="integer",
|
||||
string='Monthly total',
|
||||
multi='badge_users',
|
||||
help="The number of time this badge has been received this month."),
|
||||
'stat_my': fields.function(_get_badge_user_stats, string='My Total',
|
||||
type="integer",
|
||||
multi='badge_users',
|
||||
help="The number of time the current user has received this badge."),
|
||||
'stat_my_this_month': fields.function(_get_badge_user_stats,
|
||||
type="integer",
|
||||
string='My Monthly Total',
|
||||
multi='badge_users',
|
||||
help="The number of time the current user has received this badge this month."),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'rule_auth': 'everyone',
|
||||
'active': True,
|
||||
}
|
||||
|
||||
def check_granting(self, cr, uid, badge_id, context=None):
|
||||
def check_granting(self):
|
||||
"""Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception
|
||||
if not
|
||||
|
||||
Do not check for SUPERUSER_ID
|
||||
"""
|
||||
status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
|
||||
status_code = self._can_grant_badge()
|
||||
if status_code == self.CAN_GRANT:
|
||||
return True
|
||||
elif status_code == self.NOBODY_CAN_GRANT:
|
||||
raise UserError(_('This badge can not be sent by users.'))
|
||||
raise exceptions.UserError(_('This badge can not be sent by users.'))
|
||||
elif status_code == self.USER_NOT_VIP:
|
||||
raise UserError(_('You are not in the user allowed list.'))
|
||||
raise exceptions.UserError(_('You are not in the user allowed list.'))
|
||||
elif status_code == self.BADGE_REQUIRED:
|
||||
raise UserError(_('You do not have the required badges.'))
|
||||
raise exceptions.UserError(_('You do not have the required badges.'))
|
||||
elif status_code == self.TOO_MANY:
|
||||
raise UserError(_('You have already sent this badge too many time this month.'))
|
||||
raise exceptions.UserError(_('You have already sent this badge too many time this month.'))
|
||||
else:
|
||||
_logger.exception("Unknown badge status code: %d" % int(status_code))
|
||||
_logger.error("Unknown badge status code: %s" % status_code)
|
||||
return False
|
||||
|
||||
def _can_grant_badge(self, cr, uid, badge_id, context=None):
|
||||
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 uid == SUPERUSER_ID:
|
||||
if self.env.user._is_admin():
|
||||
return self.CAN_GRANT
|
||||
|
||||
badge = self.browse(cr, uid, badge_id, context=context)
|
||||
|
||||
if badge.rule_auth == 'nobody':
|
||||
if self.rule_auth == 'nobody':
|
||||
return self.NOBODY_CAN_GRANT
|
||||
|
||||
elif badge.rule_auth == 'users' and uid not in [user.id for user in badge.rule_auth_user_ids]:
|
||||
elif self.rule_auth == 'users' and self.env.user not in self.rule_auth_user_ids:
|
||||
return self.USER_NOT_VIP
|
||||
elif self.rule_auth == 'having':
|
||||
all_user_badges = self.env['gamification.badge.user'].search([('user_id', '=', self.env.uid)])
|
||||
if self.rule_auth_badge_ids - all_user_badges:
|
||||
return self.BADGE_REQUIRED
|
||||
|
||||
elif badge.rule_auth == 'having':
|
||||
all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', uid)], context=context)
|
||||
for required_badge in badge.rule_auth_badge_ids:
|
||||
if required_badge.id not in all_user_badges:
|
||||
return self.BADGE_REQUIRED
|
||||
|
||||
if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
|
||||
if self.rule_max and self.stat_my_monthly_sending >= self.rule_max_number:
|
||||
return self.TOO_MANY
|
||||
|
||||
# badge.rule_auth == 'everyone' -> no check
|
||||
return self.CAN_GRANT
|
||||
|
||||
def check_progress(self, cr, uid, context=None):
|
||||
try:
|
||||
model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
|
||||
except ValueError:
|
||||
return True
|
||||
badge_user_obj = self.pool.get('gamification.badge.user')
|
||||
if not badge_user_obj.search(cr, uid, [('user_id', '=', uid), ('badge_id', '=', res_id)], context=context):
|
||||
values = {
|
||||
'user_id': uid,
|
||||
'badge_id': res_id,
|
||||
}
|
||||
badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)
|
||||
return True
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+252
-299
@@ -1,21 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import fields, osv, expression
|
||||
from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||
from openerp.tools.safe_eval import safe_eval
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import UserError
|
||||
|
||||
import logging
|
||||
import time
|
||||
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
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class gamification_goal_definition(osv.Model):
|
||||
class GoalDefinition(models.Model):
|
||||
"""Goal definition
|
||||
|
||||
A goal definition contains the way to evaluate an objective
|
||||
@@ -25,273 +22,233 @@ class gamification_goal_definition(osv.Model):
|
||||
_name = 'gamification.goal.definition'
|
||||
_description = 'Gamification goal definition'
|
||||
|
||||
def _get_suffix(self, cr, uid, ids, field_name, arg, context=None):
|
||||
res = dict.fromkeys(ids, '')
|
||||
for goal in self.browse(cr, uid, ids, context=context):
|
||||
if goal.suffix and not goal.monetary:
|
||||
res[goal.id] = goal.suffix
|
||||
elif goal.monetary:
|
||||
# use the current user's company currency
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context)
|
||||
if goal.suffix:
|
||||
res[goal.id] = "%s %s" % (user.company_id.currency_id.symbol, goal.suffix)
|
||||
else:
|
||||
res[goal.id] = user.company_id.currency_id.symbol
|
||||
else:
|
||||
res[goal.id] = ""
|
||||
return res
|
||||
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="Defined how will be computed the goals. 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')
|
||||
field_id = fields.Many2one('ir.model.fields', string='Field to Sum', help='The field containing the value to evaluate')
|
||||
field_date_id = fields.Many2one('ir.model.fields', string='Date Field', help='The date to use for the time period evaluated')
|
||||
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.")
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Goal Definition', required=True, translate=True),
|
||||
'description': fields.text('Goal Description'),
|
||||
'monetary': fields.boolean('Monetary Value', 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.function(_get_suffix, type="char", string="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'),
|
||||
],
|
||||
string="Computation Mode",
|
||||
help="Defined how will be computed the goals. The result of the operation will be stored in the field 'Current'.",
|
||||
required=True),
|
||||
'display_mode': fields.selection([
|
||||
('progress', 'Progressive (using numerical values)'),
|
||||
('boolean', 'Exclusive (done or not-done)'),
|
||||
],
|
||||
string="Displayed as", required=True),
|
||||
'model_id': fields.many2one('ir.model',
|
||||
string='Model',
|
||||
help='The model object for the field to evaluate'),
|
||||
'field_id': fields.many2one('ir.model.fields',
|
||||
string='Field to Sum',
|
||||
help='The field containing the value to evaluate'),
|
||||
'field_date_id': fields.many2one('ir.model.fields',
|
||||
string='Date Field',
|
||||
help='The date to use for the time period evaluated'),
|
||||
'domain': fields.char("Filter Domain",
|
||||
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.",
|
||||
required=True),
|
||||
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 distinct one user form 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.")
|
||||
|
||||
'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 distinct one user form the other, e.g. user_id, partner_id..."),
|
||||
'batch_user_expression': fields.char("Evaluted 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')
|
||||
],
|
||||
string='Goal Performance',
|
||||
help='A goal is considered as completed when the current value is compared to the value to reach',
|
||||
required=True),
|
||||
'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 = []
|
||||
|
||||
_defaults = {
|
||||
'condition': 'higher',
|
||||
'computation_mode': 'manually',
|
||||
'domain': "[]",
|
||||
'monetary': False,
|
||||
'display_mode': 'progress',
|
||||
}
|
||||
if goal.monetary:
|
||||
items.append(self.env.user.company_id.currency_id.symbol or u'¤')
|
||||
if goal.suffix:
|
||||
items.append(goal.suffix)
|
||||
|
||||
def number_following(self, cr, uid, model_name="mail.thread", context=None):
|
||||
"""Return the number of 'model_name' objects the user is following
|
||||
goal.full_suffix = u' '.join(items)
|
||||
|
||||
The model specified in 'model_name' must inherit from mail.thread
|
||||
"""
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
return self.pool.get('mail.followers').search(cr, uid, [('res_model', '=', model_name), ('partner_id', '=', user.partner_id.id)], count=True, context=context)
|
||||
|
||||
def _check_domain_validity(self, cr, uid, ids, context=None):
|
||||
def _check_domain_validity(self):
|
||||
# take admin as should always be present
|
||||
superuser = self.pool['res.users'].browse(cr, uid, SUPERUSER_ID, context=context)
|
||||
for definition in self.browse(cr, uid, ids, context=context):
|
||||
for definition in self:
|
||||
if definition.computation_mode not in ('count', 'sum'):
|
||||
continue
|
||||
|
||||
obj = self.pool[definition.model_id.model]
|
||||
Obj = self.env[definition.model_id.model]
|
||||
try:
|
||||
domain = safe_eval(definition.domain, {'user': superuser})
|
||||
# demmy search to make sure the domain is valid
|
||||
obj.search(cr, uid, domain, context=context, count=True)
|
||||
domain = safe_eval(definition.domain, {
|
||||
'user': self.env.user.sudo(self.env.user)
|
||||
})
|
||||
# dummy search to make sure the domain is valid
|
||||
Obj.search_count(domain)
|
||||
except (ValueError, SyntaxError), e:
|
||||
msg = e.message or (e.msg + '\n' + e.text)
|
||||
raise 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 %s seems incorrect, please check it.\n\n%s") % (definition.name, msg))
|
||||
return True
|
||||
|
||||
def _check_model_validity(self, cr, uid, ids, context=None):
|
||||
def _check_model_validity(self):
|
||||
""" make sure the selected field and model are usable"""
|
||||
for definition in self.browse(cr, uid, ids, context=context):
|
||||
for definition in self:
|
||||
try:
|
||||
if not definition.model_id or not definition.field_id:
|
||||
if not (definition.model_id and definition.field_id):
|
||||
continue
|
||||
|
||||
model = self.pool[definition.model_id.model]
|
||||
field = model._fields[definition.field_id.name]
|
||||
if not field.store:
|
||||
raise UserError(
|
||||
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 %s seems incorrect, please check it.\n\n%s not stored") % (definition.name, definition.field_id.name))
|
||||
except KeyError, e:
|
||||
raise UserError(
|
||||
raise exceptions.UserError(
|
||||
_("The model configuration for the definition %s seems incorrect, please check it.\n\n%s not found") % (definition.name, e.message))
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
res_id = super(gamification_goal_definition, self).create(cr, uid, vals, context=context)
|
||||
if vals.get('computation_mode') in ('count', 'sum'):
|
||||
self._check_domain_validity(cr, uid, [res_id], context=context)
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
definition = super(GoalDefinition, self).create(vals)
|
||||
if definition.computation_mode in ('count', 'sum'):
|
||||
definition._check_domain_validity()
|
||||
if vals.get('field_id'):
|
||||
self._check_model_validity(cr, uid, [res_id], context=context)
|
||||
return res_id
|
||||
definition._check_model_validity()
|
||||
return definition
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
res = super(gamification_goal_definition, self).write(cr, uid, ids, vals, context=context)
|
||||
@api.multi
|
||||
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(cr, uid, ids, context=context)
|
||||
self._check_domain_validity()
|
||||
if vals.get('field_id') or vals.get('model_id') or vals.get('batch_mode'):
|
||||
self._check_model_validity(cr, uid, ids, context=context)
|
||||
self._check_model_validity()
|
||||
return res
|
||||
|
||||
def on_change_model_id(self, cr, uid, ids, model_id, context=None):
|
||||
@api.onchange('model_id')
|
||||
def _change_model_id(self):
|
||||
"""Force domain for the `field_id` and `field_date_id` fields"""
|
||||
if not model_id:
|
||||
if not self.model_id:
|
||||
return {'domain': {'field_id': expression.FALSE_DOMAIN, 'field_date_id': expression.FALSE_DOMAIN}}
|
||||
model = self.pool['ir.model'].browse(cr, uid, model_id, context=context)
|
||||
model_fields_domain = [('store', '=', True),
|
||||
'|', ('model_id', '=', model_id), ('model_id', 'in', model.inherited_model_ids.ids)]
|
||||
model = self.env['ir.model'].browse(self.model_id)
|
||||
model_fields_domain = [
|
||||
('store', '=', True),
|
||||
'|', ('model_id', '=', self.model_id),
|
||||
('model_id', 'in', model.inherited_model_ids.ids)]
|
||||
model_date_fields_domain = expression.AND([[('ttype', 'in', ('date', 'datetime'))], model_fields_domain])
|
||||
return {'domain': {'field_id': model_fields_domain, 'field_date_id': model_date_fields_domain}}
|
||||
|
||||
|
||||
class gamification_goal(osv.Model):
|
||||
class Goal(models.Model):
|
||||
"""Goal instance for a user
|
||||
|
||||
An individual goal for a user on a specified time period"""
|
||||
|
||||
_name = 'gamification.goal'
|
||||
_description = 'Gamification goal instance'
|
||||
|
||||
def _get_completion(self, cr, uid, ids, field_name, arg, context=None):
|
||||
"""Return the percentage of completeness of the goal, between 0 and 100"""
|
||||
res = dict.fromkeys(ids, 0.0)
|
||||
for goal in self.browse(cr, uid, ids, context=context):
|
||||
if goal.definition_condition == 'higher':
|
||||
if goal.current >= goal.target_goal:
|
||||
res[goal.id] = 100.0
|
||||
else:
|
||||
res[goal.id] = round(100.0 * goal.current / goal.target_goal, 2)
|
||||
elif goal.current < goal.target_goal:
|
||||
# a goal 'lower than' has only two values possible: 0 or 100%
|
||||
res[goal.id] = 100.0
|
||||
else:
|
||||
res[goal.id] = 0.0
|
||||
return res
|
||||
|
||||
def on_change_definition_id(self, cr, uid, ids, definition_id=False, context=None):
|
||||
goal_definition = self.pool.get('gamification.goal.definition')
|
||||
if not definition_id:
|
||||
return {'value': {'definition_id': False}}
|
||||
goal_definition = goal_definition.browse(cr, uid, definition_id, context=context)
|
||||
return {'value': {'computation_mode': goal_definition.computation_mode, 'definition_condition': goal_definition.condition}}
|
||||
|
||||
_columns = {
|
||||
'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"),
|
||||
'line_id': fields.many2one('gamification.challenge.line', string='Challenge Line', ondelete="cascade"),
|
||||
'challenge_id': fields.related('line_id', 'challenge_id',
|
||||
string="Challenge",
|
||||
type='many2one',
|
||||
relation='gamification.challenge',
|
||||
store=True, readonly=True,
|
||||
help="Challenge that generated the goal, assign challenge to users to generate goals with a value in this field."),
|
||||
'start_date': fields.date('Start Date'),
|
||||
'end_date': fields.date('End Date'), # no start and end = always active
|
||||
'target_goal': fields.float('To Reach',
|
||||
required=True,
|
||||
track_visibility='always'), # no goal = global index
|
||||
'current': fields.float('Current Value', required=True, track_visibility='always'),
|
||||
'completeness': fields.function(_get_completion, type='float', string='Completeness'),
|
||||
'state': fields.selection([
|
||||
('draft', 'Draft'),
|
||||
('inprogress', 'In progress'),
|
||||
('reached', 'Reached'),
|
||||
('failed', 'Failed'),
|
||||
('canceled', 'Canceled'),
|
||||
],
|
||||
string='State',
|
||||
required=True,
|
||||
track_visibility='always'),
|
||||
'to_update': fields.boolean('To update'),
|
||||
'closed': fields.boolean('Closed goal', help="These goals will not be recomputed."),
|
||||
|
||||
'computation_mode': fields.related('definition_id', 'computation_mode', type='char', string="Computation mode"),
|
||||
'remind_update_delay': fields.integer('Remind delay',
|
||||
help="The number of days after which the user assigned to a manual goal will be reminded. Never reminded if no value is specified."),
|
||||
'last_update': fields.date('Last Update',
|
||||
help="In case of manual goal, reminders are sent if the goal as not been updated for a while (defined in challenge). Ignored in case of non-manual goal or goal not linked to a challenge."),
|
||||
|
||||
'definition_description': fields.related('definition_id', 'description', type='char', string='Definition Description', readonly=True),
|
||||
'definition_condition': fields.related('definition_id', 'condition', type='char', string='Definition Condition', readonly=True),
|
||||
'definition_suffix': fields.related('definition_id', 'full_suffix', type="char", string="Suffix", readonly=True),
|
||||
'definition_display': fields.related('definition_id', 'display_mode', type="char", string="Display Mode", readonly=True),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'current': 0,
|
||||
'state': 'draft',
|
||||
'start_date': fields.date.today,
|
||||
}
|
||||
_order = 'start_date desc, end_date desc, definition_id, id'
|
||||
|
||||
def _check_remind_delay(self, cr, uid, goal, context=None):
|
||||
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")
|
||||
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,
|
||||
help="Challenge that generated the goal, assign challenge to users "
|
||||
"to generate goals with a value in this field.")
|
||||
start_date = fields.Date("Start Date", default=fields.Date.today)
|
||||
end_date = fields.Date("End Date") # no start and end = always active
|
||||
target_goal = fields.Float('To Reach', required=True, track_visibility='always')
|
||||
# no goal = global index
|
||||
current = fields.Float("Current Value", required=True, default=0, track_visibility='always')
|
||||
completeness = fields.Float("Completeness", compute='_get_completion')
|
||||
state = fields.Selection([
|
||||
('draft', "Draft"),
|
||||
('inprogress', "In progress"),
|
||||
('reached', "Reached"),
|
||||
('failed', "Failed"),
|
||||
('canceled', "Canceled"),
|
||||
], default='draft', string='State', required=True, track_visibility='always')
|
||||
to_update = fields.Boolean('To update')
|
||||
closed = fields.Boolean('Closed goal', help="These goals will not be recomputed.")
|
||||
|
||||
computation_mode = fields.Selection(related='definition_id.computation_mode')
|
||||
remind_update_delay = fields.Integer(
|
||||
"Remind delay", help="The number of days after which the user "
|
||||
"assigned to a manual goal will be reminded. "
|
||||
"Never reminded if no value is specified.")
|
||||
last_update = fields.Date(
|
||||
"Last Update",
|
||||
help="In case of manual goal, reminders are sent if the goal as not "
|
||||
"been updated for a while (defined in challenge). Ignored in "
|
||||
"case of non-manual goal or goal not linked to a challenge.")
|
||||
|
||||
definition_description = fields.Text("Definition Description", related='definition_id.description', readonly=True)
|
||||
definition_condition = fields.Selection("Definition Condition", related='definition_id.condition', readonly=True)
|
||||
definition_suffix = fields.Char("Suffix", related='definition_id.full_suffix', readonly=True)
|
||||
definition_display = fields.Selection("Display Mode", related='definition_id.display_mode', readonly=True)
|
||||
|
||||
@api.depends('current', 'target_goal', 'definition_id.condition')
|
||||
def _get_completion(self):
|
||||
"""Return the percentage of completeness of the goal, between 0 and 100"""
|
||||
for goal in self:
|
||||
if goal.definition_condition == 'higher':
|
||||
if goal.current >= goal.target_goal:
|
||||
goal.completeness = 100.0
|
||||
else:
|
||||
goal.completeness = round(100.0 * goal.current / goal.target_goal, 2)
|
||||
elif goal.current < goal.target_goal:
|
||||
# a goal 'lower than' has only two values possible: 0 or 100%
|
||||
goal.completeness = 100.0
|
||||
else:
|
||||
goal.completeness = 0.0
|
||||
|
||||
def _check_remind_delay(self):
|
||||
"""Verify if a goal has not been updated for some time and send a
|
||||
reminder message of needed.
|
||||
|
||||
:return: data to write on the goal object
|
||||
"""
|
||||
temp_obj = self.pool['mail.template']
|
||||
if goal.remind_update_delay and goal.last_update:
|
||||
delta_max = timedelta(days=goal.remind_update_delay)
|
||||
last_update = datetime.strptime(goal.last_update, DF).date()
|
||||
if date.today() - last_update > delta_max:
|
||||
# generate a remind report
|
||||
temp_obj = self.pool.get('mail.template')
|
||||
template_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'gamification', 'email_template_goal_reminder')[0]
|
||||
template = temp_obj.get_email_template(cr, uid, template_id, goal.id, context=context)
|
||||
body_html = temp_obj.render_template(cr, uid, template.body_html, 'gamification.goal', goal.id, context=template._context)
|
||||
self.pool['mail.thread'].message_post(cr, uid, 0, body=body_html, partner_ids=[goal.user_id.partner_id.id], context=context, subtype='mail.mt_comment')
|
||||
return {'to_update': True}
|
||||
return {}
|
||||
if not (self.remind_update_delay and self.last_update):
|
||||
return {}
|
||||
|
||||
def _get_write_values(self, cr, uid, goal, new_value, context=None):
|
||||
delta_max = timedelta(days=self.remind_update_delay)
|
||||
last_update = fields.Date.from_string(self.last_update)
|
||||
if date.today() - last_update < delta_max:
|
||||
return {}
|
||||
|
||||
# generate a reminder report
|
||||
template = self.env.ref('gamification.email_template_goal_reminder')\
|
||||
.get_email_template(self.id)
|
||||
body_html = self.env['mail.template'].with_context(template._context)\
|
||||
.render_template(template.body_html, 'gamification.goal', self.id)
|
||||
self.env['mail.thread'].message_post(
|
||||
body=body_html,
|
||||
partner_ids=[ self.user_id.partner_id.id],
|
||||
subtype='mail.mt_comment'
|
||||
)
|
||||
|
||||
return {'to_update': True}
|
||||
|
||||
def _get_write_values(self, new_value):
|
||||
"""Generate values to write after recomputation of a goal score"""
|
||||
if new_value == goal.current:
|
||||
if new_value == self.current:
|
||||
# avoid useless write if the new value is the same as the old one
|
||||
return {}
|
||||
|
||||
result = {goal.id: {'current': new_value}}
|
||||
if (goal.definition_id.condition == 'higher' and new_value >= goal.target_goal) \
|
||||
or (goal.definition_id.condition == 'lower' and new_value <= goal.target_goal):
|
||||
result = {'current': new_value}
|
||||
if (self.definition_id.condition == 'higher' and new_value >= self.target_goal) \
|
||||
or (self.definition_id.condition == 'lower' and new_value <= self.target_goal):
|
||||
# success, do no set closed as can still change
|
||||
result[goal.id]['state'] = 'reached'
|
||||
result['state'] = 'reached'
|
||||
|
||||
elif goal.end_date and fields.date.today() > goal.end_date:
|
||||
elif self.end_date and fields.Date.today() > self.end_date:
|
||||
# check goal failure
|
||||
result[goal.id]['state'] = 'failed'
|
||||
result[goal.id]['closed'] = True
|
||||
result['state'] = 'failed'
|
||||
result['closed'] = True
|
||||
|
||||
return result
|
||||
return {self: result}
|
||||
|
||||
def update_goal(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def update_goal(self):
|
||||
"""Update the goals to recomputes values and change of states
|
||||
|
||||
If a manual goal is not updated for enough time, the user will be
|
||||
@@ -299,49 +256,47 @@ class gamification_goal(osv.Model):
|
||||
If a goal reaches the target value, the status is set to reached
|
||||
If the end date is passed (at least +1 day, time not considered) without
|
||||
the target value being reached, the goal is set as failed."""
|
||||
if context is None:
|
||||
context = {}
|
||||
commit = context.get('commit_gamification', False)
|
||||
|
||||
goals_by_definition = {}
|
||||
for goal in self.browse(cr, uid, ids, context=context):
|
||||
for goal in self:
|
||||
goals_by_definition.setdefault(goal.definition_id, []).append(goal)
|
||||
|
||||
for definition, goals in goals_by_definition.items():
|
||||
goals_to_write = dict((goal.id, {}) for goal in goals)
|
||||
goals_to_write = {}
|
||||
if definition.computation_mode == 'manually':
|
||||
for goal in goals:
|
||||
goals_to_write[goal.id].update(self._check_remind_delay(cr, uid, goal, context))
|
||||
goals_to_write[goal] = goal._check_remind_delay()
|
||||
elif definition.computation_mode == 'python':
|
||||
# TODO batch execution
|
||||
for goal in goals:
|
||||
# execute the chosen method
|
||||
cxt = {
|
||||
'self': self.pool.get('gamification.goal'),
|
||||
'self': self.pool['gamification.goal'],
|
||||
'object': goal,
|
||||
'pool': self.pool,
|
||||
'cr': cr,
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
'uid': uid,
|
||||
'date': date, 'datetime': datetime, 'timedelta': timedelta, 'time': time
|
||||
'cr': self.env.cr,
|
||||
'context': dict(self.env.context), # copy context to prevent side-effects of eval
|
||||
'uid': self.env.uid,
|
||||
'date': date,
|
||||
'datetime': datetime,
|
||||
'timedelta': timedelta,
|
||||
'time': time,
|
||||
}
|
||||
code = definition.compute_code.strip()
|
||||
safe_eval(code, cxt, mode="exec", nocopy=True)
|
||||
# the result of the evaluated codeis put in the 'result' local variable, propagated to the context
|
||||
result = cxt.get('result')
|
||||
if result is not None and type(result) in (float, int, long):
|
||||
goals_to_write.update(
|
||||
self._get_write_values(cr, uid, goal, result, context=context)
|
||||
)
|
||||
|
||||
if result is not None and isinstance(result, (float, int, long)):
|
||||
goals_to_write.update(goal._get_write_values(result))
|
||||
else:
|
||||
_logger.exception(_('Invalid return content from the evaluation of code for definition %s') % definition.name)
|
||||
_logger.error(
|
||||
"Invalid return content '%r' from the evaluation "
|
||||
"of code for definition %s, expected a number",
|
||||
result, definition.name)
|
||||
|
||||
else: # count or sum
|
||||
Obj = self.env[definition.model_id.model]
|
||||
|
||||
obj = self.pool.get(definition.model_id.model)
|
||||
field_date_name = definition.field_date_id and definition.field_date_id.name or False
|
||||
|
||||
field_date_name = definition.field_date_id.name
|
||||
if definition.computation_mode == 'count' and definition.batch_mode:
|
||||
# batch mode, trying to do as much as possible in one request
|
||||
general_domain = safe_eval(definition.domain)
|
||||
@@ -363,21 +318,19 @@ class gamification_goal(osv.Model):
|
||||
|
||||
if field_name == 'id':
|
||||
# grouping on id does not work and is similar to search anyway
|
||||
user_ids = obj.search(cr, uid, subquery_domain, context=context)
|
||||
user_values = [{'id': user_id, 'id_count': 1} for user_id in user_ids]
|
||||
users = Obj.search(subquery_domain)
|
||||
user_values = [{'id': user.id, 'id_count': 1} for user in users]
|
||||
else:
|
||||
user_values = obj.read_group(cr, uid, subquery_domain, fields=[field_name], groupby=[field_name], context=context)
|
||||
user_values = Obj.read_group(subquery_domain, fields=[field_name], groupby=[field_name])
|
||||
# user_values has format of read_group: [{'partner_id': 42, 'partner_id_count': 3},...]
|
||||
for goal in [g for g in goals if g.id in query_goals.keys()]:
|
||||
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, long)):
|
||||
queried_value = queried_value[0]
|
||||
if queried_value == query_goals[goal.id]:
|
||||
new_value = user_value.get(field_name+'_count', goal.current)
|
||||
goals_to_write.update(
|
||||
self._get_write_values(cr, uid, goal, new_value, context=context)
|
||||
)
|
||||
goals_to_write.update(goal._get_write_values(new_value))
|
||||
|
||||
else:
|
||||
for goal in goals:
|
||||
@@ -393,111 +346,111 @@ class gamification_goal(osv.Model):
|
||||
if definition.computation_mode == 'sum':
|
||||
field_name = definition.field_id.name
|
||||
# TODO for master: group on user field in batch mode
|
||||
res = obj.read_group(cr, uid, domain, [field_name], [], context=context)
|
||||
res = Obj.read_group(domain, [field_name], [])
|
||||
new_value = res and res[0][field_name] or 0.0
|
||||
|
||||
else: # computation mode = count
|
||||
new_value = obj.search(cr, uid, domain, context=context, count=True)
|
||||
new_value = Obj.search_count(domain)
|
||||
|
||||
goals_to_write.update(
|
||||
self._get_write_values(cr, uid, goal, new_value, context=context)
|
||||
)
|
||||
goals_to_write.update(goal._get_write_values(new_value))
|
||||
|
||||
for goal_id, value in goals_to_write.items():
|
||||
if not value:
|
||||
for goal, values in goals_to_write.iteritems():
|
||||
if not values:
|
||||
continue
|
||||
self.write(cr, uid, [goal_id], value, context=context)
|
||||
if commit:
|
||||
cr.commit()
|
||||
goal.write(values)
|
||||
if self.env.context.get('commit_gamification'):
|
||||
self.env.cr.commit()
|
||||
return True
|
||||
|
||||
def action_start(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_start(self):
|
||||
"""Mark a goal as started.
|
||||
|
||||
This should only be used when creating goals manually (in draft state)"""
|
||||
self.write(cr, uid, ids, {'state': 'inprogress'}, context=context)
|
||||
return self.update_goal(cr, uid, ids, context=context)
|
||||
self.write({'state': 'inprogress'})
|
||||
return self.update_goal()
|
||||
|
||||
def action_reach(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_reach(self):
|
||||
"""Mark a goal as reached.
|
||||
|
||||
If the target goal condition is not met, the state will be reset to In
|
||||
Progress at the next goal update until the end date."""
|
||||
return self.write(cr, uid, ids, {'state': 'reached'}, context=context)
|
||||
return self.write({'state': 'reached'})
|
||||
|
||||
def action_fail(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_fail(self):
|
||||
"""Set the state of the goal to failed.
|
||||
|
||||
A failed goal will be ignored in future checks."""
|
||||
return self.write(cr, uid, ids, {'state': 'failed'}, context=context)
|
||||
return self.write({'state': 'failed'})
|
||||
|
||||
def action_cancel(self, cr, uid, ids, context=None):
|
||||
@api.multi
|
||||
def action_cancel(self):
|
||||
"""Reset the completion after setting a goal as reached or failed.
|
||||
|
||||
This is only the current state, if the date and/or target criterias
|
||||
This is only the current state, if the date and/or target criteria
|
||||
match the conditions for a change of state, this will be applied at the
|
||||
next goal update."""
|
||||
return self.write(cr, uid, ids, {'state': 'inprogress'}, context=context)
|
||||
return self.write({'state': 'inprogress'})
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
"""Overwrite the create method to add a 'no_remind_goal' field to True"""
|
||||
context = dict(context or {})
|
||||
context['no_remind_goal'] = True
|
||||
return super(gamification_goal, self).create(cr, uid, vals, context=context)
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
return super(Goal, self.with_context(no_remind_goal=True)).create(vals)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
"""Overwrite the write method to update the last_update field to today
|
||||
|
||||
If the current value is changed and the report frequency is set to On
|
||||
change, a report is generated
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
vals['last_update'] = fields.date.today()
|
||||
result = super(gamification_goal, self).write(cr, uid, ids, vals, context=context)
|
||||
for goal in self.browse(cr, uid, ids, context=context):
|
||||
vals['last_update'] = fields.Date.today()
|
||||
result = super(Goal, self).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
|
||||
raise UserError(_('Can not modify the configuration of a started goal'))
|
||||
raise exceptions.UserError(_('Can not modify the configuration of a started goal'))
|
||||
|
||||
if vals.get('current'):
|
||||
if 'no_remind_goal' in context:
|
||||
# new goals should not be reported
|
||||
continue
|
||||
|
||||
if goal.challenge_id and goal.challenge_id.report_message_frequency == 'onchange':
|
||||
self.pool.get('gamification.challenge').report_progress(cr, SUPERUSER_ID, goal.challenge_id, users=[goal.user_id], context=context)
|
||||
if vals.get('current') and 'no_remind_goal' not in self.env.context:
|
||||
if goal.challenge_id.report_message_frequency == 'onchange':
|
||||
goal.challenge_id.sudo().report_progress(users=goal.user_id)
|
||||
return result
|
||||
|
||||
def get_action(self, cr, uid, goal_id, context=None):
|
||||
@api.multi
|
||||
def get_action(self):
|
||||
"""Get the ir.action related to update the goal
|
||||
|
||||
In case of a manual goal, should return a wizard to update the value
|
||||
:return: action description in a dictionnary
|
||||
:return: action description in a dictionary
|
||||
"""
|
||||
goal = self.browse(cr, uid, goal_id, context=context)
|
||||
|
||||
if goal.definition_id.action_id:
|
||||
if self.definition_id.action_id:
|
||||
# open a the action linked to the goal
|
||||
action = goal.definition_id.action_id.read()[0]
|
||||
action = self.definition_id.action_id.read()[0]
|
||||
|
||||
if goal.definition_id.res_id_field:
|
||||
current_user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
action['res_id'] = safe_eval(goal.definition_id.res_id_field, {'user': current_user})
|
||||
if self.definition_id.res_id_field:
|
||||
current_user = self.env.user.sudo(self.env.user)
|
||||
action['res_id'] = safe_eval(self.definition_id.res_id_field, {
|
||||
'user': current_user
|
||||
})
|
||||
|
||||
# if one element to display, should see it in form mode if possible
|
||||
action['views'] = [(view_id, mode) for (view_id, mode) in action['views'] if mode == 'form'] or action['views']
|
||||
action['views'] = [
|
||||
(view_id, mode)
|
||||
for (view_id, mode) in action['views']
|
||||
if mode == 'form'
|
||||
] or action['views']
|
||||
return action
|
||||
|
||||
if goal.computation_mode == 'manually':
|
||||
if self.computation_mode == 'manually':
|
||||
# open a wizard window to update the value manually
|
||||
action = {
|
||||
'name': _("Update %s") % goal.definition_id.name,
|
||||
'id': goal_id,
|
||||
'name': _("Update %s") % self.definition_id.name,
|
||||
'id': self.id,
|
||||
'type': 'ir.actions.act_window',
|
||||
'views': [[False, 'form']],
|
||||
'target': 'new',
|
||||
'context': {'default_goal_id': goal_id, 'default_current': goal.current},
|
||||
'context': {'default_goal_id': self.id, 'default_current': self.current},
|
||||
'res_model': 'gamification.goal.wizard'
|
||||
}
|
||||
return action
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="base.group_hr_manager" model="res.groups">
|
||||
<field name="name">Manager</field>
|
||||
@@ -43,4 +42,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,72 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.tests import common
|
||||
from odoo.tests import common
|
||||
|
||||
|
||||
class test_challenge(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(test_challenge, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
self.data_obj = self.registry('ir.model.data')
|
||||
self.user_obj = self.registry('res.users')
|
||||
|
||||
self.challenge_obj = self.registry('gamification.challenge')
|
||||
self.line_obj = self.registry('gamification.challenge.line')
|
||||
self.goal_obj = self.registry('gamification.goal')
|
||||
self.badge_obj = self.registry('gamification.badge')
|
||||
self.badge_user_obj = self.registry('gamification.badge.user')
|
||||
|
||||
self.demo_user_id = self.data_obj.get_object_reference(cr, uid, 'base', 'user_demo')[1]
|
||||
self.group_user_id = self.data_obj.get_object_reference(cr, uid, 'base', 'group_user')[1]
|
||||
self.challenge_base_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'challenge_base_discover')[1]
|
||||
self.definition_timezone_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'definition_base_timezone')[1]
|
||||
self.badge_id = self.data_obj.get_object_reference(cr, uid, 'gamification', 'badge_good_job')[1]
|
||||
|
||||
def test_00_join_challenge(self):
|
||||
cr, uid, context = self.cr, self.uid, {}
|
||||
|
||||
user_ids = self.user_obj.search(cr, uid, [('groups_id', '=', self.group_user_id)])
|
||||
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
|
||||
employees_group = self.env.ref('base.group_user')
|
||||
user_ids = employees_group.users
|
||||
challenge = self.env.ref('gamification.challenge_base_discover')
|
||||
|
||||
self.assertGreaterEqual(len(challenge.user_ids), len(user_ids), "Not enough users in base challenge")
|
||||
|
||||
self.user_obj.create(cr, uid, {
|
||||
self.env['res.users'].with_context(no_reset_password=True).create({
|
||||
'name': 'R2D2',
|
||||
'login': 'r2d2@openerp.com',
|
||||
'email': 'r2d2@openerp.com',
|
||||
'groups_id': [(6, 0, [self.group_user_id])]
|
||||
}, {'no_reset_password': True})
|
||||
'groups_id': [(6, 0, [employees_group.id])]
|
||||
})
|
||||
|
||||
self.challenge_obj._update_all(cr, uid, [self.challenge_base_id], context=context)
|
||||
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
|
||||
challenge._update_all()
|
||||
self.assertGreaterEqual(len(challenge.user_ids), len(user_ids)+1, "These are not droids you are looking for")
|
||||
|
||||
def test_10_reach_challenge(self):
|
||||
cr, uid, context = self.cr, self.uid, {}
|
||||
|
||||
self.challenge_obj.write(cr, uid, [self.challenge_base_id], {'state': 'inprogress'}, context=context)
|
||||
challenge = self.challenge_obj.browse(cr, uid, self.challenge_base_id, context=context)
|
||||
challenge_user_ids = [user.id for user in challenge.user_ids]
|
||||
Goals = self.env['gamification.goal']
|
||||
challenge = self.env.ref('gamification.challenge_base_discover')
|
||||
|
||||
challenge.state = 'inprogress'
|
||||
self.assertEqual(challenge.state, 'inprogress', "Challenge failed the change of state")
|
||||
|
||||
line_ids = self.line_obj.search(cr, uid, [('challenge_id', '=', self.challenge_base_id)], context=context)
|
||||
goal_ids = self.goal_obj.search(cr, uid, [('challenge_id', '=', self.challenge_base_id), ('state', '!=', 'draft')], context=context)
|
||||
self.assertEqual(len(goal_ids), len(line_ids)*len(challenge_user_ids), "Incorrect number of goals generated, should be 1 goal per user, per challenge line")
|
||||
goal_ids = Goals.search([('challenge_id', '=', challenge.id), ('state', '!=', 'draft')])
|
||||
self.assertEqual(len(goal_ids), len(challenge.line_ids) * len(challenge.user_ids.ids), "Incorrect number of goals generated, should be 1 goal per user, per challenge line")
|
||||
|
||||
demo = self.env.ref('base.user_demo')
|
||||
# demo user will set a timezone
|
||||
self.user_obj.write(cr, uid, self.demo_user_id, {'tz': "Europe/Brussels"}, context=context)
|
||||
goal_ids = self.goal_obj.search(cr, uid, [('user_id', '=', self.demo_user_id), ('definition_id', '=', self.definition_timezone_id)], context=context)
|
||||
demo.tz = "Europe/Brussels"
|
||||
goal_ids = Goals.search([('user_id', '=', demo.id), ('definition_id', '=', self.env.ref('gamification.definition_base_timezone').id)])
|
||||
|
||||
self.goal_obj.update_goal(cr, uid, goal_ids, context=context)
|
||||
reached_goal_ids = self.goal_obj.search(cr, uid, [('id', 'in', goal_ids), ('state', '=', 'reached')], context=context)
|
||||
self.assertEqual(set(goal_ids), set(reached_goal_ids), "Not every goal was reached after changing timezone")
|
||||
goal_ids.update_goal()
|
||||
|
||||
missed = goal_ids.filtered(lambda g: g.state != 'reached')
|
||||
self.assertFalse(missed, "Not every goal was reached after changing timezone")
|
||||
|
||||
# reward for two firsts as admin may have timezone
|
||||
self.challenge_obj.write(cr, uid, self.challenge_base_id, {'reward_first_id': self.badge_id, 'reward_second_id': self.badge_id}, context=context)
|
||||
self.challenge_obj.write(cr, uid, self.challenge_base_id, {'state': 'done'}, context=context)
|
||||
badge_id = self.env.ref('gamification.badge_good_job').id
|
||||
challenge.write({'reward_first_id': badge_id, 'reward_second_id': badge_id})
|
||||
challenge.state = 'done'
|
||||
|
||||
badge_ids = self.badge_user_obj.search(cr, uid, [('badge_id', '=', self.badge_id), ('user_id', '=', self.demo_user_id)])
|
||||
self.assertGreater(len(badge_ids), 0, "Demo user has not received the badge")
|
||||
badge_ids = self.env['gamification.badge.user'].search([('badge_id', '=', badge_id), ('user_id', '=', demo.id)])
|
||||
self.assertEqual(len(badge_ids), 1, "Demo user has not received the badge")
|
||||
|
||||
+172
-176
@@ -1,188 +1,184 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Badge views -->
|
||||
<record id="badge_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Badges</field>
|
||||
<field name="res_model">gamification.badge</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a badge.
|
||||
</p>
|
||||
<p>
|
||||
A badge is a symbolic token granted to a user as a sign of reward.
|
||||
It can be deserved automatically when some conditions are met or manually by users.
|
||||
Some badges are harder than others to get with specific conditions.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Badge views -->
|
||||
<record id="badge_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Badges</field>
|
||||
<field name="res_model">gamification.badge</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a badge.
|
||||
</p>
|
||||
<p>
|
||||
A badge is a symbolic token granted to a user as a sign of reward.
|
||||
It can be deserved automatically when some conditions are met or manually by users.
|
||||
Some badges are harder than others to get with specific conditions.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<record id="badge_list_view" model="ir.ui.view">
|
||||
<field name="name">Badge List</field>
|
||||
<field name="model">gamification.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Badge List">
|
||||
<field name="name"/>
|
||||
<field name="stat_count"/>
|
||||
<field name="stat_this_month"/>
|
||||
<field name="stat_my"/>
|
||||
<field name="rule_auth"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="badge_list_view" model="ir.ui.view">
|
||||
<field name="name">Badge List</field>
|
||||
<field name="model">gamification.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Badge List">
|
||||
<field name="name"/>
|
||||
<field name="stat_count"/>
|
||||
<field name="stat_this_month"/>
|
||||
<field name="stat_my"/>
|
||||
<field name="rule_auth"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="badge_form_view" model="ir.ui.view">
|
||||
<field name="name">Badge Form</field>
|
||||
<field name="model">gamification.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Badge">
|
||||
<header>
|
||||
<button string="Grant this Badge" type="action" name="%(action_grant_wizard)d" class="oe_highlight" attrs="{'invisible': [('remaining_sending','=',0)]}" />
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<field name="image" widget='image' class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Badge Name"/>
|
||||
</h1>
|
||||
<record id="badge_form_view" model="ir.ui.view">
|
||||
<field name="name">Badge Form</field>
|
||||
<field name="model">gamification.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Badge">
|
||||
<header>
|
||||
<button string="Grant this Badge" type="action" name="%(action_grant_wizard)d" class="oe_highlight" attrs="{'invisible': [('remaining_sending','=',0)]}" />
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<field name="image" widget='image' class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="Badge Name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="description" nolabel="1" placeholder="Badge Description"/>
|
||||
</group>
|
||||
<group string="Granting">
|
||||
<div class="oe_grey" colspan="2">
|
||||
Security rules to define who is allowed to manually grant badges. Not enforced for administrator.
|
||||
</div>
|
||||
<group>
|
||||
<field name="description" nolabel="1" placeholder="Badge Description"/>
|
||||
</group>
|
||||
<group string="Granting">
|
||||
<div class="oe_grey" colspan="2">
|
||||
Security rules to define who is allowed to manually grant badges. Not enforced for administrator.
|
||||
</div>
|
||||
<group>
|
||||
<field name="rule_auth" widget="radio"/>
|
||||
<field name="rule_auth_user_ids" attrs="{'invisible': [('rule_auth','!=','users')]}" widget="many2many_tags" />
|
||||
<field name="rule_auth_badge_ids" attrs="{'invisible': [('rule_auth','!=','having')]}" widget="many2many_tags" />
|
||||
<field name="rule_max" attrs="{'invisible': [('rule_auth','=','nobody')]}" />
|
||||
<field name="rule_max_number" attrs="{'invisible': ['|',('rule_max','=',False),('rule_auth','=','nobody')]}"/>
|
||||
<label for="stat_my_monthly_sending"/>
|
||||
<div>
|
||||
<field name="stat_my_monthly_sending" attrs="{'invisible': [('rule_auth','=','nobody')]}" />
|
||||
<div attrs="{'invisible': [('remaining_sending','=',-1)]}" class="oe_grey">
|
||||
You can still grant <field name="remaining_sending" class="oe_inline"/> badges this month
|
||||
</div>
|
||||
<div attrs="{'invisible': [('remaining_sending','!=',-1)]}" class="oe_grey">
|
||||
No monthly sending limit
|
||||
</div>
|
||||
<field name="rule_auth" widget="radio"/>
|
||||
<field name="rule_auth_user_ids" attrs="{'invisible': [('rule_auth','!=','users')]}" widget="many2many_tags" />
|
||||
<field name="rule_auth_badge_ids" attrs="{'invisible': [('rule_auth','!=','having')]}" widget="many2many_tags" />
|
||||
<field name="rule_max" attrs="{'invisible': [('rule_auth','=','nobody')]}" />
|
||||
<field name="rule_max_number" attrs="{'invisible': ['|',('rule_max','=',False),('rule_auth','=','nobody')]}"/>
|
||||
<label for="stat_my_monthly_sending"/>
|
||||
<div>
|
||||
<field name="stat_my_monthly_sending" attrs="{'invisible': [('rule_auth','=','nobody')]}" />
|
||||
<div attrs="{'invisible': [('remaining_sending','=',-1)]}" class="oe_grey">
|
||||
You can still grant <field name="remaining_sending" class="oe_inline"/> badges this month
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Rewards for challenges">
|
||||
<field name="challenge_ids" widget="many2many_kanban" nolabel="1" context="{'default_reward_id': active_id}" />
|
||||
</group>
|
||||
<group string="Statistics">
|
||||
<group>
|
||||
<field name="stat_count"/>
|
||||
<field name="stat_this_month"/>
|
||||
<field name="stat_count_distinct"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="stat_my"/>
|
||||
<field name="stat_my_this_month"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="badge_kanban_view" model="ir.ui.view" >
|
||||
<field name="name">Badge Kanban View</field>
|
||||
<field name="model">gamification.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_badge">
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="image"/>
|
||||
<field name="stat_my"/>
|
||||
<field name="stat_count"/>
|
||||
<field name="stat_this_month"/>
|
||||
<field name="unique_owner_ids"/>
|
||||
<field name="stat_my_monthly_sending"/>
|
||||
<field name="remaining_sending" />
|
||||
<field name="rule_max_number" />
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="o_kanban_gamification oe_kanban_global_click #{record.stat_my.raw_value ? 'oe_kanban_color_5' : 'oe_kanban_color_white'}">
|
||||
<div class="o_kanban_image">
|
||||
<img t-att-src="kanban_image('gamification.badge', 'image', record.image.raw_value)" t-att-title="record.name.value"/>
|
||||
</div>
|
||||
<div class="oe_kanban_details">
|
||||
<strong><field name="name"/></strong>
|
||||
<span class="oe_grey">
|
||||
<t t-if="record.remaining_sending.value != -1">
|
||||
<t t-esc="record.stat_my_monthly_sending.value"/>/<t t-esc="record.rule_max_number.value"/>
|
||||
</t>
|
||||
<t t-if="record.remaining_sending.value == -1">
|
||||
<t t-esc="record.stat_my_monthly_sending.value"/>/∞
|
||||
</t>
|
||||
</span>
|
||||
<div t-if="record.remaining_sending.value == 0" class="oe_grey">Can not grant</div>
|
||||
<div>
|
||||
<strong><t t-esc="record.stat_count.raw_value"/></strong> granted,
|
||||
<strong><t t-esc="record.stat_this_month.raw_value"/></strong> this month
|
||||
</div>
|
||||
<div t-if="record.description.value" class="o_kanban_badge_description">
|
||||
<em><field name="description"/></em>
|
||||
<div>
|
||||
<t t-foreach="record.unique_owner_ids.raw_value.slice(0,11)" t-as="owner">
|
||||
<img width="24" height="24" class="oe_kanban_avatar" t-att-src="kanban_image('res.users', 'image_small', owner)" t-att-data-member_id="owner"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="record.remaining_sending.value != 0" class="o_kanban_button">
|
||||
<button type="action" name="%(action_grant_wizard)d" class="oe_highlight btn btn-sm btn-primary">Grant</button>
|
||||
</div>
|
||||
<div attrs="{'invisible': [('remaining_sending','!=',-1)]}" class="oe_grey">
|
||||
No monthly sending limit
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Rewards for challenges">
|
||||
<field name="challenge_ids" widget="many2many_kanban" nolabel="1" context="{'default_reward_id': active_id}" />
|
||||
</group>
|
||||
<group string="Statistics">
|
||||
<group>
|
||||
<field name="stat_count"/>
|
||||
<field name="stat_this_month"/>
|
||||
<field name="stat_count_distinct"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="stat_my"/>
|
||||
<field name="stat_my_this_month"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Badge user viewss -->
|
||||
|
||||
<record id="badge_user_kanban_view" model="ir.ui.view" >
|
||||
<field name="name">Badge User Kanban View</field>
|
||||
<field name="model">gamification.badge.user</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey">
|
||||
<field name="badge_name"/>
|
||||
<field name="badge_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="comment"/>
|
||||
<field name="create_date"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_card oe_kanban_global_click oe_kanban_badge oe_kanban_color_white o_kanban_gamification">
|
||||
<div class="o_kanban_content">
|
||||
<div class="pull-left">
|
||||
<a type="open"><img t-att-src="kanban_image('gamification.badge', 'image', record.badge_id.raw_value)" t-att-title="record.badge_name.value" width="24" height="24" /></a>
|
||||
</div>
|
||||
<h4>
|
||||
<a type="open"><t t-esc="record.badge_name.raw_value" /></a>
|
||||
</h4>
|
||||
<t t-if="record.comment.raw_value">
|
||||
<p><em><field name="comment"/></em></p>
|
||||
<record id="badge_kanban_view" model="ir.ui.view" >
|
||||
<field name="name">Badge Kanban View</field>
|
||||
<field name="model">gamification.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="o_kanban_badge">
|
||||
<field name="name"/>
|
||||
<field name="description"/>
|
||||
<field name="image"/>
|
||||
<field name="stat_my"/>
|
||||
<field name="stat_count"/>
|
||||
<field name="stat_this_month"/>
|
||||
<field name="unique_owner_ids"/>
|
||||
<field name="stat_my_monthly_sending"/>
|
||||
<field name="remaining_sending" />
|
||||
<field name="rule_max_number" />
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="o_kanban_gamification oe_kanban_global_click #{record.stat_my.raw_value ? 'oe_kanban_color_5' : 'oe_kanban_color_white'}">
|
||||
<div class="o_kanban_image">
|
||||
<img t-att-src="kanban_image('gamification.badge', 'image', record.image.raw_value)" t-att-title="record.name.value"/>
|
||||
</div>
|
||||
<div class="oe_kanban_details">
|
||||
<strong><field name="name"/></strong>
|
||||
<span class="oe_grey">
|
||||
<t t-if="record.remaining_sending.value != -1">
|
||||
<t t-esc="record.stat_my_monthly_sending.value"/>/<t t-esc="record.rule_max_number.value"/>
|
||||
</t>
|
||||
<p>Granted by <a type="open"><field name="create_uid" /></a> the <t t-esc="moment(record.create_date.raw_value).format('L')" /></p>
|
||||
<t t-if="record.remaining_sending.value == -1">
|
||||
<t t-esc="record.stat_my_monthly_sending.value"/>/∞
|
||||
</t>
|
||||
</span>
|
||||
<div t-if="record.remaining_sending.value == 0" class="oe_grey">Can not grant</div>
|
||||
<div>
|
||||
<strong><t t-esc="record.stat_count.raw_value"/></strong> granted,
|
||||
<strong><t t-esc="record.stat_this_month.raw_value"/></strong> this month
|
||||
</div>
|
||||
<div t-if="record.description.value" class="o_kanban_badge_description">
|
||||
<em><field name="description"/></em>
|
||||
<div>
|
||||
<t t-foreach="record.unique_owner_ids.raw_value.slice(0,11)" t-as="owner">
|
||||
<img width="24" height="24" class="oe_kanban_avatar" t-att-src="kanban_image('res.users', 'image_small', owner)" t-att-data-member_id="owner"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="record.remaining_sending.value != 0" class="o_kanban_button">
|
||||
<button type="action" name="%(action_grant_wizard)d" class="oe_highlight btn btn-sm btn-primary">Grant</button>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Badge user viewss -->
|
||||
|
||||
<record id="badge_user_kanban_view" model="ir.ui.view" >
|
||||
<field name="name">Badge User Kanban View</field>
|
||||
<field name="model">gamification.badge.user</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey">
|
||||
<field name="badge_name"/>
|
||||
<field name="badge_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="comment"/>
|
||||
<field name="create_date"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div class="oe_kanban_card oe_kanban_global_click oe_kanban_badge oe_kanban_color_white o_kanban_gamification">
|
||||
<div class="o_kanban_content">
|
||||
<div class="pull-left">
|
||||
<a type="open"><img t-att-src="kanban_image('gamification.badge', 'image', record.badge_id.raw_value)" t-att-title="record.badge_name.value" width="24" height="24" /></a>
|
||||
</div>
|
||||
<h4>
|
||||
<a type="open"><t t-esc="record.badge_name.raw_value" /></a>
|
||||
</h4>
|
||||
<t t-if="record.comment.raw_value">
|
||||
<p><em><field name="comment"/></em></p>
|
||||
</t>
|
||||
<p>Granted by <a type="open"><field name="create_uid" /></a> the <t t-esc="moment(record.create_date.raw_value).format('L')" /></p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -1,295 +1,229 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<record id="challenge_list_view" model="ir.ui.view">
|
||||
<field name="name">Challenges List</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Goal definitions" decoration-info="state == 'draft'" decoration-muted="state == 'done'">
|
||||
<field name="name"/>
|
||||
<field name="period"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="challenge_list_view" model="ir.ui.view">
|
||||
<field name="name">Challenges List</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Goal definitions" decoration-info="state == 'draft'" decoration-muted="state == 'done'">
|
||||
<field name="name"/>
|
||||
<field name="period"/>
|
||||
<field name="manager_id"/>
|
||||
<field name="state"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="goals_from_challenge_act" model="ir.actions.act_window">
|
||||
<field name="res_model">gamification.goal</field>
|
||||
<field name="name">Related Goals</field>
|
||||
<field name="view_mode">kanban,tree</field>
|
||||
<field name="context">{'search_default_group_by_definition': True, 'search_default_inprogress': True, 'search_default_challenge_id': active_id, 'default_challenge_id': active_id}</field>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
There is no goals associated to this challenge matching your search.
|
||||
Make sure that your challenge is active and assigned to at least one user.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goals_from_challenge_act" model="ir.actions.act_window">
|
||||
<field name="res_model">gamification.goal</field>
|
||||
<field name="name">Related Goals</field>
|
||||
<field name="view_mode">kanban,tree</field>
|
||||
<field name="context">{'search_default_group_by_definition': True, 'search_default_inprogress': True, 'search_default_challenge_id': active_id, 'default_challenge_id': active_id}</field>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
There is no goals associated to this challenge matching your search.
|
||||
Make sure that your challenge is active and assigned to at least one user.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="challenge_form_view" model="ir.ui.view">
|
||||
<field name="name">Challenge Form</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Goal definitions">
|
||||
<header>
|
||||
<button string="Start Challenge" type="object" name="action_start" states="draft" class="oe_highlight"/>
|
||||
<button string="Refresh Challenge" type="object" name="action_check" states="inprogress"/>
|
||||
<button string="Send Report" type="object" name="action_report_progress" states="inprogress,done" groups="base.group_no_one"/>
|
||||
<field name="state" widget="statusbar" clickable="True"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<!-- action buttons -->
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button type="action"
|
||||
name="%(goals_from_challenge_act)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-gift"
|
||||
attrs="{'invisible': [('state','=','draft')]}">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_text">Related</span>
|
||||
<span class="o_stat_text">Goals</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Monthly Sales Objectives"/>
|
||||
</h1>
|
||||
<label for="user_domain" class="oe_edit_only" string="Assign Challenge To"/>
|
||||
<div>
|
||||
<field name="user_domain" widget="char_domain" options="{'model': 'res.users'}" />
|
||||
<field name="user_ids" groups="base.group_no_one" widget="many2many_tags" readonly="True" />
|
||||
<record id="challenge_form_view" model="ir.ui.view">
|
||||
<field name="name">Challenge Form</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Goal definitions">
|
||||
<header>
|
||||
<button string="Start Challenge" type="object" name="action_start" states="draft" class="oe_highlight"/>
|
||||
<button string="Refresh Challenge" type="object" name="action_check" states="inprogress"/>
|
||||
<button string="Send Report" type="object" name="action_report_progress" states="inprogress,done" groups="base.group_no_one"/>
|
||||
<field name="state" widget="statusbar" clickable="True"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<!-- action buttons -->
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button type="action"
|
||||
name="%(goals_from_challenge_act)d"
|
||||
class="oe_stat_button"
|
||||
icon="fa-gift"
|
||||
attrs="{'invisible': [('state','=','draft')]}">
|
||||
<div class="o_form_field o_stat_info">
|
||||
<span class="o_stat_text">Related</span>
|
||||
<span class="o_stat_text">Goals</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="period" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="visibility_mode" widget="radio" colspan="1" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="manager_id"/>
|
||||
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="end_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Goals">
|
||||
<field name="line_ids" nolabel="1" colspan="4">
|
||||
<tree string="Line List" editable="bottom" >
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="definition_id" on_change="on_change_definition_id(definition_id)" />
|
||||
<field name="condition"/>
|
||||
<field name="target_goal"/>
|
||||
<field name="definition_full_suffix"/>
|
||||
</tree>
|
||||
</field>
|
||||
<field name="description" placeholder="Describe the challenge: what is does, who it targets, why it matters..."/>
|
||||
</page>
|
||||
<page string="Reward">
|
||||
<group>
|
||||
<field name="reward_id" attrs="{'required': [('reward_realtime','=', True)]}" />
|
||||
<field name="reward_first_id" />
|
||||
<field name="reward_second_id" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||
<field name="reward_third_id" attrs="{'invisible': ['|',('reward_first_id','=', False),('reward_second_id','=', False)]}" />
|
||||
<field name="reward_failure" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||
<field name="reward_realtime" />
|
||||
</group>
|
||||
<div class="oe_grey">
|
||||
<p>Badges are granted when a challenge is finished. This is either at the end of a running period (eg: end of the month for a monthly challenge), at the end date of a challenge (if no periodicity is set) or when the challenge is manually closed.</p>
|
||||
</div>
|
||||
</page>
|
||||
<page string="Advanced Options">
|
||||
<group string="Subscriptions">
|
||||
<field name="invited_user_ids" widget="many2many_tags" />
|
||||
</group>
|
||||
<group string="Notification Messages">
|
||||
<div class="oe_grey" colspan="4">
|
||||
<p>Depending on the Display mode, reports will be individual or shared.</p>
|
||||
</div>
|
||||
<group colspan="4">
|
||||
<field name="report_message_frequency"/>
|
||||
<field name="report_template_id" attrs="{'invisible': [('report_message_frequency','=','never')]}" />
|
||||
<field name="report_message_group_id" attrs="{'invisible': [('report_message_frequency','=','never')]}" />
|
||||
</group>
|
||||
</group>
|
||||
<group string="Reminders for Manual Goals">
|
||||
<label for="remind_update_delay" />
|
||||
<div>
|
||||
<field name="remind_update_delay" class="oe_inline"/> days
|
||||
</div>
|
||||
</group>
|
||||
<group string="Category" groups="base.group_no_one">
|
||||
<field name="category" widget="radio" />
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_challenge_kanban">
|
||||
<field name="name">Challenge Kanban</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban string="Challenges" class="oe_background_grey">
|
||||
<field name="line_ids"/>
|
||||
<field name="user_ids"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card o_kanban_gamification oe_kanban_global_click">
|
||||
<div class="o_kanban_content">
|
||||
<strong>
|
||||
<h4 class="text-center"><field name="name"/></h4>
|
||||
<div class="oe_kanban_project_list text-center">
|
||||
<a type="action" name="%(goals_from_challenge_act)d" style="margin-right: 10px">
|
||||
<span t-if="record.line_ids.raw_value.length gt 1"><t t-esc="record.line_ids.raw_value.length"/> Goals</span>
|
||||
<span t-if="record.line_ids.raw_value.length lt 2"><t t-esc="record.line_ids.raw_value.length"/> Goal</span>
|
||||
</a>
|
||||
</div>
|
||||
</strong>
|
||||
<t t-foreach="record.user_ids.raw_value.slice(0,11)" t-as="member">
|
||||
<img class="o_kanban_badge_avatars" t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="challenge_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Challenges</field>
|
||||
<field name="res_model">gamification.challenge</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{'search_default_inprogress':True, 'default_inprogress':True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a challenge.
|
||||
</p>
|
||||
<p>
|
||||
Assign a list of goals to chosen users to evaluate them.
|
||||
The challenge can use a period (weekly, monthly...) for automatic creation of goals.
|
||||
The goals are created for the specified users or member of the group.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Specify form view ID to avoid selecting view_challenge_wizard -->
|
||||
<record id="challenge_list_action_view1" model="ir.actions.act_window.view">
|
||||
<field eval="1" name="sequence"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="act_window_id" ref="challenge_list_action"/>
|
||||
<field name="view_id" ref="view_challenge_kanban"/>
|
||||
</record>
|
||||
<record id="challenge_list_action_view2" model="ir.actions.act_window.view">
|
||||
<field eval="10" name="sequence"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="act_window_id" ref="challenge_list_action"/>
|
||||
<field name="view_id" ref="challenge_form_view"/>
|
||||
</record>
|
||||
|
||||
<!-- Line -->
|
||||
<record id="challenge_line_list_view" model="ir.ui.view">
|
||||
<field name="name">Challenge line list</field>
|
||||
<field name="model">gamification.challenge.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Challenge Lines" >
|
||||
<field name="definition_id"/>
|
||||
<field name="target_goal"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="challenge_search_view" model="ir.ui.view">
|
||||
<field name="name">Challenge Search</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Challenges">
|
||||
<filter name="inprogress" string="Running Challenges"
|
||||
domain="[('state', '=', 'inprogress')]"/>
|
||||
<filter name="hr_challenges" string="HR Challenges"
|
||||
domain="[('category', '=', 'hr')]"/>
|
||||
<field name="name"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="State" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Period" domain="[]" context="{'group_by':'period'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="view_challenge_wizard" model="ir.ui.view">
|
||||
<field name="name">Challenge Wizard</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Challenge">
|
||||
<field name="reward_failure" invisible="1"/>
|
||||
<div class="oe_title">
|
||||
<h1><field name="name" nolabel="1" readonly="1"/></h1>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Monthly Sales Objectives"/>
|
||||
</h1>
|
||||
<label for="user_domain" class="oe_edit_only" string="Assign Challenge To"/>
|
||||
<div>
|
||||
<field name="user_domain" widget="char_domain" options="{'model': 'res.users'}" />
|
||||
<field name="user_ids" groups="base.group_no_one" widget="many2many_tags" readonly="True" />
|
||||
</div>
|
||||
</div>
|
||||
<field name="description" nolabel="1" readonly="1" />
|
||||
|
||||
<group>
|
||||
<field name="start_date" readonly="1" />
|
||||
<field name="end_date" readonly="1" />
|
||||
<field name="user_ids" string="Participating" readonly="1" widget="many2many_tags" />
|
||||
<field name="invited_user_ids" string="Invited" readonly="1" widget="many2many_tags" />
|
||||
</group>
|
||||
<group string="Goals">
|
||||
<field name="line_ids" nolabel="1" readonly="1" colspan="4">
|
||||
<tree string="Challenge Lines" editable="bottom" >
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="definition_id"/>
|
||||
<field name="condition"/>
|
||||
<field name="target_goal"/>
|
||||
<field name="definition_full_suffix"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<group string="Reward">
|
||||
<div class="oe_grey" attrs="{'invisible': ['|',('reward_id','!=',False),('reward_first_id','!=',False)]}">
|
||||
There is no reward upon completion of this challenge.
|
||||
</div>
|
||||
<group attrs="{'invisible': [('reward_id','=',False),('reward_first_id','=',False)]}">
|
||||
<field name="reward_id" readonly="1" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||
<field name="reward_first_id" readonly="1" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||
<field name="reward_second_id" readonly="1" attrs="{'invisible': [('reward_second_id','=', False)]}" />
|
||||
<field name="reward_third_id" readonly="1" attrs="{'invisible': [('reward_third_id','=', False)]}" />
|
||||
<group>
|
||||
<field name="period" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="visibility_mode" widget="radio" colspan="1" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="manager_id"/>
|
||||
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="end_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
</group>
|
||||
<div class="oe_grey" attrs="{'invisible': [('reward_failure','=',False)]}">
|
||||
Even if the challenge is failed, best challengers will be rewarded
|
||||
</div>
|
||||
</group>
|
||||
<footer>
|
||||
<center>
|
||||
<button string="Accept" type="object" name="accept_challenge" class="btn-primary" />
|
||||
<button string="Reject" type="object" class="btn-default" name="discard_challenge"/>
|
||||
<button string="reply later" special="cancel" class="btn-default"/>
|
||||
</center>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<notebook>
|
||||
<page string="Goals">
|
||||
<field name="line_ids" nolabel="1" colspan="4">
|
||||
<tree string="Line List" editable="bottom" >
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="definition_id" />
|
||||
<field name="condition"/>
|
||||
<field name="target_goal"/>
|
||||
<field name="definition_full_suffix"/>
|
||||
</tree>
|
||||
</field>
|
||||
<field name="description" placeholder="Describe the challenge: what is does, who it targets, why it matters..."/>
|
||||
</page>
|
||||
<page string="Reward">
|
||||
<group>
|
||||
<field name="reward_id" attrs="{'required': [('reward_realtime','=', True)]}" />
|
||||
<field name="reward_first_id" />
|
||||
<field name="reward_second_id" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||
<field name="reward_third_id" attrs="{'invisible': ['|',('reward_first_id','=', False),('reward_second_id','=', False)]}" />
|
||||
<field name="reward_failure" attrs="{'invisible': [('reward_first_id','=', False)]}" />
|
||||
<field name="reward_realtime" />
|
||||
</group>
|
||||
<div class="oe_grey">
|
||||
<p>Badges are granted when a challenge is finished. This is either at the end of a running period (eg: end of the month for a monthly challenge), at the end date of a challenge (if no periodicity is set) or when the challenge is manually closed.</p>
|
||||
</div>
|
||||
</page>
|
||||
<page string="Advanced Options">
|
||||
<group string="Subscriptions">
|
||||
<field name="invited_user_ids" widget="many2many_tags" />
|
||||
</group>
|
||||
<group string="Notification Messages">
|
||||
<div class="oe_grey" colspan="4">
|
||||
<p>Depending on the Display mode, reports will be individual or shared.</p>
|
||||
</div>
|
||||
<group colspan="4">
|
||||
<field name="report_message_frequency"/>
|
||||
<field name="report_template_id" attrs="{'invisible': [('report_message_frequency','=','never')]}" />
|
||||
<field name="report_message_group_id" attrs="{'invisible': [('report_message_frequency','=','never')]}" />
|
||||
</group>
|
||||
</group>
|
||||
<group string="Reminders for Manual Goals">
|
||||
<label for="remind_update_delay" />
|
||||
<div>
|
||||
<field name="remind_update_delay" class="oe_inline"/> days
|
||||
</div>
|
||||
</group>
|
||||
<group string="Category" groups="base.group_no_one">
|
||||
<field name="category" widget="radio" />
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
<record id="challenge_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Challenge Description</field>
|
||||
<field name="res_model">gamification.challenge</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_id" ref="view_challenge_wizard"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers"/>
|
||||
<field name="message_ids" widget="mail_thread"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
<record model="ir.ui.view" id="view_challenge_kanban">
|
||||
<field name="name">Challenge Kanban</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban string="Challenges" class="oe_background_grey">
|
||||
<field name="line_ids"/>
|
||||
<field name="user_ids"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card o_kanban_gamification oe_kanban_global_click">
|
||||
<div class="o_kanban_content">
|
||||
<strong>
|
||||
<h4 class="text-center"><field name="name"/></h4>
|
||||
<div class="oe_kanban_project_list text-center">
|
||||
<a type="action" name="%(goals_from_challenge_act)d" style="margin-right: 10px">
|
||||
<span t-if="record.line_ids.raw_value.length gt 1"><t t-esc="record.line_ids.raw_value.length"/> Goals</span>
|
||||
<span t-if="record.line_ids.raw_value.length lt 2"><t t-esc="record.line_ids.raw_value.length"/> Goal</span>
|
||||
</a>
|
||||
</div>
|
||||
</strong>
|
||||
<t t-foreach="record.user_ids.raw_value.slice(0,11)" t-as="member">
|
||||
<img class="o_kanban_badge_avatars" t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="challenge_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Challenges</field>
|
||||
<field name="res_model">gamification.challenge</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{'search_default_inprogress':True, 'default_inprogress':True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a challenge.
|
||||
</p>
|
||||
<p>
|
||||
Assign a list of goals to chosen users to evaluate them.
|
||||
The challenge can use a period (weekly, monthly...) for automatic creation of goals.
|
||||
The goals are created for the specified users or member of the group.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<!-- Specify form view ID to avoid selecting view_challenge_wizard -->
|
||||
<record id="challenge_list_action_view1" model="ir.actions.act_window.view">
|
||||
<field eval="1" name="sequence"/>
|
||||
<field name="view_mode">kanban</field>
|
||||
<field name="act_window_id" ref="challenge_list_action"/>
|
||||
<field name="view_id" ref="view_challenge_kanban"/>
|
||||
</record>
|
||||
<record id="challenge_list_action_view2" model="ir.actions.act_window.view">
|
||||
<field eval="10" name="sequence"/>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="act_window_id" ref="challenge_list_action"/>
|
||||
<field name="view_id" ref="challenge_form_view"/>
|
||||
</record>
|
||||
|
||||
<!-- Line -->
|
||||
<record id="challenge_line_list_view" model="ir.ui.view">
|
||||
<field name="name">Challenge line list</field>
|
||||
<field name="model">gamification.challenge.line</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Challenge Lines" >
|
||||
<field name="definition_id"/>
|
||||
<field name="target_goal"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="challenge_search_view" model="ir.ui.view">
|
||||
<field name="name">Challenge Search</field>
|
||||
<field name="model">gamification.challenge</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Challenges">
|
||||
<filter name="inprogress" string="Running Challenges"
|
||||
domain="[('state', '=', 'inprogress')]"/>
|
||||
<filter name="hr_challenges" string="HR Challenges"
|
||||
domain="[('category', '=', 'hr')]"/>
|
||||
<field name="name"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="State" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="Period" domain="[]" context="{'group_by':'period'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<template id="assets_backend" name="gamification assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/gamification/static/src/less/gamification.less"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</openerp>
|
||||
<odoo>
|
||||
<template id="assets_backend" name="gamification assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<link rel="stylesheet" href="/gamification/static/src/less/gamification.less"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
+288
-293
@@ -1,303 +1,298 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<!-- Goal views -->
|
||||
<record id="goal_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Goals</field>
|
||||
<field name="res_model">gamification.goal</field>
|
||||
<field name="view_mode">tree,form,kanban</field>
|
||||
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_definition': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a goal.
|
||||
</p>
|
||||
<p>
|
||||
A goal is defined by a user and a goal definition.
|
||||
Goals can be created automatically by using challenges.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Goal views -->
|
||||
<record id="goal_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Goals</field>
|
||||
<field name="res_model">gamification.goal</field>
|
||||
<field name="view_mode">tree,form,kanban</field>
|
||||
<field name="context">{'search_default_group_by_user': True, 'search_default_group_by_definition': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a goal.
|
||||
</p>
|
||||
<p>
|
||||
A goal is defined by a user and a goal definition.
|
||||
Goals can be created automatically by using challenges.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<record id="goal_list_view" model="ir.ui.view">
|
||||
<field name="name">Goal List</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Goal List" decoration-danger="state == 'failed'" decoration-success="state == 'reached'" decoration-muted="state == 'canceled'" create="false">
|
||||
<field name="definition_id" invisible="1" />
|
||||
<field name="user_id" invisible="1" />
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="current"/>
|
||||
<field name="target_goal"/>
|
||||
<field name="completeness" widget="progressbar"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="line_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goal_list_view" model="ir.ui.view">
|
||||
<field name="name">Goal List</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Goal List" decoration-danger="state == 'failed'" decoration-success="state == 'reached'" decoration-muted="state == 'canceled'" create="false">
|
||||
<field name="definition_id" invisible="1" />
|
||||
<field name="user_id" invisible="1" />
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="current"/>
|
||||
<field name="target_goal"/>
|
||||
<field name="completeness" widget="progressbar"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="line_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="goal_form_view" model="ir.ui.view">
|
||||
<field name="name">Goal Form</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Goal" create="false">
|
||||
<header>
|
||||
<button string="Start goal" type="object" name="action_start" states="draft" class="oe_highlight"/>
|
||||
|
||||
<record id="goal_form_view" model="ir.ui.view">
|
||||
<field name="name">Goal Form</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Goal" create="false">
|
||||
<header>
|
||||
<button string="Start goal" type="object" name="action_start" states="draft" class="oe_highlight"/>
|
||||
|
||||
<button string="Goal Reached" type="object" name="action_reach" states="inprogress" />
|
||||
<button string="Goal Failed" type="object" name="action_fail" states="inprogress"/>
|
||||
<button string="Reset Completion" type="object" name="action_cancel" states="failed,reached" groups="base.group_no_one" />
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,inprogress,reached" />
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="Reference">
|
||||
<field name="definition_id" on_change="on_change_definition_id(definition_id)" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="challenge_id" />
|
||||
</group>
|
||||
<group string="Schedule">
|
||||
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="end_date" />
|
||||
<field name="computation_mode" invisible="1"/>
|
||||
|
||||
<label for="remind_update_delay" attrs="{'invisible':[('computation_mode','!=', 'manually')]}"/>
|
||||
<div attrs="{'invisible':[('computation_mode','!=', 'manually')]}">
|
||||
<field name="remind_update_delay" class="oe_inline"/>
|
||||
days
|
||||
</div>
|
||||
<field name="last_update" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group string="Data" colspan="4">
|
||||
<label for="target_goal" />
|
||||
<div>
|
||||
<field name="target_goal" attrs="{'readonly':[('state','!=','draft')]}" class="oe_inline"/>
|
||||
<field name="definition_suffix" class="oe_inline"/>
|
||||
</div>
|
||||
<label for="current" />
|
||||
<div>
|
||||
<field name="current" class="oe_inline"/>
|
||||
<button string="refresh" type="object" name="update_goal" class="oe_link" attrs="{'invisible':['|',('computation_mode', '=', 'manually'),('state', '=', 'draft')]}" />
|
||||
<div class="oe_grey" attrs="{'invisible':[('definition_id', '=', False)]}">
|
||||
Reached when current value is <strong><field name="definition_condition" class="oe_inline"/></strong> than the target.
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
<button string="Goal Reached" type="object" name="action_reach" states="inprogress" />
|
||||
<button string="Goal Failed" type="object" name="action_fail" states="inprogress"/>
|
||||
<button string="Reset Completion" type="object" name="action_cancel" states="failed,reached" groups="base.group_no_one" />
|
||||
<field name="state" widget="statusbar" statusbar_visible="draft,inprogress,reached" />
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<group string="Reference">
|
||||
<field name="definition_id" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="user_id" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="challenge_id" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
<group string="Schedule">
|
||||
<field name="start_date" attrs="{'readonly':[('state','!=','draft')]}"/>
|
||||
<field name="end_date" />
|
||||
<field name="computation_mode" invisible="1"/>
|
||||
|
||||
<record id="goal_search_view" model="ir.ui.view">
|
||||
<field name="name">Goal Search</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Goals">
|
||||
<filter name="my" string="My Goals" domain="[('user_id', '=', uid)]"/>
|
||||
<separator/>
|
||||
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
|
||||
<filter name="inprogress" string="Running"
|
||||
domain="[
|
||||
'|',
|
||||
('state', '=', 'inprogress'),
|
||||
'&',
|
||||
('state', 'in', ('done', 'failed')),
|
||||
('end_date', '>=', context_today().strftime('%%Y-%%m-%%d'))
|
||||
]"/>
|
||||
<filter name="closed" string="Done"
|
||||
domain="[
|
||||
('state', 'in', ('reached', 'failed')),
|
||||
'|',
|
||||
('end_date', '=', False),
|
||||
('end_date', '<', context_today().strftime('%%Y-%%m-%%d'))
|
||||
]"/>
|
||||
<separator/>
|
||||
|
||||
<field name="user_id"/>
|
||||
<field name="definition_id"/>
|
||||
<field name="challenge_id"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="group_by_user" string="User" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter name="group_by_definition" string="Goal Definition" domain="[]" context="{'group_by':'definition_id'}"/>
|
||||
<filter string="State" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="End Date" domain="[]" context="{'group_by':'end_date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goal_kanban_view" model="ir.ui.view" >
|
||||
<field name="name">Goal Kanban View</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey">
|
||||
<field name="definition_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="current"/>
|
||||
<field name="completeness"/>
|
||||
<field name="state"/>
|
||||
<field name="target_goal"/>
|
||||
<field name="definition_condition"/>
|
||||
<field name="definition_suffix"/>
|
||||
<field name="definition_display"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="last_update"/>
|
||||
<templates>
|
||||
<t t-name="kanban-tooltip">
|
||||
<field name="definition_description"/>
|
||||
</t>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card o_kanban_gamification #{record.end_date.raw_value < record.last_update.raw_value & record.state.raw_value == 'failed' ? 'oe_kanban_color_2' : ''} #{record.end_date.raw_value < record.last_update.raw_value & record.state.raw_value == 'reached' ? 'oe_kanban_color_5' : ''}">
|
||||
<div class="o_kanban_content">
|
||||
<p><strong><h4 class="oe_goal_name text-center" tooltip="kanban-tooltip"><field name="definition_id" /></h4></strong></p>
|
||||
<div class="pull-left">
|
||||
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" />
|
||||
</div>
|
||||
<field name="user_id" />
|
||||
<div class="o_goal_state_block">
|
||||
<t t-if="record.definition_display.raw_value == 'boolean'">
|
||||
<div class="o_goal_state text-center">
|
||||
<t t-if="record.state.raw_value=='reached'"><i class="o_green fa fa-check fa-3x" title="Goal Reached"/></t>
|
||||
<t t-if="record.state.raw_value=='inprogress'"><i class="fa fa-clock-o fa-3x" title="Goal in Progress"/></t>
|
||||
<t t-if="record.state.raw_value=='failed'"><i class="o_red fa fa-times fa-3x" title="Goal Failed"/></t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="record.definition_display.raw_value == 'progress'">
|
||||
<t t-if="record.definition_condition.raw_value =='higher'">
|
||||
<field name="current" widget="gauge" style="width:160px; height: 120px;" options="{'max_field': 'target_goal', 'label_field': 'definition_suffix'}" />
|
||||
</t>
|
||||
<t t-if="record.definition_condition.raw_value != 'higher'">
|
||||
<div t-attf-class="o_goal_state #{record.current.raw_value == record.target_goal.raw_value+1 ? 'o_orange' : record.current.raw_value > record.target_goal.raw_value ? 'o_red' : 'o_green'}">
|
||||
<t t-esc="record.current.raw_value" />
|
||||
</div>
|
||||
<em>Target: less than <t t-esc="record.target_goal.raw_value" /></em>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</div>
|
||||
<p class="text-center">
|
||||
<t t-if="record.start_date.value">
|
||||
From <t t-esc="record.start_date.value" />
|
||||
</t>
|
||||
<t t-if="record.end_date.value">
|
||||
To <t t-esc="record.end_date.value" />
|
||||
</t>
|
||||
</p>
|
||||
<label for="remind_update_delay" attrs="{'invisible':[('computation_mode','!=', 'manually')]}"/>
|
||||
<div attrs="{'invisible':[('computation_mode','!=', 'manually')]}">
|
||||
<field name="remind_update_delay" class="oe_inline"/>
|
||||
days
|
||||
</div>
|
||||
<field name="last_update" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group string="Data" colspan="4">
|
||||
<label for="target_goal" />
|
||||
<div>
|
||||
<field name="target_goal" attrs="{'readonly':[('state','!=','draft')]}" class="oe_inline"/>
|
||||
<field name="definition_suffix" class="oe_inline"/>
|
||||
</div>
|
||||
<label for="current" />
|
||||
<div>
|
||||
<field name="current" class="oe_inline"/>
|
||||
<button string="refresh" type="object" name="update_goal" class="oe_link" attrs="{'invisible':['|',('computation_mode', '=', 'manually'),('state', '=', 'draft')]}" />
|
||||
<div class="oe_grey" attrs="{'invisible':[('definition_id', '=', False)]}">
|
||||
Reached when current value is <strong><field name="definition_condition" class="oe_inline"/></strong> than the target.
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Goal definitions view -->
|
||||
|
||||
<record id="goal_definition_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Goal Definitions</field>
|
||||
<field name="res_model">gamification.goal.definition</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a goal definition.
|
||||
</p>
|
||||
<p>
|
||||
A goal definition is a technical model of goal defining a condition to reach.
|
||||
The dates, values to reach or users are defined in goal instance.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goal_definition_list_view" model="ir.ui.view">
|
||||
<field name="name">Goal Definitions List</field>
|
||||
<field name="model">gamification.goal.definition</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Goal Definitions">
|
||||
<field name="name"/>
|
||||
<field name="computation_mode"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="goal_definition_form_view" model="ir.ui.view">
|
||||
<field name="name">Goal Definitions Form</field>
|
||||
<field name="model">gamification.goal.definition</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Goal definitions">
|
||||
<sheet>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<label for="description" class="oe_edit_only"/>
|
||||
<div>
|
||||
<field name="description" class="oe_inline"/>
|
||||
</div>
|
||||
|
||||
<group string="How to compute the goal?">
|
||||
|
||||
<field widget="radio" name="computation_mode"/>
|
||||
|
||||
<!-- Hide the fields below if manually -->
|
||||
<field name="model_id" class="oe_inline" on_change="on_change_model_id(model_id)"
|
||||
attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}"/>
|
||||
<field name="field_id" class="oe_inline"
|
||||
attrs="{'invisible':[('computation_mode','!=','sum')], 'required':[('computation_mode','=','sum')]}"/>
|
||||
<field name="field_date_id" class="oe_inline" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))]}"/>
|
||||
<field name="domain" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
|
||||
<field name="compute_code" attrs="{'invisible':[('computation_mode','!=','python')], 'required':[('computation_mode','=','python')]}" placeholder="e.g. result = pool.get('mail.followers').search(cr, uid, [('res_model', '=', 'mail.channel'), ('partner_id', '=', object.user_id.partner_id.id)], count=True, context=context)"/>
|
||||
<field name="condition" widget="radio"/>
|
||||
</group>
|
||||
<group string="Optimisation" attrs="{'invisible': [('computation_mode', '!=', 'count')]}">
|
||||
<field name="batch_mode" />
|
||||
<div colspan="4">In batch mode, the domain is evaluated globally. If enabled, do not use keyword 'user' in above filter domain.</div>
|
||||
<field name="batch_distinctive_field" attrs="{'invisible': [('batch_mode', '=', False)], 'required': [('batch_mode', '=', True)]}"
|
||||
domain="[('model_id', '=', model_id)]" class="oe_inline" />
|
||||
<field name="batch_user_expression" attrs="{'invisible': [('batch_mode', '=', False)], 'required': [('batch_mode', '=', True)]}" class="oe_inline"
|
||||
placeholder="e.g. user.partner_id.id"/>
|
||||
</group>
|
||||
<group string="Formating Options">
|
||||
<field name="display_mode" widget="radio" />
|
||||
<field name="suffix" placeholder="e.g. days" class="oe_inline"/>
|
||||
<field name="monetary"/>
|
||||
</group>
|
||||
<group string="Clickable Goals" attrs="{'invisible': [('computation_mode', '=', 'manually')]}">
|
||||
<field name="action_id" class="oe_inline"/>
|
||||
<field name="res_id_field" attrs="{'invisible': [('action_id', '=', False)]}" class="oe_inline"/>
|
||||
</group>
|
||||
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goal_definition_search_view" model="ir.ui.view">
|
||||
<field name="name">Goal Definition Search</field>
|
||||
<field name="model">gamification.goal.definition</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Goal Definitions">
|
||||
<field name="name"/>
|
||||
<field name="model_id"/>
|
||||
<field name="field_id"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Model" domain="[]" context="{'group_by':'model_id'}"/>
|
||||
<filter string="Computation Mode" domain="[]" context="{'group_by':'computation_mode'}"/>
|
||||
</group>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- menus in settings - technical feature required -->
|
||||
<menuitem id="gamification_menu" name="Gamification Tools" parent="base.menu_administration" groups="base.group_no_one" />
|
||||
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="0"/>
|
||||
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="10"/>
|
||||
<menuitem id="gamification_definition_menu" parent="gamification_menu" action="goal_definition_list_action" sequence="20"/>
|
||||
<menuitem id="gamification_badge_menu" parent="gamification_menu" action="badge_list_action" sequence="30"/>
|
||||
<record id="goal_search_view" model="ir.ui.view">
|
||||
<field name="name">Goal Search</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Goals">
|
||||
<filter name="my" string="My Goals" domain="[('user_id', '=', uid)]"/>
|
||||
<separator/>
|
||||
<filter name="draft" string="Draft" domain="[('state', '=', 'draft')]"/>
|
||||
<filter name="inprogress" string="Running"
|
||||
domain="[
|
||||
'|',
|
||||
('state', '=', 'inprogress'),
|
||||
'&',
|
||||
('state', 'in', ('done', 'failed')),
|
||||
('end_date', '>=', context_today().strftime('%%Y-%%m-%%d'))
|
||||
]"/>
|
||||
<filter name="closed" string="Done"
|
||||
domain="[
|
||||
('state', 'in', ('reached', 'failed')),
|
||||
'|',
|
||||
('end_date', '=', False),
|
||||
('end_date', '<', context_today().strftime('%%Y-%%m-%%d'))
|
||||
]"/>
|
||||
<separator/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
<field name="user_id"/>
|
||||
<field name="definition_id"/>
|
||||
<field name="challenge_id"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter name="group_by_user" string="User" domain="[]" context="{'group_by':'user_id'}"/>
|
||||
<filter name="group_by_definition" string="Goal Definition" domain="[]" context="{'group_by':'definition_id'}"/>
|
||||
<filter string="State" domain="[]" context="{'group_by':'state'}"/>
|
||||
<filter string="End Date" domain="[]" context="{'group_by':'end_date'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goal_kanban_view" model="ir.ui.view" >
|
||||
<field name="name">Goal Kanban View</field>
|
||||
<field name="model">gamification.goal</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban class="oe_background_grey">
|
||||
<field name="definition_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="current"/>
|
||||
<field name="completeness"/>
|
||||
<field name="state"/>
|
||||
<field name="target_goal"/>
|
||||
<field name="definition_condition"/>
|
||||
<field name="definition_suffix"/>
|
||||
<field name="definition_display"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="last_update"/>
|
||||
<templates>
|
||||
<t t-name="kanban-tooltip">
|
||||
<field name="definition_description"/>
|
||||
</t>
|
||||
<t t-name="kanban-box">
|
||||
<div t-attf-class="oe_kanban_card o_kanban_gamification #{record.end_date.raw_value < record.last_update.raw_value & record.state.raw_value == 'failed' ? 'oe_kanban_color_2' : ''} #{record.end_date.raw_value < record.last_update.raw_value & record.state.raw_value == 'reached' ? 'oe_kanban_color_5' : ''}">
|
||||
<div class="o_kanban_content">
|
||||
<p><strong><h4 class="oe_goal_name text-center" tooltip="kanban-tooltip"><field name="definition_id" /></h4></strong></p>
|
||||
<div class="pull-left">
|
||||
<img t-att-src="kanban_image('res.users', 'image_small', record.user_id.raw_value)" t-att-title="record.user_id.value" width="24" height="24" />
|
||||
</div>
|
||||
<field name="user_id" />
|
||||
<div class="o_goal_state_block">
|
||||
<t t-if="record.definition_display.raw_value == 'boolean'">
|
||||
<div class="o_goal_state text-center">
|
||||
<t t-if="record.state.raw_value=='reached'"><i class="o_green fa fa-check fa-3x" title="Goal Reached"/></t>
|
||||
<t t-if="record.state.raw_value=='inprogress'"><i class="fa fa-clock-o fa-3x" title="Goal in Progress"/></t>
|
||||
<t t-if="record.state.raw_value=='failed'"><i class="o_red fa fa-times fa-3x" title="Goal Failed"/></t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="record.definition_display.raw_value == 'progress'">
|
||||
<t t-if="record.definition_condition.raw_value =='higher'">
|
||||
<field name="current" widget="gauge" style="width:160px; height: 120px;" options="{'max_field': 'target_goal', 'label_field': 'definition_suffix'}" />
|
||||
</t>
|
||||
<t t-if="record.definition_condition.raw_value != 'higher'">
|
||||
<div t-attf-class="o_goal_state #{record.current.raw_value == record.target_goal.raw_value+1 ? 'o_orange' : record.current.raw_value > record.target_goal.raw_value ? 'o_red' : 'o_green'}">
|
||||
<t t-esc="record.current.raw_value" />
|
||||
</div>
|
||||
<em>Target: less than <t t-esc="record.target_goal.raw_value" /></em>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
</div>
|
||||
<p class="text-center">
|
||||
<t t-if="record.start_date.value">
|
||||
From <t t-esc="record.start_date.value" />
|
||||
</t>
|
||||
<t t-if="record.end_date.value">
|
||||
To <t t-esc="record.end_date.value" />
|
||||
</t>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<!-- Goal definitions view -->
|
||||
|
||||
<record id="goal_definition_list_action" model="ir.actions.act_window">
|
||||
<field name="name">Goal Definitions</field>
|
||||
<field name="res_model">gamification.goal.definition</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to create a goal definition.
|
||||
</p>
|
||||
<p>
|
||||
A goal definition is a technical model of goal defining a condition to reach.
|
||||
The dates, values to reach or users are defined in goal instance.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goal_definition_list_view" model="ir.ui.view">
|
||||
<field name="name">Goal Definitions List</field>
|
||||
<field name="model">gamification.goal.definition</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Goal Definitions">
|
||||
<field name="name"/>
|
||||
<field name="computation_mode"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="goal_definition_form_view" model="ir.ui.view">
|
||||
<field name="name">Goal Definitions Form</field>
|
||||
<field name="model">gamification.goal.definition</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Goal definitions">
|
||||
<sheet>
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
<label for="description" class="oe_edit_only"/>
|
||||
<div>
|
||||
<field name="description" class="oe_inline"/>
|
||||
</div>
|
||||
|
||||
<group string="How to compute the goal?">
|
||||
|
||||
<field widget="radio" name="computation_mode"/>
|
||||
|
||||
<!-- Hide the fields below if manually -->
|
||||
<field name="model_id" class="oe_inline"
|
||||
attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}"/>
|
||||
<field name="field_id" class="oe_inline"
|
||||
attrs="{'invisible':[('computation_mode','!=','sum')], 'required':[('computation_mode','=','sum')]}"/>
|
||||
<field name="field_date_id" class="oe_inline" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))]}"/>
|
||||
<field name="domain" attrs="{'invisible':[('computation_mode','not in',('sum', 'count'))], 'required':[('computation_mode','in',('sum', 'count'))]}" class="oe_inline"/>
|
||||
<field name="compute_code" attrs="{'invisible':[('computation_mode','!=','python')], 'required':[('computation_mode','=','python')]}" placeholder="e.g. result = pool.get('mail.followers').search(cr, uid, [('res_model', '=', 'mail.channel'), ('partner_id', '=', object.user_id.partner_id.id)], count=True, context=context)"/>
|
||||
<field name="condition" widget="radio"/>
|
||||
</group>
|
||||
<group string="Optimisation" attrs="{'invisible': [('computation_mode', '!=', 'count')]}">
|
||||
<field name="batch_mode" />
|
||||
<div colspan="4">In batch mode, the domain is evaluated globally. If enabled, do not use keyword 'user' in above filter domain.</div>
|
||||
<field name="batch_distinctive_field" attrs="{'invisible': [('batch_mode', '=', False)], 'required': [('batch_mode', '=', True)]}"
|
||||
domain="[('model_id', '=', model_id)]" class="oe_inline" />
|
||||
<field name="batch_user_expression" attrs="{'invisible': [('batch_mode', '=', False)], 'required': [('batch_mode', '=', True)]}" class="oe_inline"
|
||||
placeholder="e.g. user.partner_id.id"/>
|
||||
</group>
|
||||
<group string="Formating Options">
|
||||
<field name="display_mode" widget="radio" />
|
||||
<field name="suffix" placeholder="e.g. days" class="oe_inline"/>
|
||||
<field name="monetary"/>
|
||||
</group>
|
||||
<group string="Clickable Goals" attrs="{'invisible': [('computation_mode', '=', 'manually')]}">
|
||||
<field name="action_id" class="oe_inline"/>
|
||||
<field name="res_id_field" attrs="{'invisible': [('action_id', '=', False)]}" class="oe_inline"/>
|
||||
</group>
|
||||
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="goal_definition_search_view" model="ir.ui.view">
|
||||
<field name="name">Goal Definition Search</field>
|
||||
<field name="model">gamification.goal.definition</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Goal Definitions">
|
||||
<field name="name"/>
|
||||
<field name="model_id"/>
|
||||
<field name="field_id"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Model" domain="[]" context="{'group_by':'model_id'}"/>
|
||||
<filter string="Computation Mode" domain="[]" context="{'group_by':'computation_mode'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- menus in settings - technical feature required -->
|
||||
<menuitem id="gamification_menu" name="Gamification Tools" parent="base.menu_administration" groups="base.group_no_one" />
|
||||
<menuitem id="gamification_challenge_menu" parent="gamification_menu" action="challenge_list_action" sequence="0"/>
|
||||
<menuitem id="gamification_goal_menu" parent="gamification_menu" action="goal_list_action" sequence="10"/>
|
||||
<menuitem id="gamification_definition_menu" parent="gamification_menu" action="goal_definition_list_action" sequence="20"/>
|
||||
<menuitem id="gamification_badge_menu" parent="gamification_menu" action="badge_list_action" sequence="30"/>
|
||||
</odoo>
|
||||
|
||||
@@ -1,38 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.tools.translate import _
|
||||
from openerp.exceptions import UserError
|
||||
from odoo import api, fields, models, _, exceptions
|
||||
|
||||
|
||||
class grant_badge_wizard(osv.TransientModel):
|
||||
class grant_badge_wizard(models.TransientModel):
|
||||
""" Wizard allowing to grant a badge to a user"""
|
||||
|
||||
_name = 'gamification.badge.user.wizard'
|
||||
_columns = {
|
||||
'user_id': fields.many2one("res.users", string='User', required=True),
|
||||
'badge_id': fields.many2one("gamification.badge", string='Badge', required=True),
|
||||
'comment': fields.text('Comment'),
|
||||
}
|
||||
|
||||
def action_grant_badge(self, cr, uid, ids, context=None):
|
||||
user_id = fields.Many2one("res.users", string='User', required=True)
|
||||
badge_id = fields.Many2one("gamification.badge", string='Badge', required=True)
|
||||
comment = fields.Text('Comment')
|
||||
|
||||
@api.multi
|
||||
def action_grant_badge(self):
|
||||
"""Wizard action for sending a badge to a chosen user"""
|
||||
|
||||
badge_user_obj = self.pool.get('gamification.badge.user')
|
||||
BadgeUser = self.env['gamification.badge.user']
|
||||
|
||||
for wiz in self.browse(cr, uid, ids, context=context):
|
||||
uid = self.env.uid
|
||||
for wiz in self:
|
||||
if uid == wiz.user_id.id:
|
||||
raise UserError(_('You can not grant a badge to yourself'))
|
||||
raise exceptions.UserError(_('You can not grant a badge to yourself'))
|
||||
|
||||
#create the badge
|
||||
values = {
|
||||
BadgeUser.create({
|
||||
'user_id': wiz.user_id.id,
|
||||
'sender_id': uid,
|
||||
'badge_id': wiz.badge_id.id,
|
||||
'comment': wiz.comment,
|
||||
}
|
||||
badge_user = badge_user_obj.create(cr, uid, values, context=context)
|
||||
result = badge_user_obj._send_badge(cr, uid, badge_user, context=context)
|
||||
})._send_badge()
|
||||
|
||||
return result
|
||||
return True
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<odoo>
|
||||
<record id="view_badge_wizard_grant" model="ir.ui.view">
|
||||
<field name="name">Grant Badge User Form</field>
|
||||
<field name="model">gamification.badge.user.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Grant Badge To">
|
||||
Who would you like to reward?
|
||||
<field name="badge_id" invisible="1"/>
|
||||
<group>
|
||||
<field name="user_id" nolabel="1" colspan="4"/>
|
||||
<field name="comment" nolabel="1" placeholder="Describe what they did and why it matters (will be public)" colspan="4"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Grant Badge" type="object" name="action_grant_badge" class="btn-primary" />
|
||||
<button string="Cancel" special="cancel" class="btn-default"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_badge_wizard_grant" model="ir.ui.view">
|
||||
<field name="name">Grant Badge User Form</field>
|
||||
<field name="model">gamification.badge.user.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Grant Badge To">
|
||||
Who would you like to reward?
|
||||
<field name="badge_id" invisible="1"/>
|
||||
<group>
|
||||
<field name="user_id" nolabel="1" colspan="4"/>
|
||||
<field name="comment" nolabel="1" placeholder="Describe what they did and why it matters (will be public)" colspan="4"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Grant Badge" type="object" name="action_grant_badge" class="btn-primary" />
|
||||
<button string="Cancel" special="cancel" class="btn-default"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<act_window domain="[]" id="action_grant_wizard"
|
||||
name="Grant Badge"
|
||||
target="new"
|
||||
res_model="gamification.badge.user.wizard"
|
||||
context="{'default_badge_id': active_id, 'badge_id': active_id}"
|
||||
view_type="form" view_mode="form"
|
||||
view_id="gamification.view_badge_wizard_grant" />
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
<record id="action_grant_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Grant Badge</field>
|
||||
<field name="res_model">gamification.badge.user.wizard</field>
|
||||
<field name="view_id" ref="gamification.view_badge_wizard_grant"/>
|
||||
<field name="target">new</field>
|
||||
<field name="context">{
|
||||
'default_badge_id': active_id,
|
||||
'badge_id': active_id
|
||||
}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
from odoo import api, models, fields
|
||||
|
||||
class goal_manual_wizard(osv.TransientModel):
|
||||
class goal_manual_wizard(models.TransientModel):
|
||||
"""Wizard to update a manual goal"""
|
||||
_name = 'gamification.goal.wizard'
|
||||
_columns = {
|
||||
'goal_id': fields.many2one("gamification.goal", string='Goal', required=True),
|
||||
'current': fields.float('Current'),
|
||||
}
|
||||
|
||||
def action_update_current(self, cr, uid, ids, context=None):
|
||||
goal_id = fields.Many2one("gamification.goal", string='Goal', required=True)
|
||||
current = fields.Float('Current')
|
||||
|
||||
@api.multi
|
||||
def action_update_current(self):
|
||||
"""Wizard action for updating the current value"""
|
||||
|
||||
goal_obj = self.pool.get('gamification.goal')
|
||||
|
||||
for wiz in self.browse(cr, uid, ids, context=context):
|
||||
towrite = {
|
||||
for wiz in self:
|
||||
wiz.goal_id.write({
|
||||
'current': wiz.current,
|
||||
'goal_id': wiz.goal_id.id,
|
||||
'to_update': False,
|
||||
}
|
||||
goal_obj.write(cr, uid, [wiz.goal_id.id], towrite, context=context)
|
||||
goal_obj.update_goal(cr, uid, [wiz.goal_id.id], context=context)
|
||||
return {}
|
||||
})
|
||||
wiz.goal_id.update_goal()
|
||||
|
||||
return False
|
||||
|
||||
@@ -1,24 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="view_goal_wizard_update_current" model="ir.ui.view">
|
||||
<field name="name">Update the current value of the Goal</field>
|
||||
<field name="model">gamification.goal.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Grant Badge To">
|
||||
Set the current value you have reached for this goal
|
||||
<group>
|
||||
<field name="goal_id" invisible="1"/>
|
||||
<field name="current" />
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Update" type="object" name="action_update_current" class="btn-primary" />
|
||||
<button string="Cancel" special="cancel" class="btn-default"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
<odoo>
|
||||
<record id="view_goal_wizard_update_current" model="ir.ui.view">
|
||||
<field name="name">Update the current value of the Goal</field>
|
||||
<field name="model">gamification.goal.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Grant Badge To">
|
||||
Set the current value you have reached for this goal
|
||||
<group>
|
||||
<field name="goal_id" invisible="1"/>
|
||||
<field name="current" />
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Update" type="object" name="action_update_current" class="btn-primary" />
|
||||
<button string="Cancel" special="cancel" class="btn-default"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- goal definitions -->
|
||||
@@ -120,7 +119,7 @@
|
||||
<field name="name">Monthly Sales Targets</field>
|
||||
<field name="period">monthly</field>
|
||||
<field name="visibility_mode">ranking</field>
|
||||
<field name="user_domain" eval="[('groups_id', 'in', ref('sales_team.group_sale_salesman'))]" />
|
||||
<field name="user_domain" eval="str([('groups_id', 'in', ref('sales_team.group_sale_salesman'))])" />
|
||||
<field name="report_message_frequency">weekly</field>
|
||||
</record>
|
||||
|
||||
@@ -128,7 +127,7 @@
|
||||
<field name="name">Lead Acquisition</field>
|
||||
<field name="period">monthly</field>
|
||||
<field name="visibility_mode">ranking</field>
|
||||
<field name="user_domain" eval="[('groups_id', 'in', ref('sales_team.group_sale_salesman'))]" />
|
||||
<field name="user_domain" eval="str([('groups_id', 'in', ref('sales_team.group_sale_salesman'))])" />
|
||||
<field name="report_message_frequency">weekly</field>
|
||||
</record>
|
||||
|
||||
@@ -160,4 +159,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</odoo>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- challenges -->
|
||||
<record model="gamification.challenge" id="challenge_crm_sale">
|
||||
<field name="state">inprogress</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
<odoo>
|
||||
<!-- challenges -->
|
||||
<record model="gamification.challenge" id="challenge_crm_sale">
|
||||
<field name="state">inprogress</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
@@ -3,15 +3,10 @@
|
||||
from openerp import models, fields, api
|
||||
|
||||
|
||||
class gamification_challenge(models.Model):
|
||||
class Challenge(models.Model):
|
||||
_inherit = 'gamification.challenge'
|
||||
|
||||
@api.model
|
||||
def _get_categories(self):
|
||||
res = super(gamification_challenge, self)._get_categories()
|
||||
res.append(('forum', 'Website / Forum'))
|
||||
return res
|
||||
|
||||
category = fields.Selection(selection_add=[('forum', 'Website / Forum')])
|
||||
|
||||
class Badge(models.Model):
|
||||
_inherit = 'gamification.badge'
|
||||
|
||||
Reference in New Issue
Block a user