From 197b7eddd77f196aec3ea2ab54fef48223485129 Mon Sep 17 00:00:00 2001 From: "Audric Onockx (auon)" Date: Thu, 25 Nov 2021 08:48:57 +0000 Subject: [PATCH] [FIX] sale_timesheet : unlink timesheet from invoice when deleting invoice line Steps: - Create 2 service products with "Invoicing based on timesheet" & " Create a task in sales order's project" - Create a SO with both products > confirm - Add hours to both tasks - Create invoice > remove one invoice line > Save Issue: - The removed invoice line is still appearing in the Timesheet stat button Cause: - account_analytic_line.timesheet_invoice_id is set to the invoice when the invoice is created - It is not reset to False when the line is removed from the invoice Fix: - Reset it to False when the move the line is removed from invoice opw-2668814 closes odoo/odoo#81831 X-original-commit: a391ea82091cd4cd62914d7e21fa54f5670126c9 Signed-off-by: Laurent Stukkens (ltu) --- addons/sale_timesheet/models/account_move.py | 30 +++++++++++++++++ .../tests/test_sale_timesheet.py | 33 +++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/addons/sale_timesheet/models/account_move.py b/addons/sale_timesheet/models/account_move.py index 11595f963f7..9e395f1736a 100644 --- a/addons/sale_timesheet/models/account_move.py +++ b/addons/sale_timesheet/models/account_move.py @@ -96,3 +96,33 @@ class AccountMoveLine(models.Model): ('project_id', '!=', False), '|', ('timesheet_invoice_id', '=', False), ('timesheet_invoice_id.state', '=', 'cancel') ] + + def unlink(self): + move_line_read_group = self.env['account.move.line'].search_read([ + ('move_id.move_type', '=', 'out_invoice'), + ('move_id.state', '=', 'draft'), + ('sale_line_ids.product_id.invoice_policy', '=', 'delivery'), + ('sale_line_ids.product_id.service_type', '=', 'timesheet'), + ('id', 'in', self.ids)], + ['move_id', 'sale_line_ids']) + + sale_line_ids_per_move = defaultdict(lambda: self.env['sale.order.line']) + for move_line in move_line_read_group: + sale_line_ids_per_move[move_line['move_id'][0]] += self.env['sale.order.line'].browse(move_line['sale_line_ids']) + + timesheet_read_group = self.sudo().env['account.analytic.line'].read_group([ + ('timesheet_invoice_id.move_type', '=', 'out_invoice'), + ('timesheet_invoice_id.state', '=', 'draft'), + ('timesheet_invoice_id', 'in', self.move_id.ids)], + ['timesheet_invoice_id', 'so_line', 'ids:array_agg(id)'], + ['timesheet_invoice_id', 'so_line'], + lazy=False) + + timesheet_ids = [] + for timesheet in timesheet_read_group: + move_id = timesheet['timesheet_invoice_id'][0] + if timesheet['so_line'][0] in sale_line_ids_per_move[move_id].ids: + timesheet_ids += timesheet['ids'] + + self.sudo().env['account.analytic.line'].browse(timesheet_ids).write({'timesheet_invoice_id': False}) + super().unlink() diff --git a/addons/sale_timesheet/tests/test_sale_timesheet.py b/addons/sale_timesheet/tests/test_sale_timesheet.py index c5743272622..ac1caf39f18 100644 --- a/addons/sale_timesheet/tests/test_sale_timesheet.py +++ b/addons/sale_timesheet/tests/test_sale_timesheet.py @@ -688,3 +688,36 @@ class TestSaleTimesheet(TestCommonSaleTimesheet): ('res_id', '=', sale_order.id), ]) self.assertEqual(len(message_sent), 1, 'Sale Timesheet: An email should only be sent to the saleperson when the state of the sale order change to upselling') + + def test_unlink_timesheet(self): + sale_order = self.env['sale.order'].create({ + 'partner_id': self.partner_a.id, + 'partner_invoice_id': self.partner_a.id, + 'partner_shipping_id': self.partner_a.id, + 'pricelist_id': self.company_data['default_pricelist'].id, + }) + so_line = 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': sale_order.id, + }) + sale_order.action_confirm() + task = so_line.task_id + + # let's log some timesheets + analytic_line = self.env['account.analytic.line'].create({ + 'name': 'Test Line', + 'project_id': task.project_id.id, + 'task_id': task.id, + 'unit_amount': 50, + 'employee_id': self.employee_manager.id, + }) + + move = sale_order._create_invoices() + self.assertEqual(analytic_line.timesheet_invoice_id, move, "The timesheet should be linked to move") + + move.with_context(check_move_validity=False).line_ids[0].unlink() + self.assertFalse(analytic_line.timesheet_invoice_id, "The timesheet should have been unlinked from move")