diff --git a/addons/project_purchase/models/project.py b/addons/project_purchase/models/project.py index e7a51393183..e7c808c65d0 100644 --- a/addons/project_purchase/models/project.py +++ b/addons/project_purchase/models/project.py @@ -6,6 +6,7 @@ import json from odoo import api, fields, models, _, _lt from odoo.osv import expression +from datetime import date class Project(models.Model): _inherit = "project.project" @@ -128,7 +129,7 @@ class Project(models.Model): '|', ('qty_to_invoice', '>', 0), ('product_uom_qty', '>', 0), ]) query.add_where('purchase_order_line.analytic_distribution ? %s', [str(self.analytic_account_id.id)]) - query_string, query_param = query.select('"purchase_order_line".id', 'qty_invoiced', 'qty_to_invoice', 'product_uom_qty', 'price_unit', '"purchase_order_line".analytic_distribution') + 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 = [{ **pol, @@ -136,11 +137,17 @@ class Project(models.Model): } for pol in self._cr.dictfetchall()] purchase_order_line_invoice_line_ids = self._get_already_included_profitability_invoice_line_ids() if purchase_order_line_read: + + # Get conversion rate from currencies to currency of analytic account + currency_ids = {pol['currency_id'] for pol in purchase_order_line_read + [{'currency_id': self.analytic_account_id.currency_id.id}]} + rates = self.env['res.currency'].browse(list(currency_ids))._get_rates(self.company_id, date.today()) + conversion_rates = {cid: rates[self.analytic_account_id.currency_id.id] / rate_from for cid, rate_from in rates.items()} + amount_invoiced = amount_to_invoice = 0.0 purchase_order_line_ids = [] for pol_read in purchase_order_line_read: purchase_order_line_invoice_line_ids.extend(pol_read['invoice_lines'].ids) - price_unit = pol_read['price_unit'] + price_unit = self.analytic_account_id.currency_id.round(pol_read['price_unit'] * conversion_rates[pol_read['currency_id']]) analytic_contribution = pol_read['analytic_distribution'][str(self.analytic_account_id.id)] / 100. amount_invoiced -= price_unit * pol_read['qty_invoiced'] * analytic_contribution if pol_read['qty_invoiced'] > 0 else 0.0 if pol_read['qty_to_invoice'] > 0: @@ -171,22 +178,29 @@ class Project(models.Model): # 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 - query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line__move_id.move_type') + query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line.currency_id', 'account_move_line__move_id.move_type') self._cr.execute(query_string, query_param) bills_move_line_read = self._cr.dictfetchall() if bills_move_line_read: + + # Get conversion rate from currencies to currency of analytic account + currency_ids = {bml['currency_id'] for bml in bills_move_line_read + [{'currency_id': self.analytic_account_id.currency_id.id}]} + rates = self.env['res.currency'].browse(list(currency_ids))._get_rates(self.company_id, date.today()) + conversion_rates = {cid: rates[self.analytic_account_id.currency_id.id] / rate_from for cid, rate_from in rates.items()} + amount_invoiced = amount_to_invoice = 0.0 for moves_read in bills_move_line_read: + price_subtotal = self.analytic_account_id.currency_id.round(moves_read['price_subtotal'] * conversion_rates[moves_read['currency_id']]) if moves_read['parent_state'] == 'draft': if moves_read['move_type'] == 'in_invoice': - amount_to_invoice -= moves_read['price_subtotal'] + amount_to_invoice -= price_subtotal else: # moves_read['move_type'] == 'in_refund' - amount_to_invoice += moves_read['price_subtotal'] + amount_to_invoice += price_subtotal else: # moves_read['parent_state'] == 'posted' if moves_read['move_type'] == 'in_invoice': - amount_invoiced -= moves_read['price_subtotal'] + amount_invoiced -= price_subtotal else: # moves_read['move_type'] == 'in_refund' - amount_invoiced += moves_read['price_subtotal'] + amount_invoiced += price_subtotal # don't display the section if the final values are both 0 (bill -> vendor credit) if amount_invoiced != 0 or amount_to_invoice != 0: costs = profitability_items['costs'] diff --git a/addons/project_purchase/tests/test_project_profitability.py b/addons/project_purchase/tests/test_project_profitability.py index 75aa408c953..4bafc10e8f1 100644 --- a/addons/project_purchase/tests/test_project_profitability.py +++ b/addons/project_purchase/tests/test_project_profitability.py @@ -31,6 +31,7 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch "quantity": 1, "product_uom_id": self.product_a.uom_id.id, "price_unit": self.product_a.standard_price, + "currency_id": self.env.company.currency_id.id, })], }) # the bill_1 is in draft, therefor it should have the cost "to_bill" same as the -product_price (untaxed) @@ -74,12 +75,14 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch "quantity": 1, "product_uom_id": self.product_a.uom_id.id, "price_unit": self.product_a.standard_price, + "currency_id": self.env.company.currency_id.id, }), Command.create({ "analytic_distribution": {self.analytic_account.id: 100}, "product_id": self.product_b.id, "quantity": 2, "product_uom_id": self.product_b.uom_id.id, "price_unit": self.product_b.standard_price, + "currency_id": self.env.company.currency_id.id, })], }) # bill_2 is not posted, therefor its cost should be "to_billed" = - sum of all product_price * qty for each line @@ -125,6 +128,7 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch "product_id": self.product_order.id, "product_qty": 1, "price_unit": self.product_order.standard_price, + "currency_id": self.env.company.currency_id.id, })], }) purchase_order.button_confirm() @@ -215,6 +219,7 @@ class TestProjectPurchaseProfitability(TestProjectProfitabilityCommon, TestPurch "product_id": self.product_order.id, "product_qty": 1, "price_unit": self.product_order.standard_price, + "currency_id": self.env.company.currency_id.id, })], }) purchase_order.button_confirm() diff --git a/addons/sale_project/models/project.py b/addons/sale_project/models/project.py index 1a6be7b15cd..659bb0f49c1 100755 --- a/addons/sale_project/models/project.py +++ b/addons/sale_project/models/project.py @@ -8,6 +8,7 @@ from odoo.exceptions import ValidationError, AccessError from odoo.osv import expression from odoo.tools import Query +from datetime import date class Project(models.Model): _inherit = 'project.project' @@ -433,22 +434,29 @@ class Project(models.Model): # 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 - query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line__move_id.move_type') + query_string, query_param = query.select('price_subtotal', 'parent_state', 'account_move_line.currency_id', 'account_move_line__move_id.move_type') self._cr.execute(query_string, query_param) invoices_move_line_read = self._cr.dictfetchall() if invoices_move_line_read: + + # Get conversion rate from currencies to currency of analytic account + currency_ids = {iml['currency_id'] for iml in invoices_move_line_read + [{'currency_id': self.analytic_account_id.currency_id.id}]} + rates = self.env['res.currency'].browse(list(currency_ids))._get_rates(self.company_id, date.today()) + conversion_rates = {cid: rates[self.analytic_account_id.currency_id.id] / rate_from for cid, rate_from in rates.items()} + amount_invoiced = amount_to_invoice = 0.0 for moves_read in invoices_move_line_read: + price_subtotal = self.analytic_account_id.currency_id.round(moves_read['price_subtotal'] * conversion_rates[moves_read['currency_id']]) if moves_read['parent_state'] == 'draft': if moves_read['move_type'] == 'out_invoice': - amount_to_invoice += moves_read['price_subtotal'] + amount_to_invoice += price_subtotal else: # moves_read['move_type'] == 'out_refund' - amount_to_invoice -= moves_read['price_subtotal'] + amount_to_invoice -= price_subtotal else: # moves_read['parent_state'] == 'posted' if moves_read['move_type'] == 'out_invoice': - amount_invoiced += moves_read['price_subtotal'] + amount_invoiced += price_subtotal else: # moves_read['move_type'] == 'out_refund' - amount_invoiced -= moves_read['price_subtotal'] + amount_invoiced -= price_subtotal # don't display the section if the final values are both 0 (invoice -> credit note) if amount_invoiced != 0 or amount_to_invoice != 0: section_id = 'other_invoice_revenues'