From cb7e6ce338481ab18d8e9b2cae1b2b939c356c82 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Wed, 4 Jul 2018 17:21:50 +0200 Subject: [PATCH] [IMP] sale_timesheet: timesheet_invoice_type computed field As we plan to remove the timesheet_revenue field, we can make 'timesheet_invoice_type' a computed field, to simplify code and readability. Before, this field was readonly, and set automatically on timesheet creation. To allow searching on it, we keep it stored. Task #1857652 --- addons/sale_timesheet/models/account.py | 23 ++++++++++++++----- .../tests/test_sale_timesheet.py | 21 ++++++++++++----- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/addons/sale_timesheet/models/account.py b/addons/sale_timesheet/models/account.py index 8fe00b965f5..7708ba046b8 100644 --- a/addons/sale_timesheet/models/account.py +++ b/addons/sale_timesheet/models/account.py @@ -17,10 +17,26 @@ class AccountAnalyticLine(models.Model): ('billable_time', 'Billable Time'), ('billable_fixed', 'Billable Fixed'), ('non_billable', 'Non Billable'), - ('non_billable_project', 'No task found')], string="Billable Type", readonly=True, copy=False) + ('non_billable_project', 'No task found')], string="Billable Type", compute='_compute_timesheet_invoice_type', store=True, readonly=True) timesheet_invoice_id = fields.Many2one('account.invoice', string="Invoice", readonly=True, copy=False, help="Invoice created from the timesheet") timesheet_revenue = fields.Monetary("Revenue", default=0.0, readonly=True, copy=False) + @api.multi + @api.depends('so_line.product_id', 'project_id', 'task_id') + def _compute_timesheet_invoice_type(self): + for timesheet in self: + if timesheet.project_id: # AAL will be set to False + invoice_type = 'non_billable_project' if not timesheet.task_id else 'non_billable' + if timesheet.task_id and timesheet.so_line.product_id.type == 'service': + if timesheet.so_line.product_id.invoice_policy == 'delivery': + if timesheet.so_line.product_id.service_type == 'timesheet': + invoice_type = 'billable_time' + else: + invoice_type = 'billable_fixed' + elif timesheet.so_line.product_id.invoice_policy == 'order': + invoice_type = 'billable_fixed' + timesheet.timesheet_invoice_type = invoice_type + @api.multi def write(self, values): # prevent to update invoiced timesheets if one line is of type delivery @@ -92,7 +108,6 @@ class AccountAnalyticLine(models.Model): so_line = timesheet.so_line values = { 'timesheet_revenue': 0.0, - 'timesheet_invoice_type': 'non_billable_project' if not timesheet.task_id else 'non_billable', } # set the revenue and billable type according to the product and the SO line if timesheet.task_id and so_line.product_id.type == 'service': @@ -106,7 +121,6 @@ class AccountAnalyticLine(models.Model): # calculate the revenue on the timesheet if so_line.product_id.invoice_policy == 'delivery': values['timesheet_revenue'] = analytic_account.currency_id.round(unit_amount * sale_price * (1-(so_line.discount/100))) - values['timesheet_invoice_type'] = 'billable_time' if so_line.product_id.service_type == 'timesheet' else 'billable_fixed' elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type == 'timesheet': quantity_hour = unit_amount if so_line.product_uom.category_id == timesheet_uom.category_id: @@ -125,11 +139,8 @@ class AccountAnalyticLine(models.Model): so_line.price_unit, analytic_account.currency_id, so_line.company_id, fields.Date.today()) * (1-so_line.discount)), total_revenue_so - total_revenue_invoiced ) - values['timesheet_invoice_type'] = 'billable_fixed' # if the so line is already invoiced, and the delivered qty is still smaller than the ordered, then link the timesheet to the invoice if so_line.invoice_status == 'invoiced': values['timesheet_invoice_id'] = so_line.invoice_lines and so_line.invoice_lines[0].invoice_id.id - elif so_line.product_id.invoice_policy == 'order' and so_line.product_id.service_type != 'timesheet': - values['timesheet_invoice_type'] = 'billable_fixed' return values diff --git a/addons/sale_timesheet/tests/test_sale_timesheet.py b/addons/sale_timesheet/tests/test_sale_timesheet.py index 588480a56a7..03afeaf9326 100644 --- a/addons/sale_timesheet/tests/test_sale_timesheet.py +++ b/addons/sale_timesheet/tests/test_sale_timesheet.py @@ -66,7 +66,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): sale_order.action_invoice_create() # let's log some timesheets (on the project created by so_line_ordered_project_only) - self.env['account.analytic.line'].create({ + timesheet1 = self.env['account.analytic.line'].create({ 'name': 'Test Line', 'project_id': task_serv2.project_id.id, 'task_id': task_serv2.id, @@ -75,8 +75,9 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): }) self.assertEqual(so_line_ordered_global_project.qty_delivered, 10.5, 'Timesheet directly on project does not increase delivered quantity on so line') self.assertEqual(sale_order.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so') + self.assertEqual(timesheet1.timesheet_invoice_type, 'billable_fixed', "Timesheets linked to SO line with ordered product shoulbe be billable fixed") - self.env['account.analytic.line'].create({ + timesheet2 = self.env['account.analytic.line'].create({ 'name': 'Test Line', 'project_id': task_serv2.project_id.id, 'task_id': task_serv2.id, @@ -85,14 +86,16 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): }) self.assertEqual(so_line_ordered_global_project.qty_delivered, 50, 'Sale Timesheet: timesheet does not increase delivered quantity on so line') self.assertEqual(sale_order.invoice_status, 'invoiced', 'Sale Timesheet: "invoice on order" timesheets should not modify the invoice_status of the so') + self.assertEqual(timesheet2.timesheet_invoice_type, 'billable_fixed', "Timesheets linked to SO line with ordered product shoulbe be billable fixed") - self.env['account.analytic.line'].create({ + timesheet3 = self.env['account.analytic.line'].create({ 'name': 'Test Line', 'project_id': task_serv2.project_id.id, 'unit_amount': 10, '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'") # log timesheet on task in global project (higher than the initial ordrered qty) self.env['account.analytic.line'].create({ @@ -175,7 +178,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): self.assertEqual(sale_order.analytic_account_id, project_serv2.analytic_account_id, "The created project should be linked to the analytic account of the SO") # let's log some timesheets - self.env['account.analytic.line'].create({ + timesheet1 = self.env['account.analytic.line'].create({ 'name': 'Test Line', 'project_id': task_serv1.project_id.id, # global project 'task_id': task_serv1.id, @@ -185,6 +188,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): self.assertEqual(so_line_deliver_global_project.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged') self.assertEqual(so_line_deliver_task_project.invoice_status, 'no', 'Sale Timesheet: so line invoice status should not change when no timesheet linked to the line') self.assertEqual(sale_order.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so in "to invoice" status when logged') + self.assertEqual(timesheet1.timesheet_invoice_type, 'billable_time', "Timesheets linked to SO line with delivered product shoulbe be billable time") # invoice SO invoice_id = sale_order.action_invoice_create() @@ -192,7 +196,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): self.assertTrue(float_is_zero(invoice.amount_total - so_line_deliver_global_project.price_unit * 10.5, precision_digits=2), 'Sale: invoice generation on timesheets product is wrong') # log some timesheets again - self.env['account.analytic.line'].create({ + timesheet2 = self.env['account.analytic.line'].create({ 'name': 'Test Line', 'project_id': task_serv1.project_id.id, # global project 'task_id': task_serv1.id, @@ -202,6 +206,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): self.assertEqual(so_line_deliver_global_project.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should set the so line in "to invoice" status when logged') self.assertEqual(so_line_deliver_task_project.invoice_status, 'no', 'Sale Timesheet: so line invoice status should not change when no timesheet linked to the line') self.assertEqual(sale_order.invoice_status, 'to invoice', 'Sale Timesheet: "invoice on delivery" timesheets should not modify the invoice_status of the so') + self.assertEqual(timesheet2.timesheet_invoice_type, 'billable_time', "Timesheets linked to SO line with delivered product shoulbe be billable time") # create a second invoice sale_order.action_invoice_create() @@ -220,7 +225,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): self.assertEqual(len(sale_order.project_ids), 2, "No new project should have been created by the SO, when selling 'project only' product, since it reuse the one from 'new task in new project'.") # let's log some timesheets on the project - self.env['account.analytic.line'].create({ + timesheet3 = self.env['account.analytic.line'].create({ 'name': 'Test Line', 'project_id': project_serv2.id, # global project 'unit_amount': 7, @@ -228,6 +233,7 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): }) 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, 'no', 'Sale Timesheet: "invoice on delivery" timesheets should be invoiced completely by now') + self.assertEqual(timesheet3.timesheet_invoice_type, 'non_billable_project', "Timesheets without task shoulbe be 'no project found'") def test_timesheet_manual(self): """ Test timesheet invoicing with 'invoice on delivery' timetracked products @@ -288,3 +294,6 @@ class TestSaleTimesheet(TestCommonSaleTimesheetNoChart): self.assertEqual(so_line_manual_global_project.qty_to_invoice, 0.0, "Manual service should not be affected by timesheet on their created task.") self.assertEqual(so_line_manual_only_project.qty_to_invoice, 0.0, "Manual service should not be affected by timesheet on their created project.") 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'")