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