diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index 3a4c10b5177..7395c38e37f 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -728,7 +728,7 @@ class SaleOrderLine(models.Model): domain = expression.AND([[('so_line', 'in', self.ids)], additional_domain]) data = self.env['account.analytic.line'].read_group( domain, - ['so_line', 'unit_amount', 'product_uom_id'], ['product_uom_id', 'so_line'], lazy=False + ['so_line', 'unit_amount', 'product_uom_id', 'move_line_id:count_distinct'], ['product_uom_id', 'so_line'], lazy=False ) # convert uom and sum all unit_amount of analytic lines to get the delivered qty of SO lines @@ -744,10 +744,13 @@ class SaleOrderLine(models.Model): so_line = lines_map[so_line_id] result.setdefault(so_line_id, 0.0) uom = product_uom_map.get(item['product_uom_id'][0]) - if so_line.product_uom.category_id == uom.category_id: - qty = uom._compute_quantity(item['unit_amount'], so_line.product_uom, rounding_method='HALF-UP') + # avoid counting unit_amount twice when dealing with multiple analytic lines on the same move line + if item['move_line_id'] == 1 and item['__count'] > 1: + qty = item['unit_amount'] / item['__count'] else: qty = item['unit_amount'] + if so_line.product_uom.category_id == uom.category_id: + qty = uom._compute_quantity(qty, so_line.product_uom, rounding_method='HALF-UP') result[so_line_id] += qty return result diff --git a/addons/sale_expense/tests/test_reinvoice.py b/addons/sale_expense/tests/test_reinvoice.py index eeb02c29999..031b7ae9c5f 100644 --- a/addons/sale_expense/tests/test_reinvoice.py +++ b/addons/sale_expense/tests/test_reinvoice.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import Command from odoo.addons.hr_expense.tests.common import TestExpenseCommon from odoo.addons.sale.tests.common import TestSaleCommon from odoo.tests import tagged @@ -115,3 +116,60 @@ class TestReInvoice(TestExpenseCommon, TestSaleCommon): {'qty_delivered_method': 'analytic'}, {'qty_delivered_method': 'analytic'}, ]) + + def test_expenses_reinvoice_analytic_distribution(self): + """Test expense line with multiple analytic accounts is reinvoiced correctly""" + + (self.company_data['product_order_sales_price'] + self.company_data['product_delivery_sales_price']).write({ + 'can_be_expensed': True, + }) + + # create SO line and confirm SO (with only one line) + sale_order = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({ + 'partner_id': self.partner_a.id, + 'partner_invoice_id': self.partner_a.id, + 'partner_shipping_id': self.partner_a.id, + 'order_line': [Command.create({ + 'name': self.company_data['product_order_sales_price'].name, + 'product_id': self.company_data['product_order_sales_price'].id, + 'product_uom_qty': 2.0, + 'price_unit': 1000.0, + })], + }) + sale_order.action_confirm() + + expense_sheet = self.env['hr.expense.sheet'].create({ + 'name': 'First Expense for employee', + 'employee_id': self.expense_employee.id, + 'journal_id': self.company_data['default_journal_purchase'].id, + 'accounting_date': '2017-01-01', + 'expense_line_ids': [ + Command.create({ + 'name': 'expense_1', + 'date': '2016-01-01', + 'product_id': self.company_data['product_order_sales_price'].id, + 'quantity': 2, + 'analytic_distribution': {self.analytic_account_1.id: 50, self.analytic_account_2.id: 50}, + 'employee_id': self.expense_employee.id, + 'sale_order_id': sale_order.id, + }), + ], + }) + + expense_sheet.action_approve_expense_sheets() + expense_sheet.action_sheet_move_create() + + self.assertRecordValues(sale_order.order_line, [ + # Original SO line: + { + 'qty_delivered': 0.0, + 'product_uom_qty': 2.0, + 'is_expense': False, + }, + # Expense lines: + { + 'qty_delivered': 2.0, + 'product_uom_qty': 2.0, + 'is_expense': True, + }, + ])