diff --git a/addons/project_hr_expense/__init__.py b/addons/project_hr_expense/__init__.py index dc5e6b693d1..90b17558ffc 100644 --- a/addons/project_hr_expense/__init__.py +++ b/addons/project_hr_expense/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import models +from . import tests diff --git a/addons/project_hr_expense/models/project.py b/addons/project_hr_expense/models/project.py index aec4ebd16b0..fea4c4dc327 100644 --- a/addons/project_hr_expense/models/project.py +++ b/addons/project_hr_expense/models/project.py @@ -39,3 +39,37 @@ class Project(models.Model): action["views"] = [[False, 'form']] action["res_id"] = expenses.id return action + + # ---------------------------- + # Project Update + # ---------------------------- + def _get_expenses_profitability_items(self): + if not self.analytic_account_id: + return {} + expenses_read_group = self.env['hr.expense'].sudo()._read_group( + [('analytic_account_id', 'in', self.analytic_account_id.ids), + ('is_refused', '=', False), + ('state', 'in', ['approved', 'done'])], + ['untaxed_amount'], + [], + ) + if not expenses_read_group or not expenses_read_group[0]['__count']: + return {} + expense_data = expenses_read_group[0] + section_id = 'expenses' + return { + 'costs': {'id': section_id, 'billed': -expense_data['untaxed_amount'], 'to_bill': 0.0}, + } + + def _get_profitability_items(self, with_action=True): + profitability_data = super()._get_profitability_items(with_action) + expenses_data = self._get_expenses_profitability_items(with_action) + if expenses_data: + if 'revenues' in expenses_data: + revenues = profitability_data['revenues'] + revenues['data'].append(expenses_data['revenues']) + revenues['total'] = {k: revenues['total'][k] + expenses_data['revenues'][k] for k in ['invoiced', 'to_invoice']} + costs = profitability_data['costs'] + costs['data'].append(expenses_data['costs']) + costs['total'] = {k: costs['total'][k] + expenses_data['costs'][k] for k in ['billed', 'to_bill']} + return profitability_data diff --git a/addons/project_hr_expense/tests/__init__.py b/addons/project_hr_expense/tests/__init__.py new file mode 100644 index 00000000000..4fa67e8be11 --- /dev/null +++ b/addons/project_hr_expense/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_project_profitability diff --git a/addons/project_hr_expense/tests/test_project_profitability.py b/addons/project_hr_expense/tests/test_project_profitability.py new file mode 100644 index 00000000000..de290406ed9 --- /dev/null +++ b/addons/project_hr_expense/tests/test_project_profitability.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.addons.hr_expense.tests.common import TestExpenseCommon +from odoo.addons.project.tests.test_project_profitability import TestProjectProfitabilityCommon + + +class TestProjectHrExpenseProfitabilityCommon(TestExpenseCommon): + def check_project_profitability_before_creating_and_approving_expense_sheet(self, expense, project, project_profitability_items_empty): + self.assertDictEqual( + project._get_profitability_items(), + project_profitability_items_empty, + 'No data should be found since the expense is not approved yet.', + ) + + expense_sheet_vals_list = expense._get_default_expense_sheet_values() + expense_sheet = self.env['hr.expense.sheet'].create(expense_sheet_vals_list) + self.assertEqual(len(expense_sheet), 1, '1 expense sheet should be created.') + + expense_sheet.action_submit_sheet() + self.assertEqual(expense_sheet.state, 'submit') + + self.assertDictEqual( + project._get_profitability_items(), + project_profitability_items_empty, + 'No data should be found since the sheet is not approved yet.', + ) + + expense_sheet.approve_expense_sheets() + self.assertEqual(expense_sheet.state, 'approve') + return expense_sheet + +class TestProjectHrExpenseProfitability(TestProjectProfitabilityCommon, TestProjectHrExpenseProfitabilityCommon): + + def test_project_profitability(self): + expense = self.env['hr.expense'].create({ + 'name': 'Car Travel Expenses', + 'employee_id': self.expense_employee.id, + 'product_id': self.product_a.id, + 'unit_amount': 350.00, + 'company_id': self.project.company_id.id, + 'analytic_account_id': self.project.analytic_account_id.id, + }) + + expense_sheet = self.check_project_profitability_before_creating_and_approving_expense_sheet( + expense, + self.project, + self.project_profitability_items_empty) + + self.assertDictEqual( + self.project._get_profitability_items(), + { + 'costs': { + 'data': [{'id': 'expenses', 'to_bill': 0.0, 'billed': -expense.untaxed_amount}], + 'total': {'to_bill': 0.0, 'billed': -expense.untaxed_amount}, + }, + 'revenues': {'data': [], 'total': {'to_invoice': 0.0, 'invoiced': 0.0}}, + }, + ) + + expense_sheet.refuse_sheet('Test cancel expense') + self.assertDictEqual( + self.project._get_profitability_items(), + self.project_profitability_items_empty, + 'No data should be found since the sheet is not approved yet.', + )