diff --git a/addons/sale_margin/models/sale_order.py b/addons/sale_margin/models/sale_order.py index 4749b0885eb..7f897968245 100644 --- a/addons/sale_margin/models/sale_order.py +++ b/addons/sale_margin/models/sale_order.py @@ -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" diff --git a/addons/sale_timesheet_margin/__init__.py b/addons/sale_timesheet_margin/__init__.py new file mode 100644 index 00000000000..dc5e6b693d1 --- /dev/null +++ b/addons/sale_timesheet_margin/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/sale_timesheet_margin/__manifest__.py b/addons/sale_timesheet_margin/__manifest__.py new file mode 100644 index 00000000000..c257457bf32 --- /dev/null +++ b/addons/sale_timesheet_margin/__manifest__.py @@ -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', +} diff --git a/addons/sale_timesheet_margin/models/__init__.py b/addons/sale_timesheet_margin/models/__init__.py new file mode 100644 index 00000000000..83c06ee1343 --- /dev/null +++ b/addons/sale_timesheet_margin/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import sale_order_line diff --git a/addons/sale_timesheet_margin/models/sale_order_line.py b/addons/sale_timesheet_margin/models/sale_order_line.py new file mode 100644 index 00000000000..4ab35913c9f --- /dev/null +++ b/addons/sale_timesheet_margin/models/sale_order_line.py @@ -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)