IMP benefit

This commit is contained in:
younes 2025-09-11 07:59:56 +01:00
parent 75727017f7
commit 5bbde75ca9
9 changed files with 329 additions and 25 deletions

View File

@ -7,7 +7,7 @@
'website': 'http://exp-sa.com',
'license': 'GPL-3',
'author': 'Expert Ltd',
'depends': ['base', 'takaful_core', 'website', 'account', 'report_xlsx', 'sale', 'product', 'stock', 'hr',
'depends': ['base','survey', 'takaful_core', 'website', 'account', 'report_xlsx', 'sale', 'product', 'stock', 'hr',
'purchase','web_google_maps','odex25_account_payment_fix','otp_sms_auth_custom'],
'data': [
'security/security_view.xml',
@ -63,6 +63,7 @@
'wizards/visit_location_otp_wizard_view.xml',
'views/benefit_vehicle_model.xml',
'wizards/visit_location_refused_wizard_view.xml',
'views/visit_survey.xml',
'views/actions_and_menus.xml',
],
'external_dependencies': {

View File

@ -265,7 +265,7 @@
</table>
</field>
</record>
<!-- Visit Location OTP Email-->
<!--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"/>
@ -280,7 +280,8 @@
<tr>
<td style="font-size:14px;line-height:20px;">
<p>Hello,</p>
<p>Your OTP for confirming visit <strong>${object.name}</strong> is:</p>
<p>Your OTP for confirming visit <strong>${object.name}</strong> is:
</p>
<!-- OTP Block -->
<div style="text-align:center;margin:20px 0;">
@ -313,8 +314,7 @@
</table>
</field>
</record>
<!-- Visit Location OTP SMS-->
<!--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"/>
@ -324,5 +324,60 @@
</field>
</record>
<!--Visit Location Survey SMS-->
<record id="visit_location_survey_sms_template" model="sms.template">
<field name="name">Visit Location Survey SMS</field>
<field name="model_id" ref="odex_benefit.model_visit_location"/>
<field name="body">
Dear family, please complete the evaluation form for visit ${object.name}: ${object.survey_url}
</field>
</record>
<!--Visit Location Survey Email-->
<record id="visit_location_survey_email_template" model="mail.template">
<field name="name">Visit Location Survey Email</field>
<field name="model_id" ref="odex_benefit.model_visit_location"/>
<field name="subject">Family Evaluation Survey - ${object.name}</field>
<field name="body_html" type="html">
<div style="background-color:#f9f9f9;padding:30px;
font-family:Arial,sans-serif;color:#333;
font-size:16px;line-height:24px;">
<p>Hello,</p>
<p>Please complete the evaluation form for your visit:
<strong>${object.name}</strong>.
</p>
<!-- CTA Button -->
<p style="text-align:center;margin:30px 0;">
<a href="${object.survey_url}"
style="display:inline-block;
padding:14px 32px;
background:linear-gradient(135deg,#e6f0ff,#cce0ff);
color:#003366;
font-size:16px;
font-weight:600;
text-decoration:none;
border-radius:6px;
border:1px solid #99c2ff;
box-shadow:0 4px 6px rgba(0,0,0,0.05);">
Open Survey
</a>
</p>
<p>Thank you for your cooperation.
<br/>
<strong>${user.company_id.name}</strong>
</p>
<hr style="border:none;border-top:1px solid #ddd;margin:30px 0;"/>
<p style="font-size:12px;color:#888;">
This is an automated message. Please do not reply.
</p>
</div>
</field>
</record>
</data>
</odoo>

View File

@ -10977,6 +10977,7 @@ msgstr "إعدادات الاستبيان"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_survey_setting__survey_url
#: model:ir.model.fields,field_description:odex_benefit.field_visit_location__survey_url
msgid "Survey URL"
msgstr "رابط استبيان الزيارة"
@ -11952,6 +11953,7 @@ msgstr "ظاهرة في الموقع الحالي"
#. module: odex_benefit
#: code:addons/odex_benefit/wizards/visit_location_refused_wizard.py:0
#: model:ir.model.fields,field_description:odex_benefit.field_survey_user_input__visit_id
#: model:ir.model.fields,field_description:odex_benefit.field_visit_location_otp_wizard__visit_id
#: model:ir.model.fields,field_description:odex_benefit.field_visit_location_refusal_reason_wizard__visit_location_id
#: model:ir.model.fields,field_description:odex_benefit.field_visit_skip_otp_wizard__visit_id
@ -15411,4 +15413,144 @@ msgid ""
"The visit record <b>%s</b> state has been changed to <b>%s</b>.<br/>Refusal "
"Reason: <b>%s</b><br/>Notes: <b>%s</b>"
msgstr ""
"تم تغيير حالة سجل الزيارة <b>%s</b> إلى <b>%s</b>.<br/>سبب الرفض: <b>%s</b><br/>ملاحظات: <b>%s</b>"
"تم تغيير حالة سجل الزيارة <b>%s</b> إلى <b>%s</b>.<br/>سبب الرفض: <b>%s</b><br/>ملاحظات: <b>%s</b>"
#. module: odex_benefit
#: model_terms:ir.ui.view,arch_db:odex_benefit.visits_form
msgid "Resend Survey Link"
msgstr "إعادة إرسال رابط الاستطلاع"
#. module: odex_benefit
#: model_terms:ir.ui.view,arch_db:odex_benefit.visits_form
msgid "Surveys"
msgstr "استطلاعات"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_visit_location__response_id
msgid "Survey Responses"
msgstr "إجابات الاستطلاع"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_visit_location__response_count
msgid "Responses Count"
msgstr "عدد الإجابات"
#. module: odex_benefit
#: code:addons/odex_benefit/models/visit.py:0
#, python-format
msgid "Family Evaluation Responses"
msgstr "جابات استطلاع الأسرة"
#. module: odex_benefit
#: code:addons/odex_benefit/models/visit.py:0
#, python-format
msgid "You must send the evaluation form before closing the visit."
msgstr "يجب إرسال نموذج الاستطلاع قبل إغلاق الزيارة."
#. module: odex_benefit
#: code:addons/odex_benefit/models/visit.py:0
#, python-format
msgid ""
"The visit cannot be closed before the family completes the evaluation form."
msgstr "لا يمكن إغلاق الزيارة قبل أن تُكمل الأسرة نموذج الاستطلاع."
#. module: odex_benefit
#: code:addons/odex_benefit/models/visit.py:0
#, python-format
msgid "The family profile has no email address. Please add an email first."
msgstr "ملف الأسرة لا يحتوي على عنوان بريد إلكتروني. يرجى إضافة البريد أولاً."
#. module: odex_benefit
#: code:addons/odex_benefit/models/visit.py:0
#, python-format
msgid "The email template 'Visit Location Survey Email' is missing."
msgstr "قالب البريد الإلكتروني 'زيارة - استطلاع' غير موجود."
#. module: odex_benefit
#: code:addons/odex_benefit/models/visit.py:0
#, python-format
msgid ""
"The family profile has no mobile number. Please add a valid phone number."
msgstr "ملف الأسرة لا يحتوي على رقم جوال. يرجى إضافة رقم صالح."
#. module: odex_benefit
#: code:addons/odex_benefit/models/visit.py:0
#, python-format
msgid "The SMS template 'Visit Location Survey' is missing."
msgstr "قالب الرسائل القصيرة 'زيارة - استطلاع' غير موجود."
#. module: odex_benefit
#: model:sms.template,name:odex_benefit.visit_location_survey_sms_template
msgid "Visit Location Survey SMS"
msgstr "رسالة استطلاع الزيارة"
#. module: odex_benefit
#: model:sms.template,body:odex_benefit.visit_location_survey_sms_template
msgid ""
"\n"
" Dear family, please complete the evaluation form for visit ${object.name}: ${object.survey_url}\n"
" "
msgstr ""
"\n"
" عائلة كريمة، يرجى تعبئة نموذج الاستطلاع للزيارة ${object.name}: ${object.survey_url}\n"
" "
#. module: odex_benefit
#: model:mail.template,body_html:odex_benefit.visit_location_survey_email_template
msgid ""
"<div style=\"background-color:#f9f9f9;padding:30px; font-family:Arial,sans-serif;color:#333; font-size:16px;line-height:24px;\">\n"
"\n"
" <p>Hello,</p>\n"
"\n"
" <p>Please complete the evaluation form for your visit:\n"
" <strong>${object.name}</strong>.\n"
" </p>\n"
"\n"
" <!-- CTA Button -->\n"
" <p style=\"text-align:center;margin:30px 0;\">\n"
" <a href=\"${object.survey_url}\" style=\"display:inline-block; padding:14px 32px; background:linear-gradient(135deg,#e6f0ff,#cce0ff); color:#003366; font-size:16px; font-weight:600; text-decoration:none; border-radius:6px; border:1px solid #99c2ff; box-shadow:0 4px 6px rgba(0,0,0,0.05);\">\n"
" Open Survey\n"
" </a>\n"
" </p>\n"
"\n"
" <p>Thank you for your cooperation.\n"
" <br/>\n"
" <strong>${user.company_id.name}</strong>\n"
" </p>\n"
"\n"
" <hr style=\"border:none;border-top:1px solid #ddd;margin:30px 0;\"/>\n"
"\n"
" <p style=\"font-size:12px;color:#888;\">\n"
" This is an automated message. Please do not reply.\n"
" </p>\n"
" </div>\n"
" "
msgstr ""
"<div style=\"background-color:#f9f9f9;padding:30px; font-family:Arial,sans-serif;color:#333; font-size:16px;line-height:24px;\">\n"
"\n"
" <p>مرحباً،</p>\n"
"\n"
" <p>يرجى تعبئة نموذج الاستطلاع الخاص بزيارتكم:\n"
" <strong>${object.name}</strong>.\n"
" </p>\n"
"\n"
" <!-- زر الدعوة إلى الإجراء -->\n"
" <p style=\"text-align:center;margin:30px 0;\">\n"
" <a href=\"${object.survey_url}\" style=\"display:inline-block; padding:14px 32px; background:linear-gradient(135deg,#e6f0ff,#cce0ff); color:#003366; font-size:16px; font-weight:600; text-decoration:none; border-radius:6px; border:1px solid #99c2ff; box-shadow:0 4px 6px rgba(0,0,0,0.05);\">\n"
" فتح الاستطلاع\n"
" </a>\n"
" </p>\n"
"\n"
" <p>نشكر لكم تعاونكم.\n"
" <br/>\n"
" <strong>${user.company_id.name}</strong>\n"
" </p>\n"
"\n"
" <hr style=\"border:none;border-top:1px solid #ddd;margin:30px 0;\"/>\n"
"\n"
" <p style=\"font-size:12px;color:#888;\">\n"
" هذه رسالة آلية، يرجى عدم الرد عليها.\n"
" </p>\n"
"</div>\n"
" "

View File

@ -37,4 +37,5 @@ from . import res_users
from . import res_partner
from . import job_settings
from . import death_reason_settings
from . import benefit_vehicle_model
from . import benefit_vehicle_model
from . import visit_survey

View File

@ -1,7 +1,10 @@
from Tools.scripts.pdeps import store
from odoo import fields, models,api,_
import math, random
from odoo.exceptions import UserError, ValidationError
from datetime import timedelta
import werkzeug
class Visit(models.Model):
_name = 'visit.location'
@ -52,6 +55,25 @@ class Visit(models.Model):
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)
response_id = fields.Many2one('survey.user_input', string="Survey Responses", ondelete='restrict', copy=False)
response_count = fields.Integer(compute="_compute_response_count",store=True, string="Responses Count", copy=False)
survey_url = fields.Char(string="Survey URL")
@api.depends('response_id')
def _compute_response_count(self):
for rec in self:
rec.response_count = self.env['survey.user_input'].search_count([('visit_id', '=', rec.id)])
def action_view_responses(self):
self.ensure_one()
return {
'name': _("Family Evaluation Responses"),
'type': 'ir.actions.act_window',
'res_model': 'survey.user_input',
'view_mode': 'tree,form',
'domain': [('visit_id', '=', self.id)],
'context': {'create': False},
}
def _expand_states(self, states, domain, order):
return [key for key, val in type(self).state.selection]
@ -178,6 +200,7 @@ class Visit(models.Model):
else:
self.state = 'done'
self.benefit_id.last_visit_date = self.visit_date
self.action_send_survey()
def action_skip_otp(self):
self.ensure_one()
@ -194,24 +217,68 @@ class Visit(models.Model):
'context': context,
}
def action_send_survey(self):
self.ensure_one()
if self.visit_types.survey_id:
survey = self.visit_types.survey_id
if not self.response_id:
response = survey._create_answer(
user=self.benefit_id.user_id,
partner=self.benefit_id.partner_id,
email=self.benefit_id.email,
test_entry=False,
check_attempts=True,
visit_id=self.id
)
self.response_id = response.id
else:
response = self.response_id
self.survey_url = '%s%s?%s' % (
survey.get_base_url(),
survey.get_start_url(),
# werkzeug.urls.url_encode({'answer_token': response.access_token})
werkzeug.urls.url_encode({'answer_token': response and response.access_token or None})
)
if self.benefit_id.contact_type == 'email':
if not self.benefit_id.email:
raise UserError(_("The family profile has no email address. Please add an email first."))
template = self.env.ref('odex_benefit.visit_location_survey_email_template', False)
if not template:
raise UserError(_("The email template 'Visit Location Survey Email' is missing."))
email_values = {"email_from": self.env.user.company_id.hr_email or self.env.user.company_id.email}
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,
tracking_disable=True,
survey_url=self.survey_url
).send_mail(self.id, force_send=True, raise_exception=False, email_values=email_values)
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."))
sms_template = self.env.ref('odex_benefit.visit_location_survey_sms_template', False)
if not sms_template:
raise UserError(_("The SMS template 'Visit Location Survey' is missing."))
self.with_context(tracking_disable=True,survey_url=self.survey_url)._message_sms_with_template(
template=sms_template,
put_in_queue=False,
partner_ids=self.benefit_id.partner_id.ids,
author_id=self.env.ref('base.partner_root').id
)
def action_close(self):
survey_url = ''
survey_conf = self.env['survey.setting'].search([],limit=1)
if survey_conf:
survey_url = survey_conf.survey_url
survey_url = ' <a href=#survey link>%s</a>' % (survey_url)
self.ensure_one()
if self.visit_types.survey_id:
if not self.response_id:
raise UserError(_("You must send the evaluation form before closing the visit."))
if self.response_id.state != 'done':
raise UserError(_("The visit cannot be closed before the family completes the evaluation form."))
self.state = 'close'
if self.benefit_id.contact_type == 'email':
body = survey_url
mail = self.env['mail.mail'].create({
'body_html':survey_url ,
'subject': "Visit Close",
'email_to': self.benefit_id.email,
# 'email_cc': self.researcher_id.work_email,
})
mail.send()
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):
for rec in self:

