diff --git a/account_tax_balance/models/account_tax.py b/account_tax_balance/models/account_tax.py index e4c47356..81d5ead2 100644 --- a/account_tax_balance/models/account_tax.py +++ b/account_tax_balance/models/account_tax.py @@ -2,7 +2,7 @@ # © 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): @@ -27,9 +27,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): @@ -39,68 +39,71 @@ class AccountTax(models.Model): context.get('to_date', fields.Date.context_today(self)), context.get('company_id', self.env.user.company_id.id), context.get('target_move', 'posted'), + context.get('display_all', False), ) - 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. - """ - req = """ - SELECT id - FROM account_tax at - WHERE - company_id = %s AND - EXISTS ( - SELECT 1 FROM account_move_Line aml - WHERE - date >= %s AND - date <= %s AND - company_id = %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 - ) - ) - ) - """ - from_date, to_date, company_id, target_move = self.get_context_values() - self.env.cr.execute( - req, (company_id, from_date, to_date, company_id)) - return [r[0] for r in self.env.cr.fetchall()] - - @api.multi - 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" + from_date, to_date, company_id, target_move, display_all \ + = self.get_context_values() + if display_all: + ids_with_moves = self.env['account.tax'].search([]) + else: + 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.base_balance_regular = tax.compute_balance( - tax_or_base='base', move_type='regular') - tax.balance_refund = tax.compute_balance( - tax_or_base='tax', move_type='refund') - tax.base_balance_refund = tax.compute_balance( - tax_or_base='base', move_type='refund') + 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 = ( + total_base_balance_regular[tax.id] + if tax.id in total_base_balance_regular.keys() + else 0.0 + ) + tax.balance_refund = ( + total_balance_refund[tax.id] + if tax.id in total_balance_refund.keys() + else 0.0 + ) + 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) @@ -121,58 +124,118 @@ class AccountTax(models.Model): state = [] return state - def get_move_line_partial_domain(self, from_date, to_date, company_id): - return [ - ('date', '<=', to_date), - ('date', '>=', from_date), - ('company_id', '=', company_id), - ] + def get_move_line_partial_where(self, from_date, to_date, company_id): + query = "aml.date <= %s AND aml.date >= %s AND aml.company_id = %s" + params = [to_date, from_date, company_id] + 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) - # 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 + 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 + ) + 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): - from_date, to_date, company_id, target_move = self.get_context_values() + def get_move_lines_query(self, tax_or_base="tax"): + from_date, to_date, company_id, target_move, display_all \ + = self.get_context_values() state_list = self.get_target_state_list(target_move) + 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_id + ) + _where += where + _params += params + if tax_or_base == "tax": + 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": + 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) - domain = self.get_move_line_partial_domain( - from_date, to_date, company_id) - balance_domain = [] - if tax_or_base == 'tax': - balance_domain = self.get_balance_domain(state_list, type_list) - elif tax_or_base == 'base': - balance_domain = self.get_base_balance_domain( - state_list, type_list) - domain.extend(balance_domain) + 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 fdfd092b..7ade7524 100644 --- a/account_tax_balance/views/account_tax_view.xml +++ b/account_tax_balance/views/account_tax_view.xml @@ -46,6 +46,7 @@ + @@ -60,7 +61,7 @@ account.tax form tree - [('has_moves', '=', True)] + {"search_default_filter_has_moves":1} diff --git a/account_tax_balance/wizard/open_tax_balances.py b/account_tax_balance/wizard/open_tax_balances.py index 6063a9db..1bdbec07 100644 --- a/account_tax_balance/wizard/open_tax_balances.py +++ b/account_tax_balance/wizard/open_tax_balances.py @@ -18,6 +18,10 @@ class WizardOpenTaxBalances(models.TransientModel): ('posted', 'All Posted Entries'), ('all', 'All Entries'), ], 'Target Moves', required=True, default='posted') + display_all = fields.Boolean( + string="Display all tax accounts", + default=False + ) @api.onchange('date_range_id') def onchange_date_range_id(self): @@ -55,5 +59,7 @@ class WizardOpenTaxBalances(models.TransientModel): 'to_date': self.to_date, 'target_move': self.target_move, 'company_id': self.company_id.id, + 'search_default_filter_has_moves': True, + 'display_all': self.display_all, } return vals diff --git a/account_tax_balance/wizard/open_tax_balances_view.xml b/account_tax_balance/wizard/open_tax_balances_view.xml index 1cd5b0ef..cf965640 100644 --- a/account_tax_balance/wizard/open_tax_balances_view.xml +++ b/account_tax_balance/wizard/open_tax_balances_view.xml @@ -13,6 +13,7 @@ +