summaryrefslogtreecommitdiff
path: root/addons/sale_timesheet/tests/test_project_overview.py
blob: 90f51e1d0e81379cb05095bb17b16baa61767bd4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# -*- 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")