Merge branch 'dev_odex25_takaful' of https://github.com/expsa/odex25-standard-modules into fix_bugs

# Conflicts:
#	odex25_takaful/odex_takaful/i18n/ar_001.po
This commit is contained in:
Nossibaelhadi 2026-01-05 11:43:32 +03:00
commit 0fd479879b
7 changed files with 183 additions and 79 deletions

View File

@ -1,69 +1,45 @@
import json import json
import logging
import werkzeug import werkzeug
import werkzeug.exceptions
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo import api, SUPERUSER_ID
_logger = logging.getLogger(__name__)
class YourController(http.Controller): class ReportController(http.Controller):
@http.route([ @http.route([
'/sponsorship/<converter>/<reportname>', '/sponsorship/<converter>/<reportname>',
'/sponsorship/<converter>/<reportname>/<docids>', '/sponsorship/<converter>/<reportname>/<docids>',
], type='http', auth='public', website=True, csrf=False) ], type='http', auth='public', website=True)
def report_routes(self, reportname, docids=None, converter=None, **data): def report_routes(self, reportname, docids=None, converter=None, **data):
try: env = api.Environment(request.cr, SUPERUSER_ID, {})
# 1. Setup Report and Context report = request.env['ir.actions.report']._get_report_from_name(reportname)
report = request.env['ir.actions.report'].sudo()._get_report_from_name(reportname) context = dict(request.env.context)
if not report:
return request.not_found()
context = dict(request.env.context) if docids:
docids = [int(i) for i in docids.split(',')]
# FORCE RENDERING: This is crucial to bypass the "Attachment" check if data.get('options'):
# which is a common cause of 403 errors even with sudo() data.update(json.loads(data.pop('options')))
context.update({'force_report_rendering': True}) if data.get('context'):
# Ignore 'lang' here, because the context in data is the one from the webclient *but* if
if docids: # the user explicitely wants to change the lang, this mechanism overwrites it.
docids = [int(i) for i in docids.split(',')] data['context'] = json.loads(data['context'])
if data['context'].get('lang') and not data.get('force_context_lang'):
if data.get('options'): del data['context']['lang']
data.update(json.loads(data.pop('options'))) context.update(data['context'])
if converter == 'html':
if data.get('context'): html = report.with_env(env).with_context(context)._render_qweb_html(docids, data=data)[0]
data['context'] = json.loads(data['context']) return request.make_response(html)
if data['context'].get('lang') and not data.get('force_context_lang'): elif converter == 'pdf':
del data['context']['lang'] pdf = report.with_env(env).with_context(context)._render_qweb_pdf(docids)[0]
context.update(data['context']) pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
return request.make_response(pdf, headers=pdfhttpheaders)
# 2. Rendering Logic with SUDO elif converter == 'text':
# We use the updated context here text = report.with_env(env).with_context(context)._render_qweb_text(docids, data=data)[0]
if converter == 'html': texthttpheaders = [('Content-Type', 'text/plain'), ('Content-Length', len(text))]
html = report.with_context(context).sudo()._render_qweb_html(docids, data=data)[0] return request.make_response(text, headers=texthttpheaders)
return request.make_response(html) else:
raise werkzeug.exceptions.HTTPException(description='Converter %s not implemented.' % converter)
elif converter == 'pdf':
pdf = report.with_context(context).sudo()._render_qweb_pdf(docids, data=data)[0]
pdfhttpheaders = [('Content-Type', 'application/pdf'), ('Content-Length', len(pdf))]
return request.make_response(pdf, headers=pdfhttpheaders)
elif converter == 'text':
text = report.with_context(context).sudo()._render_qweb_text(docids, data=data)[0]
texthttpheaders = [('Content-Type', 'text/plain'), ('Content-Length', len(text))]
return request.make_response(text, headers=texthttpheaders)
else:
raise werkzeug.exceptions.HTTPException(description='Converter %s not implemented.' % converter)
except Exception as e:
# 3. DEBUGGING BLOCK
# Crucial: Rollback the database transaction so we can send a response
request.env.cr.rollback()
# Log the full stack trace to your Odoo terminal/logfile
_logger.exception("Error generating public report for %s", reportname)
# Return the error message directly to the browser
error_msg = f"DEBUG ERROR: {str(e)}"
return request.make_response(error_msg, headers=[('Content-Type', 'text/plain')])

View File

@ -7666,3 +7666,76 @@ msgstr "الفروع المسموح بها"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.view_account_payment_register_form
msgid "Donor Bank Account"
msgstr "حساب المتبرع"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.donation_extension_wizard
msgid "Months Amount"
msgstr "مبلغ الشهور"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.replace_benefit_wizard
msgid "Replacement Information"
msgstr "معلومات الاستبدال"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.replace_benefit_wizard
#: model_terms:ir.ui.view,arch_db:odex_takaful.orphan_replacement_wizard
msgid "Replacement Reason"
msgstr "سبب الاستبدال"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.add_benefit_wizard
msgid "Benefit Information"
msgstr "معلومات المستفيد"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.add_benefit_wizard
msgid "Add Benefit"
msgstr "إضافة مستفيد"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.replace_sponsor_wizard
msgid "Sponsor Information"
msgstr "معلومات المتبرع"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.replace_sponsor_wizard
msgid "Replace Sponsor"
msgstr "استبدال المتبرع"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.transfer_deduction_wizard_views
#: model_terms:ir.ui.view,arch_db:odex_takaful.benefit_month_payment_wiz_view
msgid "Print PDF"
msgstr "طباعة PDF"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.transfer_deduction_wizard_views
msgid "Print Excel"
msgstr "طباعة Excel"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.orphan_replacement_wizard
msgid "Replacement Wizard"
msgstr "معالج الاستبدال"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.esterdad_wizard
msgid "Esterdad Wizard"
msgstr "معالج الاسترداد"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.esterdad_wizard
msgid "Payment Details"
msgstr "تفاصيل الدفع"
#. module: odex_takaful
#: model_terms:ir.ui.view,arch_db:odex_takaful.takaful_reports_wizards
msgid "Print The Report"
msgstr "طباعة التقرير"

View File

