[IMP] sale_timesheet: Move not invoiced time sheet to new SOL

Description of the issue/feature this PR addresses:
There is no easy way to re-assign the time sheets to the correct SOL (changing
the task's SOL does not change the non-invoiced time sheets automatically)

Current behavior before PR:
On a project, if a user put a wrong sale order line, and after that he changes
this SOL, the timesheet line will not be moved.

Desired behavior after PR is merged:
On a project, if a user put a wrong sale order line, and after that he change
this SOL, all non invoiced timesheet line will be moved to new line.

TaskId: 2144703
This commit is contained in:
jbm-odoo
2020-01-15 15:36:21 +00:00
parent 5f65d9c9fc
commit 79cdcd13c8
5 changed files with 48 additions and 16 deletions
+13 -3
View File
@@ -71,15 +71,25 @@ class ProjectTask(models.Model):
# Actions
# ---------------------------------------------------
def _get_action_view_so_ids(self):
return self.sale_order_id.ids
def action_view_so(self):
self.ensure_one()
return {
so_ids = self._get_action_view_so_ids()
action_window = {
"type": "ir.actions.act_window",
"res_model": "sale.order",
"views": [[False, "form"]],
"res_id": self.sale_order_id.id,
"name": "Sales Order",
"views": [[False, "tree"], [False, "form"]],
"context": {"create": False, "show_sale": True},
"domain": [["id", "in", so_ids]],
}
if len(so_ids) == 1:
action_window["views"] = [[False, "form"]]
action_window["res_id"] = so_ids[0]
return action_window
def rating_get_partner_id(self):
partner = self.partner_id or self.sale_line_id.order_id.partner_id
+2 -2
View File
@@ -74,6 +74,6 @@ class AccountMoveLine(models.Model):
"""
return [
('so_line', 'in', sale_line_delivery.ids),
('timesheet_invoice_id', '=', False),
('project_id', '!=', False)
('project_id', '!=', False),
'|', ('timesheet_invoice_id', '=', False), ('timesheet_invoice_id.state', '=', 'cancel')
]
+25 -1
View File
@@ -126,6 +126,19 @@ class ProjectTask(models.Model):
is_project_map_empty = fields.Boolean("Is Project map empty", compute='_compute_is_project_map_empty')
allow_billable = fields.Boolean(related="project_id.allow_billable")
@api.onchange('sale_line_id')
def _onchange_sale_line_id(self):
if self.timesheet_ids:
if self.sale_line_id:
message = _("All timesheet hours that are not yet invoiced will be assigned to the selected Sales Order Item on save. Discard to avoid the change.")
else:
message = _("All timesheet hours that are not yet invoiced will be removed from the selected Sales Order Item on save. Discard to avoid the change.")
return {'warning': {
'title': _("Warning"),
'message': message
}}
@api.depends('sale_line_id', 'project_id', 'billable_type')
def _compute_sale_order_id(self):
for task in self:
@@ -166,11 +179,19 @@ class ProjectTask(models.Model):
# set domain on SO: on non billable project, all SOL of customer, otherwise the one from the SO
def write(self, values):
old_sale_line_id = dict([(t.id, t.sale_line_id.id) for t in self])
if values.get('project_id'):
project_dest = self.env['project.project'].browse(values['project_id'])
if project_dest.billable_type == 'employee_rate':
values['sale_line_id'] = False
return super(ProjectTask, self).write(values)
res = super(ProjectTask, self).write(values)
if 'sale_line_id' in values and self.sudo().timesheet_ids:
self.timesheet_ids.filtered(
lambda t: (not t.timesheet_invoice_id or t.timesheet_invoice_id.state == 'cancel') and t.so_line.id == old_sale_line_id[t.task_id.id]
).write({
'so_line': values['sale_line_id']
})
return res
def action_make_billable(self):
return {
@@ -185,3 +206,6 @@ class ProjectTask(models.Model):
'form_view_initial_mode': 'edit',
},
}
def _get_action_view_so_ids(self):
return list(set((self.sale_order_id + self.timesheet_ids.so_line.order_id).ids))
@@ -162,15 +162,15 @@ class TestSaleService(TestCommonSaleTimesheetNoChart):
self.assertTrue(all([billing_type == 'billable_time' for billing_type in timesheets.mapped('timesheet_invoice_type')]), "All timesheets linked to the task should be on 'billable time'")
self.assertEqual(so_line_deliver_global_project.qty_to_invoice, 5, "Quantity to invoice should have been increased when logging timesheet on delivered quantities task")
# make task non billable
task_serv2.write({'sale_line_id': False})
self.assertTrue(all([billing_type == 'billable_time' for billing_type in timesheets.mapped('timesheet_invoice_type')]), "billable type of timesheet should not change when tranfering task into another project")
self.assertEqual(task_serv2.timesheet_ids.mapped('so_line'), so_line_deliver_global_project, "Old timesheet are not modified when changing the task SO line")
# invoice SO, and validate invoice
invoice = self.sale_order._create_invoices()[0]
invoice.post()
# make task non billable
task_serv2.write({'sale_line_id': False})
self.assertTrue(all([billing_type == 'billable_time' for billing_type in timesheets.mapped('timesheet_invoice_type')]), "billable type of timesheet should not change when tranfering task into another project")
self.assertEqual(task_serv2.timesheet_ids.mapped('so_line'), so_line_deliver_global_project, "Old invoiced timesheet are not modified when changing the task SO line")
# try to update timesheets, catch error 'You cannot modify invoiced timesheet'
with self.assertRaises(UserError):
timesheets.write({'so_line': False})
@@ -57,9 +57,7 @@ class ProjectTaskCreateSalesOrder(models.TransientModel):
if self.task_id.sale_line_id:
raise UserError(_("The task is already linked to a sales order item."))
timesheet_with_so_line = self.env['account.analytic.line'].search_count([('task_id', '=', self.task_id.id), ('so_line', '!=', False), ('project_id', '!=', False)])
if timesheet_with_so_line:
raise UserError(_('The sales order cannot be created because some timesheets of this task are already linked to another sales order.'))
timesheet_with_so_line = self.env['account.analytic.line'].search([('task_id', '=', self.task_id.id), ('so_line', '!=', False), ('project_id', '!=', False)])
# create SO
sale_order = self.env['sale.order'].create({
@@ -76,7 +74,7 @@ class ProjectTaskCreateSalesOrder(models.TransientModel):
'price_unit': self.price_unit,
'project_id': self.task_id.project_id.id, # prevent to re-create a project on confirmation
'task_id': self.task_id.id,
'product_uom_qty': self.task_id.total_hours_spent,
'product_uom_qty': self.task_id.total_hours_spent - round(sum(timesheet_with_so_line.mapped('unit_amount')), 2),
})
# link task to SOL
@@ -87,7 +85,7 @@ class ProjectTaskCreateSalesOrder(models.TransientModel):
})
# assign SOL to timesheets
self.env['account.analytic.line'].search([('task_id', '=', self.task_id.id), ('so_line', '=', False)]).write({
self.env['account.analytic.line'].search([('task_id', '=', self.task_id.id), ('so_line', '=', False), ('timesheet_invoice_id', '=', False)]).write({
'so_line': sale_order_line.id
})