[MIG] sale_margin_no_onchange from v14 to v16

This commit is contained in:
Alexis de Lattre
2025-01-16 19:19:19 +01:00
parent 699ebd5893
commit 2ca1279cb5
7 changed files with 221 additions and 138 deletions

View File

@@ -1,25 +1,25 @@
# Copyright (C) 2015-2019 Akretion (http://www.akretion.com)
# Copyright 2015-2025 Akretion France (https://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Sale Margin No Onchange",
"version": "14.0.1.0.0",
"version": "16.0.1.0.0",
"category": "Sales",
"license": "AGPL-3",
"summary": "Copy standard price on sale order line and compute margins",
"description": """
This module copies the field *standard_price* of the product on the sale order line when the sale order line is created and then computes the margin of the sale order and the sale order line (in the currency of the quotation, in the currency of the company and the margin rate).
This module copies the cost of the product on the sale order line when the sale order line is created and it computes the margin amount (in the currency of the order and in the currency of the company) and the margin rate. It also computes the total margin of the sale order (in the currency of the order and in the currency of the company).
I decided to develop this module as an alternative to the OCA sale margin modules because I wanted a small and simple module. The module *account_invoice_margin*, available in the same Github repository, do the same thing on customer invoices.
I decided to develop this module as an alternative to the OCA sale margin modules because I wanted a small and simple module. The module *account_invoice_margin*, available in the same Github repository, does the same thing on customer invoices.
This module has been written by Alexis de Lattre from Akretion
<alexis.delattre@akretion.com>.
""",
"author": "Akretion",
"website": "http://www.akretion.com",
"website": "https://github.com/akretion/odoo-usability",
"depends": ["sale"],
"data": ["views/sale_view.xml"],
"installable": False,
"data": ["views/sale_order.xml", "views/sale_report.xml"],
"installable": True,
}

View File

