Rewards Module for PMS
This commit is contained in:
parent
66077d1819
commit
4ce02e58fa
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
'name': 'Project Kudos+',
|
||||
'version': '18.0.1.0',
|
||||
'summary': 'Employee reward and recognition system for project tasks',
|
||||
'description': """Employee Reward and recognition system based on project tasks""",
|
||||
'category': 'Human Resources',
|
||||
'author': 'Karuna',
|
||||
'depends': ['project', 'hr', 'mail'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/mail_template.xml',
|
||||
'data/badge_data.xml',
|
||||
'data/ir_cron.xml',
|
||||
'views/kudos_views.xml',
|
||||
'views/badge_views.xml',
|
||||
'views/leaderboard_views.xml',
|
||||
'views/menu.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<odoo>
|
||||
<record id="badge_bronze" model="project.kudos.badge">
|
||||
<field name="name">Bronze Badge</field>
|
||||
<field name="level">bronze</field>
|
||||
<field name="threshold">10</field>
|
||||
</record>
|
||||
|
||||
<record id="badge_silver" model="project.kudos.badge">
|
||||
<field name="name">Silver Badge</field>
|
||||
<field name="level">silver</field>
|
||||
<field name="threshold">25</field>
|
||||
</record>
|
||||
|
||||
<record id="badge_gold" model="project.kudos.badge">
|
||||
<field name="name">Gold Badge</field>
|
||||
<field name="level">gold</field>
|
||||
<field name="threshold">50</field>
|
||||
</record>
|
||||
|
||||
<record id="badge_platinum" model="project.kudos.badge">
|
||||
<field name="name">Platinum Badge</field>
|
||||
<field name="level">platinum</field>
|
||||
<field name="threshold">100</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<odoo>
|
||||
<record id="ir_cron_badge_assign" model="ir.cron">
|
||||
<field name="name">Assign Kudos Badges</field>
|
||||
<field name="model_id" ref="model_project_kudos_badge"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model._cron_assign_badges()</field>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">weeks</field>
|
||||
<field name="active">True</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<odoo>
|
||||
<record id="mail_template_kudos_notify" model="mail.template">
|
||||
<field name="name">Kudos Appreciation Email</field>
|
||||
<field name="model_id" ref="project_kudos_plus.model_project_kudos"/>
|
||||
<field name="subject">🎉 Great job, {{ object.employee_id.name }}!</field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>Hi {{ object.employee_id.name }},</p>
|
||||
<p>Congratulations on completing <b>{{ object.task_id.name }}</b> before the deadline!</p>
|
||||
<p>Your effort and commitment are greatly appreciated. 👏</p>
|
||||
<p>– Project Kudos+ Team</p>
|
||||
]]></field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
from . import project_kudos
|
||||
from . import project_badge
|
||||
from . import project_task_inherit
|
||||
from . import project_kudos_leaderboard
|
||||
|
||||
# from . import leaderboard
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
from odoo import fields, models, api
|
||||
|
||||
class ProjectKudosBadge(models.Model):
|
||||
_name = 'project.kudos.badge'
|
||||
_description = 'Kudos Achievement Badges'
|
||||
|
||||
name = fields.Char(required=True)
|
||||
level = fields.Selection([
|
||||
('bronze', 'Bronze'),
|
||||
('silver', 'Silver'),
|
||||
('gold', 'Gold'),
|
||||
('platinum', 'Platinum'),
|
||||
], required=True)
|
||||
threshold = fields.Integer(string="Points Required", required=True)
|
||||
employee_ids = fields.Many2many(
|
||||
'hr.employee',
|
||||
'project_kudos_badge_employee_rel', # <-- add this line
|
||||
'badge_id',
|
||||
'employee_id',
|
||||
string="Awarded Employees"
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _cron_assign_badges(self):
|
||||
employees = self.env['hr.employee'].search([])
|
||||
for emp in employees:
|
||||
total_points = sum(self.env['project.kudos'].search([
|
||||
('employee_id', '=', emp.id)
|
||||
]).mapped('points_awarded'))
|
||||
|
||||
badge = self.search([('threshold', '<=', total_points)],
|
||||
order='threshold desc',
|
||||
limit=1)
|
||||
if badge and emp not in badge.employee_ids:
|
||||
badge.employee_ids = [(4, emp.id)]
|
||||
emp.message_post(body=f"🏅 Congratulations {emp.name}! You earned the {badge.level.title()} Badge.")
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models
|
||||
from markupsafe import Markup
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProjectKudos(models.Model):
|
||||
_name = 'project.kudos'
|
||||
_description = 'Employee Task Reward'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'date_awarded desc'
|
||||
|
||||
name = fields.Char(string="Reward Title", default="Task Completion Reward", tracking=True)
|
||||
employee_id = fields.Many2one('hr.employee', string="Employee", required=True, tracking=True)
|
||||
task_id = fields.Many2one('project.task', string="Task", required=True, tracking=True)
|
||||
project_id = fields.Many2one('project.project', related='task_id.project_id', store=True, tracking=True)
|
||||
completed_early_by = fields.Float(string="Hours Early", tracking=True)
|
||||
points_awarded = fields.Integer(string="Points", default=10, tracking=True)
|
||||
date_awarded = fields.Datetime(default=fields.Datetime.now, tracking=True)
|
||||
|
||||
# ✅ New: show badges this employee currently has
|
||||
badge_ids = fields.Many2many(
|
||||
'project.kudos.badge',
|
||||
string="Badges",
|
||||
compute='_compute_badges',
|
||||
store=False,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
@api.depends('employee_id')
|
||||
def _compute_badges(self):
|
||||
"""Compute badges linked to the selected employee."""
|
||||
Badge = self.env['project.kudos.badge']
|
||||
for rec in self:
|
||||
if rec.employee_id:
|
||||
rec.badge_ids = Badge.search([('employee_ids', 'in', rec.employee_id.id)])
|
||||
else:
|
||||
rec.badge_ids = [(5, 0, 0)] # clear
|
||||
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee_id(self):
|
||||
"""Show badges immediately when selecting employee."""
|
||||
for rec in self:
|
||||
rec._compute_badges()
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super(ProjectKudos, self).create(vals_list)
|
||||
template = self.env.ref('project_kudos_plus.mail_template_kudos_notify', raise_if_not_found=False)
|
||||
|
||||
# Ensure Kudos Announcements channel exists
|
||||
channel = None
|
||||
if 'discuss.channel' in self.env.registry.models:
|
||||
channel = self.env['discuss.channel'].sudo().search(
|
||||
[('name', '=', 'Kudos Announcements')], limit=1
|
||||
)
|
||||
if not channel:
|
||||
channel = self.env['discuss.channel'].sudo().create({
|
||||
'name': 'Kudos Announcements',
|
||||
'public': 'comment',
|
||||
})
|
||||
else:
|
||||
_logger.warning("Discuss module not found, skipping channel announcement.")
|
||||
|
||||
for rec in records:
|
||||
# Send appreciation email
|
||||
if template and rec.employee_id.work_email:
|
||||
template.email_to = rec.employee_id.work_email
|
||||
template.send_mail(rec.id, force_send=True)
|
||||
|
||||
# ✅ Message content (HTML safe)
|
||||
message = Markup(
|
||||
"🌟 <b>KUDOS ALERT!</b> 🌟<br><br>"
|
||||
f"🏆 <b>Employee:</b> {rec.employee_id.name}<br>"
|
||||
f"📌 <b>Task Completed:</b> {rec.task_id.name}<br>"
|
||||
f"💎 <b>Kudos Points Earned:</b> {rec.points_awarded}<br><br>"
|
||||
"👏 Outstanding performance! Your dedication and hard work made this happen.<br>"
|
||||
"Let's keep up the momentum! 💪🔥<br><br>"
|
||||
"— <i>Project Kudos+ Team</i>"
|
||||
)
|
||||
|
||||
# Post to record’s chatter
|
||||
rec.message_post(
|
||||
body=message,
|
||||
subject=f"🌟 Kudos for {rec.employee_id.name}",
|
||||
message_type='comment',
|
||||
subtype_xmlid="mail.mt_comment"
|
||||
)
|
||||
|
||||
# Post to Kudos Announcements channel
|
||||
if channel:
|
||||
try:
|
||||
channel.message_post(
|
||||
body=message,
|
||||
subject=f"🌟 Kudos for {rec.employee_id.name}",
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment'
|
||||
)
|
||||
except Exception as e:
|
||||
_logger.error(f"Failed to post to Kudos Announcements channel: {e}")
|
||||
|
||||
# ✅ FIXED: use rec.employee_id instead of undefined 'employee'
|
||||
total_points = sum(
|
||||
self.search([('employee_id', '=', rec.employee_id.id)]).mapped('points_awarded')
|
||||
)
|
||||
|
||||
badge = self.env['project.kudos.badge'].search(
|
||||
[('threshold', '<=', total_points)],
|
||||
order='threshold desc',
|
||||
limit=1
|
||||
)
|
||||
|
||||
if badge and rec.employee_id not in badge.employee_ids:
|
||||
badge.employee_ids = [(4, rec.employee_id.id)]
|
||||
rec.employee_id.message_post(
|
||||
body=f"🏅 Congratulations {rec.employee_id.name}! You earned the {badge.level.title()} Badge!"
|
||||
)
|
||||
|
||||
return records
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
from odoo import fields, models
|
||||
|
||||
class ProjectKudosLeaderboard(models.Model):
|
||||
_name = 'project.kudos.leaderboard'
|
||||
_description = 'Kudos Leaderboard'
|
||||
_auto = False
|
||||
_order = 'total_points DESC'
|
||||
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', readonly=True)
|
||||
total_points = fields.Float(string='Total Points', readonly=True)
|
||||
badge_names = fields.Char(string='Badges Earned', readonly=True)
|
||||
|
||||
def init(self):
|
||||
self.env.cr.execute("""DROP VIEW IF EXISTS project_kudos_leaderboard CASCADE;""")
|
||||
self.env.cr.execute("""
|
||||
CREATE OR REPLACE VIEW project_kudos_leaderboard AS (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER() AS id,
|
||||
e.id AS employee_id,
|
||||
COALESCE(SUM(k.points_awarded), 0) AS total_points,
|
||||
COALESCE(STRING_AGG(DISTINCT b.name, ', '), '') AS badge_names
|
||||
FROM hr_employee e
|
||||
LEFT JOIN project_kudos k ON k.employee_id = e.id
|
||||
LEFT JOIN project_kudos_badge_employee_rel r ON r.employee_id = e.id
|
||||
LEFT JOIN project_kudos_badge b ON b.id = r.badge_id
|
||||
GROUP BY e.id
|
||||
HAVING COALESCE(SUM(k.points_awarded), 0) > 0 OR COUNT(b.id) > 0
|
||||
ORDER BY total_points DESC
|
||||
)
|
||||
""")
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
from odoo import models, fields, api
|
||||
|
||||
class ProjectTask(models.Model):
|
||||
_inherit = 'project.task'
|
||||
|
||||
def write(self, vals):
|
||||
res = super(ProjectTask, self).write(vals)
|
||||
for task in self:
|
||||
if 'stage_id' in vals:
|
||||
stage = self.env['project.task.type'].browse(vals['stage_id'])
|
||||
if stage.name.lower() == 'done' and task.date_deadline:
|
||||
if fields.Datetime.now() <= task.date_deadline:
|
||||
self.env['project.kudos'].create({
|
||||
'employee_id': task.user_id.employee_id.id,
|
||||
'task_id': task.id,
|
||||
'completed_early_by': (task.date_deadline - fields.Datetime.now()).total_seconds() / 3600,
|
||||
'points_awarded': 10
|
||||
})
|
||||
emp = task.user_id.employee_id
|
||||
emp.kudos_points = emp.kudos_points + 10 if emp.kudos_points else 10
|
||||
template = self.env.ref('project_kudos_plus.mail_template_kudos')
|
||||
template.send_mail(task.id, force_send=True)
|
||||
task.message_post(body=f"👏 Kudos! {task.user_id.name} completed this task before the deadline.")
|
||||
return res
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_project_kudos_user,project.kudos user,model_project_kudos,base.group_user,1,1,1,1
|
||||
access_project_kudos_badge_user,project.kudos.badge user,model_project_kudos_badge,base.group_user,1,1,1,1
|
||||
access_project_kudos_leaderboard,project.kudos.leaderboard user,model_project_kudos_leaderboard,base.group_user,1,0,0,0
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<odoo>
|
||||
<record id="view_project_badge_tree" model="ir.ui.view">
|
||||
<field name="name">project.kudos.badge.list</field>
|
||||
<field name="model">project.kudos.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="level"/>
|
||||
<field name="threshold"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_project_badge_form" model="ir.ui.view">
|
||||
<field name="name">project.kudos.badge.form</field>
|
||||
<field name="model">project.kudos.badge</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="level"/>
|
||||
<field name="threshold"/>
|
||||
<field name="employee_ids"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_project_badge" model="ir.actions.act_window">
|
||||
<field name="name">Badges</field>
|
||||
<field name="res_model">project.kudos.badge</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
<odoo>
|
||||
<!-- ✅ Tree (List) View (no badges here) -->
|
||||
<record id="view_project_kudos_tree" model="ir.ui.view">
|
||||
<field name="name">project.kudos.list</field>
|
||||
<field name="model">project.kudos</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="employee_id"/>
|
||||
<field name="task_id"/>
|
||||
<field name="project_id"/>
|
||||
<field name="completed_early_by"/>
|
||||
<field name="points_awarded"/>
|
||||
<field name="date_awarded"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ✅ Form View (badges only here) -->
|
||||
<record id="view_project_kudos_form" model="ir.ui.view">
|
||||
<field name="name">project.kudos.form</field>
|
||||
<field name="model">project.kudos</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<!-- Show badges only inside the form -->
|
||||
<field name="badge_ids" widget="many2many_tags" readonly="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="task_id"/>
|
||||
<field name="project_id"/>
|
||||
<field name="completed_early_by"/>
|
||||
<field name="points_awarded"/>
|
||||
<field name="date_awarded"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ✅ Action -->
|
||||
<record id="action_project_kudos" model="ir.actions.act_window">
|
||||
<field name="name">Kudos Log</field>
|
||||
<field name="res_model">project.kudos</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<odoo>
|
||||
<record id="view_project_kudos_leaderboard_tree" model="ir.ui.view">
|
||||
<field name="name">project.kudos.leaderboard.tree</field>
|
||||
<field name="model">project.kudos.leaderboard</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="employee_id"/>
|
||||
<field name="total_points"/>
|
||||
<field name="badge_names"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_project_kudos_leaderboard" model="ir.actions.act_window">
|
||||
<field name="name">Leaderboard</field>
|
||||
<field name="res_model">project.kudos.leaderboard</field>
|
||||
<field name="view_mode">list</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<odoo>
|
||||
<menuitem id="menu_project_kudos_root" name="Rewards" parent="project.menu_main_pm" sequence="20"/>
|
||||
|
||||
<menuitem id="menu_kudos_log" name="Kudos Log"
|
||||
parent="menu_project_kudos_root" action="action_project_kudos"/>
|
||||
|
||||
<menuitem id="menu_kudos_badges" name="Badges"
|
||||
parent="menu_project_kudos_root" action="action_project_badge"/>
|
||||
|
||||
<menuitem id="menu_project_kudos_leaderboard" name="Leaderboard"
|
||||
parent="menu_project_kudos_root" action="action_project_kudos_leaderboard" sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue