# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import _, api, fields, models, Command from odoo.osv import expression from odoo.tools.misc import formatLang, frozendict import markupsafe import uuid class BankRecWidgetLine(models.Model): _name = "bank.rec.widget.line" _inherit = "analytic.mixin" _description = "Line of the bank reconciliation widget" # This model is never saved inside the database. # _auto=False' & _table_query = "0" prevent the ORM to create the corresponding postgresql table. _auto = False _table_query = "0" wizard_id = fields.Many2one(comodel_name='bank.rec.widget') index = fields.Char(compute='_compute_index') flag = fields.Selection( selection=[ ('liquidity', 'liquidity'), ('new_aml', 'new_aml'), ('aml', 'aml'), ('exchange_diff', 'exchange_diff'), ('tax_line', 'tax_line'), ('manual', 'manual'), ('early_payment', 'early_payment'), ('auto_balance', 'auto_balance'), ], ) journal_default_account_id = fields.Many2one( related='wizard_id.st_line_id.journal_id.default_account_id', depends=['wizard_id'], ) account_id = fields.Many2one( comodel_name='account.account', compute='_compute_account_id', store=True, readonly=False, check_company=True, domain="""[ ('deprecated', '=', False), ('id', '!=', journal_default_account_id), ('account_type', 'not in', ('asset_cash', 'off_balance')), ]""", ) date = fields.Date( compute='_compute_date', store=True, readonly=False, ) name = fields.Char( compute='_compute_name', store=True, readonly=False, ) partner_id = fields.Many2one( comodel_name='res.partner', compute='_compute_partner_id', store=True, readonly=False, ) currency_id = fields.Many2one( comodel_name='res.currency', compute='_compute_currency_id', store=True, readonly=False, ) company_id = fields.Many2one(related='wizard_id.company_id') company_currency_id = fields.Many2one(related='wizard_id.company_currency_id') amount_currency = fields.Monetary( currency_field='currency_id', compute='_compute_amount_currency', store=True, readonly=False, ) balance = fields.Monetary( currency_field='company_currency_id', compute='_compute_balance', store=True, readonly=False, ) transaction_currency_id = fields.Many2one( related='wizard_id.st_line_id.foreign_currency_id', depends=['wizard_id'], ) amount_transaction_currency = fields.Monetary( currency_field='transaction_currency_id', related='wizard_id.st_line_id.amount_currency', depends=['wizard_id'], ) debit = fields.Monetary( currency_field='company_currency_id', compute='_compute_from_balance', ) credit = fields.Monetary( currency_field='company_currency_id', compute='_compute_from_balance', ) force_price_included_taxes = fields.Boolean() tax_base_amount_currency = fields.Monetary( currency_field='currency_id', ) source_aml_id = fields.Many2one(comodel_name='account.move.line') source_aml_move_id = fields.Many2one( comodel_name='account.move', compute='_compute_source_aml_fields', store=True, readonly=False, ) source_aml_move_name = fields.Char( compute='_compute_source_aml_fields', store=True, readonly=False, ) tax_repartition_line_id = fields.Many2one( comodel_name='account.tax.repartition.line', compute='_compute_tax_repartition_line_id', store=True, readonly=False, ) tax_ids = fields.Many2many( comodel_name='account.tax', compute='_compute_tax_ids', store=True, readonly=False, check_company=True, ) tax_tag_ids = fields.Many2many( comodel_name='account.account.tag', compute='_compute_tax_tag_ids', store=True, readonly=False, ) group_tax_id = fields.Many2one( comodel_name='account.tax', compute='_compute_group_tax_id', store=True, readonly=False, ) reconcile_model_id = fields.Many2one(comodel_name='account.reconcile.model') source_amount_currency = fields.Monetary(currency_field='currency_id') source_balance = fields.Monetary(currency_field='company_currency_id') source_debit = fields.Monetary( currency_field='company_currency_id', compute='_compute_from_source_balance', ) source_credit = fields.Monetary( currency_field='company_currency_id', compute='_compute_from_source_balance', ) display_stroked_amount_currency = fields.Boolean(compute='_compute_display_stroked_amount_currency') display_stroked_balance = fields.Boolean(compute='_compute_display_stroked_balance') partner_currency_id = fields.Many2one( comodel_name='res.currency', compute='_compute_partner_info', ) partner_receivable_account_id = fields.Many2one( comodel_name='account.account', compute='_compute_partner_info', ) partner_payable_account_id = fields.Many2one( comodel_name='account.account', compute='_compute_partner_info', ) partner_receivable_amount = fields.Monetary( currency_field='partner_currency_id', compute='_compute_partner_info', ) partner_payable_amount = fields.Monetary( currency_field='partner_currency_id', compute='_compute_partner_info', ) bank_account = fields.Char( compute='_compute_bank_account', ) suggestion_html = fields.Html( compute='_compute_suggestion', sanitize=False, ) suggestion_amount_currency = fields.Monetary( currency_field='currency_id', compute='_compute_suggestion', ) suggestion_balance = fields.Monetary( currency_field='company_currency_id', compute='_compute_suggestion', ) ref = fields.Char( compute='_compute_ref_narration', store=True, readonly=False, ) narration = fields.Html( compute='_compute_ref_narration', store=True, readonly=False, ) manually_modified = fields.Boolean() def _compute_index(self): for line in self: line.index = uuid.uuid4() @api.depends('source_aml_id') def _compute_account_id(self): for line in self: if line.flag in ('aml', 'new_aml', 'liquidity', 'exchange_diff'): line.account_id = line.source_aml_id.account_id else: line.account_id = line.account_id @api.depends('source_aml_id') def _compute_date(self): for line in self: if line.flag in ('aml', 'new_aml', 'exchange_diff'): line.date = line.source_aml_id.date elif line.flag in ('liquidity', 'auto_balance', 'manual', 'early_payment', 'tax_line'): line.date = line.wizard_id.st_line_id.date else: line.date = line.date @api.depends('source_aml_id') def _compute_name(self): for line in self: if line.flag in ('aml', 'new_aml', 'liquidity'): line.name = line.source_aml_id.name else: line.name = line.name @api.depends('source_aml_id') def _compute_partner_id(self): for line in self: if line.flag in ('aml', 'new_aml'): line.partner_id = line.source_aml_id.partner_id elif line.flag in ('liquidity', 'auto_balance', 'manual', 'early_payment', 'tax_line'): line.partner_id = line.wizard_id.partner_id else: line.partner_id = line.partner_id @api.depends('source_aml_id') def _compute_currency_id(self): for line in self: if line.flag in ('aml', 'new_aml', 'liquidity', 'exchange_diff'): line.currency_id = line.source_aml_id.currency_id elif line.flag in ('auto_balance', 'manual', 'early_payment'): line.currency_id = line.wizard_id.transaction_currency_id else: line.currency_id = line.currency_id @api.depends('source_aml_id') def _compute_balance(self): for line in self: if line.flag in ('aml', 'liquidity'): line.balance = line.source_aml_id.balance else: line.balance = line.balance @api.depends('source_aml_id') def _compute_amount_currency(self): for line in self: if line.flag in ('aml', 'liquidity'): line.amount_currency = line.source_aml_id.amount_currency else: line.amount_currency = line.amount_currency @api.depends('balance') def _compute_from_balance(self): for line in self: line.debit = line.balance if line.balance > 0.0 else 0.0 line.credit = -line.balance if line.balance < 0.0 else 0.0 @api.depends('source_balance') def _compute_from_source_balance(self): for line in self: line.source_debit = line.source_balance if line.source_balance > 0.0 else 0.0 line.source_credit = -line.source_balance if line.source_balance < 0.0 else 0.0 @api.depends('source_aml_id', 'account_id', 'partner_id') def _compute_analytic_distribution(self): cache = {} for line in self: if line.flag in ('liquidity', 'aml'): line.analytic_distribution = line.source_aml_id.analytic_distribution elif line.flag in ('tax_line', 'early_payment'): line.analytic_distribution = line.analytic_distribution else: arguments = frozendict({ "partner_id": line.partner_id.id, "partner_category_id": line.partner_id.category_id.ids, "account_prefix": line.account_id.code, "company_id": line.company_id.id, }) if arguments not in cache: cache[arguments] = self.env['account.analytic.distribution.model']._get_distribution(arguments) line.analytic_distribution = cache[arguments] or line.analytic_distribution @api.depends('source_aml_id') def _compute_tax_repartition_line_id(self): for line in self: if line.flag == 'aml': line.tax_repartition_line_id = line.source_aml_id.tax_repartition_line_id else: line.tax_repartition_line_id = line.tax_repartition_line_id @api.depends('source_aml_id') def _compute_tax_ids(self): for line in self: if line.flag == 'aml': line.tax_ids = [Command.set(line.source_aml_id.tax_ids.ids)] else: line.tax_ids = line.tax_ids @api.depends('source_aml_id') def _compute_tax_tag_ids(self): for line in self: if line.flag == 'aml': line.tax_tag_ids = [Command.set(line.source_aml_id.tax_tag_ids.ids)] else: line.tax_tag_ids = line.tax_tag_ids @api.depends('source_aml_id') def _compute_group_tax_id(self): for line in self: if line.flag == 'aml': line.group_tax_id = line.source_aml_id.group_tax_id else: line.group_tax_id = line.group_tax_id @api.depends('currency_id', 'amount_currency', 'source_amount_currency') def _compute_display_stroked_amount_currency(self): for line in self: line.display_stroked_amount_currency = \ line.flag == 'new_aml' \ and line.currency_id.compare_amounts(line.amount_currency, line.source_amount_currency) != 0 @api.depends('currency_id', 'balance', 'source_balance') def _compute_display_stroked_balance(self): for line in self: line.display_stroked_balance = \ line.flag in ['new_aml', 'exchange_diff'] \ and line.currency_id.compare_amounts(line.balance, line.source_balance) != 0 @api.depends('flag') def _compute_source_aml_fields(self): for line in self: line.source_aml_move_id = None line.source_aml_move_name = None if line.flag in ('new_aml', 'liquidity'): line.source_aml_move_id = line.source_aml_id.move_id line.source_aml_move_name = line.source_aml_id.move_id.name elif line.flag == 'aml': partials = line.source_aml_id.matched_debit_ids + line.source_aml_id.matched_credit_ids all_counterpart_lines = partials.debit_move_id + partials.credit_move_id counterpart_lines = all_counterpart_lines - line.source_aml_id - partials.exchange_move_id.line_ids if len(counterpart_lines) == 1: line.source_aml_move_id = counterpart_lines.move_id line.source_aml_move_name = counterpart_lines.move_id.name @api.depends('wizard_id.form_index', 'partner_id') def _compute_partner_info(self): for line in self: line.partner_receivable_amount = 0.0 line.partner_payable_amount = 0.0 line.partner_currency_id = None line.partner_receivable_account_id = None line.partner_payable_account_id = None if not line.partner_id or line.index != line.wizard_id.form_index: continue line.partner_currency_id = line.company_currency_id partner = line.partner_id.with_company(line.wizard_id.company_id) common_domain = [('parent_state', '=', 'posted'), ('partner_id', '=', partner.id)] line.partner_receivable_account_id = partner.property_account_receivable_id if line.partner_receivable_account_id: results = self.env['account.move.line']._read_group( domain=expression.AND([common_domain, [('account_id', '=', line.partner_receivable_account_id.id)]]), aggregates=['amount_residual:sum'], ) line.partner_receivable_amount = results[0][0] line.partner_payable_account_id = partner.property_account_payable_id if line.partner_payable_account_id: results = self.env['account.move.line']._read_group( domain=expression.AND([common_domain, [('account_id', '=', line.partner_payable_account_id.id)]]), aggregates=['amount_residual:sum'], ) line.partner_payable_amount = results[0][0] @api.depends('flag') def _compute_bank_account(self): for line in self: bank_account = line.wizard_id.st_line_id.partner_bank_id.display_name or line.wizard_id.st_line_id.account_number if line.flag == 'liquidity' and bank_account: line.bank_account = bank_account else: line.bank_account = None @api.depends('wizard_id.form_index', 'amount_currency', 'balance') def _compute_suggestion(self): for line in self: line.suggestion_html = None line.suggestion_amount_currency = None line.suggestion_balance = None if line.flag != 'new_aml' or line.index != line.wizard_id.form_index: continue aml = line.source_aml_id wizard = line.wizard_id residual_amount_before_reco = abs(aml.amount_residual_currency) residual_amount_after_reco = abs(aml.amount_residual_currency + line.amount_currency) reconciled_amount = residual_amount_before_reco - residual_amount_after_reco is_fully_reconciled = aml.currency_id.is_zero(residual_amount_after_reco) is_invoice = aml.move_id.is_invoice(include_receipts=True) if is_fully_reconciled: lines = [ _("The invoice %(display_name_html)s with an open amount of %(open_amount)s will be entirely paid by the transaction.") if is_invoice else _("%(display_name_html)s with an open amount of %(open_amount)s will be fully reconciled by the transaction.") ] partial_amounts = wizard._lines_check_partial_amount(line) if partial_amounts: lines.append( _("You might want to record a %(btn_start)spartial payment%(btn_end)s.") if is_invoice else _("You might want to make a %(btn_start)spartial reconciliation%(btn_end)s instead.") ) line.suggestion_amount_currency = partial_amounts['amount_currency'] line.suggestion_balance = partial_amounts['balance'] else: if is_invoice: lines = [ _("The invoice %(display_name_html)s with an open amount of %(open_amount)s will be reduced by %(amount)s."), _("You might want to set the invoice as %(btn_start)sfully paid%(btn_end)s."), ] else: lines = [ _("%(display_name_html)s with an open amount of %(open_amount)s will be reduced by %(amount)s."), _("You might want to %(btn_start)sfully reconcile%(btn_end)s the document."), ] line.suggestion_amount_currency = line.source_amount_currency line.suggestion_balance = line.source_balance display_name_html = markupsafe.Markup(""" """) % { 'display_name': aml.move_id.display_name, } extra_text = markupsafe.Markup('
').join(lines) % { 'amount': formatLang(self.env, reconciled_amount, currency_obj=aml.currency_id), 'open_amount': formatLang(self.env, residual_amount_before_reco, currency_obj=aml.currency_id), 'display_name_html': display_name_html, 'btn_start': markupsafe.Markup( ''), } line.suggestion_html = markupsafe.Markup("""
%s
""") % extra_text @api.depends('flag') def _compute_ref_narration(self): for line in self: if line.flag == 'liquidity': line.ref = line.wizard_id.st_line_id.ref line.narration = line.wizard_id.st_line_id.narration else: line.ref = line.narration = None def _get_aml_values(self, **kwargs): self.ensure_one() create_dict = { 'name': self.name, 'account_id': self.account_id.id, 'currency_id': self.currency_id.id, 'amount_currency': self.amount_currency, 'balance': self.debit - self.credit, 'reconcile_model_id': self.reconcile_model_id.id, 'analytic_distribution': self.analytic_distribution, 'tax_repartition_line_id': self.tax_repartition_line_id.id, 'tax_ids': [Command.set(self.tax_ids.ids)], 'tax_tag_ids': [Command.set(self.tax_tag_ids.ids)], 'group_tax_id': self.group_tax_id.id, **kwargs, } if self.flag == 'early_payment': create_dict['display_type'] = 'epd' return create_dict