# Copyright 2015 Pedro M. Baeza # Copyright 2017 Tecnativa - Vicent Cubells # License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html from odoo import _, api, exceptions, fields, models class AccountMoveMakeNetting(models.TransientModel): _name = "account.move.make.netting" _description = "Wizard to generate account moves for netting" journal_id = fields.Many2one( comodel_name="account.journal", required=True, domain="[('type', '=', 'general')]", ) move_line_ids = fields.Many2many(comodel_name="account.move.line") balance = fields.Float(readonly=True) balance_type = fields.Selection( selection=[("pay", "To pay"), ("receive", "To receive")], readonly=True, ) @api.model def default_get(self, fields_list): if len(self.env.context.get("active_ids", [])) < 2: raise exceptions.ValidationError( _("You should compensate at least 2 journal entries.") ) move_lines = self.env["account.move.line"].browse( self.env.context["active_ids"] ) if any( x not in ("payable", "receivable") for x in move_lines.mapped("account_id.user_type_id.type") ): raise exceptions.ValidationError( _("All entries must have a receivable or payable account") ) if any(move_lines.mapped("reconciled")): raise exceptions.ValidationError(_("All entries mustn't been reconciled")) if len(move_lines.mapped("account_id")) == 1: raise exceptions.ValidationError( _( "The 'Compensate' function is intended to balance " "operations on different accounts for the same partner.\n" "In this case all selected entries belong to the same " "account.\n Please use the 'Reconcile' function." ) ) if len(move_lines.mapped("partner_id")) != 1: raise exceptions.ValidationError( _( "All entries should have a partner and the partner must " "be the same for all." ) ) res = super().default_get(fields_list) res["move_line_ids"] = [(6, 0, move_lines.ids)] debit_move_lines_debit = move_lines.filtered("debit") credit_move_lines_debit = move_lines.filtered("credit") balance = abs(sum(debit_move_lines_debit.mapped("amount_residual"))) - abs( sum(credit_move_lines_debit.mapped("amount_residual")) ) res["balance"] = abs(balance) res["balance_type"] = "pay" if balance < 0 else "receive" return res def button_compensate(self): self.ensure_one() # Create account move move = self.env["account.move"].create( {"ref": _("AR/AP netting"), "journal_id": self.journal_id.id} ) # Group amounts by account account_groups = self.move_line_ids.read_group( [("id", "in", self.move_line_ids.ids)], ["account_id", "amount_residual"], ["account_id"], ) debtors = [] creditors = [] total_debtors = 0 total_creditors = 0 for account_group in account_groups: balance = account_group["amount_residual"] group_vals = { "account_id": account_group["account_id"][0], "balance": abs(balance), } if balance > 0: debtors.append(group_vals) total_debtors += balance else: creditors.append(group_vals) total_creditors += abs(balance) # Create move lines netting_amount = min(total_creditors, total_debtors) field_map = {1: "debit", 0: "credit"} move_lines = [] for i, group in enumerate([debtors, creditors]): available_amount = netting_amount for account_group in group: if account_group["balance"] > available_amount: amount = available_amount else: amount = account_group["balance"] move_line_vals = { field_map[i]: amount, "partner_id": self.move_line_ids[0].partner_id.id, "name": move.ref, "account_id": account_group["account_id"], } move_lines.append((0, 0, move_line_vals)) available_amount -= account_group["balance"] if available_amount <= 0: break if move_lines: move.write({"line_ids": move_lines}) move.action_post() # Make reconciliation for move_line in move.line_ids: to_reconcile = move_line + self.move_line_ids.filtered( lambda x: x.account_id == move_line.account_id ) to_reconcile.reconcile() # Open created move action = self.env["ir.actions.act_window"]._for_xml_id( "account.action_move_journal_line" ) action["view_mode"] = "form" del action["views"] del action["view_id"] action["res_id"] = move.id return action