[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:
@@ -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
|
||||
|
||||
@@ -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')
|
||||
]
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user