# -*- coding: utf-8 -*- from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError, Warning import logging import random import requests from odoo import SUPERUSER_ID from datetime import datetime, date, timedelta _logger = logging.getLogger(__name__) class EsterdadWizard(models.Model): _name = "esterdad.wizard" amount = fields.Monetary(currency_field='currency_id', compute='_get_total_sponsorship_amount') currency_id = fields.Many2one( 'res.currency', string="Currency", required=True, default=lambda self: self.env.company.currency_id ) cancel_date = fields.Date() pay_date = fields.Datetime(related='sponsor_id.pay_date') cancel_reason = fields.Many2one('sponsorship.reason.stop') another_reason = fields.Boolean() reason = fields.Text() sponsor_id = fields.Many2one('takaful.sponsorship') journal_entry_ids = fields.Many2many('account.move', domain="[('id', 'in', allowed_journal_entry_ids)]") allowed_journal_entry_ids = fields.Many2many('account.move', compute='_compute_allowed_journal_entry_ids') sponsor_name = fields.Char() mobile = fields.Char(required=True) id_num = fields.Char() payment_ids = fields.Many2many('account.payment', compute='_compute_payment_ids') # allowed_payment_ids = fields.Many2many( # 'account.payment', # 'esterdad_wizard_allowed_payment_rel', # 'wizard_id', # 'payment_id', # string="Allowed Payments", # ) confirmed = fields.Boolean() @api.depends('sponsor_id') def _compute_allowed_journal_entry_ids(self): for rec in self: refund_hour_limit = self.env['ir.config_parameter'].sudo().get_param('odex_takaful.cancel_refund', default=0) limit_refund_date_time = fields.Datetime.now() - timedelta(hours=int(refund_hour_limit)) # rec.allowed_journal_entry_ids = rec.sponsor_id.journal_entry_ids.ids rec.allowed_journal_entry_ids = rec.sponsor_id.journal_entry_ids.filtered(lambda r: r.move_type == 'out_invoice' and\ r.payment_state == 'paid' and\ r.create_date >= limit_refund_date_time).ids @api.depends('journal_entry_ids') def _compute_payment_ids(self): for rec in self: rec.payment_ids = [(6, 0, rec.journal_entry_ids._get_reconciled_payments().ids)] # @api.onchange('sponsor_id') # def _onchange_sponsor_id_set_payment_domain(self): # """Limit available payments to same partner within restriction_period days.""" # for rec in self: # domain = [] # partner = False # invoice_names = [] # if rec.sponsor_id: # # Assuming sponsor_id.sponsor_id is the related partner on the sponsorship # partner = getattr(rec.sponsor_id, 'sponsor_id', False) or getattr(rec.sponsor_id, 'partner_id', False) # # Get all invoices from journal_entry_ids where move_type is 'out_invoice' # invoices = rec.sponsor_id.journal_entry_ids.filtered(lambda inv: inv.move_type == 'out_invoice') # # Get all invoice names (numbers) in a list # invoice_names = invoices.mapped('name') # if partner: # # Get restriction period (in days) from config parameters # sudo_conf = self.env['ir.config_parameter'].sudo() # restriction_period = sudo_conf.get_param('odex_takaful.restriction_period') # try: # restriction_period = int(restriction_period or 0) # except (TypeError, ValueError): # restriction_period = 0 # if restriction_period and restriction_period > 0: # limit_date = date.today() - timedelta(days=restriction_period) # domain = [ # ('partner_id', '=', partner.id), # ('date', '>=', limit_date), # ] # else: # # If no restriction configured, just filter by partner # domain = [('partner_id', '=', partner.id)] # # Add filter for ref field to match invoice names if we have any # if invoice_names: # domain.append(('ref', 'in', invoice_names)) # # If we have a domain, fetch the matching payments and store them # # in the helper field, then use their IDs as the domain. # if domain: # payments = self.env['account.payment'].search(domain) # rec.allowed_payment_ids = payments # return {'domain': {'payment_ids': [('id', 'in', payments.ids)]}} # else: # rec.allowed_payment_ids = False # return {'domain': {'payment_ids': []}} def action_confirm_refund(self): for rec in self: sponsor_phone = rec.mobile if sponsor_phone: company_id = rec.env.company # Generate OTP & send it otp = 1111 if company_id.sms_mode == 'test': _logger.info(f"--- SMS TEST MODE --- OTP for mobile {sponsor_phone} is {otp}. SMS not sent.") else: otp = str(random.randint(1000, 9999)) payload = { "recipients": [sponsor_phone], "body": f"Your OTP code is: {otp}", "sender": company_id.sms_sender_name, } headers = { "Authorization": f"Bearer {company_id.sms_api_token}", "Content-Type": "application/json", } try: response = requests.post(company_id.sms_api_url, json=payload, headers=headers, timeout=10) response.raise_for_status() # Raise an error for non-2xx responses _logger.info(f"OTP {otp} sent successfully to {sponsor_phone}") except requests.exceptions.RequestException as e: _logger.error(f"Failed to send OTP to {sponsor_phone}: {e}") raise ValidationError("Failed to send OTP. Please check API configuration or try again later.") ################## context = dict(self.env.context or {}) # context['default_user_id'] = user.id context['default_otp'] = otp context['default_esterdad_id'] = rec.id context['default_invoice_ids'] = rec.journal_entry_ids.ids context['default_cancel_reason'] = rec.cancel_reason.name if not rec.another_reason else rec.reason rec.sponsor_id.write({ 'cancel_record_id': rec.id }) rec.sponsor_id.sponsor_id.write({ 'first_name': rec.sponsor_name }) if rec.sponsor_id.sponsor_or_donor_type == 'unknown' and not rec.sponsor_id.sponsor_phone: rec.sponsor_id.sponsor_phone = rec.mobile if rec.sponsor_id.sponsor_or_donor_type == 'registered' and rec.id_num: rec.sponsor_id.sponsor_id.id_number == rec.id_num # context['default_payment_ids'] = [(6, 0, rec.payment_ids.ids)] view = self.env.ref('odex_takaful.view_otp_wizard_form') return { 'name': _('OTP Confirmation'), 'view_mode': 'form', 'view_type': 'form', 'type': 'ir.actions.act_window', 'res_model': 'otp.confirmation.wizard', 'view_id': view.id, 'target': 'new', 'context': context, } else: raise UserError(_("Mobile is required!")) @api.depends('payment_ids') def _get_total_sponsorship_amount(self): for rec in self: rec.amount = sum(rec.payment_ids.mapped('amount')) class OTPWizard(models.TransientModel): _name = "otp.confirmation.wizard" otp = fields.Integer() otp_code = fields.Integer() esterdad_id = fields.Many2one('esterdad.wizard') # user_id = fields.Many2one('res.users') invoice_ids = fields.Many2many('account.move') # payment_ids = fields.Many2many( # 'account.payment', # string="Payments" # ) cancel_reason = fields.Text() def action_confirm_otp(self): for rec in self: if rec.otp == rec.otp_code: sponsor_ship = rec.esterdad_id.sponsor_id rec.esterdad_id.cancel_date = date.today() if sponsor_ship: # # Get ref values from selected payments # payment_refs = rec.payment_ids.mapped('ref') # # Filter out empty/False refs # payment_refs = [ref for ref in payment_refs if ref] # if not payment_refs: # raise UserError(_("No references found in selected payments")) # # Search for invoices in account.move matching payment refs # invoices = self.env['account.move'].search([ # ('name', 'in', payment_refs), # ]) if not rec.invoice_ids: raise UserError(_("No invoices matching the specified references")) for invoice in rec.invoice_ids: credit = self.env['account.move.reversal'].with_company(self.env.user.company_id.id).create({ 'refund_method': 'refund', 'date': date.today(), 'date_mode': 'custom', 'move_ids': [invoice.id], }) x = credit.reverse_moves() sponsor_ship.state = 'canceled' domain_ids = x.get('res_id') if domain_ids: last_id = domain_ids sponsor_ship.write({ 'journal_entry_ids': [(4, last_id)] }) credit_note = self.env['account.move'].search([('id', '=', last_id)]) credit_note.action_post() credit_note.write({ 'esterdad_id': rec.esterdad_id.id, 'sponsorship_cancel_reason': rec.cancel_reason }) rec.esterdad_id.confirmed = True rec.esterdad_id.sponsor_id.is_canceled_refund = True # Get all donation lines from donations_details_lines and donations_details_lines_mechanism_ids all_donation_lines = sponsor_ship.donations_details_lines | sponsor_ship.donations_details_lines_mechanism_ids # Set state to 'cancel' for all donation lines if all_donation_lines: all_donation_lines.write({'state': 'cancel'}) all_donation_lines.mapped('benefit_id').write({'kafala_status': 'have_not_kafala', 'sponsor_related_id': False, 'sponsorship_end_date': fields.Date.today(), }) # Process extension history for each donation line for donation_line in all_donation_lines: # Get extension_history_ids for this donation line extension_histories = donation_line.extension_history_ids if extension_histories: # Set state to 'cancel' for all extension histories extension_histories.write({'state': 'cancel'}) total_extension_months = 0 current_payment_month_count = 0 new_payment_month_count = 0 # Sum all extension_months total_extension_months = sum(extension_histories.mapped('extension_months')) # Subtract the sum from payment_month_count if total_extension_months > 0: current_payment_month_count = donation_line.payment_month_count or 0 new_payment_month_count = current_payment_month_count - total_extension_months # Ensure it doesn't go below 0 donation_line.write({ 'payment_month_count': max(0, new_payment_month_count) }) else: raise UserError(_("The entered number is incorrect"))