[MIG] sale_timesheet: migrate module to new API

This commit is contained in:
Kinjal Mehta
2016-08-04 11:09:52 +02:00
committed by Thibault Delavallée
parent 572053d9f5
commit fb9414af9a
15 changed files with 218 additions and 257 deletions
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<odoo>
<data noupdate="1">
<record id="product.service_order_01" model="product.product">
<field name="track_service">timesheet</field>
@@ -32,4 +32,4 @@
<field name="project_id" ref="project_GAP"/>
</record>
</data>
</openerp>
</odoo>
+39 -42
View File
@@ -1,12 +1,48 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, api
from odoo import api, models
class AccountAnalyticLine(models.Model):
_inherit = 'account.analytic.line'
@api.model
def create(self, values):
if values.get('task_id'):
task = self.env['project.task'].browse(values['task_id'])
values['so_line'] = task.sale_line_id.id or values.get('so_line', False)
values.update(self._get_timesheet_cost(values))
return super(AccountAnalyticLine, self).create(values)
@api.multi
def write(self, values):
if values.get('task_id'):
task = self.env['project.task'].browse(values['task_id'])
values['so_line'] = task.sale_line_id.id or values.get('so_line', False)
for line in self:
values.update(line._get_timesheet_cost(values))
super(AccountAnalyticLine, line).write(values)
return True
def _get_timesheet_cost(self, values):
values = values if values is not None else {}
if values.get('project_id') or self.project_id:
if values.get('amount'):
return {}
unit_amount = values.get('unit_amount', 0.0) or self.unit_amount
user_id = values.get('user_id') or self.user_id.id or self._default_user()
user = self.env['res.users'].browse([user_id])
emp = self.env['hr.employee'].search([('user_id', '=', user_id)], limit=1)
cost = emp and emp.timesheet_cost or 0.0
uom = (emp or user).company_id.project_time_mode_id
# Nominal employee cost = 1 * company project UoM (project_time_mode_id)
return {
'amount': -unit_amount * cost,
'product_uom_id': uom.id
}
return {}
def _get_sale_order_line(self, vals=None):
result = dict(vals or {})
if self.project_id:
@@ -26,45 +62,6 @@ class AccountAnalyticLine(models.Model):
'so_line': sol.id,
'product_id': sol.product_id.id,
})
result = self._get_timesheet_cost(result)
result.update(self._get_timesheet_cost(result))
result = super(AccountAnalyticLine, self)._get_sale_order_line(vals=result)
return result
def _get_timesheet_cost(self, vals=None):
result = dict(vals or {})
if result.get('project_id') or self.project_id:
if result.get('amount'):
return result
unit_amount = result.get('unit_amount', 0.0) or self.unit_amount
user_id = result.get('user_id') or self.user_id.id or self._default_user()
user = self.env['res.users'].browse([user_id])
emp = self.env['hr.employee'].search([('user_id', '=', user_id)], limit=1)
cost = emp and emp.timesheet_cost or 0.0
uom = (emp or user).company_id.project_time_mode_id
# Nominal employee cost = 1 * company project UoM (project_time_mode_id)
result.update(
amount=(-unit_amount * cost),
product_uom_id=uom.id
)
return result
@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 values.get('so_line', False)
@api.multi
def write(self, values):
self._update_values(values)
for line in self:
values = line._get_timesheet_cost(vals=values)
super(AccountAnalyticLine, line).write(values)
return True
@api.model
def create(self, values):
self._update_values(values)
values = self._get_timesheet_cost(vals=values)
return super(AccountAnalyticLine, self).create(values)
return super(AccountAnalyticLine, self)._get_sale_order_line(vals=result)
+3 -2
View File
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, fields
from odoo import fields, models
class HrEmployee(models.Model):
_inherit = 'hr.employee'
# FIXME: this field should be in module hr_timesheet, not sale_timesheet
timesheet_cost = fields.Float(string='Timesheet Cost', default=0.0)
timesheet_cost = fields.Float('Timesheet Cost', default=0.0)
+53 -62
View File
@@ -1,85 +1,76 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp.tools.translate import _
from odoo import api, fields, models, _
class procurement_order(osv.osv):
_name = "procurement.order"
_inherit = "procurement.order"
_columns = {
'task_id': fields.many2one('project.task', 'Task', copy=False),
}
class ProcurementOrder(models.Model):
_inherit = 'procurement.order'
def _is_procurement_task(self, cr, uid, ids, context=None):
procurement = self.browse(cr, uid, ids[0], context=context)
return procurement.product_id.type == 'service' and procurement.product_id.track_service=='task' or False
task_id = fields.Many2one('project.task', 'Task', copy=False)
def _assign(self, cr, uid, ids, context=None):
procurement = self.browse(cr, uid, ids[0], context=context)
res = super(procurement_order, self)._assign(cr, uid, ids, context=context)
def _is_procurement_task(self):
return self.product_id.type == 'service' and self.product_id.track_service == 'task'
@api.multi
def _assign(self):
self.ensure_one()
res = super(ProcurementOrder, self)._assign()
if not res:
#if there isn't any specific procurement.rule defined for the product, we may want to create a task
return procurement._is_procurement_task()
# if there isn't any specific procurement.rule defined for the product, we may want to create a task
return self._is_procurement_task()
return res
def _run(self, cr, uid, ids, context=None):
procurement = self.browse(cr, uid, ids[0], context=context)
if procurement._is_procurement_task() and not procurement.task_id:
#create a task for the procurement
return self._create_service_task(cr, uid, procurement, context=context)
return super(procurement_order, self)._run(cr, uid, ids, context=context)
@api.multi
def _run(self):
self.ensure_one()
if self._is_procurement_task() and not self.task_id:
# create a task for the procurement
return self._create_service_task()
return super(ProcurementOrder, self)._run()
def _convert_qty_company_hours(self, cr, uid, procurement, context=None):
product_uom = self.pool.get('product.uom')
company_time_uom_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.project_time_mode_id
if procurement.product_uom.id != company_time_uom_id.id and procurement.product_uom.category_id.id == company_time_uom_id.category_id.id:
planned_hours = product_uom._compute_quantity(cr, uid, [procurement.product_uom.id], procurement.product_qty, company_time_uom_id)
def _convert_qty_company_hours(self):
company_time_uom_id = self.env.user.company_id.project_time_mode_id
if self.product_uom.id != company_time_uom_id.id and self.product_uom.category_id.id == company_time_uom_id.category_id.id:
planned_hours = self.product_uom._compute_quantity(self.product_qty, company_time_uom_id)
else:
planned_hours = procurement.product_qty
planned_hours = self.product_qty
return planned_hours
def _get_project(self, cr, uid, procurement, context=None):
project_project = self.pool.get('project.project')
project = procurement.product_id.project_id
if not project and procurement.sale_line_id:
def _get_project(self):
Project = self.env['project.project']
project = self.product_id.project_id
if not project and self.sale_line_id:
# find the project corresponding to the analytic account of the sales order
account = procurement.sale_line_id.order_id.project_id
account = self.sale_line_id.order_id.project_id
if not account:
procurement.sale_line_id.order_id._create_analytic_account()
account = procurement.sale_line_id.order_id.project_id
project_ids = project_project.search(cr, uid, [('analytic_account_id', '=', account.id)])
projects = project_project.browse(cr, uid, project_ids, context=context)
project = projects and projects[0]
self.sale_line_id.order_id._create_analytic_account()
account = self.sale_line_id.order_id.project_id
project = Project.search([('analytic_account_id', '=', account.id)], limit=1)
if not project:
project_id = account.project_create({'name': account.name, 'use_tasks': True})
project = project_project.browse(cr, uid, project_id, context=context)
project = Project.browse(project_id)
return project
def _create_service_task(self, cr, uid, procurement, context=None):
project_task = self.pool.get('project.task')
project = self._get_project(cr, uid, procurement, context=context)
planned_hours = self._convert_qty_company_hours(cr, uid, procurement, context=context)
task_id = project_task.create(cr, uid, {
'name': '%s:%s' % (procurement.origin or '', procurement.product_id.name),
'date_deadline': procurement.date_planned,
def _create_service_task(self):
project = self._get_project()
planned_hours = self._convert_qty_company_hours()
task = self.env['project.task'].create({
'name': '%s:%s' % (self.origin or '', self.product_id.name),
'date_deadline': self.date_planned,
'planned_hours': planned_hours,
'remaining_hours': planned_hours,
'partner_id': procurement.sale_line_id and procurement.sale_line_id.order_id.partner_id.id or procurement.partner_dest_id.id,
'user_id': procurement.product_id.product_manager.id,
'procurement_id': procurement.id,
'description': procurement.name + '\n',
'project_id': project and project.id or False,
'company_id': procurement.company_id.id,
},context=context)
self.write(cr, uid, [procurement.id], {'task_id': task_id}, context=context)
self.project_task_create_note(cr, uid, [procurement.id], context=context)
return task_id
'partner_id': self.sale_line_id.order_id.partner_id.id or self.partner_dest_id.id,
'user_id': self.product_id.product_manager.id,
'procurement_id': self.id,
'description': self.name + '\n',
'project_id': project.id,
'company_id': self.company_id.id,
})
self.write({'task_id': task.id})
def project_task_create_note(self, cr, uid, ids, context=None):
for procurement in self.browse(cr, uid, ids, context=context):
body = _("Task created")
self.message_post(cr, uid, [procurement.id], body=body, context=context)
if procurement.sale_line_id and procurement.sale_line_id.order_id:
procurement.sale_line_id.order_id.message_post(body=body)
self.message_post(body=_("Task created"))
if self.sale_line_id.order_id:
self.sale_line_id.order_id.message_post(body=_("Task created"))
return task
+10 -8
View File
@@ -1,23 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import api, fields, models
from odoo import api, fields, models
class ProductTemplate(models.Model):
_inherit = 'product.template'
track_service = fields.Selection(selection_add=[('timesheet', 'Timesheets on project'), ('task', 'Create a task and track hours')])
project_id = fields.Many2one('project.project', string='Project',
help='Create a task under this project on sale order validation. This setting must be set for each company.',
company_dependent=True)
@api.onchange('type', 'invoice_policy')
def onchange_type_timesheet(self):
track_service = fields.Selection(selection_add=[
('timesheet', 'Timesheets on project'),
('task', 'Create a task and track hours')])
project_id = fields.Many2one(
'project.project', 'Project', company_dependent=True,
help='Create a task under this project on sale order validation. This setting must be set for each company.')
@api.onchange('type')
def _onchange_type(self):
if self.type == 'service':
self.track_service = 'timesheet'
else:
self.track_service = 'manual'
return {}
class ProductProduct(models.Model):
+19 -30
View File
@@ -1,45 +1,34 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.osv import fields, osv
from openerp.exceptions import UserError
from openerp.tools.translate import _
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class project_task(osv.osv):
_name = "project.task"
class ProjectTask(models.Model):
_inherit = "project.task"
_columns = {
'procurement_id': fields.many2one('procurement.order', 'Procurement', ondelete='set null'),
'sale_line_id': fields.related('procurement_id', 'sale_line_id', type='many2one', relation='sale.order.line', store=True, string='Sales Order Line'),
}
def unlink(self, cr, uid, ids, context=None):
if context is None:
context = {}
for task in self.browse(cr, uid, ids, context=context):
if task.sale_line_id:
raise UserError(_('You cannot delete a task related to a Sale Order. You can only archive this task.'))
res = super(project_task, self).unlink(cr, uid, ids, context)
return res
procurement_id = fields.Many2one('procurement.order', 'Procurement', ondelete='set null')
sale_line_id = fields.Many2one('sale.order.line', 'Sales Order Line', related='procurement_id.sale_line_id', store=True)
def action_view_so(self, cr, uid, ids, context=None):
task = self.browse(cr, uid, ids, context=context)[0]
@api.multi
def unlink(self):
if any(task.sale_line_id for task in self):
raise ValidationError(_('You cannot delete a task related to a Sale Order. You can only archive this task.'))
return super(ProjectTask, self).unlink()
@api.multi
def action_view_so(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"res_model": "sale.order",
"views": [[False, "form"]],
"res_id": task.sale_line_id.order_id.id,
"res_id": self.sale_line_id.order_id.id,
"context": {"create": False, "show_sale": True},
}
def onchange_parent_id(self, cr, uid, ids, parent_id, context=None):
if not parent_id:
return {'value' : {'procurement_id': False, 'sale_line_id': False }}
parent_task = self.browse(cr, uid, parent_id, context=context)
return {
'value' : {
'procurement_id' : parent_task.procurement_id.id,
'sale_line_id' : parent_task.sale_line_id.id,
}
}
@api.onchange('parent_id')
def onchange_parent_id(self):
self.procurement_id = self.parent_id.procurement_id.id
self.sale_line_id = self.parent_id.sale_line_id.id
+5 -7
View File
@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, api, fields
from odoo import models, api, fields
class ResCompany(models.Model):
_inherit = 'res.company'
@api.model
def _get_uom_hours(self):
try:
return self.env.ref("product.product_uom_hour")
except ValueError:
return False
project_time_mode_id = fields.Many2one('product.uom', string='Timesheet UoM', default=_get_uom_hours)
def _get_default_uom_id(self):
return self.env.ref("product.product_uom_hour", raise_if_not_found=False)
project_time_mode_id = fields.Many2one('product.uom', 'Timesheet UoM', default=_get_default_uom_id)
+40 -44
View File
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp import models, api, fields
from openerp.tools.translate import _
from odoo import api, fields, models, _
from openerp.exceptions import ValidationError
from odoo.exceptions import ValidationError
class SaleOrder(models.Model):
@@ -31,6 +30,19 @@ class SaleOrder(models.Model):
order.timesheet_ids = []
order.timesheet_count = len(order.timesheet_ids)
@api.multi
@api.depends('order_line.product_id.project_id')
def _compute_tasks_ids(self):
for order in self:
order.tasks_ids = self.env['project.task'].search([('sale_line_id', 'in', order.order_line.ids)])
order.tasks_count = len(order.tasks_ids)
@api.multi
@api.depends('project_id.project_ids')
def _compute_project_project_id(self):
for order in self:
order.project_project_id = self.env['project.project'].search([('analytic_account_id', '=', order.project_id.id)])
@api.multi
@api.constrains('order_line')
def _check_multi_timesheet(self):
@@ -44,19 +56,24 @@ class SaleOrder(models.Model):
return {}
@api.multi
@api.depends('order_line.product_id.project_id')
def _compute_tasks_ids(self):
def action_confirm(self):
result = super(SaleOrder, self).action_confirm()
for order in self:
order.tasks_ids = self.env['project.task'].search([('sale_line_id', 'in', order.order_line.ids)])
order.tasks_count = len(order.tasks_ids)
if not order.project_project_id:
for line in order.order_line:
if line.product_id.track_service == 'timesheet':
if not order.project_id:
order._create_analytic_account(prefix=line.product_id.default_code or None)
order.project_id.project_create({'name': order.project_id.name, 'use_tasks': True})
break
return result
@api.multi
def action_view_task(self):
self.ensure_one()
imd = self.env['ir.model.data']
action = imd.xmlid_to_object('project.action_view_task')
list_view_id = imd.xmlid_to_res_id('project.view_task_tree2')
form_view_id = imd.xmlid_to_res_id('project.view_task_form2')
action = self.env.ref('project.action_view_task')
list_view_id = self.env.ref('project.view_task_tree2').id
form_view_id = self.env.ref('project.view_task_form2').id
result = {
'name': action.name,
@@ -76,18 +93,11 @@ class SaleOrder(models.Model):
result = {'type': 'ir.actions.act_window_close'}
return result
@api.multi
@api.depends('project_id.project_ids')
def _compute_project_project_id(self):
for order in self:
order.project_project_id = self.env['project.project'].search([('analytic_account_id', '=', order.project_id.id)])
@api.multi
def action_view_project_project(self):
self.ensure_one()
imd = self.env['ir.model.data']
action = imd.xmlid_to_object('project.open_view_project_all')
form_view_id = imd.xmlid_to_res_id('project.edit_project')
action = self.env.ref('project.open_view_project_all')
form_view_id = self.env.ref('project.edit_project').id
result = {
'name': action.name,
@@ -101,26 +111,12 @@ class SaleOrder(models.Model):
}
return result
@api.multi
def action_confirm(self):
result = super(SaleOrder, self).action_confirm()
for order in self:
if not order.project_project_id:
for line in order.order_line:
if line.product_id.track_service == 'timesheet':
if not order.project_id:
order._create_analytic_account(prefix=line.product_id.default_code or None)
order.project_id.project_create({'name': order.project_id.name, 'use_tasks': True})
break
return result
@api.multi
def action_view_timesheet(self):
self.ensure_one()
imd = self.env['ir.model.data']
action = imd.xmlid_to_object('hr_timesheet.act_hr_timesheet_line')
list_view_id = imd.xmlid_to_res_id('hr_timesheet.hr_timesheet_line_tree')
form_view_id = imd.xmlid_to_res_id('hr_timesheet.hr_timesheet_line_form')
action = self.env.ref('hr_timesheet.act_hr_timesheet_line')
list_view_id = self.env.ref('hr_timesheet.hr_timesheet_line_tree').id
form_view_id = self.env.ref('hr_timesheet.hr_timesheet_line_form').id
result = {
'name': action.name,
@@ -141,16 +137,16 @@ class SaleOrder(models.Model):
class SaleOrderLine(models.Model):
_inherit = "sale.order.line"
@api.multi
def _compute_analytic(self, domain=None):
if not domain:
# To filter on analyic lines linked to an expense
domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('project_id', '!=', False)]
return super(SaleOrderLine, self)._compute_analytic(domain=domain)
@api.model
def create(self, values):
line = super(SaleOrderLine, self).create(values)
if line.state == 'sale' and not line.order_id.project_id and line.product_id.track_service in ['timesheet', 'task']:
line.order_id._create_analytic_account()
return line
@api.multi
def _compute_analytic(self, domain=None):
if not domain:
# To filter on analyic lines linked to an expense
domain = [('so_line', 'in', self.ids), '|', ('amount', '<=', 0.0), ('project_id', '!=', False)]
return super(SaleOrderLine, self)._compute_analytic(domain=domain)
@@ -1,6 +1,6 @@
# -*- 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
from odoo.addons.sale.tests.test_sale_common import TestSale
class TestSaleService(TestSale):
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from openerp.tools import float_is_zero
from odoo.tools import float_is_zero
from openerp.addons.sale.tests.test_sale_common import TestSale
from odoo.addons.sale.tests.test_sale_common import TestSale
class TestSaleTimesheet(TestSale):
+14 -16
View File
@@ -1,17 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="hr_timesheet_employee_extd_form" model="ir.ui.view">
<field name="name">hr.timesheet.employee.extd_form</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='active_group']" position="before">
<group string="Timesheets">
<field name="timesheet_cost"/>
</group>
</xpath>
</field>
</record>
</data>
</openerp>
<odoo>
<record id="hr_timesheet_employee_extd_form" model="ir.ui.view">
<field name="name">hr.timesheet.employee.extd_form</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='active_group']" position="before">
<group string="Timesheets">
<field name="timesheet_cost"/>
</group>
</xpath>
</field>
</record>
</odoo>
@@ -1,15 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_procurement_task_form" model="ir.ui.view">
<field name="name">procurement.procurement.form.view.inherit</field>
<field name="model">procurement.order</field>
<field name="inherit_id" ref="procurement.procurement_form_view"/>
<field name="arch" type="xml">
<field name="origin" position="after">
<field name="task_id"/>
</field>
<odoo>
<record id="view_procurement_task_form" model="ir.ui.view">
<field name="name">procurement.procurement.form.view.inherit</field>
<field name="model">procurement.order</field>
<field name="inherit_id" ref="procurement.procurement_form_view"/>
<field name="arch" type="xml">
<field name="origin" position="after">
<field name="task_id"/>
</field>
</record>
</data>
</openerp>
</field>
</record>
</odoo>
+15 -17
View File
@@ -1,19 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_product_timesheet_form" model="ir.ui.view">
<field name="name">product.template.timesheet.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="sale.product_template_form_view_invoice_policy"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='track_service']" position="attributes">
<attribute name="invisible">False</attribute>
<attribute name="attrs">{'invisible': [('type','!=','service')]}</attribute>
</xpath>
<field name="track_service" position="after">
<field name="project_id" attrs="{'invisible':['|', ('type','!=','service'), ('track_service', '&lt;&gt;', 'task')]}"/>
</field>
<odoo>
<record id="view_product_timesheet_form" model="ir.ui.view">
<field name="name">product.template.timesheet.form</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="sale.product_template_form_view_invoice_policy"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='track_service']" position="attributes">
<attribute name="invisible">False</attribute>
<attribute name="attrs">{'invisible': [('type','!=','service')]}</attribute>
</xpath>
<field name="track_service" position="after">
<field name="project_id" attrs="{'invisible':['|', ('type','!=','service'), ('track_service', '&lt;&gt;', 'task')]}"/>
</field>
</record>
</data>
</openerp>
</field>
</record>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_sale_service_inherit_form2" model="ir.ui.view">
<field name="name">sale.service.form.view.inherit</field>
<field name="model">project.task</field>
@@ -17,10 +16,6 @@
<field name="procurement_id" groups="base.group_no_one"/>
<field name="sale_line_id" string="Order Line" groups="base.group_no_one"/>
</field>
<field name="parent_id" position="attributes">
<attribute name="on_change">onchange_parent_id(parent_id)</attribute>
</field>
</field>
</record>
</data>
</openerp>
</odoo>
@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<odoo>
<record id="view_order_form_inherit_sale_timesheet" model="ir.ui.view">
<field name="name">sale.order.form.sale.timesheet</field>
<field name="model">sale.order</field>
@@ -39,5 +38,4 @@
</data>
</field>
</record>
</data>
</openerp>
</odoo>