279 lines
10 KiB
Python
279 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Reminder Model.
|
|
Automated reminders for incomplete training.
|
|
"""
|
|
|
|
from odoo import models, fields, api, _
|
|
from datetime import timedelta
|
|
import logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class GeniusReminder(models.Model):
|
|
_name = 'genius.reminder'
|
|
_description = 'Training Reminder'
|
|
_order = 'scheduled_date'
|
|
_inherit = ['mail.thread']
|
|
|
|
name = fields.Char(
|
|
string='Subject',
|
|
required=True,
|
|
default=_("Training Reminder")
|
|
)
|
|
user_id = fields.Many2one(
|
|
'res.users',
|
|
string='User',
|
|
required=True,
|
|
ondelete='cascade',
|
|
index=True
|
|
)
|
|
|
|
# What to remind about
|
|
reminder_type = fields.Selection([
|
|
('incomplete_topic', 'Incomplete Topic'),
|
|
('incomplete_plan', 'Incomplete Plan'),
|
|
('new_topic', 'New Topic Available'),
|
|
('quiz_due', 'Quiz Due'),
|
|
('streak', 'Keep Your Streak'),
|
|
('custom', 'Custom Message'),
|
|
], string='Type', required=True, default='incomplete_topic')
|
|
|
|
topic_id = fields.Many2one(
|
|
'genius.topic',
|
|
string='Topic',
|
|
help='Related topic (if applicable)'
|
|
)
|
|
plan_id = fields.Many2one(
|
|
'genius.plan',
|
|
string='Plan',
|
|
help='Related plan (if applicable)'
|
|
)
|
|
|
|
# Scheduling
|
|
scheduled_date = fields.Datetime(
|
|
string='Scheduled For',
|
|
required=True,
|
|
default=lambda self: fields.Datetime.now() + timedelta(days=1)
|
|
)
|
|
|
|
# Status
|
|
state = fields.Selection([
|
|
('pending', 'Pending'),
|
|
('sent', 'Sent'),
|
|
('cancelled', 'Cancelled'),
|
|
('failed', 'Failed'),
|
|
], string='Status', default='pending', required=True, index=True)
|
|
|
|
sent_date = fields.Datetime(string='Sent At')
|
|
|
|
# Message Content
|
|
message_body = fields.Html(
|
|
string='Message',
|
|
compute='_compute_message_body',
|
|
store=True
|
|
)
|
|
custom_message = fields.Html(
|
|
string='Custom Message',
|
|
help='Custom message for custom reminder type'
|
|
)
|
|
|
|
# Repeat Settings
|
|
is_recurring = fields.Boolean(
|
|
string='Recurring',
|
|
default=False
|
|
)
|
|
recurrence_interval = fields.Integer(
|
|
string='Repeat Every (days)',
|
|
default=7
|
|
)
|
|
|
|
company_id = fields.Many2one(
|
|
'res.company',
|
|
string='Company',
|
|
default=lambda self: self.env.company
|
|
)
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Computed Fields
|
|
# -------------------------------------------------------------------------
|
|
@api.depends('reminder_type', 'topic_id', 'plan_id', 'user_id', 'custom_message')
|
|
def _compute_message_body(self):
|
|
for rec in self:
|
|
if rec.reminder_type == 'custom' and rec.custom_message:
|
|
rec.message_body = rec.custom_message
|
|
elif rec.reminder_type == 'incomplete_topic' and rec.topic_id:
|
|
rec.message_body = _("""
|
|
<p>Hi %(user)s,</p>
|
|
<p>You haven't completed the training topic: <strong>%(topic)s</strong></p>
|
|
<p>Continue your learning journey today!</p>
|
|
""") % {
|
|
'user': rec.user_id.name,
|
|
'topic': rec.topic_id.name,
|
|
}
|
|
elif rec.reminder_type == 'incomplete_plan' and rec.plan_id:
|
|
rec.message_body = _("""
|
|
<p>Hi %(user)s,</p>
|
|
<p>You have an incomplete training plan: <strong>%(plan)s</strong></p>
|
|
<p>Keep up your progress!</p>
|
|
""") % {
|
|
'user': rec.user_id.name,
|
|
'plan': rec.plan_id.name,
|
|
}
|
|
elif rec.reminder_type == 'streak':
|
|
rec.message_body = _("""
|
|
<p>Hi %(user)s,</p>
|
|
<p>Don't break your learning streak!</p>
|
|
<p>Complete a quick topic today to keep your momentum.</p>
|
|
""") % {'user': rec.user_id.name}
|
|
elif rec.reminder_type == 'new_topic' and rec.topic_id:
|
|
rec.message_body = _("""
|
|
<p>Hi %(user)s,</p>
|
|
<p>A new training topic is available: <strong>%(topic)s</strong></p>
|
|
<p>Check it out!</p>
|
|
""") % {
|
|
'user': rec.user_id.name,
|
|
'topic': rec.topic_id.name,
|
|
}
|
|
elif rec.reminder_type == 'quiz_due' and rec.topic_id:
|
|
rec.message_body = _("""
|
|
<p>Hi %(user)s,</p>
|
|
<p>You have completed the tour <strong>%(topic)s</strong>, but haven't passed the quiz yet.</p>
|
|
<p>Take the quiz now to get Certified!</p>
|
|
""") % {
|
|
'user': rec.user_id.name,
|
|
'topic': rec.topic_id.name,
|
|
}
|
|
else:
|
|
rec.message_body = _("<p>You have a training reminder.</p>")
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Actions
|
|
# -------------------------------------------------------------------------
|
|
def action_send(self):
|
|
"""Send the reminder now"""
|
|
for rec in self:
|
|
rec._send_reminder()
|
|
|
|
def action_cancel(self):
|
|
"""Cancel the reminder"""
|
|
self.write({'state': 'cancelled'})
|
|
|
|
def _send_reminder(self):
|
|
"""Actually send the reminder email"""
|
|
self.ensure_one()
|
|
|
|
# Validate email before attempting to send
|
|
if not self.user_id.email:
|
|
_logger.warning("Cannot send reminder %s: user %s has no email",
|
|
self.id, self.user_id.name)
|
|
self.write({'state': 'failed'})
|
|
return False
|
|
|
|
try:
|
|
template = self.env.ref('tour_genius.email_template_training_reminder', raise_if_not_found=False)
|
|
|
|
if template:
|
|
template.send_mail(self.id, force_send=True)
|
|
else:
|
|
# Fallback: send simple email
|
|
self.env['mail.mail'].create({
|
|
'subject': self.name,
|
|
'body_html': self.message_body,
|
|
'email_to': self.user_id.email,
|
|
'auto_delete': True,
|
|
}).send()
|
|
|
|
self.write({
|
|
'state': 'sent',
|
|
'sent_date': fields.Datetime.now(),
|
|
})
|
|
|
|
# Handle recurrence
|
|
if self.is_recurring:
|
|
self.copy({
|
|
'scheduled_date': fields.Datetime.now() + timedelta(days=self.recurrence_interval),
|
|
'state': 'pending',
|
|
'sent_date': False,
|
|
})
|
|
|
|
except Exception as e:
|
|
self.write({'state': 'failed'})
|
|
raise
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Scheduled Actions
|
|
# -------------------------------------------------------------------------
|
|
@api.model
|
|
def _cron_send_reminders(self):
|
|
"""Cron job to send pending reminders"""
|
|
pending = self.search([
|
|
('state', '=', 'pending'),
|
|
('scheduled_date', '<=', fields.Datetime.now()),
|
|
])
|
|
|
|
for reminder in pending:
|
|
try:
|
|
reminder._send_reminder()
|
|
except Exception as e:
|
|
_logger.exception("Failed to send reminder %s: %s", reminder.id, str(e))
|
|
# State already marked as 'failed' in _send_reminder
|
|
|
|
@api.model
|
|
def _cron_create_auto_reminders(self):
|
|
"""Create automatic reminders for users with incomplete training or pending quizzes"""
|
|
# 1. Stale Progress (Incomplete > 3 days)
|
|
three_days_ago = fields.Datetime.now() - timedelta(days=3)
|
|
|
|
stale_progress = self.env['genius.progress'].search([
|
|
('state', '=', 'in_progress'),
|
|
('date_started', '<=', three_days_ago),
|
|
])
|
|
|
|
for progress in stale_progress:
|
|
# Check if reminder already exists
|
|
existing = self.search([
|
|
('user_id', '=', progress.user_id.id),
|
|
('topic_id', '=', progress.topic_id.id),
|
|
('reminder_type', '=', 'incomplete_topic'),
|
|
('state', '=', 'pending'),
|
|
], limit=1)
|
|
|
|
if not existing:
|
|
self.create({
|
|
'name': _("Continue your training: %s") % progress.topic_id.name,
|
|
'user_id': progress.user_id.id,
|
|
'reminder_type': 'incomplete_topic',
|
|
'topic_id': progress.topic_id.id,
|
|
'plan_id': progress.plan_id.id if progress.plan_id else False,
|
|
'scheduled_date': fields.Datetime.now() + timedelta(hours=24),
|
|
})
|
|
|
|
# 2. Quiz Reminders (Completed > 1 day but Not Verified, and Quiz exists)
|
|
one_day_ago = fields.Datetime.now() - timedelta(days=1)
|
|
unverified_done = self.env['genius.progress'].search([
|
|
('state', '=', 'done'),
|
|
('date_completed', '<=', one_day_ago),
|
|
])
|
|
|
|
for progress in unverified_done:
|
|
# Check if Topic has a Quiz
|
|
if progress.topic_id.quiz_id:
|
|
# Check if already reminded
|
|
existing = self.search([
|
|
('user_id', '=', progress.user_id.id),
|
|
('topic_id', '=', progress.topic_id.id),
|
|
('reminder_type', '=', 'quiz_due'),
|
|
('state', 'in', ['pending', 'sent']), # Don't spam if sent
|
|
], limit=1)
|
|
|
|
if not existing:
|
|
self.create({
|
|
'name': _("Get Certified: %s") % progress.topic_id.name,
|
|
'user_id': progress.user_id.id,
|
|
'reminder_type': 'quiz_due',
|
|
'topic_id': progress.topic_id.id,
|
|
'scheduled_date': fields.Datetime.now() + timedelta(hours=24),
|
|
})
|