[MOV] sale_timesheet: reorganize files and split models and views into their own files
This commit is contained in:
committed by
Thibault Delavallée
parent
f1b58b1293
commit
572053d9f5
@@ -3,7 +3,6 @@
|
||||
|
||||
{
|
||||
'name': 'Sales Timesheet',
|
||||
'version': '1.0',
|
||||
'category': 'Hidden',
|
||||
'summary': 'Sell based on timesheets',
|
||||
'description': """
|
||||
@@ -16,13 +15,16 @@ have real delivered quantities in sales orders.
|
||||
""",
|
||||
'website': 'https://www.odoo.com/page/warehouse',
|
||||
'depends': ['sale', 'hr_timesheet'],
|
||||
'data': ['views/sale_timesheet_view.xml',
|
||||
'data/sale_timesheet_data.xml',
|
||||
'views/sale_service_view.xml',
|
||||
'data': [
|
||||
'data/sale_timesheet_data.xml',
|
||||
'views/sale_order_views.xml',
|
||||
'views/product_views.xml',
|
||||
'views/hr_views.xml',
|
||||
'views/project_task_views.xml',
|
||||
'views/procurement_views.xml',
|
||||
],
|
||||
'demo': ['data/sale_timesheet_demo.xml',
|
||||
'demo/sale_service_demo.xml',
|
||||
'demo': [
|
||||
'data/sale_service_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,7 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
<!--Resource: sale.order.line for services type product-->
|
||||
<record id="product.service_order_01" model="product.product">
|
||||
<field name="track_service">timesheet</field>
|
||||
</record>
|
||||
<record id="product.product_product_2" model="product.product">
|
||||
<field name="name">Support Contract (on timesheet)</field>
|
||||
<field name="track_service">timesheet</field>
|
||||
</record>
|
||||
|
||||
<record id="line_services" model="sale.order.line">
|
||||
<field name="order_id" ref="sale.sale_order_3"/>
|
||||
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<record id="product.service_order_01" model="product.product">
|
||||
<field name="track_service">timesheet</field>
|
||||
</record>
|
||||
<record id="product.product_product_2" model="product.product">
|
||||
<field name="name">Support Contract (on timesheet)</field>
|
||||
<field name="track_service">timesheet</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -1,5 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import sale_timesheet
|
||||
import sale_service
|
||||
import account
|
||||
import hr
|
||||
import procurement
|
||||
import product
|
||||
import project
|
||||
import res_company
|
||||
import sale_order
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import models, api
|
||||
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = 'account.analytic.line'
|
||||
|
||||
def _get_sale_order_line(self, vals=None):
|
||||
result = dict(vals or {})
|
||||
if self.project_id:
|
||||
if result.get('so_line'):
|
||||
sol = self.env['sale.order.line'].browse([result['so_line']])
|
||||
else:
|
||||
sol = self.so_line
|
||||
if not sol:
|
||||
sol = self.env['sale.order.line'].search([
|
||||
('order_id.project_id', '=', self.account_id.id),
|
||||
('state', '=', 'sale'),
|
||||
('product_id.track_service', '=', 'timesheet'),
|
||||
('product_id.type', '=', 'service')],
|
||||
limit=1)
|
||||
if sol:
|
||||
result.update({
|
||||
'so_line': sol.id,
|
||||
'product_id': sol.product_id.id,
|
||||
})
|
||||
result = 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)
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import models, fields
|
||||
|
||||
|
||||
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)
|
||||
-52
@@ -1,9 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp import api, models
|
||||
from openerp.osv import fields, osv
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
@@ -85,53 +83,3 @@ class procurement_order(osv.osv):
|
||||
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)
|
||||
|
||||
|
||||
class project_task(osv.osv):
|
||||
_name = "project.task"
|
||||
_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
|
||||
|
||||
def action_view_so(self, cr, uid, ids, context=None):
|
||||
task = self.browse(cr, uid, ids, context=context)[0]
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "sale.order",
|
||||
"views": [[False, "form"]],
|
||||
"res_id": task.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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
@api.multi
|
||||
def _need_procurement(self):
|
||||
for product in self:
|
||||
if product.type == 'service' and product.track_service == 'task':
|
||||
return True
|
||||
return super(ProductProduct, self)._need_procurement()
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp 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):
|
||||
if self.type == 'service':
|
||||
self.track_service = 'timesheet'
|
||||
else:
|
||||
self.track_service = 'manual'
|
||||
return {}
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
@api.multi
|
||||
def _need_procurement(self):
|
||||
for product in self:
|
||||
if product.type == 'service' and product.track_service == 'task':
|
||||
return True
|
||||
return super(ProductProduct, self)._need_procurement()
|
||||
@@ -0,0 +1,45 @@
|
||||
# -*- 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 _
|
||||
|
||||
|
||||
class project_task(osv.osv):
|
||||
_name = "project.task"
|
||||
_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
|
||||
|
||||
def action_view_so(self, cr, uid, ids, context=None):
|
||||
task = self.browse(cr, uid, ids, context=context)[0]
|
||||
return {
|
||||
"type": "ir.actions.act_window",
|
||||
"res_model": "sale.order",
|
||||
"views": [[False, "form"]],
|
||||
"res_id": task.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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from openerp 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)
|
||||
-100
@@ -7,106 +7,6 @@ from openerp.tools.translate import _
|
||||
from openerp.exceptions import ValidationError
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
if self.type == 'service':
|
||||
self.track_service = 'timesheet'
|
||||
else:
|
||||
self.track_service = 'manual'
|
||||
return {}
|
||||
|
||||
|
||||
class AccountAnalyticLine(models.Model):
|
||||
_inherit = 'account.analytic.line'
|
||||
|
||||
def _get_sale_order_line(self, vals=None):
|
||||
result = dict(vals or {})
|
||||
if self.project_id:
|
||||
if result.get('so_line'):
|
||||
sol = self.env['sale.order.line'].browse([result['so_line']])
|
||||
else:
|
||||
sol = self.so_line
|
||||
if not sol:
|
||||
sol = self.env['sale.order.line'].search([
|
||||
('order_id.project_id', '=', self.account_id.id),
|
||||
('state', '=', 'sale'),
|
||||
('product_id.track_service', '=', 'timesheet'),
|
||||
('product_id.type', '=', 'service')],
|
||||
limit=1)
|
||||
if sol:
|
||||
result.update({
|
||||
'so_line': sol.id,
|
||||
'product_id': sol.product_id.id,
|
||||
})
|
||||
result = 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)
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?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>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?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>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?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', '<>', 'task')]}"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
-11
@@ -1,17 +1,6 @@
|
||||
<?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>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<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>
|
||||
-28
@@ -1,34 +1,6 @@
|
||||
<?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', '<>', 'task')]}"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
Reference in New Issue
Block a user