odex25_standard/odex25_takaful/odex_takaful/models/account_move.py

270 lines
14 KiB
Python

from odoo import api, fields, models, _
from odoo.exceptions import UserError
from odoo.tools import float_compare
from odoo.tools.misc import format_date, get_lang
from collections import defaultdict
class AccountMove(models.Model):
_inherit = 'account.move'
takaful_sponsorship_id = fields.Many2one('takaful.sponsorship', compute='_compute_takaful_sponsorship_id', store=True)
is_refund_sponsorship = fields.Boolean(string='Is Refund Sponsorship', default=False)
sponsorship_scheduling_line = fields.Many2one('sponsorship.scheduling.line')
payment_details_line = fields.Many2one('payment.details.lines')
sponsorship_id = fields.Many2one('takaful.sponsorship', string='Sponsorship', readonly=True, )
payment_id = fields.Many2one('account.payment', string='Payment', copy=False)
esterdad_id = fields.Many2one('esterdad.wizard')
def action_view_esterdad_id(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': 'esterdad.wizard',
'view_mode': 'form',
'view_id': False, # or specify a particular form view if you have one
'target': 'current', # or 'new' if you want a popup wizard
'res_id': self.esterdad_id.id, # open this record directly
'context': {
'create': False
},
}
@api.depends('state')
def _compute_takaful_sponsorship_id(self):
for move in self:
if not move.takaful_sponsorship_id and move.move_type == 'entry':
partials = move.line_ids.matched_debit_ids | move.line_ids.matched_credit_ids
takaful_sponsorship_ids = (partials.debit_move_id.move_id.takaful_sponsorship_id | partials.credit_move_id.move_id.takaful_sponsorship_id)
move.takaful_sponsorship_id = takaful_sponsorship_ids[-1] if takaful_sponsorship_ids else False
else:
move.takaful_sponsorship_id = move.takaful_sponsorship_id
def action_move_line_create(self):
'''
Confirm the vouchers given in ids and create the journal entries for each of them
'''
res = super(AccountMove, self).action_move_line_create()
if res:
self.write({'payment_id': self.move_id.line_ids.mapped('payment_id').id})
@api.onchange('payment_journal_id')
def on_change_payment_journal_id(self):
for rec in self:
rec.account_id = rec.payment_journal_id.default_debit_account_id
def action_register_payment(self):
''' Override the payment registration behavior. '''
# Perform any custom logic before calling the original method
# Call the original method to preserve the default behavior
action = super(AccountMove, self).action_register_payment()
for move in self:
if move.is_refund_sponsorship: # Check if it's a refund
# Add any additional custom logic after calling the original method
action['context'].update({
'default_takaful_sponsorship_id': move.takaful_sponsorship_id.id,
'default_journal_id': move.payment_details_line.journal_id.id,
'default_is_refund_sponsorship': True,
})
return action
@api.depends(
'line_ids.matched_debit_ids.debit_move_id.move_id.payment_id.is_matched',
'line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual',
'line_ids.matched_debit_ids.debit_move_id.move_id.line_ids.amount_residual_currency',
'line_ids.matched_credit_ids.credit_move_id.move_id.payment_id.is_matched',
'line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual',
'line_ids.matched_credit_ids.credit_move_id.move_id.line_ids.amount_residual_currency',
'line_ids.debit',
'line_ids.credit',
'line_ids.currency_id',
'line_ids.amount_currency',
'line_ids.amount_residual',
'line_ids.amount_residual_currency',
'line_ids.payment_id.state',
'line_ids.full_reconcile_id',
'sponsorship_scheduling_line.status'
)
def _compute_amount(self):
# Call the original method to preserve Odoo's logic
super(AccountMove, self)._compute_amount()
for move in self.filtered("takaful_sponsorship_id"):
scheduling_amount = move.sponsorship_scheduling_line.amount
if move.payment_state == 'paid':
# refund_amount = move.invoice_line_ids.price_unit
refund_amount = sum(move.invoice_line_ids.mapped("price_unit"))
refunded_scheduling_amount = move.sponsorship_scheduling_line.refunded_amount
total_refunded_amount = refund_amount + refunded_scheduling_amount
if refunded_scheduling_amount == scheduling_amount:
move.sponsorship_scheduling_line.status = 'fully_refund'
move.sponsorship_scheduling_line.refunded_amount = scheduling_amount
move.payment_details_line.is_fully_refund = True
elif refunded_scheduling_amount < scheduling_amount:
move.sponsorship_scheduling_line.status = 'partial_refund'
move.sponsorship_scheduling_line.refunded_amount += refund_amount
move.payment_details_line.is_partial_refund = True
else:
if not isinstance(scheduling_amount, (int, float)):
raise UserError(
_("Total amount should be less or equal %s") % scheduling_amount
)
has_partial_refund = False
has_fully_refund = False
has_paid = False
has_unpaid = False
for line in move.takaful_sponsorship_id.sponsorship_scheduling_line_ids:
if line.status == "partial_refund":
has_partial_refund = True
elif line.status == "fully_refund":
has_fully_refund = True
elif line.status == "paid":
has_paid = True
elif line.status == "unpaid":
has_unpaid = True
if has_partial_refund:
move.takaful_sponsorship_id.state = 'partial_refund'
elif has_fully_refund and not (has_partial_refund and has_paid and has_unpaid):
move.takaful_sponsorship_id.state = 'fully_refund'
elif has_fully_refund and (has_partial_refund or has_paid or has_unpaid):
move.takaful_sponsorship_id.state = 'partial_refund'
def _post(self, soft=True):
"""Post/Validate the documents.
Posting the documents will give it a number, and check that the document is
complete (some fields might not be required if not posted but are required
otherwise).
If the journal is locked with a hash table, it will be impossible to change
some fields afterwards.
:param soft (bool): if True, future documents are not immediately posted,
but are set to be auto posted automatically at the set accounting date.
Nothing will be performed on those documents before the accounting date.
:return Model<account.move>: the documents that have been posted
"""
if soft:
future_moves = self.filtered(lambda move: move.date > fields.Date.context_today(self))
future_moves.auto_post = True
for move in future_moves:
msg = _('This move will be posted at the accounting date: %(date)s',
date=format_date(self.env, move.date))
move.message_post(body=msg)
to_post = self - future_moves
else:
to_post = self
# `user_has_group` won't be bypassed by `sudo()` since it doesn't change the user anymore.
# if not self.env.su and not self.env.user.has_group('account.group_account_invoice'):
# raise AccessError(_("You don't have the access rights to post an invoice."))
for move in to_post:
if move.partner_bank_id and not move.partner_bank_id.active:
raise UserError(
_("The recipient bank account link to this invoice is archived.\nSo you cannot confirm the invoice."))
if move.state == 'posted':
raise UserError(_('The entry %s (id %s) is already posted.') % (move.name, move.id))
if not move.line_ids.filtered(lambda line: not line.display_type):
raise UserError(_('You need to add a line before posting.'))
if move.auto_post and move.date > fields.Date.context_today(self):
date_msg = move.date.strftime(get_lang(self.env).date_format)
raise UserError(_("This move is configured to be auto-posted on %s", date_msg))
if not move.partner_id:
if move.is_sale_document():
raise UserError(
_("The field 'Customer' is required, please complete it to validate the Customer Invoice."))
elif move.is_purchase_document():
raise UserError(
_("The field 'Vendor' is required, please complete it to validate the Vendor Bill."))
if move.is_invoice(include_receipts=True) and float_compare(move.amount_total, 0.0,
precision_rounding=move.currency_id.rounding) < 0:
raise UserError(
_("You cannot validate an invoice with a negative total amount. You should create a credit note instead. Use the action menu to transform it into a credit note or refund."))
if move.line_ids.account_id.filtered(lambda account: account.deprecated):
raise UserError(_("A line of this move is using a deprecated account, you cannot post it."))
# Handle case when the invoice_date is not set. In that case, the invoice_date is set at today and then,
# lines are recomputed accordingly.
# /!\ 'check_move_validity' must be there since the dynamic lines will be recomputed outside the 'onchange'
# environment.
if not move.invoice_date:
if move.is_sale_document(include_receipts=True):
move.invoice_date = fields.Date.context_today(self)
move.with_context(check_move_validity=False)._onchange_invoice_date()
elif move.is_purchase_document(include_receipts=True):
raise UserError(_("The Bill/Refund date is required to validate this document."))
# When the accounting date is prior to the tax lock date, move it automatically to the next available date.
# /!\ 'check_move_validity' must be there since the dynamic lines will be recomputed outside the 'onchange'
# environment.
if (move.company_id.tax_lock_date and move.date <= move.company_id.tax_lock_date) and (
move.line_ids.tax_ids or move.line_ids.tax_tag_ids):
move.date = move._get_accounting_date(move.invoice_date or move.date, True)
move.with_context(check_move_validity=False)._onchange_currency()
for move in to_post:
# Fix inconsistencies that may occure if the OCR has been editing the invoice at the same time of a user. We force the
# partner on the lines to be the same as the one on the move, because that's the only one the user can see/edit.
wrong_lines = move.is_invoice() and move.line_ids.filtered(
lambda aml: aml.partner_id != move.commercial_partner_id and not aml.display_type)
if wrong_lines:
wrong_lines.partner_id = move.commercial_partner_id.id
# Create the analytic lines in batch is faster as it leads to less cache invalidation.
to_post.mapped('line_ids').create_analytic_lines()
to_post.write({
'state': 'posted',
'posted_before': True,
})
for move in to_post:
move.message_subscribe([p.id for p in [move.partner_id] if p not in move.sudo().message_partner_ids])
for move in to_post:
if move.is_sale_document() \
and move.journal_id.sale_activity_type_id \
and (move.journal_id.sale_activity_user_id or move.invoice_user_id).id not in (
self.env.ref('base.user_root').id, False):
move.activity_schedule(
date_deadline=min((date for date in move.line_ids.mapped('date_maturity') if date),
default=move.date),
activity_type_id=move.journal_id.sale_activity_type_id.id,
summary=move.journal_id.sale_activity_note,
user_id=move.journal_id.sale_activity_user_id.id or move.invoice_user_id.id,
)
customer_count, supplier_count = defaultdict(int), defaultdict(int)
for move in to_post:
if move.is_sale_document():
customer_count[move.partner_id] += 1
elif move.is_purchase_document():
supplier_count[move.partner_id] += 1
for partner, count in customer_count.items():
(partner | partner.commercial_partner_id)._increase_rank('customer_rank', count)
for partner, count in supplier_count.items():
(partner | partner.commercial_partner_id)._increase_rank('supplier_rank', count)
# Trigger action for paid invoices in amount is zero
to_post.filtered(
lambda m: m.is_invoice(include_receipts=True) and m.currency_id.is_zero(m.amount_total)
).action_invoice_paid()
# Force balance check since nothing prevents another module to create an incorrect entry.
# This is performed at the very end to avoid flushing fields before the whole processing.
to_post._check_balanced()
return to_post
class AccountMoveLines(models.Model):
_inherit = 'account.move.line'
takaful_sponsorship_id = fields.Many2one('takaful.sponsorship', related='move_id.takaful_sponsorship_id', store=True)