[MERGE] sale_project: improve analytic account
This PR improves the analytic accounting in project: 1) Analytic account of the project is passed to the invoice lines generated from the project's SO lines. 2) Analytic account and analytic tags are added on task in order to gain in granularity. Those tags are passed to the invoice lines generated from the task's SO lines. 3) Employee timesheet cost now may be overriden in the project employee mapping, if there is an employee rate. This cost is used to compute the amount (price) of the timesheets. 4) The sale order margin is computed based on the timesheeted amounts (prices) per unit of measure, for services with quantity delivered based on timesheets. PR : #70527 PR upgrade: odoo/upgrade#2616 task-2458135 -- I confirm I have signed the CLA and read the PR guidelines at www.odoo.com/submit-pr Signed-off-by: LTU-Odoo <IT-Ideas@users.noreply.github.com>
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
from lxml import etree
|
||||
import re
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import api, Command, fields, models, _
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
from odoo.osv import expression
|
||||
|
||||
@@ -178,6 +178,18 @@ class AccountAnalyticLine(models.Model):
|
||||
Overrride this to compute on the fly some field that can not be computed fields.
|
||||
:param values: dict values for `create`or `write`.
|
||||
"""
|
||||
# task implies analytic account and tags
|
||||
if vals.get('task_id') and not vals.get('account_id'):
|
||||
task = self.env['project.task'].browse(vals.get('task_id'))
|
||||
task_analytic_account_id = task._get_task_analytic_account_id()
|
||||
vals['account_id'] = task_analytic_account_id.id
|
||||
vals['company_id'] = task_analytic_account_id.company_id.id or task.company_id.id
|
||||
if vals.get('tag_ids'):
|
||||
vals['tag_ids'] += [Command.link(tag_id.id) for tag_id in task.analytic_tag_ids]
|
||||
else:
|
||||
vals['tag_ids'] = [Command.set(task.analytic_tag_ids.ids)]
|
||||
if not task_analytic_account_id.active:
|
||||
raise UserError(_('You cannot add timesheets to a project or a task linked to an inactive analytic account.'))
|
||||
# project implies analytic account
|
||||
if vals.get('project_id') and not vals.get('account_id'):
|
||||
project = self.env['project.project'].browse(vals.get('project_id'))
|
||||
@@ -225,7 +237,7 @@ class AccountAnalyticLine(models.Model):
|
||||
# (re)compute the amount (depending on unit_amount, employee_id for the cost, and account_id for currency)
|
||||
if any(field_name in values for field_name in ['unit_amount', 'employee_id', 'account_id']):
|
||||
for timesheet in sudo_self:
|
||||
cost = timesheet.employee_id.timesheet_cost or 0.0
|
||||
cost = timesheet._employee_timesheet_cost()
|
||||
amount = -timesheet.unit_amount * cost
|
||||
amount_converted = timesheet.employee_id.currency_id._convert(
|
||||
amount, timesheet.account_id.currency_id, self.env.company, timesheet.date)
|
||||
@@ -246,3 +258,7 @@ class AccountAnalyticLine(models.Model):
|
||||
|
||||
def _get_timesheet_time_day(self):
|
||||
return self._convert_hours_to_days(self.unit_amount)
|
||||
|
||||
def _employee_timesheet_cost(self):
|
||||
self.ensure_one()
|
||||
return self.employee_id.timesheet_cost or 0.0
|
||||
|
||||
@@ -204,11 +204,11 @@ class Task(models.Model):
|
||||
def _compute_encode_uom_in_days(self):
|
||||
self.encode_uom_in_days = self._uom_in_days()
|
||||
|
||||
@api.depends('project_id.analytic_account_id.active')
|
||||
@api.depends('analytic_account_id.active', 'project_id.analytic_account_id.active')
|
||||
def _compute_analytic_account_active(self):
|
||||
""" Overridden in sale_timesheet """
|
||||
for task in self:
|
||||
task.analytic_account_active = task.project_id.analytic_account_id.active
|
||||
task.analytic_account_active = task._get_task_analytic_account_id().active
|
||||
|
||||
@api.depends('timesheet_ids.unit_amount')
|
||||
def _compute_effective_hours(self):
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import analytic_account
|
||||
from . import analytic_account_tag
|
||||
from . import project_milestone
|
||||
from . import project_project_stage
|
||||
from . import project_task_recurrence
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
class AccountAnalyticTag(models.Model):
|
||||
_inherit = 'account.analytic.tag'
|
||||
|
||||
task_ids = fields.Many2many('project.task', string='Tasks')
|
||||
@@ -1040,6 +1040,16 @@ class Task(models.Model):
|
||||
repeat_show_week = fields.Boolean(compute='_compute_repeat_visibility')
|
||||
repeat_show_month = fields.Boolean(compute='_compute_repeat_visibility')
|
||||
|
||||
# Account analytic
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', ondelete='set null',
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", check_company=True,
|
||||
help="Analytic account to which this task is linked for financial management. "
|
||||
"Use an analytic account to record cost and revenue on your task. "
|
||||
"If empty, the analytic account of the project will be used.")
|
||||
project_analytic_account_id = fields.Many2one('account.analytic.account', string='Project Analytic Account', related='project_id.analytic_account_id')
|
||||
analytic_tag_ids = fields.Many2many('account.analytic.tag',
|
||||
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", check_company=True)
|
||||
|
||||
@property
|
||||
def SELF_READABLE_FIELDS(self):
|
||||
return PROJECT_TASK_READABLE_FIELDS | self.SELF_WRITABLE_FIELDS
|
||||
@@ -1979,6 +1989,13 @@ class Task(models.Model):
|
||||
portal_users = task.message_partner_ids.user_ids.filtered('share')
|
||||
task.message_unsubscribe(partner_ids=portal_users.partner_id.ids)
|
||||
|
||||
# ---------------------------------------------------
|
||||
# Analytic accounting
|
||||
# ---------------------------------------------------
|
||||
def _get_task_analytic_account_id(self):
|
||||
self.ensure_one()
|
||||
return self.analytic_account_id or self.project_analytic_account_id
|
||||
|
||||
class ProjectTags(models.Model):
|
||||
""" Tags of project's tasks """
|
||||
_name = "project.tags"
|
||||
|
||||
@@ -146,7 +146,7 @@ class ProjectTaskRecurrence(models.Model):
|
||||
return ['message_partner_ids', 'company_id', 'description', 'displayed_image_id', 'email_cc',
|
||||
'parent_id', 'partner_email', 'partner_id', 'partner_phone', 'planned_hours',
|
||||
'project_id', 'project_privacy_visibility', 'sequence', 'tag_ids', 'recurrence_id',
|
||||
'name', 'recurring_task']
|
||||
'name', 'recurring_task', 'analytic_account_id']
|
||||
|
||||
def _get_weekdays(self, n=1):
|
||||
self.ensure_one()
|
||||
|
||||
@@ -930,6 +930,8 @@
|
||||
<page name="extra_info" string="Extra Info" groups="base.group_no_one">
|
||||
<group>
|
||||
<group>
|
||||
<field name="analytic_account_id" groups="analytic.group_analytic_accounting" context="{'default_partner_id': partner_id}"/>
|
||||
<field name="analytic_tag_ids" groups="analytic.group_analytic_tags" widget="many2many_tags"/>
|
||||
<field name="sequence" groups="base.group_no_one"/>
|
||||
<field name="email_from" invisible="1"/>
|
||||
<field name="email_cc" groups="base.group_no_one"/>
|
||||
|
||||
@@ -23,31 +23,8 @@ class SaleOrderLine(models.Model):
|
||||
line.purchase_price = 0.0
|
||||
continue
|
||||
line = line.with_company(line.company_id)
|
||||
product = line.product_id
|
||||
product_cost = product.standard_price
|
||||
if not product_cost:
|
||||
# If the standard_price is 0
|
||||
# Avoid unnecessary computations
|
||||
# and currency conversions
|
||||
if not line.purchase_price:
|
||||
line.purchase_price = 0.0
|
||||
continue
|
||||
fro_cur = product.cost_currency_id
|
||||
to_cur = line.currency_id or line.order_id.currency_id
|
||||
if line.product_uom and line.product_uom != product.uom_id:
|
||||
product_cost = product.uom_id._compute_price(
|
||||
product_cost,
|
||||
line.product_uom,
|
||||
)
|
||||
line.purchase_price = fro_cur._convert(
|
||||
from_amount=product_cost,
|
||||
to_currency=to_cur,
|
||||
company=line.company_id or self.env.company,
|
||||
date=line.order_id.date_order or fields.Date.today(),
|
||||
round=False,
|
||||
) if to_cur and product_cost else product_cost
|
||||
# The pricelist may not have been set, therefore no conversion
|
||||
# is needed because we don't know the target currency..
|
||||
product_cost = line.product_id.standard_price
|
||||
line.purchase_price = line._convert_price(product_cost, line.product_id.uom_id)
|
||||
|
||||
@api.depends('price_subtotal', 'product_uom_qty', 'purchase_price')
|
||||
def _compute_margin(self):
|
||||
@@ -55,6 +32,31 @@ class SaleOrderLine(models.Model):
|
||||
line.margin = line.price_subtotal - (line.purchase_price * line.product_uom_qty)
|
||||
line.margin_percent = line.price_subtotal and line.margin/line.price_subtotal
|
||||
|
||||
def _convert_price(self, product_cost, from_uom):
|
||||
self.ensure_one()
|
||||
if not product_cost:
|
||||
# If the standard_price is 0
|
||||
# Avoid unnecessary computations
|
||||
# and currency conversions
|
||||
if not self.purchase_price:
|
||||
return product_cost
|
||||
from_currency = self.product_id.cost_currency_id
|
||||
to_cur = self.currency_id or self.order_id.currency_id
|
||||
to_uom = self.product_uom
|
||||
if to_uom and to_uom != from_uom:
|
||||
product_cost = from_uom._compute_price(
|
||||
product_cost,
|
||||
to_uom,
|
||||
)
|
||||
return from_currency._convert(
|
||||
from_amount=product_cost,
|
||||
to_currency=to_cur,
|
||||
company=self.company_id or self.env.company,
|
||||
date=self.order_id.date_order or fields.Date.today(),
|
||||
round=False,
|
||||
) if to_cur and product_cost else product_cost
|
||||
# The pricelist may not have been set, therefore no conversion
|
||||
# is needed because we don't know the target currency..
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = "sale.order"
|
||||
|
||||
@@ -12,7 +12,7 @@ class Project(models.Model):
|
||||
|
||||
sale_line_id = fields.Many2one(
|
||||
'sale.order.line', 'Sales Order Item', copy=False,
|
||||
compute="_compute_sale_line_id", store=True, readonly=False,
|
||||
compute="_compute_sale_line_id", store=True, readonly=False, index=True,
|
||||
domain="[('is_service', '=', True), ('is_expense', '=', False), ('state', 'in', ['sale', 'done']), ('order_partner_id', '=?', partner_id), '|', ('company_id', '=', False), ('company_id', '=', company_id)]",
|
||||
help="Sales order item to which the project is linked. Link the timesheet entry to the sales order item defined on the project. "
|
||||
"Only applies on tasks without sale order item defined, and if the employee is not in the 'Employee/Sales Order Item Mapping' of the project.")
|
||||
@@ -61,7 +61,7 @@ class ProjectTask(models.Model):
|
||||
sale_order_id = fields.Many2one('sale.order', 'Sales Order', compute='_compute_sale_order_id', store=True, help="Sales order to which the task is linked.")
|
||||
sale_line_id = fields.Many2one(
|
||||
'sale.order.line', 'Sales Order Item', domain="[('company_id', '=', company_id), ('is_service', '=', True), ('order_partner_id', 'child_of', commercial_partner_id), ('is_expense', '=', False), ('state', 'in', ['sale', 'done'])]",
|
||||
compute='_compute_sale_line', recursive=True, store=True, readonly=False, copy=False, tracking=True,
|
||||
compute='_compute_sale_line', recursive=True, store=True, readonly=False, copy=False, tracking=True, index=True,
|
||||
help="Sales Order Item to which the time spent on this task will be added, in order to be invoiced to your customer.")
|
||||
project_sale_order_id = fields.Many2one('sale.order', string="Project's sale order", related='project_id.sale_order_id')
|
||||
invoice_count = fields.Integer("Number of invoices", related='sale_order_id.invoice_count')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo import api, Command, fields, models, _
|
||||
|
||||
from odoo.tools.safe_eval import safe_eval
|
||||
from odoo.tools.sql import column_exists, create_column
|
||||
@@ -353,3 +353,40 @@ class SaleOrderLine(models.Model):
|
||||
project = map_so_project[so_line.order_id.id]
|
||||
if not so_line.task_id:
|
||||
so_line._timesheet_create_task(project=project)
|
||||
|
||||
def _prepare_invoice_line(self, **optional_values):
|
||||
"""
|
||||
If the sale order line isn't linked to a sale order which already have a default analytic account,
|
||||
this method allows to retrieve the analytic account which is linked to project or task directly linked
|
||||
to this sale order line, or the analytic account of the project which uses this sale order line, if it exists.
|
||||
"""
|
||||
values = super(SaleOrderLine, self)._prepare_invoice_line(**optional_values)
|
||||
if not values['analytic_account_id']:
|
||||
if self.task_id.analytic_account_id:
|
||||
values['analytic_account_id'] = self.task_id._get_task_analytic_account_id().id
|
||||
elif self.project_id.analytic_account_id:
|
||||
values['analytic_account_id'] = self.project_id.analytic_account_id.id
|
||||
elif self.is_service and not self.is_expense:
|
||||
task_analytic_account_id = self.env['project.task'].read_group([
|
||||
('sale_line_id', '=', self.id),
|
||||
('analytic_account_id', '!=', False),
|
||||
], ['analytic_account_id'], ['analytic_account_id'])
|
||||
project_analytic_account_id = self.env['project.project'].read_group([
|
||||
('analytic_account_id', '!=', False),
|
||||
'|',
|
||||
('sale_line_id', '=', self.id),
|
||||
'&',
|
||||
('tasks.sale_line_id', '=', self.id),
|
||||
('tasks.analytic_account_id', '=', False)
|
||||
], ['analytic_account_id'], ['analytic_account_id'])
|
||||
analytic_account_ids = {rec['analytic_account_id'][0] for rec in (task_analytic_account_id + project_analytic_account_id)}
|
||||
if len(analytic_account_ids) == 1:
|
||||
values['analytic_account_id'] = analytic_account_ids.pop()
|
||||
if self.task_id.analytic_tag_ids:
|
||||
values['analytic_tag_ids'] += [Command.link(tag_id.id) for tag_id in self.task_id.analytic_tag_ids]
|
||||
elif self.is_service and not self.is_expense:
|
||||
tag_ids = self.env['account.analytic.tag'].search([
|
||||
('task_ids.sale_line_id', '=', self.id)
|
||||
])
|
||||
values['analytic_tag_ids'] += [Command.link(tag_id.id) for tag_id in tag_ids]
|
||||
return values
|
||||
|
||||
@@ -98,7 +98,7 @@ class AccountAnalyticLine(models.Model):
|
||||
|
||||
if not self.task_id:
|
||||
if self.project_id.pricing_type == 'employee_rate':
|
||||
map_entry = self.env['project.sale.line.employee.map'].search([('project_id', '=', self.project_id.id), ('employee_id', '=', self.employee_id.id)])
|
||||
map_entry = self._get_employee_mapping_entry()
|
||||
if map_entry:
|
||||
return map_entry.sale_line_id
|
||||
if self.project_id.sale_line_id:
|
||||
@@ -149,3 +149,14 @@ class AccountAnalyticLine(models.Model):
|
||||
def _unlink_except_invoiced(self):
|
||||
if any(line.timesheet_invoice_id and line.timesheet_invoice_id.state == 'posted' for line in self):
|
||||
raise UserError(_('You cannot remove a timesheet that has already been invoiced.'))
|
||||
|
||||
def _get_employee_mapping_entry(self):
|
||||
self.ensure_one()
|
||||
return self.env['project.sale.line.employee.map'].search([('project_id', '=', self.project_id.id), ('employee_id', '=', self.employee_id.id)])
|
||||
|
||||
def _employee_timesheet_cost(self):
|
||||
if self.project_id.pricing_type == 'employee_rate':
|
||||
mapping_entry = self._get_employee_mapping_entry()
|
||||
if mapping_entry:
|
||||
return mapping_entry.cost
|
||||
return super()._employee_timesheet_cost()
|
||||
|
||||
@@ -347,7 +347,7 @@ class ProjectTask(models.Model):
|
||||
return res
|
||||
|
||||
sale_order_id = fields.Many2one(domain="['|', '|', ('partner_id', '=', partner_id), ('partner_id', 'child_of', commercial_partner_id), ('partner_id', 'parent_of', partner_id)]")
|
||||
analytic_account_id = fields.Many2one('account.analytic.account', related='sale_order_id.analytic_account_id')
|
||||
so_analytic_account_id = fields.Many2one(related='sale_order_id.analytic_account_id', string='Sale Order Analytic Account')
|
||||
pricing_type = fields.Selection(related="project_id.pricing_type")
|
||||
is_project_map_empty = fields.Boolean("Is Project map empty", compute='_compute_is_project_map_empty')
|
||||
has_multi_sol = fields.Boolean(compute='_compute_has_multi_sol', compute_sudo=True)
|
||||
@@ -384,11 +384,11 @@ class ProjectTask(models.Model):
|
||||
for task in self:
|
||||
task.remaining_hours_so = mapped_remaining_hours[task._origin.id]
|
||||
|
||||
@api.depends('analytic_account_id.active')
|
||||
@api.depends('so_analytic_account_id.active')
|
||||
def _compute_analytic_account_active(self):
|
||||
super()._compute_analytic_account_active()
|
||||
for task in self:
|
||||
task.analytic_account_active = task.analytic_account_active or task.analytic_account_id.active
|
||||
task.analytic_account_active = task.analytic_account_active or task.so_analytic_account_id.active
|
||||
|
||||
@api.depends('allow_billable')
|
||||
def _compute_sale_order_id(self):
|
||||
@@ -437,4 +437,4 @@ class ProjectTaskRecurrence(models.Model):
|
||||
|
||||
@api.model
|
||||
def _get_recurring_fields(self):
|
||||
return ['analytic_account_id'] + super(ProjectTaskRecurrence, self)._get_recurring_fields()
|
||||
return ['so_analytic_account_id'] + super(ProjectTaskRecurrence, self)._get_recurring_fields()
|
||||
|
||||
@@ -10,7 +10,7 @@ class ProjectProductEmployeeMap(models.Model):
|
||||
|
||||
project_id = fields.Many2one('project.project', "Project", required=True)
|
||||
employee_id = fields.Many2one('hr.employee', "Employee", required=True)
|
||||
sale_line_id = fields.Many2one('sale.order.line', "Sale Order Item", compute="_compute_sale_line_id", store=True, readonly=False, required=True,
|
||||
sale_line_id = fields.Many2one('sale.order.line', "Sale Order Item", compute="_compute_sale_line_id", store=True, readonly=False,
|
||||
domain="""[
|
||||
('is_service', '=', True),
|
||||
('is_expense', '=', False),
|
||||
@@ -21,6 +21,10 @@ class ProjectProductEmployeeMap(models.Model):
|
||||
partner_id = fields.Many2one(related='project_id.partner_id')
|
||||
price_unit = fields.Float("Unit Price", compute='_compute_price_unit', store=True, readonly=True)
|
||||
currency_id = fields.Many2one('res.currency', string="Currency", compute='_compute_currency_id', store=True, readonly=False)
|
||||
cost = fields.Monetary(currency_field='cost_currency_id', compute='_compute_cost', store=True, readonly=False,
|
||||
help="This cost overrides the employee's default timesheet cost in employee's HR Settings")
|
||||
cost_currency_id = fields.Many2one('res.currency', string="Cost Currency", related='employee_id.currency_id', readonly=True)
|
||||
is_cost_changed = fields.Boolean('Is Cost Manually Changed', compute='_compute_is_cost_changed', store=True)
|
||||
|
||||
_sql_constraints = [
|
||||
('uniqueness_employee', 'UNIQUE(project_id,employee_id)', 'An employee cannot be selected more than once in the mapping. Please remove duplicate(s) and try again.'),
|
||||
@@ -48,6 +52,17 @@ class ProjectProductEmployeeMap(models.Model):
|
||||
for line in self:
|
||||
line.currency_id = line.sale_line_id.currency_id if line.sale_line_id else False
|
||||
|
||||
@api.depends('employee_id.timesheet_cost')
|
||||
def _compute_cost(self):
|
||||
for map_entry in self:
|
||||
if not map_entry.is_cost_changed:
|
||||
map_entry.cost = map_entry.employee_id.timesheet_cost or 0.0
|
||||
|
||||
@api.depends('cost')
|
||||
def _compute_is_cost_changed(self):
|
||||
for map_entry in self:
|
||||
map_entry.is_cost_changed = map_entry.employee_id and map_entry.cost != map_entry.employee_id.timesheet_cost
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
res = super(ProjectProductEmployeeMap, self).create(values)
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
<field name="employee_id" options="{'no_create': True}"/>
|
||||
<field name="sale_line_id" attrs="{'required': True}" options="{'no_create': True}"/>
|
||||
<field name="price_unit" widget="monetary" force_save="1" options="{'currency_field': 'currency_id'}"/>
|
||||
<field name="cost"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="cost_currency_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
@@ -115,9 +117,6 @@
|
||||
<xpath expr="//header" position='inside'>
|
||||
<field name="allow_billable" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="//group/field[@name='sequence']" position="before">
|
||||
<field name="analytic_account_id" groups="base.group_no_one"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import models
|
||||
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
{
|
||||
'name': 'Service Margins in Sales Orders',
|
||||
'version': '1.0',
|
||||
'summary': 'Bridge module between Sales Margin and Sales Timesheet',
|
||||
'description': """
|
||||
Allows to compute accurate margin for Service sales.
|
||||
======================================================
|
||||
""",
|
||||
'category': 'Hidden',
|
||||
'depends': ['sale_margin', 'sale_timesheet'],
|
||||
'demo': [],
|
||||
'data': [],
|
||||
'auto_install': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import sale_order_line
|
||||
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo import api, models
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = "sale.order.line"
|
||||
|
||||
@api.depends('analytic_line_ids.amount', 'qty_delivered_method')
|
||||
def _compute_purchase_price(self):
|
||||
timesheet_sols = self.filtered(
|
||||
lambda sol: sol.qty_delivered_method == 'timesheet' and not sol.product_id.standard_price
|
||||
)
|
||||
super(SaleOrderLine, self - timesheet_sols)._compute_purchase_price()
|
||||
if timesheet_sols:
|
||||
group_amount = self.env['account.analytic.line'].read_group(
|
||||
[('so_line', 'in', timesheet_sols.ids), ('project_id', '!=', False)],
|
||||
['so_line', 'amount:sum', 'unit_amount:sum'],
|
||||
['so_line'])
|
||||
mapped_sol_timesheet_amount = {
|
||||
amount['so_line'][0]: -amount['amount'] / amount['unit_amount']
|
||||
for amount in group_amount
|
||||
}
|
||||
for line in timesheet_sols:
|
||||
line = line.with_company(line.company_id)
|
||||
product_cost = mapped_sol_timesheet_amount.get(line.id, line.product_id.standard_price)
|
||||
if line.product_id.uom_id != line.company_id.project_time_mode_id and\
|
||||
line.product_id.uom_id.category_id.id == line.company_id.project_time_mode_id.category_id.id:
|
||||
product_cost = line.company_id.project_time_mode_id._compute_quantity(
|
||||
product_cost,
|
||||
line.product_id.uom_id
|
||||
)
|
||||
line.purchase_price = line._convert_price(product_cost, line.product_id.uom_id)
|
||||
Reference in New Issue
Block a user