TL;DR: remember `osv` and `except_orm` ? You can forget about them. * Deprecated `except_orm` dropped. * `UserError` elevated as super type of all user-related errors. * Unused `DeferredException` dropped. * Unused `QWebException` dropped (real one is in `qweb.py`). * `MailDeliveryException` made a python exception. * `name` legacy exception attribute made an alias of the python standard `args[0]` attribute and deprecated. * `value` legacy exception attribute dropped. * `exception_type` RPC error response key dropped. * Deprecated `osv` module dropped. * `--osv-memory-age-limit` cli option made an alias of `--transient-age-limit` and deprecated. The `odoo.exceptions.Warning` have long been a deprecated alias to `UserError`. It is going to be removed in a future version but first we explicitly deprecate it with a warning. The `odoo.exceptions.DeferredException` was a very old internal exception, it has been removed without deprecation notice as it is never raised. The `odoo.exceptions.except_orm` has been a deprecated exception type with deprecation warning for 5 years, it has been removed in favor of UserError which becomes the super class of all user-related errors. The `odoo.base.models.ir_mail_server.MailDeliveryException` was inheriting `except_orm`. As it is not related to a user error but is more of a problem an admin much take care of, the exception has been made a Python error. The `exception_type` JSON key in RPC error responses was holding an hardcoded value derived from the exception type. Its usage has been dropped in favor of the `name` JSON key that holds the precise exception name. Again as it was hardly used in the source code (beside the crash manager) it has been dropped without deprecation warning. Since we are here trying to clean odoo custom exceptions, we are also deprecating the `name` exception attribute in favor of the more standard `args[0]` attribute. The `name` (along with `value`) were two attributes used to raise `except_orm` exceptions before the introduction of `UserError`, `AccessError` and related exceptions. The `name` attribute, at the time, was holding the exception type/title. Nowadays it contains the error message. The `value` attribute, at the time, was holding the error message. Nowadays it is no more used. The `osv` module contains very old deprecated aliases. There is no simple way to log a deprecation warning for osv, osv_memory and osv_abstract but as they have not been in use for ages, they have been removed too. To be consistent, the `--osv-memory-age-limit` cli option has been made a deprecated alias to the `--transient-age-limit`. closes odoo/odoo#45723 Task: 2187728 Related: odoo/enterprise#9162 Signed-off-by: Raphael Collet (rco) <rco@openerp.com>
126 lines
5.8 KiB
Python
126 lines
5.8 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 %s then' % (rule.variable, rule.operator, rule.max_value)
|
|
if rule.list_base_price and not rule.list_price:
|
|
name = '%s fixed price %s' % (name, rule.list_base_price)
|
|
elif rule.list_price and not rule.list_base_price:
|
|
name = '%s %s times %s' % (name, rule.list_price, rule.variable_factor)
|
|
else:
|
|
name = '%s fixed price %s plus %s 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}
|
|
if order.company_id.currency_id.id != order.pricelist_id.currency_id.id:
|
|
price_unit = order.company_id.currency_id._convert(
|
|
price_unit, order.pricelist_id.currency_id, order.company_id, order.date_order or fields.Date.today())
|
|
|
|
return {'success': True,
|
|
'price': price_unit,
|
|
'error_message': False,
|
|
'warning_message': False}
|
|
|
|
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
|
|
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 = order.currency_id._convert(
|
|
total, order.company_id.currency_id, order.company_id, order.date_order or fields.Date.today())
|
|
|
|
return self._get_price_from_picking(total, weight, volume, quantity)
|
|
|
|
def _get_price_from_picking(self, total, weight, volume, quantity):
|
|
price = 0.0
|
|
criteria_found = False
|
|
price_dict = {'price': total, 'volume': volume, 'weight': weight, 'wv': volume * weight, 'quantity': quantity}
|
|
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()
|