Merge branch '15.0' of github.com:Dur-Pro/bemade-addons into 15.0
This commit is contained in:
commit
721d271280
7 changed files with 353 additions and 0 deletions
1
bemade_margin_vendor_pricelist/__init__.py
Normal file
1
bemade_margin_vendor_pricelist/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import models
|
||||
31
bemade_margin_vendor_pricelist/__manifest__.py
Normal file
31
bemade_margin_vendor_pricelist/__manifest__.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#
|
||||
# Bemade Inc.
|
||||
#
|
||||
# Copyright (C) September 2023 Bemade Inc. (<https://www.bemade.org>).
|
||||
# Author: Marc Durepos (Contact : marc@bemade.org)
|
||||
#
|
||||
# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1)
|
||||
# It is forbidden to publish, distribute, sublicense, or sell copies of the Software
|
||||
# or modified copies of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
# DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
{
|
||||
'name': 'Sales Margin on Vendor Price',
|
||||
'version': '15.0.0.0.1',
|
||||
'summary': 'Enables calculation of sales margins based on vendor pricelists.',
|
||||
'description': """Adds a new method for calculating """,
|
||||
'author': 'Bemade Inc.',
|
||||
'website': 'https://www.bemade.org',
|
||||
'license': 'OPL-1',
|
||||
'depends': ['sale_stock_margin'],
|
||||
'data': ['views/sale_order.xml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * bemade_margin_vendor_pricelist
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-10-19 15:08+0000\n"
|
||||
"PO-Revision-Date: 2023-10-19 15:08+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: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order__margin_actual
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_actual
|
||||
msgid "Margin"
|
||||
msgstr ""
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order__margin_percent_actual
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_percent_actual
|
||||
msgid "Margin (%)"
|
||||
msgstr ""
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_percent_vendor
|
||||
msgid "Margin (%) on Vendor Price"
|
||||
msgstr ""
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_vendor
|
||||
msgid "Margin on Vendor Price"
|
||||
msgstr ""
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__purchase_price_actual
|
||||
msgid "Purchase Price"
|
||||
msgstr ""
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model,name:bemade_margin_vendor_pricelist.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model,name:bemade_margin_vendor_pricelist.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr ""
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__purchase_price_vendor
|
||||
msgid "Vendor Price"
|
||||
msgstr ""
|
||||
58
bemade_margin_vendor_pricelist/i18n/fr.po
Normal file
58
bemade_margin_vendor_pricelist/i18n/fr.po
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# Translation of Odoo Server.
|
||||
# This file contains the translation of the following modules:
|
||||
# * bemade_margin_vendor_pricelist
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Odoo Server 15.0+e\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-10-19 15:08+0000\n"
|
||||
"PO-Revision-Date: 2023-10-19 15:08+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: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order__margin_actual
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_actual
|
||||
msgid "Margin"
|
||||
msgstr "Marge"
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order__margin_percent_actual
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_percent_actual
|
||||
msgid "Margin (%)"
|
||||
msgstr "Marge (%)"
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_percent_vendor
|
||||
msgid "Margin (%) on Vendor Price"
|
||||
msgstr "Marge (%) sur le prix du fournisseur"
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__margin_vendor
|
||||
msgid "Margin on Vendor Price"
|
||||
msgstr "Marge sur le prix du fournisseur"
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__purchase_price_actual
|
||||
msgid "Purchase Price"
|
||||
msgstr "Prix d'achat"
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model,name:bemade_margin_vendor_pricelist.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr "Commande de vente"
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model,name:bemade_margin_vendor_pricelist.model_sale_order_line
|
||||
msgid "Sales Order Line"
|
||||
msgstr "Article de commande de vente"
|
||||
|
||||
#. module: bemade_margin_vendor_pricelist
|
||||
#: model:ir.model.fields,field_description:bemade_margin_vendor_pricelist.field_sale_order_line__purchase_price_vendor
|
||||
msgid "Vendor Price"
|
||||
msgstr "Prix du fournisseur"
|
||||
1
bemade_margin_vendor_pricelist/models/__init__.py
Normal file
1
bemade_margin_vendor_pricelist/models/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import sale_order
|
||||
153
bemade_margin_vendor_pricelist/models/sale_order.py
Normal file
153
bemade_margin_vendor_pricelist/models/sale_order.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
from odoo import models, fields, api, _
|
||||
from odoo.tools.float_utils import float_is_zero, float_compare
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
margin_actual = fields.Monetary("Margin", compute='_compute_margin_actual',
|
||||
store=False)
|
||||
margin_percent_actual = fields.Float('Margin (%)', compute='_compute_margin_actual',
|
||||
store=False,
|
||||
group_operator='avg')
|
||||
|
||||
@api.depends('order_line.margin_actual', 'amount_untaxed')
|
||||
def _compute_margin_actual(self):
|
||||
for order in self:
|
||||
order.margin_actual = sum(order.order_line.mapped('margin_actual'))
|
||||
order.margin_percent_actual = order.amount_untaxed \
|
||||
and order.margin_actual / order.amount_untaxed
|
||||
|
||||
|
||||
class SaleOrderLine(models.Model):
|
||||
_inherit = 'sale.order.line'
|
||||
|
||||
purchase_price_vendor = fields.Float(compute='_compute_purchase_price_vendor',
|
||||
string="Vendor Price", groups="base.group_user",
|
||||
digits='Product Price')
|
||||
margin_percent_vendor = fields.Float(string='Margin (%) on Vendor Price',
|
||||
groups='base.group_user', group_operator='avg',
|
||||
compute='_compute_margin_vendor')
|
||||
margin_vendor = fields.Float(string='Margin on Vendor Price',
|
||||
groups='base.group_user', digits='Product Price',
|
||||
compute='_compute_margin_vendor')
|
||||
|
||||
purchase_price_actual = fields.Float(compute="_compute_actual_margins",
|
||||
digits='Product Price',
|
||||
groups="base.group_user",
|
||||
string="Purchase Price")
|
||||
margin_actual = fields.Float(compute="_compute_actual_margins",
|
||||
digits='Product Price',
|
||||
groups="base.group_user",
|
||||
string="Margin")
|
||||
margin_percent_actual = fields.Float(compute="_compute_actual_margins",
|
||||
groups="base.group_user",
|
||||
string="Margin (%)")
|
||||
|
||||
@api.depends('purchase_price', 'purchase_price_vendor', 'move_ids.product_id',
|
||||
'move_ids.product_id.qty_available', 'move_ids.state',
|
||||
'qty_to_deliver')
|
||||
def _compute_actual_margins(self):
|
||||
""" We want to use the margin based on average inventory valuation when the
|
||||
sale order line will be completely fulfilled (or has been fulfilled) from stock.
|
||||
For product not yet in stock we want to use the vendor price. We can also have
|
||||
blended calculations (partly on vendor price, partly on stock valuation). This
|
||||
occurs when an order has been or would be partially fulfilled from available
|
||||
stock.
|
||||
:return:
|
||||
"""
|
||||
non_product_lines = self.filtered(lambda r: not r.product_id)
|
||||
non_product_lines.purchase_price_actual = 0.0
|
||||
non_product_lines.margin_actual = 0.0
|
||||
non_product_lines.margin_percent_actual = 0.0
|
||||
for line in self - non_product_lines:
|
||||
stock_missing = line._determine_missing_stock()
|
||||
if float_is_zero(stock_missing,
|
||||
precision_rounding=line.product_uom.rounding):
|
||||
# everything is coming from stock, use inventory valuation
|
||||
line.purchase_price_actual = line.purchase_price
|
||||
elif float_compare(line.product_uom_qty, stock_missing,
|
||||
precision_rounding=line.product_uom.rounding) == 0:
|
||||
# everything is coming from the vendor, use vendor pricing
|
||||
line.purchase_price_actual = line.purchase_price_vendor
|
||||
else:
|
||||
# we have a mix, use blended pricing
|
||||
qty_from_stock = line.product_uom_qty - stock_missing
|
||||
line.purchase_price_actual = \
|
||||
(stock_missing * line.purchase_price_vendor
|
||||
+ qty_from_stock * line.purchase_price) \
|
||||
/ line.product_uom_qty
|
||||
line.margin_actual = line.price_subtotal - (
|
||||
line.purchase_price_actual * line.product_uom_qty)
|
||||
line.margin_percent_actual = line.price_subtotal \
|
||||
and line.margin_actual / line.price_subtotal
|
||||
|
||||
def _determine_missing_stock(self) -> float:
|
||||
""" Compute how much stock is missing to meet an order line's demand. In the
|
||||
case of a quotation line, available stock is checked as if the order were to be
|
||||
placed immediately.
|
||||
|
||||
:return: The quantity missing from available stock to fulfill the line, in
|
||||
the unit of measure matching self.product_uom.
|
||||
"""
|
||||
self.ensure_one()
|
||||
is_order = self.order_id.state in ('sale', 'done')
|
||||
if is_order and self.qty_to_deliver == 0:
|
||||
return 0
|
||||
elif is_order and self.qty_to_deliver > 0:
|
||||
reserved = sum([m.reserved_availability for m in self.move_ids])
|
||||
missing = self.qty_to_deliver - reserved
|
||||
if float_compare(missing, 0.0,
|
||||
precision_rounding=self.product_uom.rounding) == 1:
|
||||
# Not enough reserved, check stock
|
||||
missing = missing - self.product_id.qty_available
|
||||
if float_compare(missing, 0.0,
|
||||
precision_rounding=self.product_uom.rounding) == 1:
|
||||
# Missing some stock to meet demand, return the quantity
|
||||
return missing
|
||||
else:
|
||||
# Enough stock available to meet this line's demand
|
||||
return 0
|
||||
else:
|
||||
# Already have stock reserved
|
||||
return 0
|
||||
else:
|
||||
# This is a quotation, don't bother with stock reservations
|
||||
missing = self.product_uom_qty - self.product_id.qty_available
|
||||
if float_compare(missing, 0.0,
|
||||
precision_rounding=self.product_uom.rounding) == 1:
|
||||
return missing
|
||||
else:
|
||||
return 0
|
||||
|
||||
@api.depends('product_id', 'product_id.seller_ids',
|
||||
'product_id.seller_ids.price')
|
||||
def _compute_purchase_price_vendor(self):
|
||||
for line in self:
|
||||
product = line.product_id
|
||||
suppinfos = product.seller_ids.sorted('sequence')
|
||||
if not suppinfos:
|
||||
line.purchase_price_vendor = 0.0
|
||||
continue
|
||||
suppinfo = suppinfos[0]
|
||||
purch_currency = suppinfo.currency_id
|
||||
to_cur = line.currency_id or line.order_id.currency_id
|
||||
line.purchase_price_vendor = purch_currency._convert(
|
||||
from_amount=suppinfo.price,
|
||||
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 suppinfo.price else suppinfo.price
|
||||
|
||||
@api.depends('purchase_price_vendor')
|
||||
def _compute_margin_vendor(self):
|
||||
for line in self:
|
||||
if not line.price_unit or float_is_zero(line.price_unit):
|
||||
line.margin_vendor = 0
|
||||
line.margin_percent_vendor = 0
|
||||
continue
|
||||
unit_margin = line.price_unit - line.purchase_price_vendor
|
||||
line.margin_percent_vendor = unit_margin / line.price_unit
|
||||
|
||||
line.margin_vendor = unit_margin * line.product_uom_qty
|
||||
51
bemade_margin_vendor_pricelist/views/sale_order.xml
Normal file
51
bemade_margin_vendor_pricelist/views/sale_order.xml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="sale_margin_sale_order_inherit">
|
||||
<field name="name">sale.order.margin.view.form.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="margin" position="replace">
|
||||
<field name="margin_actual" class="oe_inline"/>
|
||||
</field>
|
||||
<label for="margin" position="replace">
|
||||
<label for="margin_actual" groups="base.group_user"/>
|
||||
</label>
|
||||
<field name="margin_percent" position="replace">
|
||||
<field name="margin_percent_actual" nolabel="1" class="oe_inline"
|
||||
widget="percentage" groups="base.group_user"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_margin_sale_order_line_inherit">
|
||||
<field name="name">sale.order.line.margin.view.form.inherit</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order_line"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="purchase_price" position="replace">
|
||||
<field name="purchase_price_actual" groups="base.group_user"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_margin_sale_order_line_form_inherit">
|
||||
<field name="name">sale.order.view.form</field>
|
||||
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order_line_form"/>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="purchase_price" position="replace">
|
||||
<field name="purchase_price_actual" optional="hide"/>
|
||||
</field>
|
||||
<field name="margin" position="replace">
|
||||
<field name="margin_actual" optional="hide"/>
|
||||
</field>
|
||||
<field name="margin_percent" position="replace">
|
||||
<field name="margin_percent_actual" optional="hide"
|
||||
widget="percentage" groups="base.group_user"/>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
<!-- For now, we don't override the pivot and graph views because our fields
|
||||
are not stored and this will cause performance issues. -->
|
||||
</data>
|
||||
</odoo>
|
||||
Loading…
Reference in a new issue