diff --git a/addons/account/models/account_analytic_account.py b/addons/account/models/account_analytic_account.py index 4f797e5751c..e3021c85505 100644 --- a/addons/account/models/account_analytic_account.py +++ b/addons/account/models/account_analytic_account.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from odoo import api, fields, models, _ +from odoo.tools import SQL class AccountAnalyticAccount(models.Model): @@ -24,18 +25,24 @@ class AccountAnalyticAccount(models.Model): ('move_id.move_type', 'in', sale_types), ]) query.add_where( - 'account_move_line.analytic_distribution ?| %s', - [[str(account_id) for account_id in self.ids]], + SQL( + "%s && %s", + [str(account_id) for account_id in self.ids], + self.env['account.move.line']._query_analytic_accounts(), + ) ) query_string, query_param = query.select( - 'jsonb_object_keys(account_move_line.analytic_distribution) as account_id', - 'COUNT(DISTINCT(account_move_line.move_id)) as move_count', + r"""DISTINCT move_id, (regexp_matches(jsonb_object_keys(account_move_line.analytic_distribution), '\d+', 'g'))[1]::int as account_id""" ) - query_string = f"{query_string} GROUP BY jsonb_object_keys(account_move_line.analytic_distribution)" + query_string = f""" + SELECT account_id, count(move_id) FROM + ({query_string}) distribution + GROUP BY account_id + """ self._cr.execute(query_string, query_param) - data = {int(record.get('account_id')): record.get('move_count') for record in self._cr.dictfetchall()} + data = {res['account_id']: res['count'] for res in self._cr.dictfetchall()} for account in self: account.invoice_count = data.get(account.id, 0) @@ -48,25 +55,37 @@ class AccountAnalyticAccount(models.Model): ('move_id.move_type', 'in', purchase_types), ]) query.add_where( - 'account_move_line.analytic_distribution ?| %s', - [[str(account_id) for account_id in self.ids]], + SQL( + "%s && %s", + [str(account_id) for account_id in self.ids], + self.env['account.move.line']._query_analytic_accounts(), + ) ) query_string, query_param = query.select( - 'jsonb_object_keys(account_move_line.analytic_distribution) as account_id', - 'COUNT(DISTINCT(account_move_line.move_id)) as move_count', + r"""DISTINCT move_id, (regexp_matches(jsonb_object_keys(account_move_line.analytic_distribution), '\d+', 'g'))[1]::int as account_id""" ) - query_string = f"{query_string} GROUP BY jsonb_object_keys(account_move_line.analytic_distribution)" + query_string = f""" + SELECT account_id, count(move_id) FROM + ({query_string}) distribution + GROUP BY account_id + """ self._cr.execute(query_string, query_param) - data = {int(record.get('account_id')): record.get('move_count') for record in self._cr.dictfetchall()} + data = {res['account_id']: res['count'] for res in self._cr.dictfetchall()} for account in self: account.vendor_bill_count = data.get(account.id, 0) def action_view_invoice(self): self.ensure_one() query = self.env['account.move.line']._search([('move_id.move_type', 'in', self.env['account.move'].get_sale_types())]) - query.add_where('analytic_distribution ? %s', [str(self.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.id)], + self.env['account.move.line']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('DISTINCT account_move_line.move_id') self._cr.execute(query_string, query_param) move_ids = [line.get('move_id') for line in self._cr.dictfetchall()] @@ -83,7 +102,13 @@ class AccountAnalyticAccount(models.Model): def action_view_vendor_bill(self): self.ensure_one() query = self.env['account.move.line']._search([('move_id.move_type', 'in', self.env['account.move'].get_purchase_types())]) - query.add_where('analytic_distribution ? %s', [str(self.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.id)], + self.env['account.move.line']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('DISTINCT account_move_line.move_id') self._cr.execute(query_string, query_param) move_ids = [line.get('move_id') for line in self._cr.dictfetchall()] diff --git a/addons/account/tests/test_account_analytic.py b/addons/account/tests/test_account_analytic.py index 2b46055dc2e..0ae4a2f07a4 100644 --- a/addons/account/tests/test_account_analytic.py +++ b/addons/account/tests/test_account_analytic.py @@ -29,6 +29,13 @@ class TestAccountAnalyticAccount(AccountTestInvoicingCommon): 'company_id': False, }) + cls.cross_plan = cls.env['account.analytic.plan'].create({'name': 'Cross'}) + cls.analytic_account_c = cls.env['account.analytic.account'].create({ + 'name': 'analytic_account_c', + 'plan_id': cls.cross_plan.id, + 'company_id': False, + }) + def create_invoice(self, partner, product): return self.env['account.move'].create([{ 'move_type': 'out_invoice', @@ -210,3 +217,50 @@ class TestAccountAnalyticAccount(AccountTestInvoicingCommon): invoice.invoice_line_ids.analytic_distribution = {self.analytic_account_b.id: 0.9} invoice.action_post() self.assertEqual(invoice.state, 'posted') + + def test_cross_analytics_computing(self): + + out_invoice = self.env['account.move'].create([{ + 'move_type': 'out_invoice', + 'partner_id': self.partner_a.id, + 'date': '2017-01-01', + 'invoice_date': '2017-01-01', + 'invoice_line_ids': [Command.create({ + 'product_id': self.product_b.id, + 'price_unit': 200.0, + 'analytic_distribution': { + f'{self.analytic_account_a.id},{self.analytic_account_c.id}': 20, + f'{self.analytic_account_a.id},{self.analytic_account_b.id}': 80, + }, + })] + }]) + out_invoice.action_post() + in_invoice = self.env['account.move'].create([{ + 'move_type': 'in_invoice', + 'partner_id': self.partner_b.id, + 'date': '2017-01-01', + 'invoice_date': '2017-01-01', + 'invoice_line_ids': [ + Command.create({ + 'product_id': self.product_a.id, + 'price_unit': 200.0, + 'analytic_distribution': { + f'{self.analytic_account_a.id},{self.analytic_account_b.id}': 100, + }, + }), + Command.create({ + 'product_id': self.product_a.id, + 'price_unit': 200.0, + 'analytic_distribution': { + f'{self.analytic_account_a.id},{self.analytic_account_c.id}': 50, + self.analytic_account_b.id: 50, + }, + }) + ] + }]) + in_invoice.action_post() + + self.analytic_account_a._compute_invoice_count() + self.assertEqual(self.analytic_account_a.invoice_count, 1) + self.analytic_account_a._compute_vendor_bill_count() + self.assertEqual(self.analytic_account_a.vendor_bill_count, 1) diff --git a/addons/analytic/models/analytic_distribution_model.py b/addons/analytic/models/analytic_distribution_model.py index 123776295ae..9be68e0d4cf 100644 --- a/addons/analytic/models/analytic_distribution_model.py +++ b/addons/analytic/models/analytic_distribution_model.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ +from odoo.tools import SQL from odoo.exceptions import UserError @@ -38,15 +39,20 @@ class AccountAnalyticDistributionModel(models.Model): @api.constrains('company_id') def _check_company_accounts(self): - query = """ + """Ensure accounts specific to a company isn't used in any distribution model that wouldn't be specific to the company""" + query = SQL( + """ SELECT model.id FROM account_analytic_distribution_model model JOIN account_analytic_account account - ON model.analytic_distribution ? CAST(account.id AS VARCHAR) - WHERE account.company_id IS NOT NULL + ON ARRAY[account.id::text] && %s + WHERE account.company_id IS NOT NULL AND model.id = ANY(%s) AND (model.company_id IS NULL OR model.company_id != account.company_id) - """ + """, + self._query_analytic_accounts('model'), + self.ids, + ) self.flush_model(['company_id', 'analytic_distribution']) self.env.cr.execute(query) if self.env.cr.dictfetchone(): diff --git a/addons/analytic/models/analytic_mixin.py b/addons/analytic/models/analytic_mixin.py index 4f56de03a81..13c63e286c6 100644 --- a/addons/analytic/models/analytic_mixin.py +++ b/addons/analytic/models/analytic_mixin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import models, fields, api, _ +from odoo.tools import SQL from odoo.tools.float_utils import float_round, float_compare from odoo.exceptions import UserError, ValidationError @@ -29,9 +30,9 @@ class AnalyticMixin(models.AbstractModel): WHERE table_name=%s ''' self.env.cr.execute(query, [self._table]) if self.env.cr.dictfetchone() and self._fields['analytic_distribution'].store: - query = f""" - CREATE INDEX IF NOT EXISTS {self._table}_analytic_distribution_gin_index - ON {self._table} USING gin(analytic_distribution); + query = fr""" + CREATE INDEX IF NOT EXISTS {self._table}_analytic_distribution_accounts_gin_index + ON {self._table} USING gin(regexp_split_to_array(jsonb_path_query_array(analytic_distribution, '$.keyvalue()."key"')::text, '\D+')); """ self.env.cr.execute(query) super().init() @@ -48,18 +49,33 @@ class AnalyticMixin(models.AbstractModel): pass def _search_analytic_distribution(self, operator, value): - if operator not in ['=', '!=', 'ilike', 'not ilike'] or not isinstance(value, (str, bool)): + if operator == 'in' and isinstance(value, (tuple, list)): + account_ids = value + operator_inselect = 'inselect' + elif operator in ('=', '!=', 'ilike', 'not ilike') and isinstance(value, (str, bool)): + operator_name_search = '=' if operator in ('=', '!=') else 'ilike' + account_ids = list(self.env['account.analytic.account']._name_search(name=value, operator=operator_name_search)) + operator_inselect = 'inselect' if operator in ('=', 'ilike') else 'not inselect' + else: raise UserError(_('Operation not supported')) - operator_name_search = '=' if operator in ('=', '!=') else 'ilike' - account_ids = list(self.env['account.analytic.account']._name_search(name=value, operator=operator_name_search)) - query = f""" + query = SQL( + fr""" SELECT id FROM {self._table} - WHERE analytic_distribution ?| array[%s] - """ - operator_inselect = 'inselect' if operator in ('=', 'ilike') else 'not inselect' - return [('id', operator_inselect, (query, [[str(account_id) for account_id in account_ids]]))] + WHERE %s && %s + """, + [str(account_id) for account_id in account_ids], + self._query_analytic_accounts(), + ) + + return [('id', operator_inselect, query)] + + def _query_analytic_accounts(self, table=False): + return SQL( + r"""regexp_split_to_array(jsonb_path_query_array(%s.analytic_distribution, '$.keyvalue()."key"')::text, '\D+')""", + SQL(table or self._table), + ) @api.model def _search(self, domain, offset=0, limit=None, order=None, access_rights_uid=None): @@ -109,6 +125,6 @@ class AnalyticMixin(models.AbstractModel): def _apply_analytic_distribution_domain(self, domain): return [ - ('analytic_distribution_search', leaf[1], leaf[2]) if len(leaf) == 3 and leaf[0] == 'analytic_distribution' and isinstance(leaf[2], str) else leaf + ('analytic_distribution_search', leaf[1], leaf[2]) if len(leaf) == 3 and leaf[0] == 'analytic_distribution' and isinstance(leaf[2], (str, tuple, list)) else leaf for leaf in domain ] diff --git a/addons/hr_expense/models/analytic.py b/addons/hr_expense/models/analytic.py index e3a21b8a98b..1b223bb38c9 100644 --- a/addons/hr_expense/models/analytic.py +++ b/addons/hr_expense/models/analytic.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ +from odoo.tools import SQL from odoo.exceptions import UserError @@ -28,11 +29,17 @@ class AccountAnalyticAccount(models.Model): @api.ondelete(at_uninstall=False) def _unlink_except_account_in_analytic_distribution(self): - self.env.cr.execute(""" - SELECT id FROM hr_expense - WHERE analytic_distribution::jsonb ?| array[%s] - LIMIT 1 - """, ([str(id) for id in self.ids],)) + self.env.cr.execute( + SQL( + r""" + SELECT id FROM hr_expense + WHERE %s && %s + LIMIT 1 + """, + [str(id) for id in self.ids], + self.env['hr.expense']._query_analytic_accounts(), + ) + ) expense_ids = self.env.cr.fetchall() if expense_ids: raise UserError(_("You cannot delete an analytic account that is used in an expense.")) diff --git a/addons/project_account/models/project_project.py b/addons/project_account/models/project_project.py index 906f5fbd57d..44bc4bf43a3 100644 --- a/addons/project_account/models/project_project.py +++ b/addons/project_account/models/project_project.py @@ -5,6 +5,7 @@ import json from collections import defaultdict from odoo import models, _lt +from odoo.tools import SQL from odoo.tools.misc import OrderedSet @@ -29,7 +30,13 @@ class Project(models.Model): """ This method is used in sale_project and project_purchase. Since project_account is the only common module (except project), we create the method here. """ # calculate the cost of bills without a purchase order query = self.env['account.move.line'].sudo()._search(domain) - query.add_where('account_move_line.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['account.move.line'].sudo()._query_analytic_accounts(), + ) + ) # account_move_line__move_id is the alias of the joined table account_move in the query # we can use it, because of the "move_id.move_type" clause in the domain of the query, which generates the join # this is faster than a search_read followed by a browse on the move_id to retrieve the move_type of each account.move.line @@ -45,7 +52,11 @@ class Project(models.Model): price_subtotal = self.env['res.currency'].browse(moves_read['currency_id']).with_prefetch(currency_ids)._convert( from_amount=moves_read['price_subtotal'], to_currency=self.currency_id, ) - analytic_contribution = moves_read['analytic_distribution'][str(self.analytic_account_id.id)] / 100. + # an analytic account can appear several time in an analytic distribution with different repartition percentage + analytic_contribution = sum( + percentage for ids, percentage in moves_read['analytic_distribution'].items() + if str(self.analytic_account_id.id) in ids.split(',') + ) / 100. move_ids.add(moves_read['move_id']) if moves_read['parent_state'] == 'draft': if moves_read['move_type'] == 'in_invoice': diff --git a/addons/project_hr_expense/models/project_project.py b/addons/project_hr_expense/models/project_project.py index 58ca379a94d..8ca06e78375 100644 --- a/addons/project_hr_expense/models/project_project.py +++ b/addons/project_hr_expense/models/project_project.py @@ -4,6 +4,7 @@ import json from odoo import api, fields, models, _, _lt from odoo.osv import expression +from odoo.tools import SQL from collections import defaultdict class Project(models.Model): @@ -17,15 +18,24 @@ class Project(models.Model): self.expenses_count = 0 return query = self.env['hr.expense']._search([]) - query.add_where('hr_expense.analytic_distribution ?| %s', [[str(account_id) for account_id in self.analytic_account_id.ids]]) + query.add_where( + SQL( + "%s && %s", + [str(account_id) for account_id in self.analytic_account_id.ids], + self.env['hr.expense']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select( - 'jsonb_object_keys(analytic_distribution) as account_id', - 'COUNT(DISTINCT(id)) as expense_count', + r"""DISTINCT id, (regexp_matches(jsonb_object_keys(hr_expense.analytic_distribution), '\d+', 'g'))[1]::int as account_id""" ) - query_string = f'{query_string} GROUP BY jsonb_object_keys(analytic_distribution)' + query_string = f""" + SELECT account_id, count(id) FROM + ({query_string}) distribution + GROUP BY account_id + """ self._cr.execute(query_string, query_param) - data = {int(record.get('account_id')): record.get('expense_count') for record in self._cr.dictfetchall()} + data = {res['account_id']: res['count'] for res in self._cr.dictfetchall()} for project in self: project.expenses_count = data.get(project.analytic_account_id.id, 0) @@ -88,7 +98,13 @@ class Project(models.Model): return {} can_see_expense = with_action and self.user_has_groups('hr_expense.group_hr_expense_team_approver') query = self.env['hr.expense']._search([('state', 'in', ['approved', 'done'])]) - query.add_where('hr_expense.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['hr.expense']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('currency_id', 'array_agg(id) as ids', 'SUM(untaxed_amount_currency) as untaxed_amount') query_string = f"{query_string} GROUP BY currency_id" self._cr.execute(query_string, query_param) diff --git a/addons/project_purchase/models/project_project.py b/addons/project_purchase/models/project_project.py index 93afb3c7b12..11a2b8ce2f4 100644 --- a/addons/project_purchase/models/project_project.py +++ b/addons/project_purchase/models/project_project.py @@ -5,6 +5,7 @@ import json from odoo import api, fields, models, _, _lt from odoo.osv import expression +from odoo.tools import SQL from odoo.tools.misc import OrderedSet @@ -19,16 +20,25 @@ class Project(models.Model): self.purchase_orders_count = 0 return query = self.env['purchase.order.line']._search([]) - query.add_where('purchase_order_line.analytic_distribution ?| %s', [[str(account_id) for account_id in self.analytic_account_id.ids]]) + query.add_where( + SQL( + "%s && %s", + [str(account_id) for account_id in self.analytic_account_id.ids], + self.env['purchase.order.line']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select( - 'jsonb_object_keys(purchase_order_line.analytic_distribution) as account_id', - 'COUNT(DISTINCT(order_id)) as purchase_order_count', + r"""DISTINCT order_id, (regexp_matches(jsonb_object_keys(purchase_order_line.analytic_distribution), '\d+', 'g'))[1]::int as account_id""" ) - query_string = f"{query_string} GROUP BY jsonb_object_keys(purchase_order_line.analytic_distribution)" + query_string = f""" + SELECT account_id, count(order_id) FROM + ({query_string}) distribution + GROUP BY account_id + """ self._cr.execute(query_string, query_param) - data = {int(record.get('account_id')): record.get('purchase_order_count') for record in self._cr.dictfetchall()} + data = {res['account_id']: res['count'] for res in self._cr.dictfetchall()} for project in self: project.purchase_orders_count = data.get(project.analytic_account_id.id, 0) @@ -38,7 +48,13 @@ class Project(models.Model): def action_open_project_purchase_orders(self): query = self.env['purchase.order.line']._search([]) - query.add_where('purchase_order_line.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['purchase.order.line']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('order_id') self._cr.execute(query_string, query_param) purchase_order_ids = [pol.get('order_id') for pol in self._cr.dictfetchall()] @@ -129,7 +145,13 @@ class Project(models.Model): ('qty_invoiced', '>', 0), '|', ('qty_to_invoice', '>', 0), ('product_uom_qty', '>', 0), ], order=self.env['purchase.order.line']._order) - query.add_where('purchase_order_line.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['purchase.order.line']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('"purchase_order_line".id', 'qty_invoiced', 'qty_to_invoice', 'product_uom_qty', 'price_unit', 'purchase_order_line.currency_id', '"purchase_order_line".analytic_distribution') self._cr.execute(query_string, query_param) purchase_order_line_read = [{ diff --git a/addons/project_sale_expense/models/project_project.py b/addons/project_sale_expense/models/project_project.py index 5d74a08ecfa..ab66bd1ec29 100644 --- a/addons/project_sale_expense/models/project_project.py +++ b/addons/project_sale_expense/models/project_project.py @@ -4,6 +4,7 @@ import json from odoo import models, fields +from odoo.tools import SQL from collections import defaultdict @@ -15,7 +16,13 @@ class Project(models.Model): return {} can_see_expense = with_action and self.user_has_groups('hr_expense.group_hr_expense_team_approver') query = self.env['hr.expense']._search([('state', 'in', ['approved', 'done'])]) - query.add_where('hr_expense.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['hr.expense']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('sale_order_id', 'product_id', 'currency_id', 'array_agg(id) as ids', 'SUM(untaxed_amount_currency) as untaxed_amount_currency') query_string = f"{query_string} GROUP BY sale_order_id, product_id, currency_id" self._cr.execute(query_string, query_param) diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py index 1acd12911d3..8e3cd0b65c7 100755 --- a/addons/sale_project/models/project.py +++ b/addons/sale_project/models/project.py @@ -100,15 +100,24 @@ class Project(models.Model): def _compute_invoice_count(self): query = self.env['account.move.line']._search([('move_id.move_type', 'in', ['out_invoice', 'out_refund'])]) - query.add_where('analytic_distribution ?| %s', [[str(project.analytic_account_id.id) for project in self]]) + query.add_where( + SQL( + "%s && %s", + [str(project.analytic_account_id.id) for project in self], + self.env['account.move.line']._query_analytic_accounts(), + ) + ) query.order = None query_string, query_param = query.select( - 'jsonb_object_keys(account_move_line.analytic_distribution) as account_id', - 'COUNT(DISTINCT move_id) as move_count', + r"""DISTINCT move_id, (regexp_matches(jsonb_object_keys(account_move_line.analytic_distribution), '\d+', 'g'))[1]::int as account_id""" ) - query_string = f"{query_string} GROUP BY jsonb_object_keys(account_move_line.analytic_distribution)" + query_string = f""" + SELECT account_id, count(move_id) FROM + ({query_string}) distribution + GROUP BY account_id + """ self._cr.execute(query_string, query_param) - data = {int(row.get('account_id')): row.get('move_count') for row in self._cr.dictfetchall()} + data = {res['account_id']: res['count'] for res in self._cr.dictfetchall()} for project in self: project.invoice_count = data.get(project.analytic_account_id.id, 0) @@ -226,7 +235,13 @@ class Project(models.Model): def action_open_project_invoices(self): query = self.env['account.move.line']._search([('move_id.move_type', 'in', ['out_invoice', 'out_refund'])]) - query.add_where('analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['account.move.line']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('DISTINCT move_id') self._cr.execute(query_string, query_param) invoice_ids = [line.get('move_id') for line in self._cr.dictfetchall()] @@ -558,7 +573,13 @@ class Project(models.Model): query = self.env['account.move.line'].sudo()._search( self._get_revenues_items_from_invoices_domain([('id', 'not in', excluded_move_line_ids)]), ) - query.add_where('account_move_line.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['account.move.line']._query_analytic_accounts(), + ) + ) # account_move_line__move_id is the alias of the joined table account_move in the query # we can use it, because of the "move_id.move_type" clause in the domain of the query, which generates the join # this is faster than a search_read followed by a browse on the move_id to retrieve the move_type of each account.move.line @@ -572,7 +593,11 @@ class Project(models.Model): for moves_read in invoices_move_line_read: currency = self.env['res.currency'].browse(moves_read['currency_id']).with_prefetch(currency_ids) price_subtotal = currency._convert(moves_read['price_subtotal'], self.currency_id, self.company_id) - analytic_contribution = moves_read['analytic_distribution'][str(self.analytic_account_id.id)] / 100. + # an analytic account can appear several time in an analytic distribution with different repartition percentage + analytic_contribution = sum( + percentage for ids, percentage in moves_read['analytic_distribution'].items() + if str(self.analytic_account_id.id) in ids.split(',') + ) / 100. move_ids.add(moves_read['move_id']) if moves_read['parent_state'] == 'draft': if moves_read['move_type'] == 'out_invoice': @@ -719,7 +744,13 @@ class Project(models.Model): def action_open_project_vendor_bills(self): query = self.env['account.move.line']._search([('move_id.move_type', 'in', ['in_invoice', 'in_refund'])]) - query.add_where('analytic_distribution ? %s', [str(self.analytic_account_id.id)]) + query.add_where( + SQL( + "%s && %s", + [str(self.analytic_account_id.id)], + self.env['account.move.line']._query_analytic_accounts(), + ) + ) query_string, query_param = query.select('DISTINCT move_id') self._cr.execute(query_string, query_param) vendor_bill_ids = [line.get('move_id') for line in self._cr.dictfetchall()]