[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:
@@ -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
|
||||
|
||||
|
@@ -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")
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user