From c7afd786f13f71b266a5403bf2a2bcba0ebd7058 Mon Sep 17 00:00:00 2001 From: younes Date: Mon, 15 Dec 2025 13:14:55 +0100 Subject: [PATCH 1/2] [IMP] odex_benefit: IMP benefit --- odex25_benefit/odex_benefit/models/benefit.py | 38 +++--- .../odex_benefit/models/service_request.py | 121 +++++++++++++++++- .../odex_benefit/models/services_settings.py | 24 ++++ .../static/src/css/backend_style.css | 77 +++++++++++ .../odex_benefit/views/service_request.xml | 5 + .../odex_benefit/views/services_settings.xml | 11 ++ .../odex_benefit/views/template.xml | 1 + 7 files changed, 258 insertions(+), 19 deletions(-) create mode 100644 odex25_benefit/odex_benefit/static/src/css/backend_style.css diff --git a/odex25_benefit/odex_benefit/models/benefit.py b/odex25_benefit/odex_benefit/models/benefit.py index 9d77fba9e..a9f881e52 100644 --- a/odex25_benefit/odex_benefit/models/benefit.py +++ b/odex25_benefit/odex_benefit/models/benefit.py @@ -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): diff --git a/odex25_benefit/odex_benefit/models/service_request.py b/odex25_benefit/odex_benefit/models/service_request.py index 7692988b8..efdac8c81 100644 --- a/odex25_benefit/odex_benefit/models/service_request.py +++ b/odex25_benefit/odex_benefit/models/service_request.py @@ -2,7 +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): _name = 'service.request' @@ -204,6 +204,125 @@ 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: + rec.related_information_html = f""" +
+
📋
+

{_("No service selected")}

+

{_("Please select a service category to view related information.")}

+
+ """ + continue + + family_rows = [] + member_rows = [] + + 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 Information"), family_rows) + member_table = rec._build_info_table(_("Member Information"), member_rows) if member_rows else "" + + rec.related_information_html = f""" +
+ {family_table} + {member_table} +
+ """ + + def _build_info_table(self, title, rows): + if not rows: + return f""" +
+

{title}

+
{_("No information available for this section.")}
+
+ """ + return f""" +
+

{title}

+ + + + + + + + + {''.join(rows)} + +
{_("Field Name")}{_("Value")}
+
+ """ + + 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 = '' + elif field.type == 'boolean': + display = f"✓ {_('Yes')}" if value else f"✗ {_('No')}" + elif field.type == 'selection': + sel_dict = dict(field._description_selection(record.env)) + display_val = sel_dict.get(value, value) + display = f'{html_escape(str(display_val or "—"))}' + elif field.type == 'many2one': + display = f'{html_escape(value.name or "—")}' + elif field.type == 'many2many': + if field.comodel_name == 'ir.attachment': + if not value: + display = '' + else: + links = [] + for attach in value: + filename = attach.name or _("Unnamed file") + url = f"/web/content/{attach.id}?download=true" + links.append(f'📎 {html_escape(filename)}') + display = '
'.join(links) + else: + badges = [f'{html_escape(r.name or "-")}' for r in value] + display = ' '.join(badges) or '' + elif field.type == 'one2many': + items = [f'
• {html_escape(r.display_name or r.name or "-")}
' for r in value[:20]] + more = f'
... {_("and")} {len(value)-20} {_("more records")}
' if len(value) > 20 else "" + display = ''.join(items) + more or '' + 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'📎 {html_escape(filename)}' + else: + display = '' + else: + display = html_escape(str(value)) + + return f""" + + {label} + {display} + + """ @api.depends('payment_order_ids') def _compute_payment_order(self): diff --git a/odex25_benefit/odex_benefit/models/services_settings.py b/odex25_benefit/odex_benefit/models/services_settings.py index f1e9a3e3f..e067e4b98 100644 --- a/odex25_benefit/odex_benefit/models/services_settings.py +++ b/odex25_benefit/odex_benefit/models/services_settings.py @@ -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)] diff --git a/odex25_benefit/odex_benefit/static/src/css/backend_style.css b/odex25_benefit/odex_benefit/static/src/css/backend_style.css new file mode 100644 index 000000000..1ef953f65 --- /dev/null +++ b/odex25_benefit/odex_benefit/static/src/css/backend_style.css @@ -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; +} diff --git a/odex25_benefit/odex_benefit/views/service_request.xml b/odex25_benefit/odex_benefit/views/service_request.xml index 1ce29bc02..98c9d718a 100644 --- a/odex25_benefit/odex_benefit/views/service_request.xml +++ b/odex25_benefit/odex_benefit/views/service_request.xml @@ -444,6 +444,11 @@ + + + + +
diff --git a/odex25_benefit/odex_benefit/views/services_settings.xml b/odex25_benefit/odex_benefit/views/services_settings.xml index b29d69548..ac95db2da 100644 --- a/odex25_benefit/odex_benefit/views/services_settings.xml +++ b/odex25_benefit/odex_benefit/views/services_settings.xml @@ -217,6 +217,17 @@ + + + + + + + + + + diff --git a/odex25_benefit/odex_benefit/views/template.xml b/odex25_benefit/odex_benefit/views/template.xml index 2978e9bae..5b58db1b5 100644 --- a/odex25_benefit/odex_benefit/views/template.xml +++ b/odex25_benefit/odex_benefit/views/template.xml @@ -3,6 +3,7 @@ \ No newline at end of file From ef1809aaadfa9a7bd9c69f24601eb1dab6d487ac Mon Sep 17 00:00:00 2001 From: younes Date: Mon, 15 Dec 2025 13:43:53 +0100 Subject: [PATCH 2/2] [IMP] odex_benefit: IMP benefit --- odex25_benefit/odex_benefit/i18n/ar_001.po | 67 ++++++++++++++++++- .../odex_benefit/models/service_request.py | 44 +++++++----- 2 files changed, 94 insertions(+), 17 deletions(-) diff --git a/odex25_benefit/odex_benefit/i18n/ar_001.po b/odex25_benefit/odex_benefit/i18n/ar_001.po index 01210a718..9c037620b 100644 --- a/odex25_benefit/odex_benefit/i18n/ar_001.po +++ b/odex25_benefit/odex_benefit/i18n/ar_001.po @@ -17540,4 +17540,69 @@ msgid "" "%s" msgstr "" "طلبات الخدمة التالية ليس لديها جهة الدفع محددة:\n" -"%s" \ No newline at end of file +"%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 "لا توجد معلومات متاحة." \ No newline at end of file diff --git a/odex25_benefit/odex_benefit/models/service_request.py b/odex25_benefit/odex_benefit/models/service_request.py index efdac8c81..34e06c23e 100644 --- a/odex25_benefit/odex_benefit/models/service_request.py +++ b/odex25_benefit/odex_benefit/models/service_request.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta from dateutil.relativedelta import relativedelta from odoo.tools import html_escape + class ServiceRequest(models.Model): _name = 'service.request' _inherit = ['mail.thread', 'mail.activity.mixin'] @@ -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', @@ -205,23 +206,27 @@ class ServiceRequest(models.Model): 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,) + 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"""
📋
-

{_("No service selected")}

-

{_("Please select a service category to view related information.")}

+

{no_service_title}

+

{no_service_desc}

""" 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) @@ -232,8 +237,8 @@ class ServiceRequest(models.Model): row = rec._get_field_display_value(rec.member_id, ir_field) if row: member_rows.append(row) - family_table = rec._build_info_table(_("Family Information"), family_rows) - member_table = rec._build_info_table(_("Member Information"), member_rows) if member_rows else "" + 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"""
@@ -243,11 +248,14 @@ class ServiceRequest(models.Model): """ 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"""

{title}

-
{_("No information available for this section.")}
+
{no_info_text}
""" return f""" @@ -256,8 +264,8 @@ class ServiceRequest(models.Model): - - + + @@ -304,8 +312,11 @@ class ServiceRequest(models.Model): badges = [f'{html_escape(r.name or "-")}' for r in value] display = ' '.join(badges) or '' elif field.type == 'one2many': - items = [f'
• {html_escape(r.display_name or r.name or "-")}
' for r in value[:20]] - more = f'
... {_("and")} {len(value)-20} {_("more records")}
' if len(value) > 20 else "" + items = [ + f'
• {html_escape(r.display_name or r.name or "-")}
' + for r in value[:20]] + more = f'
... {_("and")} {len(value) - 20} {_("more records")}
' if len( + value) > 20 else "" display = ''.join(items) + more or '' elif field.type == 'binary': if value: @@ -760,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( @@ -781,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:
{_("Field Name")}{_("Value")}{field_name_label}{value_label}