<reponame>VaibhavBhujade/Blockchain-ERP-interoperability<gh_stars>0
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo.addons.sale_timesheet.tests.common import TestCommonSaleTimesheetNoChart
from odoo.exceptions import UserError


class TestProjectBilling(TestCommonSaleTimesheetNoChart):
    """ This test suite provide checks for miscellaneous small things. """

    @classmethod
    def setUpClass(cls):
        super(TestProjectBilling, cls).setUpClass()
        # set up
        cls.setUpServiceProducts()
        cls.setUpEmployees()
        cls.employee_tde = cls.env['hr.employee'].create({
            'name': 'Employee TDE',
            'timesheet_cost': 42,
        })

        cls.partner_2 = cls.env['res.partner'].create({
            'name': 'Customer from the South',
            'email': '<EMAIL>',
            'property_account_payable_id': cls.account_payable.id,
            'property_account_receivable_id': cls.account_receivable.id,
        })

        # Sale Order 1, no project/task created, used to timesheet at employee rate
        SaleOrder = cls.env['sale.order'].with_context(tracking_disable=True)
        SaleOrderLine = cls.env['sale.order.line'].with_context(tracking_disable=True)
        cls.sale_order_1 = SaleOrder.create({
            'partner_id': cls.partner_customer_usd.id,
            'partner_invoice_id': cls.partner_customer_usd.id,
            'partner_shipping_id': cls.partner_customer_usd.id,
        })

        cls.so1_line_order_no_task = SaleOrderLine.create({
            'name': cls.product_order_timesheet1.name,
            'product_id': cls.product_order_timesheet1.id,
            'product_uom_qty': 10,
            'product_uom': cls.product_order_timesheet1.uom_id.id,
            'price_unit': cls.product_order_timesheet1.list_price,
            'order_id': cls.sale_order_1.id,
        })

        cls.so1_line_deliver_no_task = SaleOrderLine.create({
            'name': cls.product_delivery_timesheet1.name,
            'product_id': cls.product_delivery_timesheet1.id,
            'product_uom_qty': 10,
            'product_uom': cls.product_delivery_timesheet1.uom_id.id,
            'price_unit': cls.product_delivery_timesheet1.list_price,
            'order_id': cls.sale_order_1.id,
        })
        # Sale Order 2, creates 2 project billed at task rate
        cls.sale_order_2 = SaleOrder.create({
            'partner_id': cls.partner_2.id,
            'partner_invoice_id': cls.partner_2.id,
            'partner_shipping_id': cls.partner_2.id,
        })
        cls.so2_line_deliver_project_task = SaleOrderLine.create({
            'order_id': cls.sale_order_2.id,
            'name': cls.product_delivery_timesheet3.name,
            'product_id': cls.product_delivery_timesheet3.id,
            'product_uom_qty': 5,
            'product_uom': cls.product_delivery_timesheet3.uom_id.id,
            'price_unit': cls.product_delivery_timesheet3.list_price
        })
        cls.so2_line_deliver_project_template = SaleOrderLine.create({
            'order_id': cls.sale_order_2.id,
            'name': cls.product_delivery_timesheet5.name,
            'product_id': cls.product_delivery_timesheet5.id,
            'product_uom_qty': 7,
            'product_uom': cls.product_delivery_timesheet5.uom_id.id,
            'price_unit': cls.product_delivery_timesheet5.list_price
        })
        cls.sale_order_2.action_confirm()

        # Projects: at least one per billable type
        Project = cls.env['project.project'].with_context(tracking_disable=True)
        cls.project_subtask = Project.create({
            'name': "Sub Task Project (non billable)",
            'allow_timesheets': True,
            'billable_type': 'no',
            'partner_id': False,
        })
        cls.project_non_billable = Project.create({
            'name': "Non Billable Project",
            'allow_timesheets': True,
            'billable_type': 'no',
            'partner_id': False,
            'subtask_project_id': cls.project_subtask.id,
        })
        cls.project_task_rate = cls.env['project.project'].search([('sale_line_id', '=', cls.so2_line_deliver_project_task.id)], limit=1)
        cls.project_task_rate2 = cls.env['project.project'].search([('sale_line_id', '=', cls.so2_line_deliver_project_template.id)], limit=1)

        cls.project_employee_rate = Project.create({
            'name': "Project billed at Employee Rate",
            'allow_timesheets': True,
            'billable_type': 'employee_rate',
            'sale_order_id': cls.sale_order_1.id,
            'partner_id': cls.sale_order_1.partner_id.id,
            'subtask_project_id': cls.project_subtask.id,
        })
        cls.project_employee_rate_manager = cls.env['project.sale.line.employee.map'].create({
            'project_id': cls.project_employee_rate.id,
            'sale_line_id': cls.so1_line_order_no_task.id,
            'employee_id': cls.employee_manager.id,
        })
        cls.project_employee_rate_user = cls.env['project.sale.line.employee.map'].create({
            'project_id': cls.project_employee_rate.id,
            'sale_line_id': cls.so1_line_deliver_no_task.id,
            'employee_id': cls.employee_user.id,
        })

    def test_make_billable_at_task_rate(self):
        """ Starting from a non billable project, make it billable at task rate, using the wizard """
        Timesheet = self.env['account.analytic.line']
        Task = self.env['project.task']
        # set a customer on the project
        self.project_non_billable.write({
            'partner_id': self.partner_2.id
        })
        # create a task and 2 timesheets
        task = Task.with_context(default_project_id=self.project_non_billable.id).create({
            'name': '<NAME>',
            'partner_id': self.project_non_billable.partner_id.id,
            'planned_hours': 10,
        })
        timesheet1 = Timesheet.create({
            'name': 'Test Line',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 3,
            'employee_id': self.employee_manager.id,
        })
        timesheet2 = Timesheet.create({
            'name': 'Test Line tde',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 2,
            'employee_id': self.employee_tde.id,
        })

        # create wizard
        wizard = self.env['project.create.sale.order'].with_context(active_id=self.project_non_billable.id, active_model='project.project').create({
            'product_id': self.product_delivery_timesheet3.id,  # product creates new T in new P
            'price_unit': self.product_delivery_timesheet3.list_price,
            'billable_type': 'project_rate',
        })

        self.assertEqual(self.project_non_billable.billable_type, 'no', "The project should still be non billable")
        self.assertEqual(wizard.partner_id, self.project_non_billable.partner_id, "The wizard should have the same partner as the project")

        # create the SO from the project
        action = wizard.action_create_sale_order()
        sale_order = self.env['sale.order'].browse(action['res_id'])

        self.assertEqual(self.project_non_billable.billable_type, 'task_rate', "The project should be 'task rate' billable")
        self.assertEqual(sale_order.partner_id, self.project_non_billable.partner_id, "The customer of the SO should be the same as the project")
        self.assertEqual(len(sale_order.order_line), 1, "The SO should have 1 line")
        self.assertEqual(sale_order.order_line.product_id, wizard.product_id, "The product of the only SOL should be the selected on the wizard")
        self.assertEqual(sale_order.order_line.project_id, self.project_non_billable, "SOL should be linked to the project")
        self.assertTrue(sale_order.order_line.task_id, "The SOL creates a task as they were no task already present in the project (system limitation)")
        self.assertEqual(sale_order.order_line.task_id.project_id, self.project_non_billable, "The created task should be in the project")
        self.assertEqual(sale_order.order_line.qty_delivered, timesheet1.unit_amount + timesheet2.unit_amount, "The create SOL should have an delivered quantity equals to the sum of tasks'timesheets")

    def test_make_billable_at_employee_rate(self):
        """ Starting from a non billable project, make it billable at employee rate, using the wizard """
        Timesheet = self.env['account.analytic.line']
        Task = self.env['project.task']
        # set a customer on the project
        self.project_non_billable.write({
            'partner_id': self.partner_2.id
        })
        # create a task and 2 timesheets
        task = Task.with_context(default_project_id=self.project_non_billable.id).create({
            'name': '<NAME>',
            'partner_id': self.project_non_billable.partner_id.id,
            'planned_hours': 10,
        })
        timesheet1 = Timesheet.create({
            'name': '<NAME>',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 3,
            'employee_id': self.employee_manager.id,
        })
        timesheet2 = Timesheet.create({
            'name': 'Test Line tde',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 2,
            'employee_id': self.employee_user.id,
        })

        # create wizard
        wizard = self.env['project.create.sale.order'].with_context(active_id=self.project_non_billable.id, active_model='project.project').create({
            'billable_type': 'employee_rate',
            'partner_id': self.partner_2.id,
            'line_ids': [
                (0, 0, {'product_id': self.product_delivery_timesheet1.id, 'price_unit': 15, 'employee_id': self.employee_tde.id}),  # product creates no T
                (0, 0, {'product_id': self.product_delivery_timesheet1.id, 'price_unit': 15, 'employee_id': self.employee_manager.id}),  # product creates no T (same product than previous one)
                (0, 0, {'product_id': self.product_delivery_timesheet3.id, 'price_unit': self.product_delivery_timesheet3.list_price, 'employee_id': self.employee_user.id}),  # product creates new T in new P
            ]
        })

        self.assertEqual(self.project_non_billable.billable_type, 'no', "The project should still be non billable")
        self.assertEqual(wizard.partner_id, self.project_non_billable.partner_id, "The wizard should have the same partner as the project")
        self.assertEqual(wizard.project_id, self.project_non_billable, "The wizard'project should be the non billable project")

        # create the SO from the project
        action = wizard.action_create_sale_order()
        sale_order = self.env['sale.order'].browse(action['res_id'])

        self.assertEqual(self.project_non_billable.billable_type, 'employee_rate', "The project should be 'employee rate' billable")
        self.assertEqual(sale_order.partner_id, self.project_non_billable.partner_id, "The customer of the SO should be the same as the project")
        self.assertEqual(len(sale_order.order_line), 2, "The SO should have 2 lines, as in wizard map there were 2 time the same product with the same price (for 2 different employees)")
        self.assertEqual(len(self.project_non_billable.sale_line_employee_ids), 3, "The project have 3 lines in its map")
        self.assertEqual(self.project_non_billable.sale_line_id, sale_order.order_line[0], "The wizard sets sale line fallbakc on project as the first of the list")
        self.assertEqual(task.sale_line_id, sale_order.order_line[0], "The wizard sets sale line fallback on tasks")
        self.assertEqual(task.partner_id, wizard.partner_id, "The wizard sets the customer on tasks to make SOL line field visible")

        line1 = sale_order.order_line.filtered(lambda sol: sol.product_id == self.product_delivery_timesheet1)
        line2 = sale_order.order_line.filtered(lambda sol: sol.product_id == self.product_delivery_timesheet3)

        self.assertTrue(line1, "Sale line 1 with product 1 should exists")
        self.assertTrue(line2, "Sale line 2 with product 3 should exists")

        self.assertFalse(line1.project_id, "Sale line 1 should be linked to the 'non billable' project")
        self.assertEqual(line2.project_id, self.project_non_billable, "Sale line 3 should be linked to the 'non billable' project")
        self.assertEqual(line1.price_unit, 15, "The unit price of SOL 1 should be 15")
        self.assertEqual(line1.product_uom_qty, 0, "The ordered qty of SOL 1 should be one")
        self.assertEqual(line2.product_uom_qty, 0, "The ordered qty of SOL 1 should be one")

        self.assertEqual(self.project_non_billable.sale_line_employee_ids.mapped('sale_line_id'), sale_order.order_line, "The SO lines of the map should be the same of the sales order")
        self.assertEqual(timesheet1.so_line, line1, "Timesheet1 should be linked to sale line 1, as employee manager create the timesheet")
        self.assertEqual(timesheet2.so_line, line2, "Timesheet2 should be linked to sale line 2, as employee tde create the timesheet")
        self.assertEqual(timesheet1.unit_amount, line1.qty_delivered, "Sale line 1 should have a delivered qty equals to the sum of its linked timesheets")
        self.assertEqual(timesheet2.unit_amount, line2.qty_delivered, "Sale line 2 should have a delivered qty equals to the sum of its linked timesheets")

    def test_billing_employee_rate(self):
        """ Check task and subtask creation, and timesheeting in a project billed at 'employee rate'. Then move the task into a 'task rate' project. """
        Task = self.env['project.task'].with_context(tracking_disable=True)
        Timesheet = self.env['account.analytic.line']

        # create a task
        task = Task.with_context(default_project_id=self.project_employee_rate.id).create({
            'name': '<NAME>',
            'partner_id': self.partner_customer_usd.id,
        })

        self.assertEqual(task.billable_type, 'employee_rate', "Task in project 'employee rate' should be billed at employee rate")
        self.assertFalse(task.sale_line_id, "Task created in a project billed on 'employee rate' should not be linked to a SOL")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        # log timesheet on task
        timesheet1 = Timesheet.create({
            'name': '<NAME>',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 50,
            'employee_id': self.employee_manager.id,
        })

        self.assertEqual(self.project_employee_rate_manager.sale_line_id, timesheet1.so_line, "The timesheet should be linked to the SOL associated to the Employee manager in the map")
        self.assertEqual(self.project_employee_rate_manager.project_id, timesheet1.project_id, "The timesheet should be linked to the project of the map entry")

        # create a subtask
        subtask = Task.with_context(default_project_id=self.project_employee_rate.subtask_project_id.id).create({
            'name': '<NAME>',
            'parent_id': task.id,
        })

        self.assertEqual(subtask.billable_type, 'no', "Subtask in non billable project should be non billable too")
        self.assertEqual(subtask.project_id.billable_type, 'no', "The subtask project is non billable even if the subtask is")
        self.assertEqual(subtask.partner_id, subtask.parent_id.partner_id, "Subtask should have the same customer as the one from their mother")

        # log timesheet on subtask
        timesheet2 = Timesheet.create({
            'name': '<NAME>task',
            'project_id': subtask.project_id.id,
            'task_id': subtask.id,
            'unit_amount': 50,
            'employee_id': self.employee_user.id,
        })

        self.assertEqual(subtask.project_id, timesheet2.project_id, "The timesheet is in the subtask project")
        self.assertNotEqual(self.project_employee_rate_user.project_id, timesheet2.project_id, "The timesheet should not be linked to the billing project for the map")
        self.assertFalse(timesheet2.so_line, "The timesheet should not be linked to SOL as the task is in a non billable project")

        # move task into task rate project
        task.write({
            'project_id': self.project_task_rate.id,
        })
        task._onchange_project()

        self.assertEqual(task.billable_type, 'task_rate', "Task in project 'task rate' should be billed at task rate")
        self.assertEqual(task.sale_line_id, self.project_task_rate.sale_line_id, "Task moved in a task rate billable project")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        # move subtask into task rate project
        subtask.write({
            'project_id': self.project_task_rate2.id,
        })

        self.assertEqual(task.billable_type, 'task_rate', "Subtask should keep the billable type from its parent, even when they are moved into another project")
        self.assertEqual(task.sale_line_id, self.project_task_rate.sale_line_id, "Subtask should keep the same sale order line than their mother, even when they are moved into another project")

        # create a second task in employee rate project
        task2 = Task.with_context(default_project_id=self.project_employee_rate.id).create({
            'name': '<NAME>',
            'partner_id': self.partner_customer_usd.id,
            'sale_line_id': False
        })

        # log timesheet on task in 'employee rate' project without any fallback (no map, no SOL on task, no SOL on project)
        timesheet3 = Timesheet.create({
            'name': '<NAME>',
            'project_id': task2.project_id.id,
            'task_id': task2.id,
            'unit_amount': 3,
            'employee_id': self.employee_tde.id,
        })

        self.assertFalse(timesheet3.so_line, "The timesheet should not be linked to SOL as there is no fallback at all (no map, no SOL on task, no SOL on project)")

        # add a SOL on the project as fallback
        self.project_employee_rate.write({'sale_line_id': self.so1_line_deliver_no_task.id})

        # log timesheet on task in 'employee rate' project wit the project fallback only (no map, no SOL on task, but SOL on project)
        timesheet4 = Timesheet.create({
            'name': '<NAME> ',
            'project_id': task2.project_id.id,
            'task_id': task2.id,
            'unit_amount': 4,
            'employee_id': self.employee_tde.id,
        })

        self.assertEquals(timesheet4.so_line, self.so1_line_deliver_no_task, "The timesheet should be linked to SOL on the project, as no entry for TDE in project map and no SOL on task")

    def test_billing_task_rate(self):
        """ Check task and subtask creation, and timesheeting in a project billed at 'task rate'. Then move the task into a 'employee rate' project then, 'non billable'. """
        Task = self.env['project.task'].with_context(tracking_disable=True)
        Timesheet = self.env['account.analytic.line']

        # set subtask project on task rate project
        self.project_task_rate.write({'subtask_project_id': self.project_subtask.id})

        # create a task
        task = Task.with_context(default_project_id=self.project_task_rate.id).create({
            'name': '<NAME>',
            'partner_id': self.partner_customer_usd.id,
        })
        task._onchange_project()

        self.assertEqual(task.billable_type, 'task_rate', "Task in project 'task rate' should be billed at task rate")
        self.assertEqual(task.sale_line_id, self.project_task_rate.sale_line_id, "Task created in a project billed on 'task rate' should be linked to a SOL of the project")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        # log timesheet on task
        timesheet1 = Timesheet.create({
            'name': 'Test Line',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 50,
            'employee_id': self.employee_manager.id,
        })

        self.assertEqual(self.project_task_rate.sale_line_id, timesheet1.so_line, "The timesheet should be linked to the SOL associated to the Employee manager in the map")

        # create a subtask
        subtask = Task.with_context(default_project_id=self.project_task_rate.subtask_project_id.id).create({
            'name': 'first subtask task',
            'parent_id': task.id,
        })

        self.assertEqual(subtask.billable_type, 'task_rate', "Subtask in a non billable project with a so line set is task rate billable")
        self.assertEqual(subtask.project_id.billable_type, 'no', "The subtask project is non billable even if the subtask is")
        self.assertEqual(subtask.partner_id, subtask.parent_id.partner_id, "Subtask should have the same customer as the one from their mother")

        # log timesheet on subtask
        timesheet2 = Timesheet.create({
            'name': 'Test Line on subtask',
            'project_id': subtask.project_id.id,
            'task_id': subtask.id,
            'unit_amount': 50,
            'employee_id': self.employee_user.id,
        })

        self.assertEqual(subtask.project_id, timesheet2.project_id, "The timesheet is in the subtask project")
        self.assertEqual(timesheet2.so_line, subtask.sale_line_id, "The timesheet should be linked to SOL as the task even in a non billable project")

        # move task and subtask into task rate project
        task.write({
            'project_id': self.project_employee_rate.id,
        })
        task._onchange_project()
        subtask.write({
            'project_id': self.project_employee_rate.id,
        })
        subtask._onchange_project()

        self.assertEqual(task.billable_type, 'employee_rate', "Task moved in project 'employee rate' should be billed at employee rate")
        self.assertFalse(task.sale_line_id, "Task moved in a employee rate billable project have empty so line")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        self.assertEqual(subtask.billable_type, 'employee_rate', "subtask moved in project 'employee rate' should be billed at employee rate")
        self.assertFalse(subtask.sale_line_id, "Subask moved in a employee rate billable project have empty so line")
        self.assertEqual(subtask.partner_id, task.project_id.partner_id, "Subask created in a project billed on 'employee rate' should have the same customer as the one from the project")

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from odoo.addons.sale_timesheet.tests.common import TestCommonSaleTimesheetNoChart
from odoo.exceptions import UserError


class TestProjectBilling(TestCommonSaleTimesheetNoChart):
    """ This test suite provide checks for miscellaneous small things. """

    @classmethod
    def setUpClass(cls):
        super(TestProjectBilling, cls).setUpClass()
        # set up
        cls.setUpServiceProducts()
        cls.setUpEmployees()
        cls.employee_tde = cls.env['hr.employee'].create({
            'name': 'Employee TDE',
            'timesheet_cost': 42,
        })

        cls.partner_2 = cls.env['res.partner'].create({
            'name': 'Customer from the South',
            'email': '<EMAIL>',
            'property_account_payable_id': cls.account_payable.id,
            'property_account_receivable_id': cls.account_receivable.id,
        })

        # Sale Order 1, no project/task created, used to timesheet at employee rate
        SaleOrder = cls.env['sale.order'].with_context(tracking_disable=True)
        SaleOrderLine = cls.env['sale.order.line'].with_context(tracking_disable=True)
        cls.sale_order_1 = SaleOrder.create({
            'partner_id': cls.partner_customer_usd.id,
            'partner_invoice_id': cls.partner_customer_usd.id,
            'partner_shipping_id': cls.partner_customer_usd.id,
        })

        cls.so1_line_order_no_task = SaleOrderLine.create({
            'name': cls.product_order_timesheet1.name,
            'product_id': cls.product_order_timesheet1.id,
            'product_uom_qty': 10,
            'product_uom': cls.product_order_timesheet1.uom_id.id,
            'price_unit': cls.product_order_timesheet1.list_price,
            'order_id': cls.sale_order_1.id,
        })

        cls.so1_line_deliver_no_task = SaleOrderLine.create({
            'name': cls.product_delivery_timesheet1.name,
            'product_id': cls.product_delivery_timesheet1.id,
            'product_uom_qty': 10,
            'product_uom': cls.product_delivery_timesheet1.uom_id.id,
            'price_unit': cls.product_delivery_timesheet1.list_price,
            'order_id': cls.sale_order_1.id,
        })
        # Sale Order 2, creates 2 project billed at task rate
        cls.sale_order_2 = SaleOrder.create({
            'partner_id': cls.partner_2.id,
            'partner_invoice_id': cls.partner_2.id,
            'partner_shipping_id': cls.partner_2.id,
        })
        cls.so2_line_deliver_project_task = SaleOrderLine.create({
            'order_id': cls.sale_order_2.id,
            'name': cls.product_delivery_timesheet3.name,
            'product_id': cls.product_delivery_timesheet3.id,
            'product_uom_qty': 5,
            'product_uom': cls.product_delivery_timesheet3.uom_id.id,
            'price_unit': cls.product_delivery_timesheet3.list_price
        })
        cls.so2_line_deliver_project_template = SaleOrderLine.create({
            'order_id': cls.sale_order_2.id,
            'name': cls.product_delivery_timesheet5.name,
            'product_id': cls.product_delivery_timesheet5.id,
            'product_uom_qty': 7,
            'product_uom': cls.product_delivery_timesheet5.uom_id.id,
            'price_unit': cls.product_delivery_timesheet5.list_price
        })
        cls.sale_order_2.action_confirm()

        # Projects: at least one per billable type
        Project = cls.env['project.project'].with_context(tracking_disable=True)
        cls.project_subtask = Project.create({
            'name': "Sub Task Project (non billable)",
            'allow_timesheets': True,
            'billable_type': 'no',
            'partner_id': False,
        })
        cls.project_non_billable = Project.create({
            'name': "Non Billable Project",
            'allow_timesheets': True,
            'billable_type': 'no',
            'partner_id': False,
            'subtask_project_id': cls.project_subtask.id,
        })
        cls.project_task_rate = cls.env['project.project'].search([('sale_line_id', '=', cls.so2_line_deliver_project_task.id)], limit=1)
        cls.project_task_rate2 = cls.env['project.project'].search([('sale_line_id', '=', cls.so2_line_deliver_project_template.id)], limit=1)

        cls.project_employee_rate = Project.create({
            'name': "Project billed at Employee Rate",
            'allow_timesheets': True,
            'billable_type': 'employee_rate',
            'sale_order_id': cls.sale_order_1.id,
            'partner_id': cls.sale_order_1.partner_id.id,
            'subtask_project_id': cls.project_subtask.id,
        })
        cls.project_employee_rate_manager = cls.env['project.sale.line.employee.map'].create({
            'project_id': cls.project_employee_rate.id,
            'sale_line_id': cls.so1_line_order_no_task.id,
            'employee_id': cls.employee_manager.id,
        })
        cls.project_employee_rate_user = cls.env['project.sale.line.employee.map'].create({
            'project_id': cls.project_employee_rate.id,
            'sale_line_id': cls.so1_line_deliver_no_task.id,
            'employee_id': cls.employee_user.id,
        })

    def test_make_billable_at_task_rate(self):
        """ Starting from a non billable project, make it billable at task rate, using the wizard """
        Timesheet = self.env['account.analytic.line']
        Task = self.env['project.task']
        # set a customer on the project
        self.project_non_billable.write({
            'partner_id': self.partner_2.id
        })
        # create a task and 2 timesheets
        task = Task.with_context(default_project_id=self.project_non_billable.id).create({
            'name': '<NAME>',
            'partner_id': self.project_non_billable.partner_id.id,
            'planned_hours': 10,
        })
        timesheet1 = Timesheet.create({
            'name': 'Test Line',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 3,
            'employee_id': self.employee_manager.id,
        })
        timesheet2 = Timesheet.create({
            'name': 'Test Line tde',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 2,
            'employee_id': self.employee_tde.id,
        })

        # create wizard
        wizard = self.env['project.create.sale.order'].with_context(active_id=self.project_non_billable.id, active_model='project.project').create({
            'product_id': self.product_delivery_timesheet3.id,  # product creates new T in new P
            'price_unit': self.product_delivery_timesheet3.list_price,
            'billable_type': 'project_rate',
        })

        self.assertEqual(self.project_non_billable.billable_type, 'no', "The project should still be non billable")
        self.assertEqual(wizard.partner_id, self.project_non_billable.partner_id, "The wizard should have the same partner as the project")

        # create the SO from the project
        action = wizard.action_create_sale_order()
        sale_order = self.env['sale.order'].browse(action['res_id'])

        self.assertEqual(self.project_non_billable.billable_type, 'task_rate', "The project should be 'task rate' billable")
        self.assertEqual(sale_order.partner_id, self.project_non_billable.partner_id, "The customer of the SO should be the same as the project")
        self.assertEqual(len(sale_order.order_line), 1, "The SO should have 1 line")
        self.assertEqual(sale_order.order_line.product_id, wizard.product_id, "The product of the only SOL should be the selected on the wizard")
        self.assertEqual(sale_order.order_line.project_id, self.project_non_billable, "SOL should be linked to the project")
        self.assertTrue(sale_order.order_line.task_id, "The SOL creates a task as they were no task already present in the project (system limitation)")
        self.assertEqual(sale_order.order_line.task_id.project_id, self.project_non_billable, "The created task should be in the project")
        self.assertEqual(sale_order.order_line.qty_delivered, timesheet1.unit_amount + timesheet2.unit_amount, "The create SOL should have an delivered quantity equals to the sum of tasks'timesheets")

    def test_make_billable_at_employee_rate(self):
        """ Starting from a non billable project, make it billable at employee rate, using the wizard """
        Timesheet = self.env['account.analytic.line']
        Task = self.env['project.task']
        # set a customer on the project
        self.project_non_billable.write({
            'partner_id': self.partner_2.id
        })
        # create a task and 2 timesheets
        task = Task.with_context(default_project_id=self.project_non_billable.id).create({
            'name': '<NAME>',
            'partner_id': self.project_non_billable.partner_id.id,
            'planned_hours': 10,
        })
        timesheet1 = Timesheet.create({
            'name': '<NAME>',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 3,
            'employee_id': self.employee_manager.id,
        })
        timesheet2 = Timesheet.create({
            'name': 'Test Line tde',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 2,
            'employee_id': self.employee_user.id,
        })

        # create wizard
        wizard = self.env['project.create.sale.order'].with_context(active_id=self.project_non_billable.id, active_model='project.project').create({
            'billable_type': 'employee_rate',
            'partner_id': self.partner_2.id,
            'line_ids': [
                (0, 0, {'product_id': self.product_delivery_timesheet1.id, 'price_unit': 15, 'employee_id': self.employee_tde.id}),  # product creates no T
                (0, 0, {'product_id': self.product_delivery_timesheet1.id, 'price_unit': 15, 'employee_id': self.employee_manager.id}),  # product creates no T (same product than previous one)
                (0, 0, {'product_id': self.product_delivery_timesheet3.id, 'price_unit': self.product_delivery_timesheet3.list_price, 'employee_id': self.employee_user.id}),  # product creates new T in new P
            ]
        })

        self.assertEqual(self.project_non_billable.billable_type, 'no', "The project should still be non billable")
        self.assertEqual(wizard.partner_id, self.project_non_billable.partner_id, "The wizard should have the same partner as the project")
        self.assertEqual(wizard.project_id, self.project_non_billable, "The wizard'project should be the non billable project")

        # create the SO from the project
        action = wizard.action_create_sale_order()
        sale_order = self.env['sale.order'].browse(action['res_id'])

        self.assertEqual(self.project_non_billable.billable_type, 'employee_rate', "The project should be 'employee rate' billable")
        self.assertEqual(sale_order.partner_id, self.project_non_billable.partner_id, "The customer of the SO should be the same as the project")
        self.assertEqual(len(sale_order.order_line), 2, "The SO should have 2 lines, as in wizard map there were 2 time the same product with the same price (for 2 different employees)")
        self.assertEqual(len(self.project_non_billable.sale_line_employee_ids), 3, "The project have 3 lines in its map")
        self.assertEqual(self.project_non_billable.sale_line_id, sale_order.order_line[0], "The wizard sets sale line fallbakc on project as the first of the list")
        self.assertEqual(task.sale_line_id, sale_order.order_line[0], "The wizard sets sale line fallback on tasks")
        self.assertEqual(task.partner_id, wizard.partner_id, "The wizard sets the customer on tasks to make SOL line field visible")

        line1 = sale_order.order_line.filtered(lambda sol: sol.product_id == self.product_delivery_timesheet1)
        line2 = sale_order.order_line.filtered(lambda sol: sol.product_id == self.product_delivery_timesheet3)

        self.assertTrue(line1, "Sale line 1 with product 1 should exists")
        self.assertTrue(line2, "Sale line 2 with product 3 should exists")

        self.assertFalse(line1.project_id, "Sale line 1 should be linked to the 'non billable' project")
        self.assertEqual(line2.project_id, self.project_non_billable, "Sale line 3 should be linked to the 'non billable' project")
        self.assertEqual(line1.price_unit, 15, "The unit price of SOL 1 should be 15")
        self.assertEqual(line1.product_uom_qty, 0, "The ordered qty of SOL 1 should be one")
        self.assertEqual(line2.product_uom_qty, 0, "The ordered qty of SOL 1 should be one")

        self.assertEqual(self.project_non_billable.sale_line_employee_ids.mapped('sale_line_id'), sale_order.order_line, "The SO lines of the map should be the same of the sales order")
        self.assertEqual(timesheet1.so_line, line1, "Timesheet1 should be linked to sale line 1, as employee manager create the timesheet")
        self.assertEqual(timesheet2.so_line, line2, "Timesheet2 should be linked to sale line 2, as employee tde create the timesheet")
        self.assertEqual(timesheet1.unit_amount, line1.qty_delivered, "Sale line 1 should have a delivered qty equals to the sum of its linked timesheets")
        self.assertEqual(timesheet2.unit_amount, line2.qty_delivered, "Sale line 2 should have a delivered qty equals to the sum of its linked timesheets")

    def test_billing_employee_rate(self):
        """ Check task and subtask creation, and timesheeting in a project billed at 'employee rate'. Then move the task into a 'task rate' project. """
        Task = self.env['project.task'].with_context(tracking_disable=True)
        Timesheet = self.env['account.analytic.line']

        # create a task
        task = Task.with_context(default_project_id=self.project_employee_rate.id).create({
            'name': '<NAME>',
            'partner_id': self.partner_customer_usd.id,
        })

        self.assertEqual(task.billable_type, 'employee_rate', "Task in project 'employee rate' should be billed at employee rate")
        self.assertFalse(task.sale_line_id, "Task created in a project billed on 'employee rate' should not be linked to a SOL")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        # log timesheet on task
        timesheet1 = Timesheet.create({
            'name': '<NAME>',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 50,
            'employee_id': self.employee_manager.id,
        })

        self.assertEqual(self.project_employee_rate_manager.sale_line_id, timesheet1.so_line, "The timesheet should be linked to the SOL associated to the Employee manager in the map")
        self.assertEqual(self.project_employee_rate_manager.project_id, timesheet1.project_id, "The timesheet should be linked to the project of the map entry")

        # create a subtask
        subtask = Task.with_context(default_project_id=self.project_employee_rate.subtask_project_id.id).create({
            'name': '<NAME>',
            'parent_id': task.id,
        })

        self.assertEqual(subtask.billable_type, 'no', "Subtask in non billable project should be non billable too")
        self.assertEqual(subtask.project_id.billable_type, 'no', "The subtask project is non billable even if the subtask is")
        self.assertEqual(subtask.partner_id, subtask.parent_id.partner_id, "Subtask should have the same customer as the one from their mother")

        # log timesheet on subtask
        timesheet2 = Timesheet.create({
            'name': '<NAME>task',
            'project_id': subtask.project_id.id,
            'task_id': subtask.id,
            'unit_amount': 50,
            'employee_id': self.employee_user.id,
        })

        self.assertEqual(subtask.project_id, timesheet2.project_id, "The timesheet is in the subtask project")
        self.assertNotEqual(self.project_employee_rate_user.project_id, timesheet2.project_id, "The timesheet should not be linked to the billing project for the map")
        self.assertFalse(timesheet2.so_line, "The timesheet should not be linked to SOL as the task is in a non billable project")

        # move task into task rate project
        task.write({
            'project_id': self.project_task_rate.id,
        })
        task._onchange_project()

        self.assertEqual(task.billable_type, 'task_rate', "Task in project 'task rate' should be billed at task rate")
        self.assertEqual(task.sale_line_id, self.project_task_rate.sale_line_id, "Task moved in a task rate billable project")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        # move subtask into task rate project
        subtask.write({
            'project_id': self.project_task_rate2.id,
        })

        self.assertEqual(task.billable_type, 'task_rate', "Subtask should keep the billable type from its parent, even when they are moved into another project")
        self.assertEqual(task.sale_line_id, self.project_task_rate.sale_line_id, "Subtask should keep the same sale order line than their mother, even when they are moved into another project")

        # create a second task in employee rate project
        task2 = Task.with_context(default_project_id=self.project_employee_rate.id).create({
            'name': '<NAME>',
            'partner_id': self.partner_customer_usd.id,
            'sale_line_id': False
        })

        # log timesheet on task in 'employee rate' project without any fallback (no map, no SOL on task, no SOL on project)
        timesheet3 = Timesheet.create({
            'name': '<NAME>',
            'project_id': task2.project_id.id,
            'task_id': task2.id,
            'unit_amount': 3,
            'employee_id': self.employee_tde.id,
        })

        self.assertFalse(timesheet3.so_line, "The timesheet should not be linked to SOL as there is no fallback at all (no map, no SOL on task, no SOL on project)")

        # add a SOL on the project as fallback
        self.project_employee_rate.write({'sale_line_id': self.so1_line_deliver_no_task.id})

        # log timesheet on task in 'employee rate' project wit the project fallback only (no map, no SOL on task, but SOL on project)
        timesheet4 = Timesheet.create({
            'name': '<NAME> ',
            'project_id': task2.project_id.id,
            'task_id': task2.id,
            'unit_amount': 4,
            'employee_id': self.employee_tde.id,
        })

        self.assertEquals(timesheet4.so_line, self.so1_line_deliver_no_task, "The timesheet should be linked to SOL on the project, as no entry for TDE in project map and no SOL on task")

    def test_billing_task_rate(self):
        """ Check task and subtask creation, and timesheeting in a project billed at 'task rate'. Then move the task into a 'employee rate' project then, 'non billable'. """
        Task = self.env['project.task'].with_context(tracking_disable=True)
        Timesheet = self.env['account.analytic.line']

        # set subtask project on task rate project
        self.project_task_rate.write({'subtask_project_id': self.project_subtask.id})

        # create a task
        task = Task.with_context(default_project_id=self.project_task_rate.id).create({
            'name': '<NAME>',
            'partner_id': self.partner_customer_usd.id,
        })
        task._onchange_project()

        self.assertEqual(task.billable_type, 'task_rate', "Task in project 'task rate' should be billed at task rate")
        self.assertEqual(task.sale_line_id, self.project_task_rate.sale_line_id, "Task created in a project billed on 'task rate' should be linked to a SOL of the project")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        # log timesheet on task
        timesheet1 = Timesheet.create({
            'name': 'Test Line',
            'project_id': task.project_id.id,
            'task_id': task.id,
            'unit_amount': 50,
            'employee_id': self.employee_manager.id,
        })

        self.assertEqual(self.project_task_rate.sale_line_id, timesheet1.so_line, "The timesheet should be linked to the SOL associated to the Employee manager in the map")

        # create a subtask
        subtask = Task.with_context(default_project_id=self.project_task_rate.subtask_project_id.id).create({
            'name': 'first subtask task',
            'parent_id': task.id,
        })

        self.assertEqual(subtask.billable_type, 'task_rate', "Subtask in a non billable project with a so line set is task rate billable")
        self.assertEqual(subtask.project_id.billable_type, 'no', "The subtask project is non billable even if the subtask is")
        self.assertEqual(subtask.partner_id, subtask.parent_id.partner_id, "Subtask should have the same customer as the one from their mother")

        # log timesheet on subtask
        timesheet2 = Timesheet.create({
            'name': 'Test Line on subtask',
            'project_id': subtask.project_id.id,
            'task_id': subtask.id,
            'unit_amount': 50,
            'employee_id': self.employee_user.id,
        })

        self.assertEqual(subtask.project_id, timesheet2.project_id, "The timesheet is in the subtask project")
        self.assertEqual(timesheet2.so_line, subtask.sale_line_id, "The timesheet should be linked to SOL as the task even in a non billable project")

        # move task and subtask into task rate project
        task.write({
            'project_id': self.project_employee_rate.id,
        })
        task._onchange_project()
        subtask.write({
            'project_id': self.project_employee_rate.id,
        })
        subtask._onchange_project()

        self.assertEqual(task.billable_type, 'employee_rate', "Task moved in project 'employee rate' should be billed at employee rate")
        self.assertFalse(task.sale_line_id, "Task moved in a employee rate billable project have empty so line")
        self.assertEqual(task.partner_id, task.project_id.partner_id, "Task created in a project billed on 'employee rate' should have the same customer as the one from the project")

        self.assertEqual(subtask.billable_type, 'employee_rate', "subtask moved in project 'employee rate' should be billed at employee rate")
        self.assertFalse(subtask.sale_line_id, "Subask moved in a employee rate billable project have empty so line")
        self.assertEqual(subtask.partner_id, task.project_id.partner_id, "Subask created in a project billed on 'employee rate' should have the same customer as the one from the project")
# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. This test suite provide checks for miscellaneous small things. # set up # Sale Order 1, no project/task created, used to timesheet at employee rate # Sale Order 2, creates 2 project billed at task rate # Projects: at least one per billable type Starting from a non billable project, make it billable at task rate, using the wizard # set a customer on the project # create a task and 2 timesheets # create wizard # product creates new T in new P # create the SO from the project Starting from a non billable project, make it billable at employee rate, using the wizard # set a customer on the project # create a task and 2 timesheets # create wizard # product creates no T # product creates no T (same product than previous one) # product creates new T in new P # create the SO from the project Check task and subtask creation, and timesheeting in a project billed at 'employee rate'. Then move the task into a 'task rate' project. # create a task # log timesheet on task # create a subtask # log timesheet on subtask # move task into task rate project # move subtask into task rate project # create a second task in employee rate project # log timesheet on task in 'employee rate' project without any fallback (no map, no SOL on task, no SOL on project) # add a SOL on the project as fallback # log timesheet on task in 'employee rate' project wit the project fallback only (no map, no SOL on task, but SOL on project) Check task and subtask creation, and timesheeting in a project billed at 'task rate'. Then move the task into a 'employee rate' project then, 'non billable'. # set subtask project on task rate project # create a task # log timesheet on task # create a subtask # log timesheet on subtask # move task and subtask into task rate project