Rewards Module for PMS

This commit is contained in:
karuna 2025-11-13 12:43:31 +05:30
parent 66077d1819
commit 4ce02e58fa
15 changed files with 411 additions and 0 deletions

View File

@ -0,0 +1 @@
from . import models

View File

@ -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',
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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.")

View File

@ -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 records 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

View File

@ -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
)
""")

View File

@ -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

View File

@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_project_kudos_user project.kudos user model_project_kudos base.group_user 1 1 1 1
3 access_project_kudos_badge_user project.kudos.badge user model_project_kudos_badge base.group_user 1 1 1 1
4 access_project_kudos_leaderboard project.kudos.leaderboard user model_project_kudos_leaderboard base.group_user 1 0 0 0

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>