270 lines
14 KiB
Python
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)
|
|
|