Merge pull request #5736 from expsa/yoy

Yoy
This commit is contained in:
kchyounes19 2025-12-15 13:44:53 +01:00 committed by GitHub
commit fdf798e9c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 341 additions and 25 deletions

View File

@ -17540,4 +17540,69 @@ msgid ""
"%s" "%s"
msgstr "" msgstr ""
"طلبات الخدمة التالية ليس لديها جهة الدفع محددة:\n" "طلبات الخدمة التالية ليس لديها جهة الدفع محددة:\n"
"%s" "%s"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_service_request__related_information_html
#: model_terms:ir.ui.view,arch_db:odex_benefit.service_request_form
#: model_terms:ir.ui.view,arch_db:odex_benefit.services_settings_form
msgid "Related Information"
msgstr "المعلومات المرتبطة"
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "Family Information"
msgstr "معلومات الأسرة"
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "Member Information"
msgstr "معلومات الأفراد"
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "Unnamed file"
msgstr "ملف بدون اسم"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_services_settings__family_related_fields
msgid "Related Family Fields"
msgstr "الحقول المرتبطة بالأسرة"
#. module: odex_benefit
#: model:ir.model.fields,field_description:odex_benefit.field_services_settings__member_related_fields
msgid "Related Member Fields"
msgstr "الحقول المرتبطة بالفرد"
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "No service selected"
msgstr "لم يتم اختيار خدمة"
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "Please select a service category to view related information."
msgstr "رجاءً اختر خدمة لعرض المعلومات المرتبطة."
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "Field Name"
msgstr "اسم الحقل"
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "Value"
msgstr "القيمة"
#. module: odex_benefit
#: code:addons/odex_benefit/models/service_request.py:0
#, python-format
msgid "No information available for this section."
msgstr "لا توجد معلومات متاحة."

View File

@ -628,9 +628,9 @@ class GrantBenefitProfile(models.Model):
def _compute_total_families(self): def _compute_total_families(self):
for record in self: for record in self:
record.total_father_families = self.search_count([('father_id_number','=',self.father_id_number),('id','!=',self.id)]) record.total_father_families = self.search_count([('father_id_number','=',record.father_id_number),('id','!=',record.id)])
record.total_mother_families = self.search_count([('mother_id_number','=',self.mother_id_number),('id','!=',self.id)]) record.total_mother_families = self.search_count([('mother_id_number','=',record.mother_id_number),('id','!=',record.id)])
record.total_replacement_mother_families = self.search_count([('replacement_mother_id_number','=',self.replacement_mother_id_number),('id','!=',self.id)]) record.total_replacement_mother_families = self.search_count([('replacement_mother_id_number','=',record.replacement_mother_id_number),('id','!=',record.id)])
def action_open_related_father_families(self): def action_open_related_father_families(self):
self.ensure_one() self.ensure_one()
@ -827,6 +827,7 @@ class GrantBenefitProfile(models.Model):
record.required_attach = 'true' record.required_attach = 'true'
def get_attached_domain(self): def get_attached_domain(self):
self.ensure_one()
visit_location = self.env['visit.location'].search([('benefit_id', '=', self.id)]).ids visit_location = self.env['visit.location'].search([('benefit_id', '=', self.id)]).ids
service_requests = self.env['service.request'].search([('family_id', '=', self.id)]).ids service_requests = self.env['service.request'].search([('family_id', '=', self.id)]).ids
family_complaints = self.env['family.complaints'].search([('family_id', '=', self.id)]).ids family_complaints = self.env['family.complaints'].search([('family_id', '=', self.id)]).ids
@ -865,7 +866,7 @@ class GrantBenefitProfile(models.Model):
def _compute_attached_docs_count(self): def _compute_attached_docs_count(self):
Attachment = self.env['ir.attachment'] Attachment = self.env['ir.attachment']
for benefit in self: for benefit in self:
benefit.doc_count = Attachment.search_count(self.get_attached_domain()) benefit.doc_count = Attachment.search_count(benefit.get_attached_domain())
def view_attachments(self): def view_attachments(self):
action = self.env['ir.actions.act_window']._for_xml_id('base.action_attachment') action = self.env['ir.actions.act_window']._for_xml_id('base.action_attachment')
@ -1881,20 +1882,21 @@ class GrantBenefitProfile(models.Model):
@api.depends('name') @api.depends('name')
def _compute_qr_code(self): def _compute_qr_code(self):
qr = qrcode.QRCode( for rec in self:
version=1, qr = qrcode.QRCode(
error_correction=qrcode.constants.ERROR_CORRECT_L, version=1,
box_size=15, error_correction=qrcode.constants.ERROR_CORRECT_L,
border=4, box_size=15,
) border=4,
name = self.name )
qr.add_data(name) name = rec.name
qr.make(fit=True) qr.add_data(name)
img = qr.make_image() qr.make(fit=True)
temp = BytesIO() img = qr.make_image()
img.save(temp, format="PNG") temp = BytesIO()
qr_image = base64.b64encode(temp.getvalue()) img.save(temp, format="PNG")
self.qr_code = qr_image qr_image = base64.b64encode(temp.getvalue())
rec.qr_code = qr_image
@api.onchange('bank_id') @api.onchange('bank_id')
def _compute_prefix_iban(self): def _compute_prefix_iban(self):

