diff --git a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
index a51a39d81f4..33ecfdeb765 100644
--- a/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
+++ b/addons/sale_timesheet/static/tests/tours/sale_timesheet_tour.js
@@ -247,29 +247,21 @@ tour.register('sale_timesheet_tour', {
trigger: ".o_project_updates_breadcrumb",
content: 'Open Updates'
}, {
- trigger: ".o_rightpanel_button a:contains('See Overview')",
- content: 'See Overview'
+ trigger: ".o_rightpanel_title:eq(0):contains('Sold')",
+ content: 'Check the user sees Sold section'
}, {
- trigger: ".breadcrumb :contains('Overview')",
- content: 'Check the user sees Overview'
-}, {
- trigger: ".o_back_button"
-},{
- trigger: ".o_rightpanel_left_text:eq(0):contains('Timesheets')",
- content: 'Check the user sees Timesheet stat',
+ trigger: ".o_rightpanel_left_text:eq(0):contains('Prepaid Hours')",
+ content: 'Check the user sees Sold subsection',
run: function () {},
+}, {
+ trigger: ".o_rightpanel_title:eq(1):contains('Total Sold')",
+ content: 'Check the user sees Total Sold section'
}, {
- trigger: ".o_rightpanel_left_text:eq(1):contains('Revenues')",
- content: 'Check the user sees Revenues stat',
- run: function () {},
+ trigger: ".o_rightpanel_title:eq(2):contains('Profitability')",
+ content: 'Check the user sees Profitability section'
}, {
- trigger: ".o_rightpanel_left_text:eq(2):contains('Costs')",
- content: 'Check the user sees Cost stat',
- run: function () {},
-}, {
- trigger: ".o_rightpanel_left_text:eq(3):contains('Margin')",
- content: 'Check the user sees Margin stat',
- run: function () {},
+ trigger: ".o_rightpanel_title:eq(3):contains('Milestones')",
+ content: 'Check the user sees Milestones section'
}, {
trigger: ".o-kanban-button-new"
}, {
@@ -277,22 +269,6 @@ tour.register('sale_timesheet_tour', {
run: 'text New update'
}, {
trigger: ".o_form_button_save"
-}, {
- trigger: ".o_field_widget[name=description] :contains('Profitability')",
- content: 'Check the user sees Profitability section',
- run: function () {},
-}, {
- trigger: ".o_field_widget[name=description] :contains('billing rate')",
- content: 'Check the user sees Profitability section content',
- run: function () {},
-}, {
- trigger: ".o_field_widget[name=description] :contains('People')",
- content: 'Check the user sees People section',
- run: function () {},
-}, {
- trigger: ".o_field_widget[name=description] :contains('(new this month)')",
- content: 'Check the user sees People section content',
- run: function () {},
}]);
});
diff --git a/addons/sale_timesheet/tests/__init__.py b/addons/sale_timesheet/tests/__init__.py
index 13e3de402e5..9206f5f0eab 100644
--- a/addons/sale_timesheet/tests/__init__.py
+++ b/addons/sale_timesheet/tests/__init__.py
@@ -7,7 +7,6 @@ from . import test_sale_service
from . import test_project_billing
from . import test_reinvoice
from . import test_reporting
-from . import test_project_overview
from . import test_project_billing_multicompany
from . import test_upsell_warning
from . import test_edit_so_line_timesheet
diff --git a/addons/sale_timesheet/tests/test_project_overview.py b/addons/sale_timesheet/tests/test_project_overview.py
deleted file mode 100644
index 90f51e1d0e8..00000000000
--- a/addons/sale_timesheet/tests/test_project_overview.py
+++ /dev/null
@@ -1,130 +0,0 @@
-# -*- coding: utf-8 -*-
-# Part of Odoo. See LICENSE file for full copyright and licensing details.
-
-from odoo.addons.sale_timesheet.tests.test_reporting import TestReporting
-from odoo.tools import float_compare
-from odoo.tests import tagged
-
-
-@tagged('-at_install', 'post_install')
-class TestSaleProject(TestReporting):
-
- def test_project_overview_by_project(self):
- rounding = self.env.company.currency_id.rounding
-
- so_line_deliver_global_project = self.env['sale.order.line'].create({
- 'name': self.product_delivery_timesheet2.name,
- 'product_id': self.product_delivery_timesheet2.id,
- 'product_uom_qty': 50,
- 'product_uom': self.product_delivery_timesheet2.uom_id.id,
- 'price_unit': self.product_delivery_timesheet2.list_price,
- 'order_id': self.sale_order_2.id,
- })
- so_line_deliver_no_task = self.env['sale.order.line'].create({
- 'name': self.product_delivery_manual1.name,
- 'product_id': self.product_delivery_manual1.id,
- 'product_uom_qty': 50,
- 'product_uom': self.product_delivery_manual1.uom_id.id,
- 'price_unit': self.product_delivery_manual1.list_price,
- 'order_id': self.sale_order_2.id,
- })
- so_line_deliver_no_task.write({'qty_delivered': 1.0})
-
- self.sale_order_2.action_confirm()
- project_so = self.so_line_order_project.project_id
- # log timesheet for billable time
- timesheet1 = self._log_timesheet_manager(project_so, 10, so_line_deliver_global_project.task_id)
-
- task_so = self.so_line_order_project.task_id
- # logged some timesheets: on project only, then on tasks with different employees
- timesheet2 = self._log_timesheet_user(project_so, 2)
- timesheet3 = self._log_timesheet_user(project_so, 3, task_so)
- timesheet4 = self._log_timesheet_manager(project_so, 1, task_so)
-
- # create a task which is not linked to sales order and fill non-billable timesheet
- task = self.env['project.task'].create({
- 'name': 'Task',
- 'project_id': project_so.id,
- 'allow_billable': False,
- 'sale_line_id': False
- })
- timesheet5 = self._log_timesheet_user(project_so, 5, task)
-
- # invoice the Sales Order SO2
- context = {
- "active_model": 'sale.order',
- "active_ids": [self.sale_order_2.id],
- "active_id": self.sale_order_2.id,
- 'open_invoices': True,
- }
-
- payment = self.env['sale.advance.payment.inv'].create({
- 'advance_payment_method': 'delivered',
- })
-
- action_invoice = payment.with_context(context).create_invoices()
- invoice = self.env['account.move'].browse(action_invoice['res_id'])
- invoice.action_post()
-
- # simulate the auto creation of the SO line for expense, like we confirm a vendor bill.
- so_line_expense = self.env['sale.order.line'].create({
- 'name': self.product_expense.name,
- 'product_id': self.product_expense.id,
- 'product_uom_qty': 0.0,
- 'product_uom': self.product_expense.uom_id.id,
- 'price_unit': self.product_expense.list_price, # reinvoice at sales price
- 'order_id': self.sale_order_2.id,
- 'is_expense': True,
- })
-
- expense = self.env['account.analytic.line'].create({
- 'name': 'expense on project_so',
- 'account_id': project_so.analytic_account_id.id,
- 'so_line': so_line_expense.id,
- 'employee_id': self.employee_user.id,
- 'unit_amount': 4,
- 'amount': 4 * self.product_expense.list_price * -1,
- 'product_id': self.product_expense.id,
- 'product_uom_id': self.product_expense.uom_id.id,
- })
-
- other_revenues = self.env['account.analytic.line'].create({
- 'name': 'pther revenues on project_so',
- 'account_id': project_so.analytic_account_id.id,
- 'employee_id': self.employee_user.id,
- 'unit_amount': 1,
- 'amount': self.product_expense.list_price,
- 'product_id': self.product_expense.id,
- 'product_uom_id': self.product_expense.uom_id.id,
- })
-
- view_id = self.env.ref('sale_timesheet.project_timesheet_action_client_timesheet_plan').id
- vals = self.env['project.project']._qweb_prepare_qcontext(view_id, [['id', '=', project_so.id]])
-
- dashboard_value = timesheet2.unit_amount + timesheet3.unit_amount + timesheet4.unit_amount + timesheet5.unit_amount + timesheet1.unit_amount
- project_so_timesheet_sold_unit = timesheet3.unit_amount + timesheet4.unit_amount
- project_rate_non_billable = timesheet5.unit_amount / dashboard_value * 100
- project_rate_non_billable_project = timesheet2.unit_amount / dashboard_value * 100
- project_rate_billable_time = timesheet1.unit_amount / dashboard_value * 100
- project_rate_billable_fixed = project_so_timesheet_sold_unit / dashboard_value * 100
- project_rate_total = project_rate_non_billable + project_rate_non_billable_project + project_rate_billable_time + project_rate_billable_fixed
- project_invoiced = self.so_line_order_project.price_unit * self.so_line_order_project.product_uom_qty * timesheet1.unit_amount
- project_timesheet_cost = timesheet2.amount + timesheet3.amount + timesheet4.amount + timesheet5.amount + timesheet1.amount
- project_other_revenues = invoice.invoice_line_ids.search([('product_id', '=', self.product_delivery_manual1.id)])
-
- self.assertEqual(float_compare(vals['dashboard']['time']['non_billable'], timesheet5.unit_amount, precision_rounding=rounding), 0, "The hours non-billable should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['time']['non_billable_project'], timesheet2.unit_amount, precision_rounding=rounding), 0, "The hours non-billable-project should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['time']['billable_time'], timesheet1.unit_amount, precision_rounding=rounding), 0, "The hours billable-time should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['time']['billable_fixed'], project_so_timesheet_sold_unit, precision_rounding=rounding), 0, "The hours billable-fixed should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['time']['total'], dashboard_value, precision_rounding=rounding), 0, "The total hours should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['rates']['non_billable'], project_rate_non_billable, precision_rounding=rounding), 0, "The rate non-billable should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['rates']['non_billable_project'], project_rate_non_billable_project, precision_rounding=rounding), 0, "The rate non-billable-project should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['rates']['billable_time'], project_rate_billable_time, precision_rounding=rounding), 0, "The rate billable-time should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['rates']['billable_fixed'], project_rate_billable_fixed, precision_rounding=rounding), 0, "The rate billable-fixed should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['rates']['total'], project_rate_total, precision_rounding=rounding), 0, "The total rates should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['profit']['invoiced'], project_invoiced, precision_rounding=rounding), 0, "The amount invoiced should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['profit']['cost'], project_timesheet_cost, precision_rounding=rounding), 0, "The amount cost should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['profit']['expense_cost'], expense.amount, precision_rounding=rounding), 0, "The amount expense-cost should be the one from the SO2 line, as we are in ordered quantity")
- self.assertEqual(float_compare(vals['dashboard']['profit']['other_revenues'], other_revenues.amount + project_other_revenues.price_total, precision_rounding=rounding), 0, "The amount of the other revenues should be equal to the corresponding account move line and the one from the SO line")
- self.assertEqual(float_compare(vals['dashboard']['profit']['total'], project_invoiced + project_timesheet_cost + expense.amount + other_revenues.amount + project_other_revenues.price_total, precision_rounding=rounding), 0, "The total amount should be the sum of the SO2 line and the created other_revenues account analytic line")
- self.assertEqual(float_compare(vals['repartition_employee_max'], 11.0, precision_rounding=rounding), 0, "The amount of repartition-employee-max should be the one from SO2 line")
diff --git a/addons/sale_timesheet/tests/test_project_update.py b/addons/sale_timesheet/tests/test_project_update.py
index de79a04f397..4ed4759fc9a 100644
--- a/addons/sale_timesheet/tests/test_project_update.py
+++ b/addons/sale_timesheet/tests/test_project_update.py
@@ -1,124 +1,31 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from datetime import date
-from dateutil.relativedelta import relativedelta
from freezegun import freeze_time
-from odoo import fields
from odoo.tests import tagged
-from odoo.tests.common import Form
-from odoo.tools import format_date
-
from odoo.addons.project.tests.test_project_update_flow import TestProjectUpdate
@tagged('-at_install', 'post_install')
class TestProjectUpdateSaleTimesheet(TestProjectUpdate):
- def test_project_update_form_profitability(self):
- try:
- with Form(self.env['project.update'].with_context({'default_project_id': self.project_pigs.id})) as update_form:
- update_form.name = "Test"
- update_form.progress = 65
- update = update_form.save()
- except Exception as e:
- raise AssertionError("Error raised unexpectedly while filling the project update form ! Exception : " + e.args[0])
-
- self.assertTrue("Profitability" in update.description, "The description should contain 'Profitability'.")
-
def test_project_update_description_profitability(self):
self.project_pigs.allow_billable = True
template_values = self.env['project.update']._get_template_values(self.project_pigs)
- today = fields.Date.context_today(self.project_pigs)
- self.assertEqual(template_values['profitability']['month'], format_date(self.env, today, date_format='LLLL y'),
- "The month used in the template should be well defined")
- self.assertEqual(template_values['profitability']['previous_month'], format_date(self.env, (today + relativedelta(months=-1)), date_format='LLLL'),
- "The previous month used in the template should be well defined")
- self.assertTrue(template_values['profitability']['is_timesheet_uom_hour'], "Default timesheet uom is hour.")
- self.assertEqual(template_values['profitability']['timesheet_uom'], "hours", "Default timesheet uom should be displayed as 'hours'")
- self.assertEqual(template_values['profitability']['timesheet_unit_amount'], '0', "Timesheet unit amount used in the template should be defined.")
- self.assertEqual(template_values['profitability']['previous_timesheet_unit_amount'], '0', "Previous Timesheet unit amount used in the template should be defined.")
- self.assertEqual(template_values['profitability']['timesheet_trend'], "0", "Timesheet trend used in the template should be well defined.")
self.assertEqual(template_values['profitability']['costs'], "$\xa00.00", "Project costs used in the template should be well defined")
self.assertEqual(template_values['profitability']['revenues'], "$\xa00.00", "Project revenues used in the template should be well defined")
self.assertEqual(template_values['profitability']['margin'], 0, "Margin used in the template should be well defined")
self.assertEqual(template_values['profitability']['margin_formatted'], "$\xa00.00", "Margin formatted used in the template should be well defined")
self.assertEqual(template_values['profitability']['margin_percentage'], "0", "Margin percentage used in the template should be well defined")
- self.assertEqual(template_values['profitability']['billing_rate'], "0", "Billing rate used in the template should be well defined")
def test_project_update_panel_profitability_no_billable(self):
panel_data = self.project_pigs.get_panel_data()
- self.assertEqual(len(panel_data['profitability_items']['data']), 1, "Panel data should contain 'profitability_items>data' entries")
+ self.assertEqual(panel_data['profitability_items']['allow_billable'], False, "Project should not be billable by default")
try:
self.project_pigs.action_view_timesheet()
except Exception as e:
raise AssertionError("Error raised unexpectedly while calling the action defined in profitalities action panel data ! Exception : " + e.args[0])
- def test_project_update_panel_profitability_no_timesheet(self):
- self.project_pigs.allow_billable = True
- panel_data = self.project_pigs.get_panel_data()
-
- self.assertEqual(len(panel_data['profitability_items']['data']), 4, "Panel data should contain 'profitability_items>data' entries")
- self.assertEqual(panel_data['profitability_items']['action'], "action_view_timesheet", "Panel data should contain 'profitability_items>action' entry")
-
try:
self.project_pigs.action_view_timesheet()
except Exception as e:
raise AssertionError("Error raised unexpectedly while calling the action defined in profitalities action panel data ! Exception : " + e.args[0])
-
- def test_project_update_description_date(self):
- self.project_pigs.allow_timesheets = True
- self.project_pigs.allow_billable = True
- self.employee_user = self.env['hr.employee'].create({
- 'name': 'Employee User',
- 'timesheet_cost': 15,
- })
- self.env['account.analytic.line'].create({
- 'project_id': self.project_pigs.id,
- 'task_id': self.task_1.id,
- 'name': 'my first timesheet',
- 'unit_amount': 4,
- 'employee_id': self.employee_user.id,
- 'date': '2021-07-23'
- })
- self.env['account.analytic.line'].create({
- 'project_id': self.project_pigs.id,
- 'task_id': self.task_1.id,
- 'name': 'my second timesheet',
- 'unit_amount': 6,
- 'employee_id': self.employee_user.id,
- 'date': '2021-08-23'
- })
- with freeze_time('2021-08-24'):
- template_values = self.env['project.update']._get_template_values(self.project_pigs)
- self.assertEqual(template_values['profitability']['timesheet_unit_amount'], '6', "Timesheet unit amount used in the template should be defined.")
- self.assertEqual(template_values['profitability']['previous_timesheet_unit_amount'], '4', "Previous Timesheet unit amount used in the template should be defined.")
-
- def test_project_update_description_date_lang(self):
- self.project_pigs.allow_timesheets = True
- self.project_pigs.allow_billable = True
- self.env['res.lang']._activate_lang('fr_FR')
- self.env.user.write({'lang': 'fr_FR'})
- self.employee_user = self.env['hr.employee'].create({
- 'name': 'Employee User',
- 'timesheet_cost': 15,
- })
- self.env['account.analytic.line'].create({
- 'project_id': self.project_pigs.id,
- 'task_id': self.task_1.id,
- 'name': 'my first timesheet',
- 'unit_amount': 4,
- 'employee_id': self.employee_user.id,
- 'date': '2021-07-23'
- })
- self.env['account.analytic.line'].create({
- 'project_id': self.project_pigs.id,
- 'task_id': self.task_1.id,
- 'name': 'my second timesheet',
- 'unit_amount': 6,
- 'employee_id': self.employee_user.id,
- 'date': '2021-08-23'
- })
- with freeze_time('2021-08-24'):
- template_values = self.env['project.update']._get_template_values(self.project_pigs)
- self.assertEqual(template_values['profitability']['timesheet_unit_amount'], '6', "Timesheet unit amount used in the template should be defined.")
- self.assertEqual(template_values['profitability']['previous_timesheet_unit_amount'], '4', "Previous Timesheet unit amount used in the template should be defined.")
diff --git a/addons/sale_timesheet/tests/test_sale_timesheet.py b/addons/sale_timesheet/tests/test_sale_timesheet.py
index ca1a5e0bcec..e64c79eb97e 100644
--- a/addons/sale_timesheet/tests/test_sale_timesheet.py
+++ b/addons/sale_timesheet/tests/test_sale_timesheet.py
@@ -95,7 +95,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
'employee_id': self.employee_user.id,
})
self.assertEqual(so_line_ordered_project_only.qty_delivered, 0.0, 'Timesheet directly on project does not increase delivered quantity on so line')
- self.assertEqual(timesheet3.timesheet_invoice_type, 'non_billable_project', "Timesheets without task shoulbe be 'no project found'")
+ self.assertEqual(timesheet3.timesheet_invoice_type, 'non_billable', "Timesheets without SO should be be 'non-billable'")
self.assertFalse(timesheet3.timesheet_invoice_id, "The timesheet should not be linked to the invoice, since we are in ordered quantity")
# log timesheet on task in global project (higher than the initial ordrered qty)
@@ -270,7 +270,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
})
self.assertTrue(float_is_zero(so_line_deliver_only_project.qty_delivered, precision_digits=2), "Timesheeting on project should not incremented the delivered quantity on the SO line")
self.assertEqual(sale_order.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should have quantity to invoice')
- self.assertEqual(timesheet3.timesheet_invoice_type, 'non_billable_project', "Timesheets without task shoulbe be 'no project found'")
+ self.assertEqual(timesheet3.timesheet_invoice_type, 'billable_time', "Timesheets with an amount > 0 should be 'billable time'")
self.assertFalse(timesheet3.timesheet_invoice_id, "The timesheet3 should not be linked to the invoice yet")
# let's log some timesheets on the task (new task/new project)
@@ -357,7 +357,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheet):
self.assertEqual(sale_order.invoice_status, 'no', 'Sale Timesheet: "invoice on delivery" should not need to be invoiced on so confirmation')
self.assertEqual(timesheet1.timesheet_invoice_type, 'billable_fixed', "Timesheets linked to SO line with ordered product shoulbe be billable fixed since it is a milestone")
- self.assertEqual(timesheet2.timesheet_invoice_type, 'non_billable_project', "Timesheets without task shoulbe be 'no project found'")
+ self.assertEqual(timesheet2.timesheet_invoice_type, 'non_billable', "Timesheets without SO should be be 'non-billable'")
self.assertFalse(timesheet1.timesheet_invoice_id, "The timesheet1 should not be linked to the invoice")
self.assertFalse(timesheet2.timesheet_invoice_id, "The timesheet2 should not be linked to the invoice")
diff --git a/addons/sale_timesheet/views/hr_timesheet_templates.xml b/addons/sale_timesheet/views/hr_timesheet_templates.xml
deleted file mode 100644
index 400711b854e..00000000000
--- a/addons/sale_timesheet/views/hr_timesheet_templates.xml
+++ /dev/null
@@ -1,486 +0,0 @@
-
-
-
-
- Timesheet Plan
- qweb
- project.project
-
-
-
-
-
-
-In , have been recorded on the project vs.
-
- in ( %).
-
-
-
+
+
Profitability
The cost of the project is now at , for a revenue of , leading to a
@@ -23,19 +42,10 @@ The cost of the project is now at , for a rev
- margin ( %), and a % billing rate.
+ margin (%).