Merge pull request #4941 from expsa/abdulrahman_dev_odex25_ensan
[IMP] odex_takaful: schedule payment & bill grouping
This commit is contained in:
commit
99a226c712
|
|
@ -48,5 +48,21 @@
|
|||
<field name="priority" eval="1" />
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
|
||||
<!-- Scheduler for Processing Scheduled Payments Daily -->
|
||||
<record id="scheduler_process_scheduled_payments_action" forcecreate='True' model="ir.cron">
|
||||
<field name="name">Process Scheduled Payments Daily</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="nextcall" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 03:00:00')" />
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="model_id" ref="model_sponsorship_scheduling_line"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.with_context(from_cron=True).cron_process_scheduled_payments()</field>
|
||||
<field name="priority" eval="3" />
|
||||
<field name="active" eval="True"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
@ -182,7 +182,7 @@ class AccountRegisterPayment(models.TransientModel):
|
|||
def _create_payments(self):
|
||||
sponsorship_line_ids = self.env.context.get('sponsorship_line_ids')
|
||||
sponsorship_lines = []
|
||||
if sponsorship_line_ids:
|
||||
if sponsorship_line_ids and not self.env.context.get('from_cron',False):
|
||||
sponsorship_lines = self.env['donations.details.lines'].browse(sponsorship_line_ids)
|
||||
for line in sponsorship_lines:
|
||||
if line.record_type == 'sponsorship':
|
||||
|
|
@ -200,13 +200,15 @@ class AccountRegisterPayment(models.TransientModel):
|
|||
sponsorship = sponsorship_lines.sponsorship_id or sponsorship_lines.sponsorship_mechanism_id
|
||||
all_lines = sponsorship.donations_details_lines_mechanism_ids + sponsorship.donations_details_lines
|
||||
|
||||
if not self.env.context.get('schedule_line_payment'):
|
||||
sponsorship_lines.write({'is_paid': True})
|
||||
|
||||
sponsorship_lines.filtered(lambda l: l.record_type == 'sponsorship' and (l.benefit_id + l.benefit_ids)).write({'state': 'active'})
|
||||
sponsorship_lines.filtered(lambda l: l.record_type == 'sponsorship' and not (l.benefit_id + l.benefit_ids)).write({'state': 'waiting'})
|
||||
sponsorship_lines.filtered(lambda l: l.record_type == 'donation').write({'state': 'paid'})
|
||||
|
||||
if self.env.context.get('schedule_line_payment'):
|
||||
schedule_line = self.env['sponsorship.scheduling.line'].browse(self.env.context.get('schedule_line_id'))
|
||||
schedule_line.sudo().write({'status': 'paid'})
|
||||
all_schedule_lines_paid = schedule_line.donation_detail_linked_id.sponsorship_scheduling_line_ids.filtered(lambda l: l.status == 'paid')
|
||||
if len(all_schedule_lines_paid) == len(schedule_line.donation_detail_linked_id.sponsorship_scheduling_line_ids):
|
||||
schedule_line.donation_detail_linked_id.state = 'paid'
|
||||
for line in sponsorship_lines:
|
||||
if line.record_type != 'sponsorship':
|
||||
continue
|
||||
|
|
@ -216,7 +218,8 @@ class AccountRegisterPayment(models.TransientModel):
|
|||
line.benefit_id.write({'sponsor_related_id': line.sponsorship_mechanism_id.sponsor_id.id, 'sponsorship_id': line.sponsorship_mechanism_id.id})
|
||||
line.sponsorship_mechanism_id.sponsor_id.sudo().is_sponsor_portal = True
|
||||
|
||||
if 'draft' in all_lines.mapped('state'):
|
||||
states = all_lines.mapped('state')
|
||||
if any(state in ['draft', 'waiting', 'active'] for state in states):
|
||||
sponsorship.state = 'wait_pay'
|
||||
else:
|
||||
sponsorship.state = 'paid'
|
||||
|
|
@ -224,24 +227,26 @@ class AccountRegisterPayment(models.TransientModel):
|
|||
payments.write({'takaful_sponsorship_id': sponsorship.id})
|
||||
vendor_bill_vals = []
|
||||
benefit_journal_id = self.env.company.kafala_benefit_journal_id.id
|
||||
for line in sponsorship_lines:
|
||||
for benefit in (line.benefit_id + line.benefit_ids):
|
||||
bill_values = {
|
||||
'takaful_sponsorship_id': line.sponsorship_id.id if line.sponsorship_id else line.sponsorship_mechanism_id.id,
|
||||
'name': self.env['ir.sequence'].next_by_code('account.move.accrsp'),
|
||||
'move_type': 'in_invoice',
|
||||
'journal_id': benefit_journal_id,
|
||||
'date': fields.Date.today(),
|
||||
'partner_id': benefit.partner_id.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'product_id': line.product_id.id,
|
||||
'price_unit': line.donation_amount,
|
||||
'quantity': 1,
|
||||
'name': _("Benefit Number %s") % line.sequence_no,
|
||||
'analytic_account_id': line.sponsorship_id.branch_custom_id.branch.analytic_account_id.id,
|
||||
})]
|
||||
}
|
||||
vendor_bill_vals.append(bill_values)
|
||||
benefit_ids = sponsorship_lines.benefit_id + sponsorship_lines.benefit_ids
|
||||
for benefit in benefit_ids:
|
||||
benefit_lines = sponsorship_lines.filtered(lambda l: l.benefit_id == benefit or benefit in l.benefit_ids)
|
||||
sponsorship_id = (benefit_lines.sponsorship_id + benefit_lines.sponsorship_mechanism_id)[0]
|
||||
bill_values = {
|
||||
'takaful_sponsorship_id': sponsorship_id.id,
|
||||
'name': self.env['ir.sequence'].next_by_code('account.move.accrsp'),
|
||||
'move_type': 'in_invoice',
|
||||
'journal_id': benefit_journal_id,
|
||||
'date': fields.Date.today(),
|
||||
'partner_id': benefit.partner_id.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'product_id': line.product_id.id,
|
||||
'price_unit': line.donation_amount,
|
||||
'quantity': 1,
|
||||
'name': _("Benefit Number %s") % line.sequence_no,
|
||||
'analytic_account_id': sponsorship_id.branch_custom_id.branch.analytic_account_id.id,
|
||||
}) for line in benefit_lines]
|
||||
}
|
||||
vendor_bill_vals.append(bill_values)
|
||||
bill_id = self.env['account.move'].sudo().create(vendor_bill_vals)
|
||||
for line in bill_id.invoice_line_ids:
|
||||
line.account_id = line._get_computed_account()
|
||||
|
|
@ -251,11 +256,6 @@ class AccountRegisterPayment(models.TransientModel):
|
|||
line.tax_ids = taxes
|
||||
line.product_uom_id = line._get_computed_uom()
|
||||
|
||||
if self.env.context.get('schedule_line_id'):
|
||||
schedule_line = self.env['sponsorship.scheduling.line'].browse(self.env.context.get('schedule_line_id'))
|
||||
schedule_line.sudo().write({'status': 'paid'})
|
||||
|
||||
|
||||
return payments
|
||||
|
||||
def action_create_payments(self):
|
||||
|
|
|
|||
|
|
@ -512,25 +512,18 @@ class DonationsDetailsLines(models.Model):
|
|||
# Format the amount to 2 decimal places for better representation
|
||||
amount = round(amount, 2)
|
||||
# Calculate the month and year
|
||||
month_year = (rec.start_date + relativedelta(months=index)).strftime("%m/%Y")
|
||||
scheduled_date = (rec.start_date + relativedelta(months=index))
|
||||
month_year = scheduled_date.strftime("%m/%Y")
|
||||
# Create the Sponsorship Scheduling Link
|
||||
SponsorshipSchedulingLink.create({
|
||||
'sponsorship_id': sponsorship_id.id,
|
||||
'donation_detail_linked_id': rec.id,
|
||||
'beneficiary_id': rec.benefit_id.id,
|
||||
'month_year': month_year,
|
||||
'amount': amount
|
||||
'amount': amount,
|
||||
'scheduled_date': scheduled_date
|
||||
})
|
||||
# else:
|
||||
# month_year = (sponsorship_id.sponsorship_creation_date + relativedelta(months=1)).strftime("%m/%Y")
|
||||
# # Create the Sponsorship Scheduling Link
|
||||
# SponsorshipSchedulingLink.create({
|
||||
# 'sponsorship_id': sponsorship_id.id,
|
||||
# 'donation_detail_linked_id': rec.id,
|
||||
# 'beneficiary_id': rec.benefit_id.id,
|
||||
# 'month_year': month_year,
|
||||
# 'amount': rec.donation_amount
|
||||
# })
|
||||
|
||||
rec.sponsorships_computed = True
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from math import floor
|
|||
import re
|
||||
import requests
|
||||
from odoo.osv import expression
|
||||
import logging
|
||||
|
||||
|
||||
|
||||
|
|
@ -1745,6 +1746,7 @@ class SponsorshipSchedulingLine(models.Model):
|
|||
sequence_no = fields.Char(default='/', readonly=True, copy=False)
|
||||
beneficiary_id = fields.Many2one('family.member')
|
||||
month_year = fields.Char()
|
||||
scheduled_date = fields.Date(string='Scheduled Date', help='Date when the payment should be automatically processed')
|
||||
amount = fields.Float()
|
||||
refunded_amount = fields.Float(string='Refunded Amount')
|
||||
status = fields.Selection([
|
||||
|
|
@ -1839,6 +1841,70 @@ class SponsorshipSchedulingLine(models.Model):
|
|||
vals['sequence_no'] = self.env['ir.sequence'].next_by_code('sponsorship.scheduling.line.sequence') or '/'
|
||||
return super(SponsorshipSchedulingLine, self).create(vals)
|
||||
|
||||
@api.model
|
||||
def cron_process_scheduled_payments(self):
|
||||
today = fields.Date.today()
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
scheduled_lines = self.search([
|
||||
('scheduled_date', '<=', today),
|
||||
('status', '=', 'unpaid'),
|
||||
('sponsorship_state', 'in', ['confirmed', 'wait_pay'])
|
||||
])
|
||||
|
||||
_logger.info(f"Found {len(scheduled_lines)} scheduling lines scheduled for {today}")
|
||||
|
||||
for line in scheduled_lines:
|
||||
try:
|
||||
if line.payment_method == 'direct_debit':
|
||||
invoice_id = self.env['account.move'].sudo().search([
|
||||
('takaful_sponsorship_id', '=', line.sponsorship_id.id),
|
||||
('move_type', '=', 'out_invoice'),
|
||||
('state', '=', 'posted'),
|
||||
('payment_state', 'in', ['not_paid', 'partial'])
|
||||
], limit=1)
|
||||
|
||||
if not invoice_id:
|
||||
_logger.warning(f"No unpaid invoice found for sponsorship {line.sponsorship_id.code}, skipping line {line.sequence_no}")
|
||||
continue
|
||||
|
||||
journal_id = line.donation_detail_linked_id.payment_method_id.journal_id
|
||||
if not journal_id:
|
||||
_logger.warning(f"No journal found for payment method of line {line.sequence_no}")
|
||||
continue
|
||||
|
||||
payment_register_vals = {
|
||||
'payment_date': fields.Date.today(),
|
||||
'amount': line.amount,
|
||||
'journal_id': journal_id.id,
|
||||
'communication': f"{line.sponsorship_id.code} - {line.sequence_no}",
|
||||
}
|
||||
|
||||
if line.donation_detail_linked_id.payment_method_id.bank_id:
|
||||
payment_register_vals['partner_bank_id'] = line.donation_detail_linked_id.payment_method_id.bank_id.id
|
||||
|
||||
payment_register = self.env['account.payment.register'].sudo().with_context(
|
||||
active_model='account.move',
|
||||
active_ids=invoice_id.ids,
|
||||
schedule_line_payment=True,
|
||||
dont_redirect_to_payments=True,
|
||||
schedule_line_id=line.id,
|
||||
sponsorship_line_ids=line.donation_detail_linked_id.ids,
|
||||
from_cron=True,
|
||||
).create(payment_register_vals)
|
||||
|
||||
payment_register.action_create_payments()
|
||||
|
||||
_logger.info(f"Successfully processed scheduled payment for line {line.sequence_no}")
|
||||
else:
|
||||
_logger.warning(f"Scheduling line {line.sequence_no} payment method is not direct debit, skipping auto-payment")
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f"Error processing scheduled payment for line {line.sequence_no}: {str(e)}")
|
||||
continue
|
||||
|
||||
return True
|
||||
|
||||
def action_refund(self):
|
||||
refund_amount = self.amount - self.refunded_amount
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
<field name="donation_detail_linked_id"/>
|
||||
<field name="beneficiary_id"/>
|
||||
<field name="month_year"/>
|
||||
<field name="scheduled_date"/>
|
||||
<field name="amount" sum="Total Amount"/>
|
||||
<field name="refunded_amount" sum="Total Refunded"/>
|
||||
<field name="payment_method"/>
|
||||
|
|
@ -38,8 +39,12 @@
|
|||
<field name="sponsorship_id"/>
|
||||
<field name="beneficiary_id"/>
|
||||
<field name="month_year"/>
|
||||
<field name="scheduled_date"/>
|
||||
<field name="donation_detail_linked_id"/>
|
||||
<separator/>
|
||||
<filter string="Scheduled Today" name="scheduled_today" domain="[('scheduled_date', '=', context_today().strftime('%Y-%m-%d'))]"/>
|
||||
<filter string="Scheduled This Week" name="scheduled_this_week" domain="[('scheduled_date', '>=', (context_today() - datetime.timedelta(days=context_today().weekday())).strftime('%Y-%m-%d')), ('scheduled_date', '<=', (context_today() + datetime.timedelta(days=6-context_today().weekday())).strftime('%Y-%m-%d'))]"/>
|
||||
<separator/>
|
||||
<filter string="Unpaid" name="unpaid" domain="[('status', '=', 'unpaid')]"/>
|
||||
<filter string="Paid" name="paid" domain="[('status', '=', 'paid')]"/>
|
||||
<filter string="Approve Refund" name="approve_refund" domain="[('status', '=', 'approve_refund')]"/>
|
||||
|
|
@ -58,6 +63,7 @@
|
|||
<filter string="Status" name="group_status" context="{'group_by': 'status'}"/>
|
||||
<filter string="Payment Method" name="group_payment_method" context="{'group_by': 'payment_method'}"/>
|
||||
<filter string="Month/Year" name="group_month_year" context="{'group_by': 'month_year'}"/>
|
||||
<filter string="Scheduled Date" name="group_scheduled_date" context="{'group_by': 'scheduled_date'}"/>
|
||||
<filter string="Sponsorship State" name="group_sponsorship_state" context="{'group_by': 'sponsorship_state'}"/>
|
||||
</group>
|
||||
</search>
|
||||
|
|
|
|||
Loading…
Reference in New Issue