IMP benefit

This commit is contained in:
younes 2025-08-28 16:07:38 +01:00
parent abf0630b28
commit 0eb931165f
13 changed files with 192 additions and 102 deletions

View File

@ -57,6 +57,7 @@
'wizards/entity_black_list_wizard_view.xml',
'wizards/service_refuse_reason_wizard.xml',
'wizards/reason_for_return_wizard.xml',
'wizards/visit_location_otp_wizard_view.xml',
'views/actions_and_menus.xml',
],
'external_dependencies': {

View File

@ -265,77 +265,65 @@
</table>
</field>
</record>
<!-- Schedule a visit -->
<!-- <record id="schedule_a_visit_email_template" model="mail.template">-->
<!-- <field name="name">Schedule a visit</field>-->
<!-- <field name="model_id" ref="odex_benefit.model_visit_location"/>-->
<!-- <field name="email_from">-->
<!-- <![CDATA[${user.company_id.name} <${(user.company_id.email or user.partner_id.email or 'noreply@localhost')|safe}>]]></field>-->
<!-- <field name="email_to">${(object.benefit_id.email)|safe}</field>-->
<!-- <field name="email_cc">${(object.get_researchers_email())|safe}</field>-->
<!-- <field name="subject"> Schedule a visit </field>-->
<!-- <field name="body_html" type="html">-->
<!-- <table border="0" cellpadding="0" cellspacing="0" style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial,sans-serif; color: #454748; width: 100%; border-collapse:separate;"><tr><td align="center">-->
<!-- <table border="0" cellpadding="0" cellspacing="0" width="590" style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">-->
<!-- <tbody>-->
<!-- &lt;!&ndash; HEADER &ndash;&gt;-->
<!-- <tr>-->
<!-- <td align="center" style="min-width: 590px;">-->
<!-- <table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">-->
<!-- <tr><td valign="middle">-->
<!-- </td><td valign="middle" align="right">-->
<!-- <img src="/logo.png?company=${object.create_uid.company_id.id}" style="padding: 0px; margin: 0px; height: auto; width: 80px;" alt="${object.create_uid.company_id.name}"/>-->
<!-- </td></tr>-->
<!-- <tr><td colspan="2" style="text-align:center;">-->
<!-- <hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>-->
<!-- </td></tr>-->
<!-- </table>-->
<!-- </td>-->
<!-- </tr>-->
<!-- &lt;!&ndash; CONTENT &ndash;&gt;-->
<!-- <tr>-->
<!-- <td align="center" style="min-width: 590px;">-->
<!-- <table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">-->
<!-- <tr><td valign="top" style="font-size: 13px;">-->
<!-- <div>-->
<!-- <center>-->
<!-- We want to schedule visit date<br/><br/><br/>-->
<!-- </center><br/>-->
<!-- </div>-->
<!-- </td></tr>-->
<!-- <tr><td style="text-align:center;">-->
<!-- <hr width="100%" style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>-->
<!-- </td></tr>-->
<!-- </table>-->
<!-- </td>-->
<!-- </tr>-->
<!-- &lt;!&ndash; FOOTER &ndash;&gt;-->
<!-- <tr>-->
<!-- <td align="center" style="min-width: 590px;">-->
<!-- <table border="0" cellpadding="0" cellspacing="0" width="590" style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">-->
<!-- <tr><td valign="middle" align="left">-->
<!-- ${object.create_uid.company_id.name}-->
<!-- </td></tr>-->
<!-- <tr><td valign="middle" align="left" style="opacity: 0.7;">-->
<!-- ${object.create_uid.company_id.phone}-->
<!-- % if object.create_uid.company_id.email-->
<!-- | <a href="'mailto:%s' % ${object.create_uid.company_id.email}" style="text-decoration:none; color: #454748;">${object.create_uid.company_id.email}</a>-->
<!-- % endif-->
<!-- % if object.create_uid.company_id.website-->
<!-- | <a href="'%s' % ${object.create_uid.company_id.website}" style="text-decoration:none; color: #454748;">-->
<!-- ${object.create_uid.company_id.website}-->
<!-- </a>-->
<!-- % endif-->
<!-- </td></tr>-->
<!-- </table>-->
<!-- </td>-->
<!-- </tr>-->
<!-- </tbody>-->
<!-- </table>-->
<!-- </td></tr>-->
<!-- </table>-->
<!-- </field>-->
<!-- </record>-->
<!-- Visit Location OTP Email-->
<record id="visit_location_otp_email_template" model="mail.template">
<field name="name">Visit Location OTP Email</field>
<field name="model_id" ref="odex_benefit.model_visit_location"/>
<field name="subject">Your OTP Code</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
style="padding-top:16px;background-color:#F9F9F9;font-family:Verdana,Arial,sans-serif;color:#333;width:100%;border-collapse:separate;">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="600"
style="background-color:#fff;padding:24px;border-radius:8px;border-collapse:separate;">
<tr>
<td style="font-size:14px;line-height:20px;">
<p>Hello,</p>
<p>Your OTP for confirming visit <strong>${object.benefit_id.name}</strong> is:
</p>
<!-- OTP Block -->
<div style="text-align:center;margin:20px 0;">
<span style="display:inline-block;background-color:#2c3e50;color:#ffffff;
font-size:24px;font-weight:bold;padding:12px 24px;
border-radius:6px;letter-spacing:3px;">
${object.otp_code}
</span>
</div>
<p>This code is valid for <strong>
${object.visit_types.otp_validity_minutes or 5}
</strong> minutes.
</p>
<br/>
<p>Regards,
<br/>
<strong>${user.company_id.name}</strong>
</p>
<br/>
<p style="font-size:11px;color:grey;">
This is an automated message. Please do not reply.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
</field>
</record>
<!-- Visit Location OTP SMS-->
<record id="visit_location_otp_sms_template" model="sms.template">
<field name="name">Visit Location OTP</field>
<field name="model_id" ref="odex_benefit.model_visit_location"/>
<field name="body">
Your verification code for visit ${object.benefit_id.name} is ${object.otp_code}.
It is valid for ${object.visit_types.otp_validity_minutes or 5} minutes.
</field>
</record>
</data>
</odoo>

View File

@ -3222,9 +3222,9 @@ msgid "Contact Phone"
msgstr "رقم الجوال للتواصل"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_visit_location__contact_type
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__contact_type
msgid "Contact Type"
msgstr "نوع التواصل"
msgstr "طريقة التواصل الافتراضية"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__contract_num
@ -4756,7 +4756,7 @@ msgstr ""
#: model:ir.model.fields,field_description:odex_benefit.field_benefits_representative__email
#: model:ir.model.fields,field_description:odex_benefit.field_external_benefits__email
#: model:ir.model.fields,field_description:odex_benefit.field_grant_benefit__email
#: model:ir.model.fields.selection,name:odex_benefit.selection__visit_location__contact_type__email
#: model:ir.model.fields.selection,name:odex_benefit.selection__grant_benefit__contact_type__email
msgid "Email"
msgstr "البريد الإلكتروني"
@ -10244,7 +10244,7 @@ msgid "SEO optimized"
msgstr "تم تحسين محركات البحث"
#. module: odex_benefit
#: model:ir.model.fields.selection,name:odex_benefit.selection__visit_location__contact_type__sms
#: model:ir.model.fields.selection,name:odex_benefit.selection__grant_benefit__contact_type__sms
msgid "SMS"
msgstr "الرسائل النصية القصيرة "

View File

@ -584,6 +584,10 @@ class GrantBenefitProfile(models.Model):
('suspended', 'Suspended'),
('exception', 'Exception'),
], string='Action Type', default='new')
contact_type = fields.Selection([
('email', 'Email'),
('sms', 'SMS'),
], string='Contact Type',default='sms')
_sql_constraints = [
('unique_code', "unique (code) WHERE state NOT IN ('draft', 'new')", 'This code already exists')
@ -2324,7 +2328,6 @@ class GrantBenefitProfile(models.Model):
'benefit_id': rec.id,
'visit_date': date.today(),
'visit_types': 2,
'contact_type': 'email',
'selector': 'researcher',
'researcher_id': rec.researcher_id.id,
# 'researcher_team': rec.researcher_team.id,
@ -2336,7 +2339,6 @@ class GrantBenefitProfile(models.Model):
'benefit_id': self.id,
'visit_date': date.today(),
'visit_types': 2,
'contact_type': 'email',
# 'selector': 'researcher',
'researcher_id': self.researcher_id.id,
# 'researcher_team': rec.researcher_team.id,

View File

@ -809,7 +809,6 @@ class FamilyMemberProfile(models.Model):
'benefit_id': self.id,
'visit_date': date.today(),
'visit_types': 2,
'contact_type': 'email',
# 'selector': 'researcher',
'researcher_id': self.researcher_id.id,
# 'researcher_team': rec.researcher_team.id,

View File

@ -1,6 +1,7 @@
from odoo import fields, models,api,_
import math, random
from odoo.exceptions import UserError, ValidationError
from datetime import timedelta
class Visit(models.Model):
_name = 'visit.location'
@ -32,10 +33,6 @@ class Visit(models.Model):
visit_types = fields.Many2one(
'visits.types',
string='Visits Types')
contact_type = fields.Selection([
('email', 'Email'),
('sms', 'SMS'),
], string='Contact Type')
priority = fields.Selection(
[('0', 'Normal'), ('1', 'Low'), ('2', 'High'), ('3', 'Very High'), ('4', 'Very Very High')],
string='Priority')
@ -52,6 +49,8 @@ class Visit(models.Model):
reason = fields.Text(string='Reason/Justification')
name = fields.Char(string='Reference', required=True, copy=False, readonly=True, index=True,
default=lambda self: _('New'))
otp_code = fields.Char(string="OTP Code", readonly=True, copy=False)
otp_generated_at = fields.Datetime(string="OTP Generated At", readonly=True, copy=False)
def _expand_states(self, states, domain, order):
return [key for key, val in type(self).state.selection]
@ -89,13 +88,13 @@ class Visit(models.Model):
def action_contact(self):
self.state = 'contact'
if self.contact_type == 'email':
if self.benefit_id.contact_type == 'email':
template = self.env.ref('odex_benefit.schedule_a_visit_email_template', False)
if not template:
return
template.with_context(lang=self.env.user.lang).send_mail(self.id, force_send=True,
raise_exception=False)
elif self.contact_type == 'sms':
elif self.benefit_id.contact_type == 'sms':
self.benefit_id.partner_id.send_sms_notification(self.message, self.benefit_id.sms_phone)
def action_schedule_a_visit(self):
@ -104,19 +103,69 @@ class Visit(models.Model):
for rec in self:
rec.state = 'cancel'
def action_done(self):
self.state = 'done'
self.benefit_id.last_visit_date = self.visit_date
if self.contact_type == 'email':
body = self.generateOTP()
mail = self.env['mail.mail'].create({
'body_html': body,
'subject': "Visit Confirmation",
'email_to': self.benefit_id.email,
# 'email_cc': self.benefit_id.email,
})
mail.send()
elif self.contact_type == 'sms':
self.benefit_id.user_id.sudo().request_otp(self.benefit_id.sms_phone)
if self.visit_types.otp_verification:
# self.otp_code = self.generateOTP()
# self.otp_generated_at = fields.Datetime.now()
otp_validity = self.visit_types.otp_validity_minutes or 5
expired = (
not self.otp_code
or not self.otp_generated_at
or fields.Datetime.now() > self.otp_generated_at + timedelta(minutes=otp_validity)
)
if expired:
self.otp_code = self.generateOTP()
self.otp_generated_at = fields.Datetime.now()
if self.benefit_id.contact_type == 'email':
if not self.benefit_id.email:
raise UserError(_("The family profile has no email address. OTP cannot be sent. Please add an email first."))
template = self.env.ref('odex_benefit.visit_location_otp_email_template', False)
if not template:
raise UserError(
_("The email template 'Visit Location OTP Email' is missing. Please contact your administrator."))
self.state = 'done'
template.write({'email_to': self.benefit_id.email,
'email_cc': self.env.user.company_id.hr_email or self.env.user.company_id.email,})
template.with_context(lang=self.env.user.lang,mail_post_autofollow=False,mail_notify_force_send= False,).send_mail(self.id, force_send=True,
raise_exception=False)
elif self.benefit_id.contact_type == 'sms':
# if not self.benefit_id.sms_phone:
# raise UserError(_("The family profile has no mobile number. Please add a valid phone number before sending OTP."))
# bot = self.env.ref('base.partner_root').id
# sms_template_id = self.env.ref('odex_benefit.visit_location_otp_sms_template')
# if not sms_template_id:
# raise UserError(_("The SMS template 'Visit Location OTP' is missing. Please contact your administrator."))
# self._message_sms_with_template(
# template=sms_template_id,
# put_in_queue=False,
# partner_ids=self.partner_id.ids,
# author_id=bot
# )
message = _("Your verification code is %s. It is valid for %s minutes.") % (
self.otp_code,
self.visit_types.otp_validity_minutes or 5
)
self.benefit_id.user_id.send_sms_to_user(message, self.benefit_id.sms_phone)
context = dict(self.env.context or {})
context['active_id'] = self.id
return {
'name': _('Verify OTP'),
'view_mode': 'form',
'view_type': 'form',
'type': 'ir.actions.act_window',
'res_model': 'visit.location.otp.wizard',
'view_id': self.env.ref('odex_benefit.visit_location_otp_wizard_form').id,
'target': 'new',
'context': context,
}
else:
self.state = 'done'
def action_close(self):
survey_url = ''
survey_conf = self.env['survey.setting'].search([],limit=1)
@ -124,7 +173,7 @@ class Visit(models.Model):
survey_url = survey_conf.survey_url
survey_url = ' <a href=#survey link>%s</a>' % (survey_url)
self.state = 'close'
if self.contact_type == 'email':
if self.benefit_id.contact_type == 'email':
body = survey_url
mail = self.env['mail.mail'].create({
'body_html':survey_url ,
@ -133,7 +182,7 @@ class Visit(models.Model):
# 'email_cc': self.researcher_id.work_email,
})
mail.send()
elif self.contact_type == 'sms':
elif self.benefit_id.contact_type == 'sms':
self.benefit_id.partner_id.send_sms_notification(survey_url , self.benefit_id.sms_phone)
@api.depends("researcher_team")
def get_researcher_ids(self):

View File

@ -153,3 +153,4 @@ access_transportation_insurance,access_transportation_insurance,model_transporta
access_res_city,access_res_city,model_res_city,base.group_user,1,0,0,0
access_res_city,access_res_city,model_res_city,odex_benefit.group_benefit_manager,1,1,1,1
access_grant_benefit_portal_user,access_grant_benefit_portal_user,model_grant_benefit,base.group_portal,1,1,1,1
access_visit_location_otp_wizard,access_visit_location_otp_wizard,model_visit_location_otp_wizard,,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
153 access_res_city access_res_city model_res_city base.group_user 1 0 0 0
154 access_res_city access_res_city model_res_city odex_benefit.group_benefit_manager 1 1 1 1
155 access_grant_benefit_portal_user access_grant_benefit_portal_user model_grant_benefit base.group_portal 1 1 1 1
156 access_visit_location_otp_wizard access_visit_location_otp_wizard model_visit_location_otp_wizard 1 1 1 1

View File

@ -134,7 +134,7 @@
string="First Refuse" class="btn btn-danger"
confirm="Are you sure you want to refuse ?"
groups="odex_benefit.group_benefit_manager,odex_benefit.group_benefit_branch_manager,odex_benefit.group_benefit_woman_commitee"
attrs="{'invisible': ['|',('action_type','!=','suspended'),('state','not in',['waiting_approve'])]}"
attrs="{'invisible': ['|',('action_type','!=','new'),('state','not in',['waiting_approve'])]}"
/>
<button name="action_set_to_new" type="object"
string="Set To new" class="btn btn-warning"
@ -333,6 +333,7 @@
invisible="1"/>
</group>
<group>
<field name="contact_type"/>
<field name="applicant_name"/>
<field name="request_producer" readonly="1" force_save="1" invisible="1"/>
<field name="request_producer_relation"

View File

@ -113,7 +113,7 @@
<field name="visit_date" attrs="{'readonly':[('state', 'in', ['close','cancel'])]}"/>
<field name="visit_objective" invisible="1"
attrs="{'readonly':[('state', 'in', ['close','cancel'])]}"/>
<field name="contact_type" attrs="{'readonly':[('state', 'in', ['close','cancel'])]}"/>
<field name="otp_code" invisible="1"/>
</group>
</group>
</sheet>
@ -139,7 +139,6 @@
<field name="visit_objective" invisible="1"/>
<field name="activity_ids" widget="list_activity"/>
<field name="visit_types"/>
<field name="contact_type"/>
<field name="state" widget="badge" decoration-muted="state in ['draft']"
decoration-info="state in ['contact']" decoration-warning="state in ['schedule_a_visit']"
decoration-success="state in ['done', 'close']" decoration-danger="state in ['cancel']"/>

View File

@ -7,3 +7,4 @@ from . import exception_wizard
from . import exchange_order_wizard
from . import service_refuse_reason_wizard
from . import reason_for_return_wizard
from . import visit_location_otp_wizard

View File

@ -55,13 +55,13 @@ class ReasearcherFamilyWizard(models.TransientModel):
for rec in self:
rec.benefit_id.with_context(bypass_attachments_requirement=True).write({
'state': 'complete_info',
'contact_type': 'email',
'researcher_id': rec.researcher_team.id
})
visit_record = self.env['visit.location'].create({
'benefit_id': rec.benefit_id.id,
'visit_date': date.today(),
'visit_types': 1,
'contact_type': 'email',
# 'selector': rec.selector,
# 'researcher_id': rec.researcher_id.id,
'researcher_team': rec.researcher_team.id,

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class VisitLocationOtpWizard(models.TransientModel):
_name = 'visit.location.otp.wizard'
_description = 'Visit Location OTP Verification Wizard'
def _default_visit(self):
return self._context.get('active_id')
otp_code = fields.Char(string="Enter OTP", required=True)
visit_id = fields.Many2one('visit.location', string="Visit", default=_default_visit)
visit_otp_code = fields.Char(string="Visit OTP", related="visit_id.otp_code")
def action_confirm_otp(self):
"""Check OTP entered by user"""
self.ensure_one()
if self.otp_code != self.visit_id.otp_code:
raise UserError("Invalid OTP. Please try again.")
# OTP is correct → set visit as done
self.visit_id.write({'state': 'done',})

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="visit_location_otp_wizard_form" model="ir.ui.view">
<field name="name">visit.location.otp.wizard.form</field>
<field name="model">visit.location.otp.wizard</field>
<field name="arch" type="xml">
<form string="OTP Verification">
<sheet>
<group>
<field name="visit_otp_code"/>
<field name="otp_code"/>
<field name="visit_id" invisible="1"/>
</group>
<footer>
<button string="Confirm" type="object" name="action_confirm_otp" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</data>
</openerp>