diff --git a/addons/sale_timesheet/__manifest__.py b/addons/sale_timesheet/__manifest__.py index 6b3c91b6723..00d5cdeef96 100644 --- a/addons/sale_timesheet/__manifest__.py +++ b/addons/sale_timesheet/__manifest__.py @@ -31,7 +31,6 @@ have real delivered quantities in sales orders. 'wizard/project_create_sale_order_views.xml', 'wizard/project_create_invoice_views.xml', 'wizard/sale_make_invoice_advance_views.xml', - 'wizard/project_task_create_sale_order_views.xml' ], 'qweb': [ 'static/src/xml/sale_project_templates.xml', diff --git a/addons/sale_timesheet/security/ir.model.access.csv b/addons/sale_timesheet/security/ir.model.access.csv index a391d6610e4..cda5ac60cc7 100644 --- a/addons/sale_timesheet/security/ir.model.access.csv +++ b/addons/sale_timesheet/security/ir.model.access.csv @@ -5,4 +5,3 @@ access_project_sale_line_employee_map_manager,access_project_sale_line_employee_ access_project_create_sale_order,access.project.create.sale.order,model_project_create_sale_order,sales_team.group_sale_salesman,1,1,1,0 access_project_create_sale_order_line,access.project.create.sale.order.line,model_project_create_sale_order_line,sales_team.group_sale_salesman,1,1,1,1 access_project_create_invoice,access.project.create.invoice,model_project_create_invoice,sales_team.group_sale_salesman_all_leads,1,1,1,0 -access_project_task_create_sale_order,access.project.task.create.sale.order,model_project_task_create_sale_order,sales_team.group_sale_salesman,1,1,1,0 diff --git a/addons/sale_timesheet/tests/test_sale_timesheet.py b/addons/sale_timesheet/tests/test_sale_timesheet.py index 9e3411e1845..ca1a5e0bcec 100644 --- a/addons/sale_timesheet/tests/test_sale_timesheet.py +++ b/addons/sale_timesheet/tests/test_sale_timesheet.py @@ -530,14 +530,15 @@ class TestSaleTimesheet(TestCommonSaleTimesheet): Task = self.env['project.task'] today = Date.context_today(self.env.user) - task = Task.with_context(default_project_id=self.project_global.id).create({ + task = Task.with_context(default_project_id=self.project_template.id).create({ 'name': 'first task', - 'partner_id': self.partner_a.id, + 'partner_id': self.partner_b.id, 'planned_hours': 10, + 'sale_line_id': self.so.order_line[0].id }) Timesheet.create({ - 'project_id': self.project_global.id, + 'project_id': self.project_template.id, 'task_id': task.id, 'name': 'my first timesheet', 'unit_amount': 4, @@ -545,51 +546,11 @@ class TestSaleTimesheet(TestCommonSaleTimesheet): timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_global.id)]) timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_template.id)]) - self.assertEqual(timesheet_count1, 1, "One timesheet in project_global") - self.assertEqual(timesheet_count2, 0, "No timesheet in project_template") + self.assertEqual(timesheet_count1, 0, "No timesheet in project_global") + self.assertEqual(timesheet_count2, 1, "One timesheet in project_template") self.assertEqual(len(task.timesheet_ids), 1, "The timesheet should be linked to task") # change project of task, as the timesheet is not yet invoiced, the timesheet will change his project - task.write({ - 'project_id': self.project_template.id - }) - - timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_global.id)]) - timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_template.id)]) - self.assertEqual(timesheet_count1, 0, "No timesheet in project_global") - self.assertEqual(timesheet_count2, 1, "One timesheet in project_template") - self.assertEqual(len(task.timesheet_ids), 1, "The timesheet still should be linked to task") - - wizard = self.env['project.task.create.sale.order'].with_context(active_id=task.id, active_model='project.task').create({ - 'product_id': self.product_delivery_timesheet3.id - }) - - # We create the SO and the invoice - action = wizard.action_create_sale_order() - sale_order = self.env['sale.order'].browse(action['res_id']) - self.context = { - 'active_model': 'sale.order', - 'active_ids': [sale_order.id], - 'active_id': sale_order.id, - 'default_journal_id': self.company_data['default_journal_sale'].id - } - wizard = self.env['sale.advance.payment.inv'].with_context(self.context).create({ - 'advance_payment_method': 'delivered', - 'date_start_invoice_timesheet': today - timedelta(days=4), - 'date_end_invoice_timesheet': today - }) - wizard.create_invoices() - - Timesheet.create({ - 'project_id': self.project_template.id, - 'task_id': task.id, - 'name': 'my second timesheet', - 'unit_amount': 6, - }) - - self.assertEqual(Timesheet.search_count([('project_id', '=', self.project_template.id)]), 2, "2 timesheets in project_template") - - # change project of task, the timesheet not yet invoiced will change its project. The timesheet already invoiced will not change his project. task.write({ 'project_id': self.project_global.id }) @@ -597,5 +558,39 @@ class TestSaleTimesheet(TestCommonSaleTimesheet): timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_global.id)]) timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_template.id)]) self.assertEqual(timesheet_count1, 1, "One timesheet in project_global") - self.assertEqual(timesheet_count2, 1, "Still one timesheet in project_template") - self.assertEqual(len(task.timesheet_ids), 2, "The 2 timesheet still should be linked to task") + self.assertEqual(timesheet_count2, 0, "No timesheet in project_template") + self.assertEqual(len(task.timesheet_ids), 1, "The timesheet still should be linked to task") + + # Create an invoice + context = { + 'active_model': 'sale.order', + 'active_ids': [self.so.id], + 'active_id': self.so.id, + 'default_journal_id': self.company_data['default_journal_sale'].id + } + wizard = self.env['sale.advance.payment.inv'].with_context(context).create({ + 'advance_payment_method': 'delivered', + 'date_start_invoice_timesheet': today - timedelta(days=4), + 'date_end_invoice_timesheet': today, + }) + wizard.create_invoices() + + Timesheet.create({ + 'project_id': self.project_global.id, + 'task_id': task.id, + 'name': 'my second timesheet', + 'unit_amount': 6, + }) + + self.assertEqual(Timesheet.search_count([('project_id', '=', self.project_global.id)]), 2, "2 timesheets in project_global") + + # change project of task, the timesheet not yet invoiced will change its project. The timesheet already invoiced will not change his project. + task.write({ + 'project_id': self.project_template.id + }) + + timesheet_count1 = Timesheet.search_count([('project_id', '=', self.project_global.id)]) + timesheet_count2 = Timesheet.search_count([('project_id', '=', self.project_template.id)]) + self.assertEqual(timesheet_count1, 1, "Still one timesheet in project_global") + self.assertEqual(timesheet_count2, 1, "One timesheet in project_template") + self.assertEqual(len(task.timesheet_ids), 2, "The 2 timesheets still should be linked to task") diff --git a/addons/sale_timesheet/wizard/__init__.py b/addons/sale_timesheet/wizard/__init__.py index 42502ae67a9..a2800ce6ba3 100644 --- a/addons/sale_timesheet/wizard/__init__.py +++ b/addons/sale_timesheet/wizard/__init__.py @@ -4,4 +4,3 @@ from . import project_create_sale_order from . import project_create_invoice from . import sale_make_invoice_advance -from . import project_task_create_sale_order diff --git a/addons/sale_timesheet/wizard/project_task_create_sale_order.py b/addons/sale_timesheet/wizard/project_task_create_sale_order.py deleted file mode 100644 index 723ea2111c4..00000000000 --- a/addons/sale_timesheet/wizard/project_task_create_sale_order.py +++ /dev/null @@ -1,145 +0,0 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - -from odoo import api, fields, models, _ -from odoo.exceptions import UserError - - -class ProjectTaskCreateSalesOrder(models.TransientModel): - _name = 'project.task.create.sale.order' - _description = "Create SO from task" - - @api.model - def default_get(self, fields): - result = super(ProjectTaskCreateSalesOrder, self).default_get(fields) - - active_model = self._context.get('active_model') - if active_model != 'project.task': - raise UserError(_("You can only apply this action from a task.")) - - active_id = self._context.get('active_id') - if 'task_id' in fields and active_id: - task = self.env['project.task'].browse(active_id) - if task.sale_order_id: - raise UserError(_("The task has already a sale order.")) - result['task_id'] = active_id - if not result.get('partner_id', False): - result['partner_id'] = task.partner_id.id - return result - - link_selection = fields.Selection([('create', 'Create a new sales order'), ('link', 'Link to an existing sales order')], required=True, default='create') - - task_id = fields.Many2one('project.task', "Task", domain=[('sale_line_id', '=', False)], help="Task for which we are creating a sales order", required=True) - partner_id = fields.Many2one('res.partner', string="Customer", help="Customer of the sales order", required=True) - product_id = fields.Many2one('product.product', domain=[('type', '=', 'service'), ('invoice_policy', '=', 'delivery'), ('service_type', '=', 'timesheet')], string="Service", help="Product of the sales order item. Must be a service invoiced based on timesheets on tasks. The existing timesheet will be linked to this product.", required=True) - price_unit = fields.Float("Unit Price", help="Unit price of the sales order item.") - currency_id = fields.Many2one('res.currency', string="Currency", related='product_id.currency_id', readonly=False) - commercial_partner_id = fields.Many2one(related='partner_id.commercial_partner_id') - sale_order_id = fields.Many2one( - 'sale.order', string="Sales Order", - domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]") - sale_line_id = fields.Many2one( - 'sale.order.line', 'Sales Order Item', - domain="[('is_service', '=', True), ('order_partner_id', 'child_of', commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), ('order_id', '=?', sale_order_id)]") - - info_invoice = fields.Char(compute='_compute_info_invoice') - - @api.depends('sale_line_id', 'price_unit', 'link_selection') - def _compute_info_invoice(self): - for line in self: - domain = self.env['sale.order.line']._timesheet_compute_delivered_quantity_domain() - timesheet = self.env['account.analytic.line'].read_group(domain + [('task_id', '=', self.task_id.id), ('so_line', '=', False), ('timesheet_invoice_id', '=', False)], ['unit_amount'], ['task_id']) - unit_amount = round(timesheet[0].get('unit_amount', 0), 2) if timesheet else 0 - if not unit_amount: - line.info_invoice = False - continue - company_uom = self.env.company.timesheet_encode_uom_id - label = _("hours") - if company_uom == self.env.ref('uom.product_uom_day'): - label = _("days") - if line.link_selection == 'create' and line.price_unit: - line.info_invoice = _("%(amount)s %(label)s will be added to the new Sales Order.", amount=unit_amount, label=label) - else: - line.info_invoice = _("%(amount)s %(label)s will be added to the selected Sales Order.", amount=unit_amount, label=label) - - @api.onchange('product_id') - def _onchange_product_id(self): - if self.product_id: - self.price_unit = self.product_id.lst_price - else: - self.price_unit = 0.0 - - @api.onchange('partner_id') - def _onchange_partner_id(self): - self.sale_order_id = False - self.sale_line_id = False - - @api.onchange('sale_order_id') - def _onchange_sale_order_id(self): - self.sale_line_id = False - - def action_link_sale_order(self): - # link task to SOL - self.task_id.write({ - 'sale_line_id': self.sale_line_id.id, - 'partner_id': self.partner_id.id, - 'email_from': self.partner_id.email, - }) - - # assign SOL to timesheets - self.env['account.analytic.line'].search([('task_id', '=', self.task_id.id), ('so_line', '=', False), ('timesheet_invoice_id', '=', False)]).write({ - 'so_line': self.sale_line_id.id - }) - - def action_create_sale_order(self): - sale_order = self._prepare_sale_order() - sale_order.action_confirm() - view_form_id = self.env.ref('sale.view_order_form').id - action = self.env["ir.actions.actions"]._for_xml_id("sale.action_orders") - action.update({ - 'views': [(view_form_id, 'form')], - 'view_mode': 'form', - 'name': sale_order.name, - 'res_id': sale_order.id, - }) - return action - - def _prepare_sale_order(self): - # if task linked to SO line, then we consider it as billable. - if self.task_id.sale_line_id: - raise UserError(_("The task is already linked to a sales order item.")) - - # create SO - sale_order = self.env['sale.order'].create({ - 'partner_id': self.partner_id.id, - 'company_id': self.task_id.company_id.id, - 'analytic_account_id': self.task_id.project_id.analytic_account_id.id, - }) - sale_order.onchange_partner_id() - sale_order.onchange_partner_shipping_id() - # rewrite the user as the onchange_partner_id erases it - sale_order.write({'user_id': self.task_id.user_id.id}) - sale_order.onchange_user_id() - - sale_order_line = self.env['sale.order.line'].create({ - 'order_id': sale_order.id, - 'product_id': self.product_id.id, - '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': round(sum(self.task_id.timesheet_ids.filtered(lambda t: not t.so_line).mapped('unit_amount')), 2), - }) - - # link task to SOL - self.task_id.write({ - 'sale_line_id': sale_order_line.id, - 'partner_id': sale_order.partner_id.id, - 'email_from': sale_order.partner_id.email, - }) - - # assign SOL to timesheets - 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 - }) - - return sale_order diff --git a/addons/sale_timesheet/wizard/project_task_create_sale_order_views.xml b/addons/sale_timesheet/wizard/project_task_create_sale_order_views.xml deleted file mode 100644 index 2521b53bd36..00000000000 --- a/addons/sale_timesheet/wizard/project_task_create_sale_order_views.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - project.task.create.sale.order.wizard.form - project.task.create.sale.order - -
- - - - - - - - - - - - - - - - - - - - -
-
- -
-
- - - Create a Sales Order - project.task.create.sale.order - form - - new - - -