diff --git a/account_tax_balance/models/account_tax.py b/account_tax_balance/models/account_tax.py index a2560e6f..9abacf11 100644 --- a/account_tax_balance/models/account_tax.py +++ b/account_tax_balance/models/account_tax.py @@ -2,7 +2,7 @@ # Copyright 2016 Antonio Espinosa # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -from odoo import _, api, fields, models +from odoo import api, fields, models class AccountTax(models.Model): @@ -19,9 +19,9 @@ class AccountTax(models.Model): string="Base Balance Refund", compute="_compute_balance" ) has_moves = fields.Boolean( - string="Has balance in period", - compute="_compute_has_moves", + compute="_compute_balance", search="_search_has_moves", + string="Has balance in period", ) def get_context_values(self): @@ -34,68 +34,62 @@ class AccountTax(models.Model): context.get("target_move", "posted"), ) - def _account_tax_ids_with_moves(self): - """ Return all account.tax ids for which there is at least - one account.move.line in the context period - for the user company. - - Caveat: this ignores record rules and ACL but it is good - enough for filtering taxes with activity during the period. - """ - from_date, to_date, company_ids, _ = self.get_context_values() - company_ids = tuple(company_ids) - req = """ - SELECT id - FROM account_tax at - WHERE - company_id in %s AND - EXISTS ( - SELECT 1 FROM account_move_Line aml - WHERE - date >= %s AND - date <= %s AND - company_id in %s AND ( - tax_line_id = at.id OR - EXISTS ( - SELECT 1 FROM account_move_line_account_tax_rel - WHERE account_move_line_id = aml.id AND - account_tax_id = at.id - ) - ) - ) - """ - self.env.cr.execute(req, (company_ids, from_date, to_date, company_ids)) - return [r[0] for r in self.env.cr.fetchall()] - - def _compute_has_moves(self): - ids_with_moves = set(self._account_tax_ids_with_moves()) - for tax in self: - tax.has_moves = tax.id in ids_with_moves - - @api.model - def _is_unsupported_search_operator(self, operator): - return operator != "=" - @api.model def _search_has_moves(self, operator, value): - if self._is_unsupported_search_operator(operator) or not value: - raise ValueError(_("Unsupported search operator")) - ids_with_moves = self._account_tax_ids_with_moves() - return [("id", "in", ids_with_moves)] + assert isinstance(value, bool), "Not implemented" + assert operator == "=", "Not implemented" + ids_with_moves = self.search([]).filtered(lambda t: t.has_moves == value) + return [("id", "in", ids_with_moves.ids)] + + def _compute_regular_and_refund(self, total): + tax_ids = total.keys() + total_refund = {} + total_regular = {} + for tax_id in tax_ids: + total_refund[tax_id] = 0.0 + total_regular[tax_id] = 0.0 + move_types = total[tax_id].keys() + for move_type in move_types: + if move_type in ["receivable_refund", "payable_refund"]: + total_refund[tax_id] += (-1) * total[tax_id][move_type] + else: + total_regular[tax_id] += (-1) * total[tax_id][move_type] + return total_regular, total_refund def _compute_balance(self): + total_balance_tax = self.compute_balance(tax_or_base="tax") + total_balance_base = self.compute_balance(tax_or_base="base") + total_balance_regular, total_balance_refund = self._compute_regular_and_refund( + total_balance_tax + ) + ( + total_base_balance_regular, + total_base_balance_refund, + ) = self._compute_regular_and_refund(total_balance_base) + founded_taxes_ids = set(list(total_balance_tax.keys())).union( + set(list(total_balance_base.keys())) + ) for tax in self: - tax.balance_regular = tax.compute_balance( - tax_or_base="tax", move_type="regular" + tax.has_moves = tax.id in list(founded_taxes_ids) + tax.balance_regular = ( + total_balance_regular[tax.id] + if tax.id in total_balance_regular.keys() + else 0.0 ) - tax.base_balance_regular = tax.compute_balance( - tax_or_base="base", move_type="regular" + tax.base_balance_regular = ( + total_base_balance_regular[tax.id] + if tax.id in total_base_balance_regular.keys() + else 0.0 ) - tax.balance_refund = tax.compute_balance( - tax_or_base="tax", move_type="refund" + tax.balance_refund = ( + total_balance_refund[tax.id] + if tax.id in total_balance_refund.keys() + else 0.0 ) - tax.base_balance_refund = tax.compute_balance( - tax_or_base="base", move_type="refund" + tax.base_balance_refund = ( + total_base_balance_refund[tax.id] + if tax.id in total_base_balance_refund.keys() + else 0.0 ) tax.balance = tax.balance_regular + tax.balance_refund tax.base_balance = tax.base_balance_regular + tax.base_balance_refund @@ -116,58 +110,117 @@ class AccountTax(models.Model): state = [] return state - def get_move_line_partial_domain(self, from_date, to_date, company_ids): - return [ - ("date", "<=", to_date), - ("date", ">=", from_date), - ("company_id", "in", company_ids), - ] + def get_move_line_partial_where(self, from_date, to_date, company_ids): + query = "aml.date <= %s AND aml.date >= %s AND aml.company_id IN %s" + params = [to_date, from_date, tuple(company_ids)] + return query, params - def compute_balance(self, tax_or_base="tax", move_type=None): - self.ensure_one() - domain = self.get_move_lines_domain( - tax_or_base=tax_or_base, move_type=move_type + def compute_balance(self, tax_or_base="tax"): + # There's really bad performace in m2m fields. + # So we better do a direct query. + # See https://github.com/odoo/odoo/issues/30350 + _select, _group_by, query, params = self.get_move_lines_query( + tax_or_base=tax_or_base ) - # balance is debit - credit whereas on tax return you want to see what - # vat has to be paid so: - # VAT on sales (credit) - VAT on purchases (debit). - - balance = self.env["account.move.line"].read_group(domain, ["balance"], [])[0][ - "balance" - ] - return balance and -balance or 0 - - def get_balance_domain(self, state_list, type_list): - domain = [ - ("move_id.state", "in", state_list), - ("tax_line_id", "=", self.id), - ("tax_exigible", "=", True), - ] - if type_list: - domain.append(("move_id.move_type", "in", type_list)) - return domain - - def get_base_balance_domain(self, state_list, type_list): - domain = [ - ("move_id.state", "in", state_list), - ("tax_ids", "in", self.id), - ("tax_exigible", "=", True), - ] - if type_list: - domain.append(("move_id.move_type", "in", type_list)) - return domain + query = query.format(select_clause=_select, group_by_clause=_group_by) + self.env.cr.execute(query, params) # pylint: disable=E8103 + results = self.env.cr.fetchall() + total_balance = {} + for balance, tax_id, move_type in results: + if tax_id not in total_balance.keys(): + total_balance[tax_id] = {} + total_balance[tax_id][move_type] = balance + return total_balance - def get_move_lines_domain(self, tax_or_base="tax", move_type=None): + def get_move_lines_query(self, tax_or_base="tax"): from_date, to_date, company_ids, target_move = self.get_context_values() state_list = self.get_target_state_list(target_move) - type_list = self.get_target_type_list(move_type) - domain = self.get_move_line_partial_domain(from_date, to_date, company_ids) - balance_domain = [] + base_query = self.get_move_lines_base_query() + _where = "" + _joins = "" + _group_by = "" + _params = [] + _select = "SELECT SUM(balance)" + _group_by = " GROUP BY am.move_type, " + where, params = self.get_move_line_partial_where( + from_date, to_date, company_ids + ) + _where += where + _params += params if tax_or_base == "tax": - balance_domain = self.get_balance_domain(state_list, type_list) + select, where, group_by, params = self.get_balance_where(state_list) + _where += where + _params += params + _select += select + _group_by += group_by elif tax_or_base == "base": - balance_domain = self.get_base_balance_domain(state_list, type_list) - domain.extend(balance_domain) + select, joins, where, group_by, params = self.get_base_balance_where( + state_list + ) + _where += where + _joins += joins + _params += params + _select += select + _group_by += group_by + query = base_query.format( + select_clause="{select_clause}", + where_clause=_where, + additional_joins=_joins, + group_by_clause="{group_by_clause}", + ) + return _select, _group_by, query, _params + + def get_move_lines_base_query(self): + return ( + "{select_clause} FROM account_move_line AS aml " + "INNER JOIN account_move AS am ON aml.move_id = am.id " + "{additional_joins}" + " WHERE {where_clause}" + "{group_by_clause}" + ) + + def get_balance_where(self, state_list): + select = ", aml.tax_line_id as tax_id, am.move_type" + where = ( + " AND am.state IN %s" + " AND aml.tax_line_id IS NOT NULL" + " AND aml.tax_exigible = True" + ) + group_by = "aml.tax_line_id" + params = [tuple(state_list)] + return select, where, group_by, params + + def get_base_balance_where(self, state_list): + select = ", rel.account_tax_id as tax_id, am.move_type" + joins = ( + " INNER JOIN account_move_line_account_tax_rel AS rel " + "ON aml.id = rel.account_move_line_id" + ) + group_by = "rel.account_tax_id" + where = " AND am.state IN %s" " AND aml.tax_exigible = True " + params = [tuple(state_list)] + return select, joins, where, group_by, params + + def get_move_lines_domain(self, tax_or_base="tax", move_type=None): + _select, _group_by, query, params = self.get_move_lines_query( + tax_or_base=tax_or_base + ) + _select = "SELECT aml.id" + _group_by = "" + if tax_or_base == "tax": + query += " AND aml.tax_line_id = " + str(self.id) + elif tax_or_base == "base": + query += " AND rel.account_tax_id = " + str(self.id) + type_list = self.get_target_type_list(move_type) + if type_list: + query += " AND am.move_type IN %s" + params += [tuple(type_list)] + query = query.format(select_clause=_select, group_by_clause=_group_by) + self.env.cr.execute(query, params) # pylint: disable=E8103 + amls = [] + for (aml_id,) in self.env.cr.fetchall(): + amls.append(aml_id) + domain = [("id", "in", amls)] return domain def get_lines_action(self, tax_or_base="tax", move_type=None): diff --git a/account_tax_balance/views/account_tax_view.xml b/account_tax_balance/views/account_tax_view.xml index 71972e49..deab5ef2 100644 --- a/account_tax_balance/views/account_tax_view.xml +++ b/account_tax_balance/views/account_tax_view.xml @@ -65,6 +65,11 @@ + Taxes Balance account.tax tree - [('has_moves', '=', True)] + {"search_default_filter_has_moves":1}