From 55ca3dd2b11d1da3774790f37cf5350164ce2190 Mon Sep 17 00:00:00 2001 From: jem-odoo Date: Thu, 1 Mar 2018 13:59:46 +0100 Subject: [PATCH] [IMP] sale_timesheet: invoice from plan project We want to invoice from the project overview. To do so, a button can help people: it is only visible when an order linked to task in that project has something to invoice Any sale order line, even the ones with no service product will be invoiced. However, the user will have to choose which SO (involved in the project) to invoice. Then the "invoice order" wizard takes over. --- addons/sale_timesheet/__manifest__.py | 1 + addons/sale_timesheet/controllers/main.py | 33 ++++++++--- addons/sale_timesheet/wizard/__init__.py | 1 + .../wizard/project_create_invoice.py | 56 +++++++++++++++++++ .../wizard/project_create_invoice_views.xml | 34 +++++++++++ 5 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 addons/sale_timesheet/wizard/project_create_invoice.py create mode 100644 addons/sale_timesheet/wizard/project_create_invoice_views.xml 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 + + + + +