View File

@ -2,6 +2,7 @@ from odoo import fields, models, api, _
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from odoo.tools import html_escape
class ServiceRequest(models.Model): class ServiceRequest(models.Model):
@ -185,8 +186,8 @@ class ServiceRequest(models.Model):
car_owner_id = fields.Many2one('family.member', domain="[('benefit_id','=',family_id)]", string="Car Owner") car_owner_id = fields.Many2one('family.member', domain="[('benefit_id','=',family_id)]", string="Car Owner")
car_model_id = fields.Many2one('benefit.vehicle.model', string='Car model') car_model_id = fields.Many2one('benefit.vehicle.model', string='Car model')
car_price = fields.Float(string='Car Price') car_price = fields.Float(string='Car Price')
car_remaining_amount = fields.Float(string='Remaining Amount',compute='_compute_remaining_amount',store=True,) car_remaining_amount = fields.Float(string='Remaining Amount', compute='_compute_remaining_amount', store=True, )
show_car_remaining_amount = fields.Boolean(string='Show Car Remaining Amount',compute='_compute_remaining_amount') show_car_remaining_amount = fields.Boolean(string='Show Car Remaining Amount', compute='_compute_remaining_amount')
application_form = fields.Many2many('ir.attachment', 'request_application_form_rel', 'request_id', 'attachment_id', application_form = fields.Many2many('ir.attachment', 'request_application_form_rel', 'request_id', 'attachment_id',
string="Application Form") string="Application Form")
driving_license = fields.Many2many('ir.attachment', 'request_driving_license_rel', 'request_id', 'attachment_id', driving_license = fields.Many2many('ir.attachment', 'request_driving_license_rel', 'request_id', 'attachment_id',
@ -204,6 +205,135 @@ class ServiceRequest(models.Model):
total_moves = fields.Integer(string="Total Move", compute='_get_total_move_lines') total_moves = fields.Integer(string="Total Move", compute='_get_total_move_lines')
return_reason_id = fields.Many2one("return.reason", string="Return Reason") return_reason_id = fields.Many2one("return.reason", string="Return Reason")
agree_terms = fields.Boolean(string="I agree to the Terms and Conditions", default=False, ) agree_terms = fields.Boolean(string="I agree to the Terms and Conditions", default=False, )
related_information_html = fields.Html(string="Related Information",
compute='_compute_related_information_html', store=True, )
@api.depends('service_cat', 'family_id', 'member_id', 'benefit_type')
def _compute_related_information_html(self):
for rec in self:
if not rec.service_cat:
no_service_title = _("No service selected")
no_service_desc = _("Please select a service category to view related information.")
rec.related_information_html = f"""
<div class="o_form_sheet_not_found">
<div class="o_form_sheet_not_found_icon">📋</div>
<h4>{no_service_title}</h4>
<p>{no_service_desc}</p>
</div>
"""
continue
family_rows = []
member_rows = []
family_title = _("Family Information")
member_title = _("Member Information")
for ir_field in rec.service_cat.family_related_fields:
row = rec._get_field_display_value(rec.family_id, ir_field)
if row:
family_rows.append(row)
if rec.benefit_type == 'member' and rec.member_id:
for ir_field in rec.service_cat.member_related_fields:
row = rec._get_field_display_value(rec.member_id, ir_field)
if row:
member_rows.append(row)
family_table = rec._build_info_table(family_title, family_rows)
member_table = rec._build_info_table(member_title, member_rows) if member_rows else ""
rec.related_information_html = f"""
<div class="info-container">
{family_table}
{member_table}
</div>
"""
def _build_info_table(self, title, rows):
field_name_label = _("Field Name")
value_label = _("Value")
no_info_text = _("No information available for this section.")
if not rows:
return f"""
<div class="info-section">
<h3>{title}</h3>
<div class="info-empty">{no_info_text}</div>
</div>
"""
return f"""
<div class="info-section">
<h3>{title}</h3>
<table class="table info-table">
<thead>
<tr>
<th>{field_name_label}</th>
<th>{value_label}</th>
</tr>
</thead>
<tbody>
{''.join(rows)}
</tbody>
</table>
</div>
"""
def _get_field_display_value(self, record, ir_field):
if not record or not ir_field:
return ""
field_name = ir_field.name
field = record._fields.get(field_name)
if not field:
return ""
value = record[field_name]
label = html_escape(ir_field.field_description or field_name)
if value in (False, None, '') or (hasattr(value, '__len__') and len(value) == 0):
display = '<span class="info-empty">—</span>'
elif field.type == 'boolean':
display = f"<span class='badge badge-success'>✓ {_('Yes')}</span>" if value else f"<span class='badge badge-danger'>✗ {_('No')}</span>"
elif field.type == 'selection':
sel_dict = dict(field._description_selection(record.env))
display_val = sel_dict.get(value, value)
display = f'<span class="badge">{html_escape(str(display_val or ""))}</span>'
elif field.type == 'many2one':
display = f'<strong>{html_escape(value.name or "")}</strong>'
elif field.type == 'many2many':
if field.comodel_name == 'ir.attachment':
if not value:
display = '<span class="info-empty">—</span>'
else:
links = []
for attach in value:
filename = attach.name or _("Unnamed file")
url = f"/web/content/{attach.id}?download=true"
links.append(f'<a class="file-link" href="{url}" target="_blank">📎 {html_escape(filename)}</a>')
display = '<br/>'.join(links)
else:
badges = [f'<span class="badge badge-secondary">{html_escape(r.name or "-")}</span>' for r in value]
display = ' '.join(badges) or '<span class="info-empty">—</span>'
elif field.type == 'one2many':
items = [
f'<div style="margin:8px 0; padding:6px 0; border-bottom:1px dotted #ddd;">• {html_escape(r.display_name or r.name or "-")}</div>'
for r in value[:20]]
more = f'<div style="color:#875A92; font-weight:600; margin-top:10px;">... {_("and")} {len(value) - 20} {_("more records")}</div>' if len(
value) > 20 else ""
display = ''.join(items) + more or '<span class="info-empty">—</span>'
elif field.type == 'binary':
if value:
filename = getattr(record, f"{field_name}_fname", None) or _("Download file")
url = f"/web/content/{record._name}/{record.id}/{field_name}?download=true"
display = f'<a class="file-link" href="{url}" target="_blank">📎 {html_escape(filename)}</a>'
else:
display = '<span class="info-empty">—</span>'
else:
display = html_escape(str(value))
return f"""
<tr>
<td class="info-label">{label}</td>
<td class="info-value">{display}</td>
</tr>
"""
@api.depends('payment_order_ids') @api.depends('payment_order_ids')
def _compute_payment_order(self): def _compute_payment_order(self):
@ -641,9 +771,9 @@ class ServiceRequest(models.Model):
if rec.service_cat.service_type == 'recruiting_driver': if rec.service_cat.service_type == 'recruiting_driver':
if not rec.family_id.has_car: if not rec.family_id.has_car:
raise ValidationError(_("You cannot request this service because you do not have a car")) raise ValidationError(_("You cannot request this service because you do not have a car"))
#son_members_above_age = rec.family_id.member_ids.filtered( # son_members_above_age = rec.family_id.member_ids.filtered(
# lambda x: x.relationn.relation_type == 'son' and x.age > 18) # lambda x: x.relationn.relation_type == 'son' and x.age > 18)
#daughter_members_above_age = rec.family_id.member_ids.filtered( # daughter_members_above_age = rec.family_id.member_ids.filtered(
# lambda x: x.relationn.relation_type == 'daughter' and x.age > 18) # lambda x: x.relationn.relation_type == 'daughter' and x.age > 18)
children_above_18_living_with_family = rec.family_id.member_ids.filtered( children_above_18_living_with_family = rec.family_id.member_ids.filtered(
@ -662,10 +792,11 @@ class ServiceRequest(models.Model):
work_mother = rec.family_id.member_ids.filtered( work_mother = rec.family_id.member_ids.filtered(
lambda x: x.relationn.relation_type == 'mother' and x.is_work) lambda x: x.relationn.relation_type == 'mother' and x.is_work)
disable_replacement_mother = rec.family_id.member_ids.filtered( disable_replacement_mother = rec.family_id.member_ids.filtered(
lambda x: x.relationn.relation_type == 'replacement_mother' and bool(x.disabilities_attachment_ids)) lambda x: x.relationn.relation_type == 'replacement_mother' and bool(
x.disabilities_attachment_ids))
work_replacement_mother = rec.family_id.member_ids.filtered( work_replacement_mother = rec.family_id.member_ids.filtered(
lambda x: x.relationn.relation_type == 'replacement_mother' and x.is_work) lambda x: x.relationn.relation_type == 'replacement_mother' and x.is_work)
#if son_members_above_age or daughter_members_above_age: # if son_members_above_age or daughter_members_above_age:
# raise ValidationError( # raise ValidationError(
# _("You cannot request this service because children above 18 years")) # _("You cannot request this service because children above 18 years"))
if rec.family_id.add_replacement_mother and not disable_replacement_mother and not work_replacement_mother: if rec.family_id.add_replacement_mother and not disable_replacement_mother and not work_replacement_mother:

View File

@ -113,6 +113,30 @@ class ServicesSettings(models.Model):
('payment_order', 'Payment Order'), ('payment_order', 'Payment Order'),
('invoice', 'Invoice'), ('invoice', 'Invoice'),
], string='Payment Method',default="payment_order") ], string='Payment Method',default="payment_order")
family_related_fields = fields.Many2many(
comodel_name='ir.model.fields',
relation='services_settings_family_field_rel',
column1='services_settings_id',
column2='field_id',
string='Related Family Fields',
domain="[('model_id.model', '=', 'grant.benefit')]",
help="Select fields from the Family profile (grant.benefit) to display in the service request."
)
member_related_fields = fields.Many2many(
comodel_name='ir.model.fields',
relation='services_settings_member_field_rel',
column1='services_settings_id',
column2='field_id',
string='Related Member Fields',
domain="[('model_id.model', '=', 'family.member')]",
help="Select fields from the Member profile to display only when the service is for a member."
)
@api.onchange('benefit_type')
def _onchange_benefit_type(self):
if self.benefit_type != 'member':
self.member_related_fields = [(5, 0, 0)]

