[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.
This commit is contained in:
jem-odoo
2018-06-01 15:14:23 +02:00
parent 9f3040ee1f
commit 55ca3dd2b1
5 changed files with 118 additions and 7 deletions
+1
View File
@@ -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',
+26 -7
View File
@@ -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):
+1
View File
@@ -3,3 +3,4 @@
from . import project_task_assign_so_line
from . import project_create_sale_order
from . import project_create_invoice
@@ -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
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="project_create_invoice_view_form" model="ir.ui.view">
<field name="name">project.create.invoice.view.form</field>
<field name="model">project.create.invoice</field>
<field name="arch" type="xml">
<form string="Create Sales Order from Project">
<group>
<field name="project_id" readonly="1"/>
<field name="sale_order_id" options="{'no_create_edit': True}" context="{'sale_show_partner_name': True}"/>
<field name="amount_to_invoice"/>
</group>
<footer>
<button string="Create Invoice" type="object" name="action_create_invoice" class="oe_highlight"/>
<button string="Cancel" special="cancel" type="object" class="btn btn-default oe_inline"/>
</footer>
</form>
</field>
</record>
<record id="project_project_action_multi_create_invoice" model="ir.actions.act_window">
<field name="name">Create Invoice</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">project.create.invoice</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="view_id" ref="project_create_invoice_view_form"/>
<field name="target">new</field>
<field name="multi" eval="False"/>
<field name="groups_id" eval="[(4, ref('sales_team.group_sale_salesman_all_leads'))]"/>
</record>
</odoo>