uniform model

This commit is contained in:
Benoît Vézina 2024-03-06 08:23:13 -05:00
parent 0c46514bc5
commit 8aa1dfdc4b
10 changed files with 209 additions and 190 deletions

View file

@ -34,6 +34,7 @@
],
"assets": {
"web.assets_backend": [
# BV: Commented out the following lines to avoid errors when installing the module.
# "bemade_mailcow_integration/static/src/js/mailcow.js",
# "bemade_mailcow_integration/static/src/xml/mailcow_templates.xml",
],

View file

@ -1 +1,2 @@
from . import sale_order
from . import sale_order_line

View file

@ -21,157 +21,3 @@ class SaleOrder(models.Model):
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="Our Margin"
)
margin_percent_actual = fields.Float(
compute="_compute_actual_margins",
groups="base.group_user",
string="Our 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,158 @@
from odoo import models, fields, api, _
from odoo.tools.float_utils import float_is_zero, float_compare
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="Our Margin"
)
margin_percent_actual = fields.Float(
compute="_compute_actual_margins",
groups="base.group_user",
string="Our 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

@ -17,22 +17,27 @@
# DEALINGS IN THE SOFTWARE.
#
{
'name': 'bemade_multiple_billing_contacts',
'name': 'Multiple Billing Contacts',
'version': '17.0.1.0.1',
'summary': 'Send invoices to multiple contacts by default.',
'description': """By default, newly created invoices add all invoice addresses for the given partner as
followers on the invoice. If billing contacts are set manually on the sales order, those billing
contacts are added as followers on the invoice instead.""",
'description': """
By default, newly created invoices add all invoice addresses for the given partner as
followers on the invoice. If billing contacts are set manually on the sales order, those billing
contacts are added as followers on the invoice instead.
""",
'category': 'Invoicing Management',
'author': 'Bemade Inc.',
'website': 'https://www.bemade.org',
'license': 'OPL-1',
'depends': ['sale',
'account',
'bemade_partner_root_ancestor',
],
'data': ['views/account_move_views.xml',
'views/res_partner_views.xml'],
'depends': [
'sale',
'account',
'bemade_partner_root_ancestor',
],
'data': [
'views/account_move_views.xml',
'views/res_partner_views.xml'
],
'demo': [],
'installable': True,
'auto_install': False,

View file

@ -4,11 +4,13 @@ from odoo import models, fields, api
class AccountMove(models.Model):
_inherit = 'account.move'
billing_contacts = fields.Many2many(comodel_name='res.partner',
string="Billing Contacts",
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True,)
billing_contacts = fields.Many2many(
comodel_name='res.partner',
string="Billing Contacts",
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True
)
@api.depends('line_ids.sale_line_ids.order_id', 'partner_id')
def _compute_billing_contacts(self):

View file

@ -4,12 +4,17 @@ from odoo import models, fields, api, _, Command
class Partner(models.Model):
_inherit = 'res.partner'
billing_contacts = fields.Many2many(string='Default Billing Contacts',
comodel_name='res.partner',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts')
potential_billing_contacts = fields.Many2many(comodel_name='res.partner',
compute='_compute_billing_contacts')
billing_contacts = fields.Many2many(
string='Default Billing Contacts',
comodel_name='res.partner',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts'
)
potential_billing_contacts = fields.Many2many(
comodel_name='res.partner',
compute='_compute_billing_contacts'
)
@api.depends('child_ids.type')
def _compute_billing_contacts(self):

View file

@ -4,11 +4,13 @@ from odoo import models, fields, api, _, Command
class SaleOrder(models.Model):
_inherit = 'sale.order'
billing_contacts = fields.Many2many(comodel_name='res.partner',
string='Billing Contacts',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True)
billing_contacts = fields.Many2many(
comodel_name='res.partner',
string='Billing Contacts',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True
)
@api.depends('partner_id')
def _compute_billing_contacts(self):

View file

@ -8,13 +8,13 @@
<field name="partner_to">{{object.id}}</field>
<field name="subject">Select Your Division at {{object.company_id.name}}</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial, sans-serif; color: #454748; width: 100%; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0"
style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial, sans-serif; color: #454748; width: 100%; border-collapse:separate;">
<tbody>
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<tbody>
<!-- HEADER -->
<tr>
@ -25,8 +25,8 @@
<!-- CONTENT -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tbody>
<tr>
<td valign="top" style="font-size: 13px;">
@ -55,8 +55,8 @@
<!-- FOOTER -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tbody>
<tr>
<td valign="middle" align="left">
@ -104,4 +104,3 @@
</record>
</data>
</odoo>

View file

@ -14,4 +14,4 @@
</t>
</template>
</data>
</odoo>
</odoo>