Skip to content
Snippets Groups Projects
Commit de423ba1 authored by Nicolas Martinelli's avatar Nicolas Martinelli
Browse files

[IMP] sale_service: adaptation due to the new Sale module

Major changes:
- Link with account analytic lines

Reason: complete rewrite of the Sale module.

Responsible: fp, dbo, nim
parent 4258fc4c
No related branches found
No related tags found
No related merge requests found
...@@ -28,10 +28,9 @@ order line, the sale order line will be considered delivered when the task is ...@@ -28,10 +28,9 @@ order line, the sale order line will be considered delivered when the task is
completed. completed.
""", """,
'website': 'https://www.odoo.com/page/crm', 'website': 'https://www.odoo.com/page/crm',
'depends': ['project', 'sale'], 'depends': ['project', 'sale', 'project_timesheet', 'sale_timesheet'],
'data': ['views/sale_service_view.xml'], 'data': ['views/sale_service_view.xml'],
'demo': ['demo/sale_service_demo.xml'], 'demo': ['demo/sale_service_demo.xml'],
'test': ['test/project_task_procurement.yml'],
'installable': True, 'installable': True,
'auto_install': True, 'auto_install': True,
} }
...@@ -15,5 +15,20 @@ ...@@ -15,5 +15,20 @@
<record id="project.project_stage_2" model="project.task.type"> <record id="project.project_stage_2" model="project.task.type">
<field name="closed" eval="True"/> <field name="closed" eval="True"/>
</record> </record>
<record id="project_GAP" model="project.project">
<field name="date_start" eval="time.strftime('%Y-%m-01 10:00:00')"/>
<field name="state">open</field>
<field name="name">Internal - GAP Analysis</field>
<field name="color">2</field>
<field name="privacy_visibility">employees</field>
<field name="alias_model">project.task</field>
<field name="type_ids" eval="[(4, ref('project.project_stage_0')) ,(4,ref('project.project_stage_1')) ,(4,ref('project.project_stage_2')) ,(4,ref('project.project_stage_3'))]"/>
</record>
<record id="product.product_product_1" model="product.product">
<field name="track_service">task</field>
<field name="project_id" ref="project_GAP"/>
</record>
</data> </data>
</openerp> </openerp>
import sale_service # -*- coding: utf-8 -*-
\ No newline at end of file # Part of Odoo. See LICENSE file for full copyright and licensing details.
import sale_service
import timesheet
...@@ -12,14 +12,13 @@ class procurement_order(osv.osv): ...@@ -12,14 +12,13 @@ class procurement_order(osv.osv):
} }
def _is_procurement_task(self, cr, uid, procurement, context=None): def _is_procurement_task(self, cr, uid, procurement, context=None):
return procurement.product_id.type == 'service' and procurement.product_id.auto_create_task or False return procurement.product_id.type == 'service' and procurement.product_id.track_service=='task' or False
def _assign(self, cr, uid, procurement, context=None): def _assign(self, cr, uid, procurement, context=None):
res = super(procurement_order, self)._assign(cr, uid, procurement, context=context) res = super(procurement_order, self)._assign(cr, uid, procurement, context=context)
if not res: if not res:
#if there isn't any specific procurement.rule defined for the product, we may want to create a task #if there isn't any specific procurement.rule defined for the product, we may want to create a task
if self._is_procurement_task(cr, uid, procurement, context=context): return self._is_procurement_task(cr, uid, procurement, context=context)
return True
return res return res
def _run(self, cr, uid, procurement, context=None): def _run(self, cr, uid, procurement, context=None):
...@@ -120,18 +119,4 @@ class project_task(osv.osv): ...@@ -120,18 +119,4 @@ class project_task(osv.osv):
self._validate_subflows(cr, uid, ids, context=context) self._validate_subflows(cr, uid, ids, context=context)
return res return res
class product_template(osv.osv):
_inherit = "product.template"
_columns = {
'project_id': fields.many2one('project.project', 'Project', ondelete='set null',),
'auto_create_task': fields.boolean('Create Task Automatically', help="Tick this option if you want to create a task automatically each time this product is sold"),
}
class product_product(osv.osv):
_inherit = "product.product"
def need_procurement(self, cr, uid, ids, context=None):
for product in self.browse(cr, uid, ids, context=context):
if product.type == 'service' and product.auto_create_task:
return True
return super(product_product, self).need_procurement(cr, uid, ids, context=context)
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, api, fields, exceptions
from openerp.tools.translate import _
class product_template(models.Model):
_inherit = "product.template"
project_id = fields.Many2one('project.project', string='Project', ondelete='set null')
track_service = fields.Selection(selection_add=[('task', 'Create a task and track hours')])
class account_analytic_line(models.Model):
_inherit = 'account.analytic.line'
@api.model
def _update_values(self, values):
if values.get('task_id', False):
task = self.env['project.task'].browse(values['task_id'])
values['so_line'] = task.sale_line_id and task.sale_line_id.id or False
@api.model
def create(self, values):
self._update_values(values)
result = super(account_analytic_line, self).create(values)
return result
@api.multi
def write(self, values):
self._update_values(values)
result = super(account_analytic_line, self).write(values)
return result
-
Update product to automatically create tasks
-
!record {model: product.product, id: auto_task_service}:
auto_create_task: True
name: Advanced auto task Service
type: service
list_price: 150.0
standard_price: 100.0
uom_id: product.product_uom_day
uom_po_id: product.product_uom_day
-
Create a new sales order with a service product
-
!record {model: sale.order, id: sale_order_service}:
partner_id: base.res_partner_2
pricelist_id: product.list0
-
Associate a sale order line
-
!record {model: sale.order.line, id: service_line}:
product_id: auto_task_service
product_uom_qty: 50.0
name: Fixing the bugs
order_id: sale_order_service
-
In order to test process to generate task automatic from procurement, I confirm sale order to sale service product.
-
!workflow {model: sale.order, action: order_confirm, ref: sale_order_service}
-
I run the scheduler.
-
!python {model: procurement.order}: |
self.run_scheduler(cr, uid)
-
Now I check the details of the generated task
-
!python {model: procurement.order}: |
from datetime import datetime
procurement_ids = self.search(cr, uid, [('sale_line_id', '=', ref('service_line'))])
assert procurement_ids, "Procurement is not generated for Service Order Line."
procurement = self.browse(cr, uid, procurement_ids[0], context=context)
assert procurement.state != 'done' , "Procurement should not be closed."
task = procurement.task_id
assert task, "Task is not generated."
# check whether task project either is the product's project, or corresponds to the analytic account of sale order
project = task.project_id
if procurement.product_id.project_id:
assert project == procurement.product_id.project_id, "Project does not correspond."
elif procurement.sale_line_id:
account = procurement.sale_line_id.order_id.project_id
assert (not project and not account) or project.analytic_account_id == account, "Project does not correspond."
planned_hours = self._convert_qty_company_hours(cr, uid, procurement, context=context)
assert task.planned_hours == planned_hours, 'Planned Hours do not correspond.'
assert datetime.strptime(task.date_deadline, '%Y-%m-%d') == datetime.strptime(procurement.date_planned[:10], '%Y-%m-%d'), 'Deadline does not correspond.'
if procurement.product_id.product_manager:
assert task.user_id.id == procurement.product_id.product_manager.id, 'Allocated Person does not correspond with Service Product Manager.'
-
I close that task.
-
!python {model: project.task}: |
task_ids = self.search(cr, uid, [('sale_line_id', '=', ref('service_line'))])
assert task_ids, "Task is not generated for Service Order Line."
self.write(cr, uid, task_ids, {'stage_id': ref('project.project_stage_2')}, context=context)
-
I check procurement of Service Order Line after closed task.
-
!python {model: procurement.order}: |
procurement_ids = self.search(cr, uid, [('sale_line_id', '=', ref('service_line'))])
assert procurement_ids, "Procurement is not generated for Service Order Line."
procurement = self.browse(cr, uid, procurement_ids[0], context=context)
assert procurement.state == 'done' , "Procurement should be closed."
\ No newline at end of file
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import test_sale_service
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.addons.sale.tests.test_sale_common import TestSale
class TestSaleService(TestSale):
def test_sale_service(self):
""" Test task creation when confirming a so with the corresponding product """
prod_task = self.env.ref('product.product_product_1')
so_vals = {
'partner_id': self.partner.id,
'partner_invoice_id': self.partner.id,
'partner_shipping_id': self.partner.id,
'order_line': [(0, 0, {'name': prod_task.name, 'product_id': prod_task.id, 'product_uom_qty': 50, 'product_uom': prod_task.uom_id.id, 'price_unit': prod_task.list_price})],
'pricelist_id': self.env.ref('product.list0').id,
}
so = self.env['sale.order'].create(so_vals)
so.action_confirm()
self.assertEqual(so.invoice_status, 'no', 'Sale Service: there should be nothing to invoice after validation')
# check task creation
project = self.env.ref('sale_service.project_GAP')
task = project.task_ids.filtered(lambda t: t.name == '%s:%s' % (so.name, prod_task.name))
self.assertTrue(task, 'Sale Service: task is not created')
self.assertEqual(task.partner_id, so.partner_id, 'Sale Service: customer should be the same on task and on SO')
# register timesheet on task
self.env['account.analytic.line'].create({
'name': 'Test Line',
'account_id': project.id,
'task_id': task.id,
'unit_amount': 50,
'user_id': self.manager.id,
'is_timesheet': True,
})
self.assertEqual(so.invoice_status, 'to invoice', 'Sale Service: there should be something to invoice after registering timesheets')
so.action_invoice_create()
line = so.order_line
self.assertTrue(line.product_uom_qty == line.qty_delivered == line.qty_invoiced, 'Sale Service: line should be invoiced completely')
self.assertEqual(so.invoice_status, 'invoiced', 'Sale Service: SO should be invoiced')
...@@ -14,15 +14,15 @@ ...@@ -14,15 +14,15 @@
<record id="view_product_task_form" model="ir.ui.view"> <record id="view_product_task_form" model="ir.ui.view">
<field name="name">product.form.view.inherit</field> <field name="name">product.form.view.inherit</field>
<field name="model">product.template</field> <field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/> <field name="inherit_id" ref="sale.product_template_form_view_invoice_policy"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<group name="email_template_and_project" position="attributes"> <xpath expr="//field[@name='track_service']" position="attributes">
<attribute name="string">Miscellaneous</attribute> <attribute name="invisible">False</attribute>
</group> <attribute name="attrs">{'invisible': [('type','!=','service')]}</attribute>
<group name="email_template_and_project" position="inside"> </xpath>
<field name="auto_create_task" attrs="{'invisible': [('type', '!=', 'service')]}"/> <field name="track_service" position="after">
<field name="project_id" attrs="{'invisible': ['|', ('type', '!=', 'service'), ('auto_create_task', '=', False)]}"/> <field name="project_id" attrs="{'invisible':['|', ('type','!=','service'), ('track_service', '&lt;&gt;', 'task')]}"/>
</group> </field>
</field> </field>
</record> </record>
<record id="task_type_edit_mrp_inherit" model="ir.ui.view"> <record id="task_type_edit_mrp_inherit" model="ir.ui.view">
...@@ -41,33 +41,9 @@ ...@@ -41,33 +41,9 @@
<field name="inherit_id" ref="project.view_task_form2"/> <field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="company_id" position="before"> <field name="company_id" position="before">
<field name="sale_line_id" string="Order Line"/> <field name="sale_line_id" string="Order Line" groups="base.group_no_one"/>
</field> </field>
</field> </field>
</record> </record>
<record id="open_service_type_products" model="ir.actions.act_window">
<field name="name">Services</field>
<field name="res_model">product.template</field>
<field name="view_mode">kanban,tree,form</field>
<field name="view_type">form</field>
<field name="search_view_id" ref="product.product_template_search_view"/>
<field name="view_id" ref="product.product_template_kanban_view"/>
<field name="context">{'search_default_services': 1,'default_auto_create_task':True,'default_sale_ok':True,'default_purchase_ok':False,'default_type':'service'}</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to define a new service.
</p><p>
Services can be sold on quotations or sales order.
All services defined from this menu will create tasks when the
quotation is confirmed.
</p><p>
Another way to create tasks from sales is to use template of contracts
linked to a project having pre-defined tasks that will be duplicated
per contract reusing this project.
</p>
</field>
</record>
<menuitem action="open_service_type_products" id="menu_products_services" name="Services" parent="project.menu_project_config" sequence="4" />
</data> </data>
</openerp> </openerp>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment