[IMP] sale_timesheet: remove project.task.create.sale.order model

This commit removes the project.task.create.sale.order model because
this wizard is no longer used. Before we use this wizard to create a SO
from ticket and task, but in reality this feature is no used.

The test using this wizard has been changed to keep the behaviour of it.
This test checks if the invoiced timesheet does not change the project
when the linked task changes the project.
Moreover, the both projects used in this test have been switched because
when we invoice the SO the timesheet is in the template project and this
one is not billable.
Thus, we create no invoice. To create an invoice as we expect, we must
have the timesheet in the global project.

task-2439304
This commit is contained in:
Xavier BOL (xbo)
2021-02-01 16:20:53 +01:00
parent dd86be3367
commit 9f72d70c75
6 changed files with 42 additions and 241 deletions
-1
View File
@@ -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',
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_project_create_sale_order access.project.create.sale.order model_project_create_sale_order sales_team.group_sale_salesman 1 1 1 0
6 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
7 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
@@ -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")
-1
View File
@@ -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
@@ -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
@@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="project_task_create_sale_order_view_form" model="ir.ui.view">
<field name="name">project.task.create.sale.order.wizard.form</field>
<field name="model">project.task.create.sale.order</field>
<field name="arch" type="xml">
<form string="Create a Sales Order">
<field name="link_selection" nolabel="1" widget="radio" options="{'horizontal': true}" invisible="1"/>
<group>
<group>
<field name="task_id" readonly="1"/>
<field name="partner_id"/>
</group>
<group attrs="{'invisible': [('link_selection', '=', 'link')]}">
<field name="product_id" context="{'default_type': 'service', 'default_service_policy': 'delivered_timesheet'}" attrs="{'required': [('link_selection', '!=', 'link')]}"/>
<field name="price_unit" widget='monetary' options="{'currency_field': 'currency_id', 'field_digits': True}" attrs="{'required': [('link_selection', '!=', 'link')]}"/>
<field name="currency_id" invisible="1"/>
</group>
<group attrs="{'invisible': [('link_selection', '!=', 'link')]}">
<field name="sale_order_id" options="{'no_create': True, 'no_create_edit': True}" attrs="{'required': [('link_selection', '=', 'link')]}"/>
<field name="sale_line_id" options="{'no_create': True, 'no_create_edit': True}" attrs="{'required': [('link_selection', '=', 'link')]}"/>
<field name="commercial_partner_id" invisible="1"/>
</group>
</group>
<group attrs="{'invisible': [('info_invoice', '=', False)]}">
<field name="info_invoice" nolabel="1"/>
</group>
<footer>
<button string="Link to Sales Order" type="object" name="action_link_sale_order" class="oe_highlight" attrs="{'invisible': [('link_selection', '!=', 'link')]}"/>
<button string="Create Sales Order" type="object" name="action_create_sale_order" class="oe_highlight" attrs="{'invisible': [('link_selection', '=', 'link')]}"/>
<button string="Cancel" special="cancel" type="object" class="btn btn-secondary oe_inline"/>
</footer>
</form>
</field>
</record>
<record id="project_task_action_multi_create_sale_order" model="ir.actions.act_window">
<field name="name">Create a Sales Order</field>
<field name="res_model">project.task.create.sale.order</field>
<field name="view_mode">form</field>
<field name="view_id" ref="project_task_create_sale_order_view_form"/>
<field name="target">new</field>
</record>
</odoo>