View File

@ -0,0 +1,77 @@
.info-container {
font-family: inherit;
}
.info-container .info-section {
margin-bottom: 32px;
}
.info-container .info-section h3 {
color: #000000;
font-size: 15px;
font-weight: 600;
margin: 0 0 16px 0;
padding-bottom: 8px;
display: inline-block;
}
.info-container .info-table {
width: 70%;
overflow: hidden;
}
.info-container .info-table th {
background-color: #EEE;
font-weight: 600;
font-size: 15px;
}
.info-container .info-table td {
border-bottom: 1px solid #e9ecef;
}
.info-container .info-table tr:last-child td {
border-bottom: none;
}
.info-container .info-table tr:hover td {
background-color: #f8f5f9;
}
.info-container .info-value {
color: #212529;
line-height: 1.6;
}
.info-container .badge {
padding: 5px 12px;
border-radius: 20px;
font-size: 13px;
font-weight: 500;
display: inline-block;
margin: 3px 6px 3px 0;
}
.info-container .badge-success { background:#d4edda; color:#155724; }
.info-container .badge-danger { background:#f8d7da; color:#721c24; }
.info-container .badge-info { background:#d1ecf1; color:#0c5460; }
.info-container .badge-secondary{ background:#e2e3e5; color:#383d41; }
.info-container .file-link {
color: #875A92;
text-decoration: none;
font-weight: 500;
}
.info-container .file-link:hover {
text-decoration: underline;
}
.info-container .info-empty {
color: #adb5bd;
font-style: italic;
text-align: center;
background: #f8f9fa;
}
.o_form_sheet_not_found {
text-align:center;
padding:50px;
background:#f8f9fa;
border:2px dashed #dee2e6;
border-radius:12px;
color:#6c757d;
}
.o_form_sheet_not_found .o_form_sheet_not_found_icon {
font-size:48px;
opacity:0.3;
margin-bottom:16px;
}

View File

@ -444,6 +444,11 @@
</group> </group>
</group> </group>
</page> </page>
<page string="Related Information">
<group>
<field name="related_information_html" nolabel="1" readonly="1"/>
</group>
</page>
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">

View File

@ -217,6 +217,17 @@
</group> </group>
</group> </group>
</page> </page>
<page string="Related Information">
<group>
<group>
<field name="family_related_fields" widget="many2many_tags"/>
</group>
<group>
<field name="member_related_fields" widget="many2many_tags"
attrs="{'invisible': [('benefit_type', '!=', 'member')]}"/>
</group>
</group>
</page>
</notebook> </notebook>
</sheet> </sheet>
</form> </form>

View File

@ -3,6 +3,7 @@
<template id="custom_assets_backend" inherit_id="web.assets_backend"> <template id="custom_assets_backend" inherit_id="web.assets_backend">
<xpath expr="." position="inside"> <xpath expr="." position="inside">
<link rel="stylesheet" type="text/scss" href="/odex_benefit/static/src/scss/custom_style.scss"/> <link rel="stylesheet" type="text/scss" href="/odex_benefit/static/src/scss/custom_style.scss"/>
<link rel="stylesheet" type="text/scss" href="/odex_benefit/static/src/css/backend_style.css"/>
</xpath> </xpath>
</template> </template>
</odoo> </odoo>