diff --git a/odex25_takaful/odex_takaful/__manifest__.py b/odex25_takaful/odex_takaful/__manifest__.py index 596931012..022b81eac 100644 --- a/odex25_takaful/odex_takaful/__manifest__.py +++ b/odex25_takaful/odex_takaful/__manifest__.py @@ -43,6 +43,7 @@ 'views/payment_details_lines_views.xml', 'wizards/account_payment_register.xml', 'views/res_partner_bank.xml', + 'views/acount_move.xml', 'views/res_users_inherit.xml', 'views/takaful_push_notification_view.xml', diff --git a/odex25_takaful/odex_takaful/models/donation_details_lines.py b/odex25_takaful/odex_takaful/models/donation_details_lines.py index aa65450a4..5c672448e 100644 --- a/odex25_takaful/odex_takaful/models/donation_details_lines.py +++ b/odex25_takaful/odex_takaful/models/donation_details_lines.py @@ -281,6 +281,7 @@ class DonationsDetailsLines(models.Model): rec.end_date and rec.end_date >= today and rec.state == 'active' + and rec.sponsorship_id.state in ['canceled' , 'closed'] ) rec.show_extend_button = show_extend_button @@ -683,7 +684,10 @@ class DonationsDetailsLines(models.Model): for rec in self: if rec.record_type == 'donation': if rec.sponsorship_id.donation_mechanism == 'without_conditions': - rec.total_donation_amount = rec.donation_amount * rec.donation_qty + if rec.direct_debit: + rec.total_donation_amount = rec.donation_amount * rec.donation_qty * rec.payment_month_count + else: + rec.total_donation_amount = rec.donation_amount * rec.donation_qty else: rec.total_donation_amount = rec.total_months_amount else: @@ -873,14 +877,15 @@ class DonationsDetailsLines(models.Model): def compute_sponsorships_lines(self): for rec in self: + rec.write({'sponsorship_scheduling_line_ids': [(5, 0, 0)]}) sponsorship_id = rec.sponsorship_mechanism_id if rec.sponsorship_mechanism_id.donations_details_lines_mechanism_ids else rec.sponsorship_id SponsorshipSchedulingLink = self.env['sponsorship.scheduling.line'].sudo() SponsorshipSchedulingLink.search([('sponsorship_id', 'in', sponsorship_id.ids), ('donation_detail_linked_id.sponsorships_computed', '=', False)]).unlink() - if rec.sponsorships_computed: - return + # if rec.sponsorships_computed: + # return if rec.payment_option == 'month' and rec.direct_debit: # Divide the total donation amount by the number of months if rec.payment_month_count > 0: @@ -981,18 +986,45 @@ class DonationsDetailsLines(models.Model): record = super(DonationsDetailsLines, self).create(vals) if record.benefit_ids == 1: record.benefit_id = record.benefit_ids[0].id - print('Vals >>> ' , vals) + + + # Ensure the line is linked to the sponsor's donation_detail_ids M2M + if record.sponsor_id: + record.sponsor_id.write({ + 'donation_detail_ids': [(4, record.id)], + }) + # Auto-trigger kafel_state recomputation for the sponsor + record.sponsor_id._compute_kafel_state() return record def write(self, vals): res = False + # Track sponsors that need kafel_state recomputation + sponsors_to_recompute = set() + for rec in self: + # Track old sponsor before write + old_sponsor_id = rec.sponsor_id.id if rec.sponsor_id else False old_benefit = rec.benefit_id.name old_benefits = rec.benefit_ids.mapped('name') # assuming benefit has a 'name' old_benefits_set = set(old_benefits) res |= super(DonationsDetailsLines, rec).write(vals) rec.invalidate_cache() rec = self.browse(rec.id) + + # Track sponsors for recomputation if relevant fields change + if any(field in vals for field in ['sponsor_id', 'sponsorship_duration', 'state']): + # Add old sponsor if it existed + if old_sponsor_id: + sponsors_to_recompute.add(old_sponsor_id) + # Add new sponsor if sponsor_id changed + if 'sponsor_id' in vals: + new_sponsor_id = vals.get('sponsor_id') + if new_sponsor_id: + sponsors_to_recompute.add(new_sponsor_id) + # Add current sponsor if other relevant fields changed + elif rec.sponsor_id: + sponsors_to_recompute.add(rec.sponsor_id.id) if 'display_type' in vals and self.filtered(lambda line: line.display_type != vals.get('display_type')): raise UserError( @@ -1045,6 +1077,18 @@ class DonationsDetailsLines(models.Model): # vals['replace_date'] = False self.onset_benefit_id() + # Ensure each line is linked to its sponsor's donation_detail_ids M2M + for rec in self: + if rec.sponsor_id: + rec.sponsor_id.write({ + 'donation_detail_ids': [(4, rec.id)], + }) + + # Auto-trigger kafel_state recomputation for affected sponsors + if sponsors_to_recompute: + sponsors = self.env['res.partner'].browse(list(sponsors_to_recompute)) + sponsors._compute_kafel_state() + return res def action_view_replacement_wizard(self): diff --git a/odex25_takaful/odex_takaful/models/res_partner.py b/odex25_takaful/odex_takaful/models/res_partner.py index 274150833..45528c43b 100644 --- a/odex25_takaful/odex_takaful/models/res_partner.py +++ b/odex25_takaful/odex_takaful/models/res_partner.py @@ -53,6 +53,7 @@ class ResPartner(models.Model): ('verified', 'Verified') ], string='State',default='draft', tracking=True) + check_lines = fields.Boolean() def _get_default_preferred_communication(self): """Get first preferred communication method as default""" first_comm = self.env['preferred.communication'].search([], limit=1, order='id asc') @@ -96,17 +97,22 @@ class ResPartner(models.Model): string="# of Gifts", readonly=True ) + donation_line_ids = fields.Many2many( 'donations.details.lines', + string="Donation Lines", + ) + kafel_state = fields.Selection([ + ('active', "Active"), + ('not_active', "Not Active")] ,string='Status') _sql_constraints = [ ('id_number_uniq', 'unique (id_number)', 'The ID Number Already Exist!'), ] - def view_sponsorship_payment_action(self): """Enable The Sponsor To Pay Sponsorships Entries""" - sponsorship_id = self.env['takaful.sponsorship'].sudo().search([('sponsor_id', '=', self.id), ('has_delay', '=', True)], limit=1) - + sponsorship_id = self.env['takaful.sponsorship'].sudo().search([('sponsor_id', '=', self.id), ('has_delay', '=', True)], limit=1) + context = dict(self.env.context or {}) context['default_sponsor_id'] = self.id or False context['default_sponsorship_id'] = sponsorship_id.id or False @@ -124,27 +130,48 @@ class ResPartner(models.Model): 'context': context, } - + @api.constrains('is_family', 'is_benefit', 'is_donor', 'is_sponsor_portal') def _check_family_beneficiary_exclusivity(self): for rec in self: is_family_or_beneficiary = rec.is_family or rec.is_benefit - + is_donor_vendor_sponsor = rec.is_donor or rec.is_sponsor_portal or rec.is_vendor - + if is_family_or_beneficiary and is_donor_vendor_sponsor: raise ValidationError(_("A contact cannot be both Family/Beneficiary and Donor/Member/Sponsor at the same time!")) + def _normalize_phone_search_args(self, args): + """Normalize phone/mobile values in domain by stripping a leading '0' if present.""" + + def _normalize(term): + # Term can be an operator ('&', '|', '!') or a domain triplet/list (possibly nested) + if isinstance(term, list): + # Domain leaf: ['phone', 'ilike', '0123'] or ['mobile', '=', '0123'] + if len(term) == 3 and term[0] in ('phone', 'mobile') and isinstance(term[2], str): + val = term[2] + if val and val.startswith('0'): + term = [term[0], term[1], val[1:]] + else: + # Nested list: normalize inner items + term = [_normalize(t) for t in term] + return term + + return [_normalize(t) for t in args] if args else args + @api.model def search(self, args, offset=0, limit=None, order=None, count=False): - if not self.env.context.get('from_contact_search'): - return super().search(args, offset=offset, limit=limit, order=order, count=count) + # if not self.env.context.get('from_contact_search'): + # return super().search(args, offset=offset, limit=limit, order=order, count=count) if self.env.context.get('mail_read') or self.env.context.get('mail_message_origin'): return super().search(args, offset=offset, limit=limit, order=order, count=count) - base_results = super().search(args, offset=offset, limit=limit, order=order) + # Normalize phone/mobile search values (strip leading zero) + normalized_args = self._normalize_phone_search_args(args) + + base_results = super().search(normalized_args, offset=offset, limit=limit, order=order) if not base_results: return base_results @@ -159,7 +186,21 @@ class ResPartner(models.Model): def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): if not args: args = [] - domain = ['|', '|', ('name', operator, name),('id_number', operator, name),('mobile', operator, name)] + + # Build search domains for the raw name and (optionally) the name + # without leading zero, to "ignore" a starting 0 in the search term. + search_terms = [name] if name else [] + if name and name.startswith('0'): + search_terms.append(name[1:]) + + domain = [] + for term in search_terms: + term_domain = ['|', '|', + ('name', operator, term), + ('id_number', operator, term), + ('mobile', operator, term)] + domain = term_domain if not domain else expression.OR([domain, term_domain]) + domain = expression.AND([domain, args]) parent_record_ids = self.search(domain).ids domain = expression.OR([domain, [('parent_id', 'in', parent_record_ids)]]) @@ -227,7 +268,7 @@ class ResPartner(models.Model): res = all(bank_info) return res - + def unlink(self): related_contact = self.env['res.partner'].search([('parent_id', 'in', self.ids)]) sponsorships = self.env['takaful.sponsorship'].search([('sponsor_id', 'in', self.ids)]) @@ -272,9 +313,9 @@ class ResPartner(models.Model): values.update({'is_sponsor_portal': True}) if not values.get('serial_code'): values['serial_code'] = self.env['ir.sequence'].next_by_code('takaful.sponsor.sequence') or 'S/0000' - + res = super(ResPartner, self).create(values) - + if context.get('sponsor_contact'): if context.get('parent_model') == 'takaful.sponsorship' and context.get('parent_id'): parent_record = self.env[context['parent_model']].browse(context['parent_id']) @@ -311,8 +352,8 @@ class ResPartner(models.Model): if 'mobile' in vals and self.mobile != False: kafeel = self.env['res.users'].with_user(2).create({ - 'name' : vals['name'], - 'branch_custom_id':vals['branch_custom_id'], + 'name' : self.name, + 'branch_custom_id':self.branch_custom_id.id or vals['branch_custom_id'], 'sel_groups_1_9_10' : 9, 'partner_id' : self.id, 'login' : vals['mobile'], @@ -327,7 +368,7 @@ class ResPartner(models.Model): def create_user(self): for follower in self.message_follower_ids: follower.sudo().unlink() - # If you add 'no_reset_password' to the context to True, it won't send an email. + # If you add 'no_reset_password' to the context to True, it won't send an email. # You can then set the password manually (and find a way to let the user know it). user = self.env['res.users'].sudo().with_context(no_reset_password=False).create({ 'name': self.name, @@ -353,7 +394,7 @@ class ResPartner(models.Model): self.user_id = user.id return self - + def action_open_sponsor_operation(self): """Open Operations History for a Sponsor""" domain = [('sponsor_id', '=', self.id or False)] @@ -372,7 +413,7 @@ class ResPartner(models.Model): 'context': context, } - + def view_sponsorship_action(self): """List Sponsorships For The Sponsor""" domain = [('sponsor_id', '=', self.id or False)] @@ -395,9 +436,9 @@ class ResPartner(models.Model): @api.model def get_active_sponsors_users(self): sponsors_users = self.env['res.users'].sudo().search([ - ("active", "=", True), + ("active", "=", True), ("groups_id", "=", self.env.ref("odex_takaful.takaful_group_user_sponsor").id)])#self.env['res.users'].sudo().search([]) - + count = len(sponsors_users) or 0 sponsors = self.env['res.partner'].sudo().search([]) for rec in sponsors: @@ -472,7 +513,7 @@ class ResPartner(models.Model): rec.name = " ".join(names) else: rec.name = " " - + class ResPartnerBank(models.Model): _inherit = 'res.partner.bank' diff --git a/odex25_takaful/odex_takaful/models/takaful_sponorship_model.py b/odex25_takaful/odex_takaful/models/takaful_sponorship_model.py index 589e07083..2094bbc9d 100644 --- a/odex25_takaful/odex_takaful/models/takaful_sponorship_model.py +++ b/odex25_takaful/odex_takaful/models/takaful_sponorship_model.py @@ -1394,8 +1394,8 @@ class TakafulSponsorship(models.Model): "Please choose different beneficiaries for these lines before proceeding with the payment." % line.sequence_no)) - if not all(all_donation_lines.mapped("sponsorships_computed")): - (self.donations_details_lines + self.donations_details_lines_mechanism_ids).compute_sponsorships_lines() + + (self.donations_details_lines + self.donations_details_lines_mechanism_ids).compute_sponsorships_lines() if self.state == 'draft': # Send SMS Notification diff --git a/odex25_takaful/odex_takaful/views/acount_move.xml b/odex25_takaful/odex_takaful/views/acount_move.xml index 548d283b8..0eb0ea278 100644 --- a/odex25_takaful/odex_takaful/views/acount_move.xml +++ b/odex25_takaful/odex_takaful/views/acount_move.xml @@ -9,19 +9,27 @@ {'invisible': False} - - + + - - - + + + account.move.inherit.form.attachment + account.move + + + + + + + diff --git a/odex25_takaful/odex_takaful/views/takaful_sponorship_view.xml b/odex25_takaful/odex_takaful/views/takaful_sponorship_view.xml index ddf6f9c9b..0959a3fad 100644 --- a/odex25_takaful/odex_takaful/views/takaful_sponorship_view.xml +++ b/odex25_takaful/odex_takaful/views/takaful_sponorship_view.xml @@ -180,7 +180,7 @@ }"/>
- + + + + res_partner_view_form + res.partner + + + + + + + + + + + + + + + + Takaful Sponsor Sequence takaful.sponsor.sequence @@ -106,6 +125,7 @@ +