Files
odoo_source/addons/delivery/models/delivery_grid.py
T
Ahmed Khalaf (ahkh) be58a41cbb [IMP] delivery: set weight for shipping rate
Previously, when adding shipment to sale order, the user did not know
the total weight of the order when getting rate of shipping method.
This commit shows the total order weight to the user with the ability to
set it to any value to get the rate with.

Taskid: 2797613
Part-of: odoo/odoo#96660
2022-12-15 10:31:12 +01:00

157 lines
7.1 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models, fields, api, _
from odoo.tools.safe_eval import safe_eval
from odoo.exceptions import UserError, ValidationError
class PriceRule(models.Model):
_name = "delivery.price.rule"
_description = "Delivery Price Rules"
_order = 'sequence, list_price, id'
@api.depends('variable', 'operator', 'max_value', 'list_base_price', 'list_price', 'variable_factor')
def _compute_name(self):
for rule in self:
name = 'if %s %s %.02f then' % (rule.variable, rule.operator, rule.max_value)
if rule.list_base_price and not rule.list_price:
name = '%s fixed price %.02f' % (name, rule.list_base_price)
elif rule.list_price and not rule.list_base_price:
name = '%s %.02f times %s' % (name, rule.list_price, rule.variable_factor)
else:
name = '%s fixed price %.02f plus %.02f times %s' % (name, rule.list_base_price, rule.list_price, rule.variable_factor)
rule.name = name
name = fields.Char(compute='_compute_name')
sequence = fields.Integer(required=True, default=10)
carrier_id = fields.Many2one('delivery.carrier', 'Carrier', required=True, ondelete='cascade')
variable = fields.Selection([('weight', 'Weight'), ('volume', 'Volume'), ('wv', 'Weight * Volume'), ('price', 'Price'), ('quantity', 'Quantity')], required=True, default='weight')
operator = fields.Selection([('==', '='), ('<=', '<='), ('<', '<'), ('>=', '>='), ('>', '>')], required=True, default='<=')
max_value = fields.Float('Maximum Value', required=True)
list_base_price = fields.Float(string='Sale Base Price', digits='Product Price', required=True, default=0.0)
list_price = fields.Float('Sale Price', digits='Product Price', required=True, default=0.0)
variable_factor = fields.Selection([('weight', 'Weight'), ('volume', 'Volume'), ('wv', 'Weight * Volume'), ('price', 'Price'), ('quantity', 'Quantity')], 'Variable Factor', required=True, default='weight')
class ProviderGrid(models.Model):
_inherit = 'delivery.carrier'
delivery_type = fields.Selection(selection_add=[
('base_on_rule', 'Based on Rules'),
], ondelete={'base_on_rule': lambda recs: recs.write({
'delivery_type': 'fixed', 'fixed_price': 0,
})})
price_rule_ids = fields.One2many('delivery.price.rule', 'carrier_id', 'Pricing Rules', copy=True)
def base_on_rule_rate_shipment(self, order):
carrier = self._match_address(order.partner_shipping_id)
if not carrier:
return {'success': False,
'price': 0.0,
'error_message': _('Error: this delivery method is not available for this address.'),
'warning_message': False}
try:
price_unit = self._get_price_available(order)
except UserError as e:
return {'success': False,
'price': 0.0,
'error_message': e.args[0],
'warning_message': False}
price_unit = self._compute_currency(order, price_unit, 'company_to_pricelist')
return {'success': True,
'price': price_unit,
'error_message': False,
'warning_message': False}
def _get_conversion_currencies(self, order, conversion):
if conversion == 'company_to_pricelist':
from_currency, to_currency = order.company_id.currency_id, order.pricelist_id.currency_id
elif conversion == 'pricelist_to_company':
from_currency, to_currency = order.currency_id, order.company_id.currency_id
return from_currency, to_currency
def _compute_currency(self, order, price, conversion):
from_currency, to_currency = self._get_conversion_currencies(order, conversion)
if from_currency.id == to_currency.id:
return price
return from_currency._convert(price, to_currency, order.company_id, order.date_order or fields.Date.today())
def _get_price_available(self, order):
self.ensure_one()
self = self.sudo()
order = order.sudo()
total = weight = volume = quantity = 0
total_delivery = 0.0
for line in order.order_line:
if line.state == 'cancel':
continue
if line.is_delivery:
total_delivery += line.price_total
if not line.product_id or line.is_delivery:
continue
if line.product_id.type == "service":
continue
qty = line.product_uom._compute_quantity(line.product_uom_qty, line.product_id.uom_id)
weight += (line.product_id.weight or 0.0) * qty
volume += (line.product_id.volume or 0.0) * qty
quantity += qty
total = (order.amount_total or 0.0) - total_delivery
total = self._compute_currency(order, total, 'pricelist_to_company')
# weight is either,
# 1- weight chosen by user in choose.delivery.carrier wizard passed by context
# 2- saved weight to use on sale order
# 3- total order line weight as fallback
weight = self.env.context.get('order_weight') or order.shipping_weight or weight
return self._get_price_from_picking(total, weight, volume, quantity)
def _get_price_dict(self, total, weight, volume, quantity):
'''Hook allowing to retrieve dict to be used in _get_price_from_picking() function.
Hook to be overridden when we need to add some field to product and use it in variable factor from price rules. '''
return {
'price': total,
'volume': volume,
'weight': weight,
'wv': volume * weight,
'quantity': quantity
}
def _get_price_from_picking(self, total, weight, volume, quantity):
price = 0.0
criteria_found = False
price_dict = self._get_price_dict(total, weight, volume, quantity)
if self.free_over and total >= self.amount:
return 0
for line in self.price_rule_ids:
test = safe_eval(line.variable + line.operator + str(line.max_value), price_dict)
if test:
price = line.list_base_price + line.list_price * price_dict[line.variable_factor]
criteria_found = True
break
if not criteria_found:
raise UserError(_("No price rule matching this order; delivery cost cannot be computed."))
return price
def base_on_rule_send_shipping(self, pickings):
res = []
for p in pickings:
carrier = self._match_address(p.partner_id)
if not carrier:
raise ValidationError(_('There is no matching delivery rule.'))
res = res + [{'exact_price': p.carrier_id._get_price_available(p.sale_id) if p.sale_id else 0.0, # TODO cleanme
'tracking_number': False}]
return res
def base_on_rule_get_tracking_link(self, picking):
return False
def base_on_rule_cancel_shipment(self, pickings):
raise NotImplementedError()