View File

@ -0,0 +1,6 @@
from odoo import api, fields, models, exceptions, _
class SurveyUserInput(models.Model):
_inherit = 'survey.user_input'
visit_id = fields.Many2one('visit.location', string="Visit")

View File

@ -93,12 +93,24 @@
<button name="action_cancel" string="Visit Cancel" type="object"
states="contact,schedule_a_visit"/>
<button name="action_close" string="Close" type="object" states="done"/>
<button name="action_send_survey"
attrs="{'invisible':[('state', '!=', 'done')]}"
type="object" string="Resend Survey Link" class="oe_highlight"/>
<button name="geo_localize" string="Map" type="object" class="oe_highlight"
attrs="{'invisible':[('benefit_id', '=', False)]}"/>
<field name="state" widget="statusbar"
statusbar_visible="draft,contact,schedule_a_visit,cancel,done,close"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button type="object"
class="oe_stat_button"
icon="fa-pencil-square-o"
name="action_view_responses"
attrs="{'invisible': [('response_count', '=', 0)]}">
<field string="Surveys" name="response_count" widget="statinfo"/>
</button>
</div>
<div class="oe_title">
<h1>
<field placeholder="Name" name="name" class="oe_inline"/>
@ -108,7 +120,8 @@
<group>
<field name="visit_types" domain="[('creation_method','=','manual')]"
attrs="{'readonly':[('state', 'in', ['close','cancel'])]}" required="1"/>
<field name="benefit_id" attrs="{'readonly':[('state', 'in', ['close','cancel'])]}" required="1"/>
<field name="benefit_id" attrs="{'readonly':[('state', 'in', ['close','cancel'])]}"
required="1"/>
<field name="sms_phone"/>
<field name="researcher_team" required="1"/>
<field name="researcher_ids" invisible="1" widget="many2many_tags"
@ -119,11 +132,14 @@
attrs="{'invisible':[('state', 'not in', ['cancel'])],'readonly':[('state', 'in', ['close','cancel'])]}"/>
</group>
<group>
<field name="visit_date" attrs="{'readonly':[('state', 'in', ['close','cancel'])]}" required="1"/>
<field name="visit_date" attrs="{'readonly':[('state', 'in', ['close','cancel'])]}"
required="1"/>
<field name="evaluation" widget="priority"/>
<field name="visit_objective" invisible="1"
attrs="{'readonly':[('state', 'in', ['close','cancel'])]}"/>
<field name="otp_code" invisible="1"/>
<field name="response_id" invisible="1"/>
<field name="survey_url" invisible="1"/>
</group>
</group>
</sheet>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="survey_user_input_visit_form_inherit" model="ir.ui.view">
<field name="name">survey.user_input.view.form.inherit</field>
<field name="model">survey.user_input</field>
<field name="inherit_id" ref="survey.survey_user_input_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='survey_id']" position="after">
<field name="visit_id"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@ -22,6 +22,7 @@ class VisitLocationOtpWizard(models.TransientModel):
self.visit_id.write({'state': 'done',})
self.visit_id.benefit_id.last_visit_date = self.visit_id.visit_date
self.visit_id.action_send_survey()
class VisitSkipOtpWizard(models.TransientModel):
_name = 'visit.skip.otp.wizard'