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"
msgstr ""
"طلبات الخدمة التالية ليس لديها جهة الدفع محددة:\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):
for record in self:
record.total_father_families = self.search_count([('father_id_number','=',self.father_id_number),('id','!=',self.id)])
record.total_mother_families = self.search_count([('mother_id_number','=',self.mother_id_number),('id','!=',self.id)])
record.total_replacement_mother_families = self.search_count([('replacement_mother_id_number','=',self.replacement_mother_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','=',record.mother_id_number),('id','!=',record.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):
self.ensure_one()
@ -827,6 +827,7 @@ class GrantBenefitProfile(models.Model):
record.required_attach = 'true'
def get_attached_domain(self):
self.ensure_one()
visit_location = self.env['visit.location'].search([('benefit_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
@ -865,7 +866,7 @@ class GrantBenefitProfile(models.Model):
def _compute_attached_docs_count(self):
Attachment = self.env['ir.attachment']
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):
action = self.env['ir.actions.act_window']._for_xml_id('base.action_attachment')
@ -1881,20 +1882,21 @@ class GrantBenefitProfile(models.Model):
@api.depends('name')
def _compute_qr_code(self):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=15,
border=4,
)
name = self.name
qr.add_data(name)
qr.make(fit=True)
img = qr.make_image()
temp = BytesIO()
img.save(temp, format="PNG")
qr_image = base64.b64encode(temp.getvalue())
self.qr_code = qr_image
for rec in self:
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=15,
border=4,
)
name = rec.name
qr.add_data(name)
qr.make(fit=True)
img = qr.make_image()
temp = BytesIO()
img.save(temp, format="PNG")
qr_image = base64.b64encode(temp.getvalue())
rec.qr_code = qr_image
@api.onchange('bank_id')
def _compute_prefix_iban(self):

View File

@ -2,6 +2,7 @@ from odoo import fields, models, api, _
from odoo.exceptions import UserError, ValidationError
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from odoo.tools import html_escape
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_model_id = fields.Many2one('benefit.vehicle.model', string='Car model')
car_price = fields.Float(string='Car Price')
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')
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')
application_form = fields.Many2many('ir.attachment', 'request_application_form_rel', 'request_id', 'attachment_id',
string="Application Form")
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')
return_reason_id = fields.Many2one("return.reason", string="Return Reason")
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')
def _compute_payment_order(self):
@ -641,9 +771,9 @@ class ServiceRequest(models.Model):
if rec.service_cat.service_type == 'recruiting_driver':
if not rec.family_id.has_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)
#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)
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(
lambda x: x.relationn.relation_type == 'mother' and x.is_work)
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(
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(
# _("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:

View File

@ -113,6 +113,30 @@ class ServicesSettings(models.Model):
('payment_order', 'Payment Order'),
('invoice', 'Invoice'),
], 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>
</page>
<page string="Related Information">
<group>
<field name="related_information_html" nolabel="1" readonly="1"/>
</group>
</page>
</notebook>
</sheet>
<div class="oe_chatter">

View File

@ -217,6 +217,17 @@
</group>
</group>
</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>
</sheet>
</form>

View File

@ -3,6 +3,7 @@
<template id="custom_assets_backend" inherit_id="web.assets_backend">
<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/css/backend_style.css"/>
</xpath>
</template>
</odoo>