diff --git a/addons/sale_timesheet/__manifest__.py b/addons/sale_timesheet/__manifest__.py index 420fb7fbf66..c3c44ab8e48 100644 --- a/addons/sale_timesheet/__manifest__.py +++ b/addons/sale_timesheet/__manifest__.py @@ -29,6 +29,7 @@ have real delivered quantities in sales orders. 'data/sale_timesheet_filters.xml', 'wizard/project_task_assign_so_line_views.xml', 'wizard/project_create_sale_order_views.xml', + 'wizard/project_create_invoice_views.xml', ], 'demo': [ 'data/sale_service_demo.xml', diff --git a/addons/sale_timesheet/controllers/main.py b/addons/sale_timesheet/controllers/main.py index 912ca6a5313..3e93500914b 100644 --- a/addons/sale_timesheet/controllers/main.py +++ b/addons/sale_timesheet/controllers/main.py @@ -311,13 +311,32 @@ class SaleTimesheetController(http.Controller): def _plan_prepare_actions(self, projects): actions = [] - if len(projects) == 1 and not projects.sale_line_id and not projects.tasks.mapped('sale_line_id') and request.env.user.has_group('sales_team.group_sale_salesman'): - actions.append({ - 'label': _("Create a Sales Order"), - 'type': 'action', - 'action_id': 'sale_timesheet.project_project_action_multi_create_sale_order', - 'context': json.dumps({'active_id': projects.id}), - }) + if len(projects) == 1: + if request.env.user.has_group('sales_team.group_sale_salesman'): + if not projects.sale_line_id and not projects.tasks.mapped('sale_line_id'): + actions.append({ + 'label': _("Create a Sales Order"), + 'type': 'action', + 'action_id': 'sale_timesheet.project_project_action_multi_create_sale_order', + 'context': json.dumps({'active_id': projects.id, 'active_model': 'project.project'}), + }) + if request.env.user.has_group('sales_team.group_sale_salesman_all_leads'): + sale_orders = projects.tasks.mapped('sale_line_id.order_id').filtered(lambda so: so.invoice_status == 'to invoice') + if sale_orders: + if len(sale_orders) == 1: + actions.append({ + 'label': _("Create Invoice"), + 'type': 'action', + 'action_id': 'sale.action_view_sale_advance_payment_inv', + 'context': json.dumps({'active_ids': sale_orders.ids, 'active_model': 'project.project'}), + }) + else: + actions.append({ + 'label': _("Create Invoice"), + 'type': 'action', + 'action_id': 'sale_timesheet.project_project_action_multi_create_invoice', + 'context': json.dumps({'active_id': projects.id, 'active_model': 'project.project'}), + }) return actions def _plan_get_stat_button(self, projects): diff --git a/addons/sale_timesheet/wizard/__init__.py b/addons/sale_timesheet/wizard/__init__.py index 08132642389..0a4df69a6c2 100644 --- a/addons/sale_timesheet/wizard/__init__.py +++ b/addons/sale_timesheet/wizard/__init__.py @@ -3,3 +3,4 @@ from . import project_task_assign_so_line from . import project_create_sale_order +from . import project_create_invoice diff --git a/addons/sale_timesheet/wizard/project_create_invoice.py b/addons/sale_timesheet/wizard/project_create_invoice.py new file mode 100644 index 00000000000..db56e5f8fcb --- /dev/null +++ b/addons/sale_timesheet/wizard/project_create_invoice.py @@ -0,0 +1,56 @@ +# -*- 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 ProjectCreateInvoice(models.TransientModel): + _name = 'project.create.invoice' + _description = "Create a Invoice from project" + + @api.model + def default_get(self, fields): + result = super(ProjectCreateInvoice, self).default_get(fields) + + active_model = self._context.get('active_model') + if active_model != 'project.project': + raise UserError(_('You can only apply this action from a project.')) + + active_id = self._context.get('active_id') + if 'project_id' in fields and active_id: + result['project_id'] = active_id + return result + + project_id = fields.Many2one('project.project', "Project", help="Project to make billable", required=True) + sale_order_id = fields.Many2one('sale.order', string="Choose the Sales Order to invoice", required=True) + amount_to_invoice = fields.Monetary("Amount to invoice", compute='_compute_amount_to_invoice', currency_field='currency_id', help="Total amount to invoice on the sales order, including all items (services, stockables, expenses, ...)") + currency_id = fields.Many2one(related='sale_order_id.currency_id', readonly=True) + + @api.onchange('project_id') + def _onchange_project_id(self): + sale_orders = self.project_id.tasks.mapped('sale_line_id.order_id').filtered(lambda so: so.invoice_status == 'to invoice') + return { + 'domain': {'sale_order_id': [('id', 'in', sale_orders.ids)]}, + } + + @api.multi + @api.depends('sale_order_id') + def _compute_amount_to_invoice(self): + for wizard in self: + amount_untaxed = 0.0 + amount_tax = 0.0 + for line in wizard.sale_order_id.order_line.filtered(lambda sol: sol.invoice_status == 'to invoice'): + amount_untaxed += line.price_reduce * line.qty_to_invoice + amount_tax += line.price_tax + wizard.amount_to_invoice = amount_untaxed + amount_tax + + @api.multi + def action_create_invoice(self): + if not self.sale_order_id and self.sale_order_id.invoice_status != 'to invoice': + raise UserError(_("The selected Sales Order should contain something to invoice.")) + action = self.env.ref('sale.action_view_sale_advance_payment_inv').read()[0] + action['context'] = { + 'active_ids': self.sale_order_id.ids + } + return action diff --git a/addons/sale_timesheet/wizard/project_create_invoice_views.xml b/addons/sale_timesheet/wizard/project_create_invoice_views.xml new file mode 100644 index 00000000000..3eb5a23a671 --- /dev/null +++ b/addons/sale_timesheet/wizard/project_create_invoice_views.xml @@ -0,0 +1,34 @@ + + + + + project.create.invoice.view.form + project.create.invoice + +
+ + + + + +
+
+
+
+
+ + + Create Invoice + ir.actions.act_window + project.create.invoice + form + form + + new + + + + +