@@ -0,0 +1,102 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * sale_margin_no_onchange
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-16 18:17+0000\n"
"PO-Revision-Date: 2025-01-16 18:17+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order__company_currency_id
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order_line__company_currency_id
msgid "Company Currency"
msgstr "Devise de la société"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_report__margin
#: model_terms:ir.ui.view,arch_db:sale_margin_no_onchange.view_order_form
msgid "Margin"
msgstr "Marge"
#. module: sale_margin_no_onchange
#: model_terms:ir.ui.view,arch_db:sale_margin_no_onchange.view_order_form
#: model_terms:ir.ui.view,arch_db:sale_margin_no_onchange.view_order_line_tree
msgid "Margin %"
msgstr "Marge %"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order_line__margin_rate
msgid "Margin Rate"
msgstr "Taux de marge"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order__margin_company_currency
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order_line__margin_company_currency
msgid "Margin in Company Currency"
msgstr "Marge dans la devise de la société"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order__margin_sale_currency
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order_line__margin_sale_currency
msgid "Margin in Sale Currency"
msgstr "Marge dans la devise de vente"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,help:sale_margin_no_onchange.field_sale_order_line__margin_rate
msgid "Margin rate in percentage of the sale price"
msgstr "Taux de marge en pourcentage du prix de vente"
#. module: sale_margin_no_onchange
#: model:ir.model,name:sale_margin_no_onchange.model_sale_report
msgid "Sales Analysis Report"
msgstr "Rapport d'analyse des ventes"
#. module: sale_margin_no_onchange
#: model:ir.model,name:sale_margin_no_onchange.model_sale_order
msgid "Sales Order"
msgstr "Bon de commande"
#. module: sale_margin_no_onchange
#: model:ir.model,name:sale_margin_no_onchange.model_sale_order_line
msgid "Sales Order Line"
msgstr "Ligne de commande"
#. module: sale_margin_no_onchange
#: model_terms:ir.ui.view,arch_db:sale_margin_no_onchange.view_order_form
msgid "Unit Cost"
msgstr "Coût unitaire"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order_line__standard_price_company_currency
msgid "Unit Cost in Company Currency"
msgstr "Coût unitaire dans la devise de la société"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,field_description:sale_margin_no_onchange.field_sale_order_line__standard_price_sale_currency
msgid "Unit Cost in Sale Currency"
msgstr "Coût unitaire dans la devise de vente"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,help:sale_margin_no_onchange.field_sale_order_line__standard_price_company_currency
msgid ""
"Unit cost in company currency in the unit of measure of the sale order line"
msgstr ""
"Coût unitaire dans la devise de la société dans l'unité de mesure de la "
"ligne de commande client"
#. module: sale_margin_no_onchange
#: model:ir.model.fields,help:sale_margin_no_onchange.field_sale_order_line__standard_price_sale_currency
msgid ""
"Unit cost in sale currency in the unit of measure of the sale order line"
msgstr ""
"Coût unitaire dans la devise de vente dans l'unité de mesure de la ligne de "
"commande client"

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2015-2019 Akretion (http://www.akretion.com)
# Copyright 2015-2025 Akretion France (https://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -14,33 +14,32 @@ class SaleOrderLine(models.Model):
related='order_id.company_id.currency_id',
store=True, string='Company Currency')
standard_price_company_currency = fields.Float(
string='Cost Price in Company Currency', readonly=True,
digits="Product Price",
help="Cost price in company currency in the unit of measure "
compute='_compute_margin', store=True, digits="Product Price",
string='Unit Cost in Company Currency',
help="Unit cost in company currency in the unit of measure "
"of the sale order line")
standard_price_sale_currency = fields.Float(
string='Cost Price in Sale Currency',
compute='_compute_margin', store=True,
digits="Product Price",
help="Cost price in sale currency in the unit of measure "
compute='_compute_margin', store=True, digits="Product Price",
string='Unit Cost in Sale Currency',
help="Unit cost in sale currency in the unit of measure "
"of the sale order line")
margin_sale_currency = fields.Monetary(
string='Margin in Sale Currency', store=True,
compute='_compute_margin', currency_field='currency_id')
compute='_compute_margin', store=True, currency_field='currency_id',
string='Margin in Sale Currency')
margin_company_currency = fields.Monetary(
string='Margin in Company Currency', store=True,
compute='_compute_margin', currency_field='company_currency_id')
compute='_compute_margin', store=True, currency_field='company_currency_id',
string='Margin in Company Currency')
margin_rate = fields.Float(
string="Margin Rate", store=True,
compute='_compute_margin',
digits=(16, 2), help="Margin rate in percentage of the sale price")
compute='_compute_margin', store=True, digits=(16, 2),
string="Margin Rate", help="Margin rate in percentage of the sale price")
@api.depends(
'standard_price_company_currency', 'order_id.pricelist_id.currency_id',
'order_id.date_order', 'product_uom_qty', 'price_subtotal',
'product_id', 'product_uom', 'display_type', 'product_uom_qty', 'price_subtotal',
'order_id.pricelist_id.currency_id', 'order_id.date_order',
'order_id.company_id')
def _compute_margin(self):
for line in self:
standard_price_comp_cur = 0.0
standard_price_sale_cur = 0.0
margin_sale_cur = 0.0
margin_comp_cur = 0.0
@@ -48,84 +47,52 @@ class SaleOrderLine(models.Model):
order_cur = line.order_id.pricelist_id.currency_id
company = line.order_id.company_id
company_cur = company.currency_id
if order_cur and company_cur:
if (
not line.display_type and
line.product_id and
line.product_uom and
order_cur and
company_cur):
standard_price_comp_cur = line.product_id.with_company(company.id).standard_price
if line.product_uom != line.product_id.uom_id:
standard_price_comp_cur = line.product_id.uom_id._compute_price(
standard_price_comp_cur, line.product_uom)
date = line.order_id.date_order
standard_price_sale_cur =\
company_cur._convert(
line.standard_price_company_currency, order_cur,
company, date)
standard_price_sale_cur = company_cur._convert(
standard_price_comp_cur, order_cur, company, date)
margin_sale_cur =\
line.price_subtotal\
- line.product_uom_qty * standard_price_sale_cur
- (line.product_uom_qty * standard_price_sale_cur)
margin_comp_cur = order_cur._convert(
margin_sale_cur, company_cur, company, date)
if line.price_subtotal:
margin_rate = 100 * margin_sale_cur / line.price_subtotal
line.standard_price_company_currency = standard_price_comp_cur
line.standard_price_sale_currency = standard_price_sale_cur
line.margin_sale_currency = margin_sale_cur
line.margin_company_currency = margin_comp_cur
line.margin_rate = margin_rate
# We want to copy standard_price on sale order line
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get('product_id'):
pp = self.env['product.product'].browse(vals['product_id'])
std_price = pp.standard_price
sale_uom_id = vals.get('product_uom')
if sale_uom_id and sale_uom_id != pp.uom_id.id:
sale_uom = self.env['uom.uom'].browse(sale_uom_id)
# convert from product UoM to sale UoM
std_price = pp.uom_id._compute_price(
pp.standard_price, sale_uom)
vals['standard_price_company_currency'] = std_price
return super().create(vals_list)
def write(self, vals):
if not vals:
vals = {}
if 'product_id' in vals or 'product_uom' in vals:
for sol in self:
# product_uom and product_id are required fields
if 'product_id' in vals:
pp = self.env['product.product'].browse(vals['product_id'])
else:
pp = sol.product_id
if 'product_uom' in vals:
sale_uom = self.env['uom.uom'].browse(
vals['product_uom'])
else:
sale_uom = sol.product_uom
std_price = pp.standard_price
if sale_uom != pp.uom_id:
std_price = pp.uom_id._compute_price(std_price, sale_uom)
sol.write({'standard_price_company_currency': std_price})
return super().write(vals)
class SaleOrder(models.Model):
_inherit = 'sale.order'
# Also defined in bi_sale_company_currency
company_currency_id = fields.Many2one(
related='company_id.currency_id', store=True,
string="Company Currency")
related='company_id.currency_id', store=True, string="Company Currency")
margin_sale_currency = fields.Monetary(
string='Margin in Sale Currency',
currency_field='currency_id',
compute='_compute_margin', store=True)
compute='_compute_margin', store=True, currency_field='currency_id',
string='Margin in Sale Currency')
margin_company_currency = fields.Monetary(
string='Margin in Company Currency',
currency_field='company_currency_id',
compute='_compute_margin', store=True)
compute='_compute_margin', store=True, currency_field='company_currency_id',
string='Margin in Company Currency')
@api.depends(
'order_line.margin_sale_currency',
'order_line.margin_company_currency')
def _compute_margin(self):
rg_res = self.env['sale.order.line'].read_group(
[('order_id', 'in', self.ids)],
[('order_id', 'in', self.ids), ('display_type', '=', False)],
['order_id', 'margin_sale_currency:sum', 'margin_company_currency:sum'],
['order_id'])
mapped_data = dict([

View File

@@ -1,4 +1,4 @@
# Copyright 2018-2019 Akretion (http://www.akretion.com)
# Copyright 2018-2025 Akretion France (https://www.akretion.com)
# @author Alexis de Lattre <alexis.delattre@akretion.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
@@ -10,10 +10,14 @@ class SaleReport(models.Model):
margin = fields.Float(string='Margin', readonly=True)
def _query(self, with_clause='', fields={}, groupby='', from_clause=''):
fields['margin_company_currency'] =\
", SUM(l.margin_company_currency) AS margin"
res = super(SaleReport, self)._query(
with_clause=with_clause, fields=fields, groupby=groupby,
from_clause=from_clause)
def _select_additional_fields(self):
res = super()._select_additional_fields()
margin_sum = f"""
CASE WHEN l.product_id IS NOT NULL THEN SUM(l.margin_sale_currency
/ {self._case_value_or_one('s.currency_rate')}
* {self._case_value_or_one('currency_table.rate')}
) ELSE 0
END
"""
res["margin"] = margin_sum
return res

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2015-2025 Akretion France (https://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_order_form" model="ir.ui.view">
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<group name="sales_person" position="inside">
<field name="margin_sale_currency" string="Margin"/>
<field name="margin_company_currency" groups="base.group_multi_currency"/>
<field name="company_currency_id" invisible="1"/>
</group>
<xpath expr="//field[@name='order_line']/tree" position="inside">
<field name="standard_price_sale_currency" optional="hide" string="Unit Cost"/>
<field name="margin_sale_currency" optional="hide" string="Margin"/>
<field name="margin_rate" optional="hide" string="Margin %"/>
<field name="company_currency_id" invisible="1"/>
</xpath>
</field>
</record>
<record id="view_order_line_tree" model="ir.ui.view">
<field name="model">sale.order.line</field>
<field name="inherit_id" ref="sale.view_order_line_tree" />
<field name="arch" type="xml">
<xpath expr="/tree" position="inside">
<field name="standard_price_company_currency" optional="hide" groups="base.group_multi_currency"/>
<field name="standard_price_sale_currency" optional="hide"/>
<field name="margin_company_currency" optional="hide" groups="base.group_multi_currency"/>
<field name="margin_sale_currency" optional="hide"/>
<field name="margin_rate" optional="hide" string="Margin %"/>
<field name="company_currency_id" invisible="1"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2025 Akretion France (https://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="sale_report_view_tree" model="ir.ui.view">
<field name="model">sale.report</field>
<field name="inherit_id" ref="sale.sale_report_view_tree"/>
<field name="arch" type="xml">
<field name="price_total" position="after">
<field name="margin" sum="1" optional="hide"/>
</field>
</field>
</record>
</odoo>

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015-2019 Akretion (http://www.akretion.com/)
@author: Alexis de Lattre <alexis.delattre@akretion.com>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
-->
<odoo>
<record id="view_order_form" model="ir.ui.view">
<field name="name">margin.sale.order.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<group name="technical" position="inside">
<field name="margin_sale_currency" string="Margin"
groups="account.group_account_user"/>
<field name="margin_company_currency"
groups="account.group_account_user"/>
<field name="company_currency_id" invisible="1"/>
</group>
<xpath expr="//field[@name='order_line']/form/group/group//field[@name='analytic_tag_ids']/.." position="after">
<group>
<field name="standard_price_sale_currency" groups="base.group_no_one"/>
<field name="standard_price_company_currency" groups="base.group_no_one"/>
<field name="margin_sale_currency" groups="base.group_no_one"/>
<field name="margin_company_currency" groups="base.group_no_one"/>
<label for="margin_rate"/>
<div name="margin_rate">
<field name="margin_rate" groups="base.group_no_one" class="oe_inline"/> %
</div>
<field name="company_currency_id" invisible="1"/>
<field name="currency_id" invisible="1"/>
</group>
</xpath>
</field>
</record>
<record id="view_order_line_tree" model="ir.ui.view">
<field name="name">margin.sale.order.line.tree</field>
<field name="model">sale.order.line</field>
<field name="inherit_id" ref="sale.view_order_line_tree" />
<field name="arch" type="xml">
<field name="price_subtotal" position="after">
<field name="standard_price_company_currency" groups="account.group_account_user"/>
<field name="margin_company_currency" groups="account.group_account_user"/>
<field name="margin_rate" groups="account.group_account_user"/>
<field name="company_currency_id" invisible="1"/>
</field>
</field>
</record>
</odoo>