Merge branch '15.0' of github.com:Dur-Pro/bemade-addons into 15.0

This commit is contained in:
Benoît Vézina 2023-11-01 12:10:03 -04:00
commit 721d271280
7 changed files with 353 additions and 0 deletions

View file

@ -0,0 +1 @@
from . import models

View 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,
}

View 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 ""
#. 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 ""

View 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"

View file

@ -0,0 +1 @@
from . import sale_order

View 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

View 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>