From 62d508bb2077e22683883713fd1dadd1430f6d73 Mon Sep 17 00:00:00 2001 From: Enric Tobella Date: Fri, 13 Sep 2024 10:33:50 +0200 Subject: [PATCH] [IMP] account_reconcile_oca: Improve multicurrency management. We will use currency of the line in order to get the suspense and max line value --- .../models/account_account_reconcile.py | 6 +- .../models/account_bank_statement_line.py | 152 ++++++++++++++++-- .../models/account_reconcile_abstract.py | 39 +++-- .../js/widgets/reconcile_data_widget.esm.js | 12 ++ .../static/src/xml/reconcile.xml | 13 +- 5 files changed, 195 insertions(+), 27 deletions(-) diff --git a/account_reconcile_oca/models/account_account_reconcile.py b/account_reconcile_oca/models/account_account_reconcile.py index 0ee42da6..c03ff2ee 100644 --- a/account_reconcile_oca/models/account_account_reconcile.py +++ b/account_reconcile_oca/models/account_account_reconcile.py @@ -164,7 +164,11 @@ class AccountAccountReconcile(models.Model): for line_id in counterparts: max_amount = amount if line_id == counterparts[-1] else 0 lines = self._get_reconcile_line( - self.env["account.move.line"].browse(line_id), "other", True, max_amount + self.env["account.move.line"].browse(line_id), + "other", + True, + max_amount, + move=True, ) new_data["data"] += lines amount += sum(line["amount"] for line in lines) diff --git a/account_reconcile_oca/models/account_bank_statement_line.py b/account_reconcile_oca/models/account_bank_statement_line.py index fded5c83..434f18e2 100644 --- a/account_reconcile_oca/models/account_bank_statement_line.py +++ b/account_reconcile_oca/models/account_bank_statement_line.py @@ -201,9 +201,17 @@ class AccountBankStatementLine(models.Model): new_data = [] is_new_line = True pending_amount = 0.0 + currency = self._get_reconcile_currency() for line in data: if line["kind"] != "suspense": - pending_amount += line["amount"] + pending_amount += currency._convert( + line["currency_amount"], + self.env["res.currency"].browse( + line.get("line_currency_id", currency.id) + ), + self.company_id, + self.date, + ) if self.add_account_move_line_id.id in line.get( "counterpart_line_ids", [] ): @@ -212,7 +220,11 @@ class AccountBankStatementLine(models.Model): new_data.append(line) if is_new_line: reconcile_auxiliary_id, lines = self._get_reconcile_line( - self.add_account_move_line_id, "other", True, pending_amount + self.add_account_move_line_id, + "other", + True, + max_amount=pending_amount, + move=True, ) new_data += lines self.reconcile_data_info = self._recompute_suspense_line( @@ -226,6 +238,7 @@ class AccountBankStatementLine(models.Model): def _recompute_suspense_line(self, data, reconcile_auxiliary_id, manual_reference): can_reconcile = True total_amount = 0 + currency_amount = 0 new_data = [] suspense_line = False counterparts = [] @@ -240,10 +253,28 @@ class AccountBankStatementLine(models.Model): if line["kind"] != "suspense": new_data.append(line) total_amount += line["amount"] + if line.get("currency_amount"): + currency_amount += ( + self.env["res.currency"] + .browse(line["line_currency_id"]) + ._convert( + line["currency_amount"], + self._get_reconcile_currency(), + self.company_id, + self.date, + ) + ) + else: + currency_amount += self.company_id.currency_id._convert( + line["amount"], + self._get_reconcile_currency(), + self.company_id, + self.date, + ) else: suspense_line = line if not float_is_zero( - total_amount, precision_digits=self.currency_id.decimal_places + total_amount, precision_digits=self.company_id.currency_id.decimal_places ): can_reconcile = False if suspense_line: @@ -255,6 +286,7 @@ class AccountBankStatementLine(models.Model): } ) else: + suspense_line = { "reference": "reconcile_auxiliary;%s" % reconcile_auxiliary_id, "id": False, @@ -269,8 +301,8 @@ class AccountBankStatementLine(models.Model): "debit": -total_amount if total_amount < 0 else 0.0, "kind": "suspense", "currency_id": self.company_id.currency_id.id, - "line_currency_id": self.company_id.currency_id.id, - "currency_amount": -total_amount, + "line_currency_id": self.currency_id.id, + "currency_amount": -currency_amount, } reconcile_auxiliary_id += 1 new_data.append(suspense_line) @@ -375,7 +407,7 @@ class AccountBankStatementLine(models.Model): if self.manual_line_id.exists() and self.manual_line_id: self.manual_amount = self.manual_in_currency_id._convert( self.manual_amount_in_currency, - self.company_id.currency_id, + self._get_reconcile_currency(), self.company_id, self.manual_line_id.date, ) @@ -529,7 +561,10 @@ class AccountBankStatementLine(models.Model): reconcile_auxiliary_id = 1 for line in liquidity_lines: reconcile_auxiliary_id, lines = self._get_reconcile_line( - line, "liquidity", reconcile_auxiliary_id=reconcile_auxiliary_id + line, + "liquidity", + reconcile_auxiliary_id=reconcile_auxiliary_id, + move=True, ) data += lines if not from_unreconcile: @@ -574,16 +609,87 @@ class AccountBankStatementLine(models.Model): self.manual_reference, ) for line in other_lines: - reconcile_auxiliary_id, lines = self._get_reconcile_line( - line, "other", from_unreconcile=from_unreconcile - ) - data += lines + partial_lines = self._all_partials_lines(line) if from_unreconcile else [] + if partial_lines: + for reconciled_line in ( + partial_lines.debit_move_id + partial_lines.credit_move_id - line + ): + if ( + reconciled_line.move_id.journal_id + == self.company_id.currency_exchange_journal_id + ): + reconcile_auxiliary_id, lines = self._get_reconcile_line( + reconciled_line.move_id.line_ids - reconciled_line, + "other", + from_unreconcile=False, + move=True, + ) + data += lines + continue + partial = partial_lines.filtered( + lambda r: r.debit_move_id == reconciled_line + or r.credit_move_id == reconciled_line + ) + partial_amount = sum( + partial.filtered( + lambda r: r.credit_move_id == reconciled_line + ).mapped("amount") + ) - sum( + partial.filtered( + lambda r: r.debit_move_id == reconciled_line + ).mapped("amount") + ) + reconcile_auxiliary_id, lines = self._get_reconcile_line( + reconciled_line, + "other", + from_unreconcile={ + "amount": partial_amount, + "credit": partial_amount > 0 and partial_amount, + "debit": partial_amount < 0 and -partial_amount, + "currency_amount": sum( + partial.filtered( + lambda r: r.credit_move_id == reconciled_line + ).mapped("credit_amount_currency") + ) + - sum( + partial.filtered( + lambda r: r.debit_move_id == reconciled_line + ).mapped("debit_amount_currency") + ), + }, + move=True, + ) + data += lines + else: + reconcile_auxiliary_id, lines = self._get_reconcile_line( + line, "other", from_unreconcile=False + ) + data += lines + return self._recompute_suspense_line( data, reconcile_auxiliary_id, self.manual_reference, ) + def _all_partials_lines(self, lines): + reconciliation_lines = lines.filtered( + lambda x: x.account_id.reconcile + or x.account_id.account_type in ("asset_cash", "liability_credit_card") + ) + current_lines = reconciliation_lines + current_partials = self.env["account.partial.reconcile"] + partials = self.env["account.partial.reconcile"] + while current_lines: + current_partials = ( + current_lines.matched_debit_ids + current_lines.matched_credit_ids + ) - current_partials + current_lines = ( + current_partials.debit_move_id + current_partials.credit_move_id + ) - current_lines + partials += current_partials + return partials + def clean_reconcile(self): self.reconcile_data_info = self._default_reconcile_data() self.can_reconcile = self.reconcile_data_info.get("can_reconcile", False) @@ -737,12 +843,13 @@ class AccountBankStatementLine(models.Model): to_reverse._reverse_moves(default_values_list, cancel=True) def _reconcile_move_line_vals(self, line, move_id=False): - return { + vals = { "move_id": move_id or self.move_id.id, "account_id": line["account_id"][0], "partner_id": line.get("partner_id") and line["partner_id"][0], "credit": line["credit"], "debit": line["debit"], + "currency_id": line.get("line_currency_id", self.company_id.currency_id.id), "tax_ids": line.get("tax_ids", []), "tax_tag_ids": line.get("tax_tag_ids", []), "group_tax_id": line.get("group_tax_id"), @@ -751,6 +858,11 @@ class AccountBankStatementLine(models.Model): "name": line.get("name"), "reconcile_model_id": line.get("reconcile_model_id"), } + if line.get("line_currency_id") and line["currency_id"] != line.get( + "line_currency_id" + ): + vals["amount_currency"] = line["currency_amount"] + return vals @api.model_create_multi def create(self, mvals): @@ -770,7 +882,9 @@ class AccountBankStatementLine(models.Model): data = [] for line in liquidity_lines: reconcile_auxiliary_id, lines = record._get_reconcile_line( - line, "liquidity" + line, + "liquidity", + move=True, ) data += lines reconcile_auxiliary_id = 1 @@ -785,7 +899,7 @@ class AccountBankStatementLine(models.Model): amount = self.amount for line in res.get("amls", []): reconcile_auxiliary_id, line_datas = record._get_reconcile_line( - line, "other", is_counterpart=True, max_amount=amount + line, "other", is_counterpart=True, max_amount=amount, move=True ) amount -= sum(line_data.get("amount") for line_data in line_datas) data += line_datas @@ -847,6 +961,7 @@ class AccountBankStatementLine(models.Model): is_counterpart=True, reconcile_auxiliary_id=reconcile_auxiliary_id, max_amount=original_amount, + move=True, ) new_data += lines new_data.append( @@ -894,6 +1009,7 @@ class AccountBankStatementLine(models.Model): max_amount=False, from_unreconcile=False, reconcile_auxiliary_id=False, + move=False, ): new_vals = super()._get_reconcile_line( line, @@ -901,6 +1017,7 @@ class AccountBankStatementLine(models.Model): is_counterpart=is_counterpart, max_amount=max_amount, from_unreconcile=from_unreconcile, + move=move, ) rates = [] for vals in new_vals: @@ -989,3 +1106,10 @@ class AccountBankStatementLine(models.Model): "split_line_id": self.id, } return action + + def _get_reconcile_currency(self): + return ( + self.currency_id + or self.journal_id.currency_id + or self.company_id._currency_id + ) diff --git a/account_reconcile_oca/models/account_reconcile_abstract.py b/account_reconcile_oca/models/account_reconcile_abstract.py index fff2772f..bd8c5025 100644 --- a/account_reconcile_oca/models/account_reconcile_abstract.py +++ b/account_reconcile_oca/models/account_reconcile_abstract.py @@ -33,24 +33,39 @@ class AccountReconcileAbstract(models.AbstractModel): related="company_id.currency_id", string="Company Currency" ) + def _get_reconcile_currency(self): + return self.currency_id or self.company_id._currency_id + def _get_reconcile_line( - self, line, kind, is_counterpart=False, max_amount=False, from_unreconcile=False + self, + line, + kind, + is_counterpart=False, + max_amount=False, + from_unreconcile=False, + move=False, ): date = self.date if "date" in self._fields else line.date original_amount = amount = net_amount = line.debit - line.credit if is_counterpart: currency_amount = -line.amount_residual_currency or line.amount_residual amount = -line.amount_residual - currency = line.currency_id or self.company_id.currency_id + currency = line.currency_id or line.company_id.currency_id original_amount = net_amount = -line.amount_residual if max_amount: - currency_max_amount = self.company_id.currency_id._convert( - max_amount, currency, self.company_id, date + real_currency_amount = currency._convert( + currency_amount, + self._get_reconcile_currency(), + self.company_id, + date, ) if ( - -currency_amount > currency_max_amount > 0 - or -currency_amount < currency_max_amount < 0 + -real_currency_amount > max_amount > 0 + or -real_currency_amount < max_amount < 0 ): + currency_max_amount = self._get_reconcile_currency()._convert( + max_amount, currency, self.company_id, date + ) amount = currency_max_amount net_amount = -max_amount currency_amount = -amount @@ -63,6 +78,8 @@ class AccountReconcileAbstract(models.AbstractModel): else: currency_amount = line.amount_currency vals = { + "move_id": move and line.move_id.id, + "move": move and line.move_id.name, "reference": "account.move.line;%s" % line.id, "id": line.id, "account_id": line.account_id.name_get()[0], @@ -82,11 +99,11 @@ class AccountReconcileAbstract(models.AbstractModel): if from_unreconcile: vals.update( { - "id": False, - "counterpart_line_ids": ( - line.matched_debit_ids.mapped("debit_move_id") - | line.matched_credit_ids.mapped("credit_move_id") - ).ids, + "credit": vals["debit"] and from_unreconcile["debit"], + "debit": vals["credit"] and from_unreconcile["credit"], + "amount": from_unreconcile["amount"], + "net_amount": from_unreconcile["amount"], + "currency_amount": from_unreconcile["currency_amount"], } ) if not float_is_zero( diff --git a/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js b/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js index 37f524e8..cd34592e 100644 --- a/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js +++ b/account_reconcile_oca/static/src/js/widgets/reconcile_data_widget.esm.js @@ -3,12 +3,15 @@ import fieldUtils from "web.field_utils"; import {registry} from "@web/core/registry"; import session from "web.session"; +import {useService} from "@web/core/utils/hooks"; const {Component} = owl; export class AccountReconcileDataWidget extends Component { setup() { super.setup(...arguments); + this.orm = useService("orm"); + this.action = useService("action"); this.foreignCurrency = this.props && this.props.record && @@ -83,6 +86,15 @@ export class AccountReconcileDataWidget extends Component { }); this.env.bus.trigger("RECONCILE_PAGE_NAVIGATE", triggerEv); } + async openMove(ev, moveId) { + ev.preventDefault(); + ev.stopPropagation(); + console.log(moveId); + const action = await this.orm.call("account.move", "get_formview_action", [ + [moveId], + ]); + this.action.doAction(action); + } } AccountReconcileDataWidget.template = "account_reconcile_oca.ReconcileDataWidget"; diff --git a/account_reconcile_oca/static/src/xml/reconcile.xml b/account_reconcile_oca/static/src/xml/reconcile.xml index ac26c792..f1f77d71 100644 --- a/account_reconcile_oca/static/src/xml/reconcile.xml +++ b/account_reconcile_oca/static/src/xml/reconcile.xml @@ -116,7 +116,18 @@ t-on-click="(ev) => this.selectReconcileLine(ev, reconcile_line)" t-att-class="'o_reconcile_widget_line ' + reconcile_line.kind + (props.record.data.manual_reference == reconcile_line.reference ? ' selected ' : ' ')" > - + +
+
+ + + +
+