@ -236,15 +236,30 @@ class DonationsDetailsLines(models.Model):
# bank_transfer_attachment_file_name = fields.Char('Bank Transfer File Name', required=False) # bank_transfer_attachment_file_name = fields.Char('Bank Transfer File Name', required=False)
@api.depends('sponsorship_duration' , 'product_template_id' , 'payment_month_count' , 'direct_debit') @api.depends('sponsorship_duration', 'product_template_id', 'payment_month_count', 'direct_debit', 'record_type')
def _compute_get_age_range(self): def _compute_get_age_range(self):
for rec in self: for rec in self:
# ages field is only used for sponsorships (in _compute_members_domain_ids)
# Skip expensive computation for donations entirely
if rec.record_type != 'sponsorship':
rec.ages = 100 # Default value, not used for donations
continue
x = [] x = []
branch_id = rec.branch_custom_id.id branch_id = rec.branch_custom_id.id
records = self.env['donations.details.lines'].search(['|' , ('state', '=', 'replace') , ('state', '=', 'waiting') ]) if not branch_id:
filtered_records = records.filtered(lambda r: r.sponsorship_mechanism_id.branch_custom_id.id == branch_id) rec.ages = 100
continue
# Optimized: Include branch filter in search domain instead of Python filtering
records = self.env['donations.details.lines'].search([
'|', ('state', '=', 'replace'), ('state', '=', 'waiting'),
'|',
('sponsorship_mechanism_id.branch_custom_id', '=', branch_id),
('sponsorship_id.branch_custom_id', '=', branch_id)
])
for record in filtered_records: for record in records:
if record.sponsorship_duration == 'permanent' : if record.sponsorship_duration == 'permanent' :
x.append(1) x.append(1)
elif record.sponsorship_duration != 'permanent' and record.payment_month_count < 6 and record.direct_debit : elif record.sponsorship_duration != 'permanent' and record.payment_month_count < 6 and record.direct_debit :
@ -254,7 +269,6 @@ class DonationsDetailsLines(models.Model):
elif record.sponsorship_duration != 'permanent' and record.payment_month_count < 6 : elif record.sponsorship_duration != 'permanent' and record.payment_month_count < 6 :
x.append(18) x.append(18)
if len(x) > 0 : if len(x) > 0 :
lowest_value = min(x) if x else None lowest_value = min(x) if x else None
else: else:
@ -721,17 +735,19 @@ class DonationsDetailsLines(models.Model):
@api.depends('family_id','record_type') @api.depends('family_id','record_type')
def _compute_family_domain_ids(self): def _compute_family_domain_ids(self):
for rec in self: for rec in self:
domain = [] # Only fetch families for conditional donations where family_id field is used
if rec.record_type == 'donation': if rec.record_type == 'donation' and rec.donation_mechanism == 'with_conditions':
domain = [ domain = [
'|', '|',
('state', '=', 'second_approve'), ('state', '=', 'second_approve'),
'&', '&',
('state', 'in', ('waiting_approve', 'first_approve')), ('state', 'in', ('waiting_approve', 'first_approve')),
('action_type', '=', 'suspended')] ('action_type', '=', 'suspended')]
family = self.env['grant.benefit'].sudo().search(domain) family = self.env['grant.benefit'].sudo().search(domain)
rec.family_domain_ids = [(6, 0, family.ids)] if family else False
rec.family_domain_ids = [(6, 0, family.ids)] if family else False else:
# No family selection needed for sponsorships, unconditional donations, or other types
rec.family_domain_ids = False
@api.depends('gender', @api.depends('gender',
'record_type', 'record_type',
@ -839,8 +855,13 @@ class DonationsDetailsLines(models.Model):
elif rec.record_type == 'donation' and rec.donation_mechanism == "with_conditions" and rec.family_id: elif rec.record_type == 'donation' and rec.donation_mechanism == "with_conditions" and rec.family_id:
domain = [("benefit_id", "=", rec.family_id.id)] domain = [("benefit_id", "=", rec.family_id.id)]
members = self.env['family.member'].sudo().search(domain) else:
# No beneficiary selection needed (e.g., unconditional donations, waqf)
# Return empty recordset to avoid loading all records - fixes browser hang
rec.members_domain_ids = self.env['family.member'].sudo().browse()
continue
members = self.env['family.member'].sudo().search(domain)
rec.members_domain_ids = members if members else self.env['family.member'].sudo().browse() rec.members_domain_ids = members if members else self.env['family.member'].sudo().browse()

View File

@ -829,7 +829,6 @@ class TakafulSponsorship(models.Model):
family_lines = sponsorship_lines.filtered(lambda l: l.family_id == family) family_lines = sponsorship_lines.filtered(lambda l: l.family_id == family)
bill_values = { bill_values = {
'takaful_sponsorship_id': sponsorship.id, 'takaful_sponsorship_id': sponsorship.id,
'name': self.env['ir.sequence'].next_by_code('account.move.accrsp'),
'move_type': 'in_invoice', 'move_type': 'in_invoice',
'journal_id': benefit_journal_id, 'journal_id': benefit_journal_id,
'date': fields.Date.today(), 'date': fields.Date.today(),
@ -1480,7 +1479,7 @@ class TakafulSponsorship(models.Model):
current_invoice.sudo().write(invoice_values) current_invoice.sudo().write(invoice_values)
invoice_id = current_invoice invoice_id = current_invoice
else: else:
invoice_values.update({'invoice_line_ids': invoice_line_vals, 'name': self.env['ir.sequence'].next_by_code('account.move.accrsp')}) invoice_values.update({'invoice_line_ids': invoice_line_vals})
invoice_id = Invoice.create(invoice_values) invoice_id = Invoice.create(invoice_values)
for line in invoice_id.invoice_line_ids: for line in invoice_id.invoice_line_ids:

View File

@ -137,7 +137,6 @@ class AccountRegisterPayment(models.TransientModel):
bill_values = { bill_values = {
'takaful_sponsorship_id': sponsorship.id, 'takaful_sponsorship_id': sponsorship.id,
'name': self.env['ir.sequence'].next_by_code('account.move.accrsp'),
'move_type': 'in_invoice', 'move_type': 'in_invoice',
'journal_id': benefit_journal_id, 'journal_id': benefit_journal_id,
'date': fields.Date.today(), 'date': fields.Date.today(),

View File

@ -7,6 +7,39 @@
<field name="model">account.payment.register</field> <field name="model">account.payment.register</field>
<field name="inherit_id" ref="account.view_account_payment_register_form"/> <field name="inherit_id" ref="account.view_account_payment_register_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//label[@for='amount']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//div[@name='amount_div']" position="attributes">
<attribute name="colspan">2</attribute>
</xpath>
<xpath expr="//field[@name='amount']" position="attributes">
<attribute name="class">oe_center saudi_amount_field</attribute>
<attribute name="style">font-size: 60px !important; height: 90px !important; font-weight: 800 !important; background-color: transparent; border: none !important; flex: 1 !important; min-width: 0 !important; text-align: center; font-family: 'SF Pro Display', 'Roboto', sans-serif; display: flex !important; flex-direction: row-reverse !important; justify-content: center !important; align-items: center !important; letter-spacing: -2px;</attribute>
<attribute name="placeholder">0.00</attribute>
</xpath>
<xpath expr="//group" position="before">
<style>
.saudi_amount_field {
color: #006C35 !important; /* Saudi Green for Symbol */
}
.saudi_amount_field .o_input {
color: #2C3E50 !important; /* Dark Blue-Grey for Number */
border: none !important;
border-bottom: none !important;
box-shadow: none !important;
background-color: transparent !important;
width: auto !important; /* Fix: Allow flex to control width */
flex: 1 !important;
min-width: 0 !important;
}
.saudi_amount_field .o_input:focus {
outline: none !important;
box-shadow: none !important;
border: none !important;
}
</style>
</xpath>
<xpath expr="//field[@name='amount']" position="after"> <xpath expr="//field[@name='amount']" position="after">
<field name="is_refund_sponsorship" invisible="1"/> <field name="is_refund_sponsorship" invisible="1"/>
<field name="show_payment_group" invisible="1"/> <field name="show_payment_group" invisible="1"/>
@ -35,12 +68,15 @@
<attribute name="invisible">context.get('sponsorship_payment')</attribute> <attribute name="invisible">context.get('sponsorship_payment')</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='payment_date']" position="attributes"> <xpath expr="//field[@name='payment_date']" position="replace">
<attribute name="readonly">context.get('sponsorship_payment')</attribute> <field name="payment_date" invisible="1"/>
</xpath>
<xpath expr="//field[@name='communication']" position="replace">
<field name="communication" invisible="1"/>
</xpath> </xpath>
<xpath expr="//field[@name='journal_id']" position="before"> <xpath expr="//field[@name='journal_id']" position="before">
<field name="takaful_payment_method_id" attrs="{'invisible': [('sponsorship_payment', '=', False)]}" options="{'no_create': True, 'no_create_edit': True}"/> <field name="takaful_payment_method_id" string="Payment Method" attrs="{'invisible': [('sponsorship_payment', '=', False)]}" options="{'no_create': True, 'no_create_edit': True}"/>
</xpath> </xpath>
<xpath expr="//field[@name='partner_bank_id']" position="after"> <xpath expr="//field[@name='partner_bank_id']" position="after">
@ -50,17 +86,18 @@
<field name="payment_method" invisible="1"/> <field name="payment_method" invisible="1"/>
<field name="check_number" attrs="{'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','!=','check')], 'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','check')]}"/> <field name="check_number" attrs="{'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','!=','check')], 'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','check')]}"/>
<field name="check_due_date" attrs="{'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','!=','check')], 'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','check')]}"/> <field name="check_due_date" attrs="{'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','!=','check')], 'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','check')]}"/>
<field name="partner_bank_id" options="{'skip_disable_quick_create': True}" context="{'form_view_ref': 'odex_takaful.res_partner_bank_view_form_quick_create', 'default_partner_id': context.get('force_sponsorship_line_partner_id')}" attrs="{'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','bank')], 'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','!=','bank')]}" create="1" edit="1"/> <field name="partner_bank_id" string="Donor Bank Account" options="{'skip_disable_quick_create': True}" context="{'form_view_ref': 'odex_takaful.res_partner_bank_view_form_quick_create', 'default_partner_id': context.get('force_sponsorship_line_partner_id')}" attrs="{'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','bank')], 'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','!=','bank')]}" create="1" edit="1"/>
<field name="transaction_file_attachment" widget="binary" <field name="transaction_file_attachment" widget="binary"
filename="transaction_attachment_file_name" filename="transaction_attachment_file_name"
attrs="{'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','not in',['bank', 'check'])], 'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','bank')]}"/> attrs="{'invisible': ['|', ('sponsorship_payment', '=', False), ('takaful_payment_method','not in',['bank', 'check'])], 'required': [('sponsorship_payment', '=', True), ('takaful_payment_method','=','bank')]}"/>
<field name="transaction_attachment_file_name" invisible="1"/> <field name="transaction_attachment_file_name" invisible="1"/>
<field name="payment_date" attrs="{'readonly': [('sponsorship_payment', '=', True)]}"/>
<field name="communication"/>
</xpath> </xpath>
<xpath expr="//footer/button[2]" position="replace"> <xpath expr="//footer/button[2]" position="replace">
<!-- <field name="show_cancel_button" invisible="1"/>--> <!-- <field name="show_cancel_button" invisible="1"/>-->
<button name="action_cancel" <button string="Cancel"
type="object" special="cancel"
string="Cancel"
class="btn-secondary"/> class="btn-secondary"/>
<!-- attrs="{'invisible': [('show_cancel_button', '=', True)]}" />--> <!-- attrs="{'invisible': [('show_cancel_button', '=', True)]}" />-->
</xpath> </xpath>

View File

@ -437,7 +437,6 @@ class DonationExtensionWizardLine(models.TransientModel):
Invoice = self.env["account.move"].sudo() Invoice = self.env["account.move"].sudo()
invoice_values = { invoice_values = {
'takaful_sponsorship_id': sponsorship.id, 'takaful_sponsorship_id': sponsorship.id,
'name': self.env['ir.sequence'].next_by_code('account.move.accrsp'),
'move_type': 'out_invoice', 'move_type': 'out_invoice',
'journal_id': int(kafala_journal_id), 'journal_id': int(kafala_journal_id),
'date': fields.Date.today(), 'date': fields.Date.today(),