Files
enterprise-addons/sale_renting/models/product_template.py
T

226 lines
9.1 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from math import ceil
from odoo import api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import format_amount
class ProductTemplate(models.Model):
_inherit = 'product.template'
rent_ok = fields.Boolean(
string="Rental",
default=lambda self: bool(self.env.context.get('in_rental_app')),
help="Allow renting of this product.")
qty_in_rent = fields.Float("Quantity currently in rent", compute='_get_qty_in_rent')
product_pricing_ids = fields.One2many(
comodel_name='product.pricing',
inverse_name='product_template_id',
string="Custom Pricings",
bypass_search_access=True,
copy=False,
domain=['|', ('pricelist_id', '=', False), ('pricelist_id.active', '=', True)],
)
display_price = fields.Char(
string="Rental price",
compute='_compute_display_price',
help="First rental pricing of the product",
)
# Delays pricing
extra_hourly = fields.Float("Hourly Fine", help="Fine per hour late", company_dependent=True)
extra_daily = fields.Float("Daily Fine", help="Fine per day late", company_dependent=True)
def _compute_display_price(self):
rental_products = self.filtered('rent_ok')
rental_priced_products = rental_products.filtered('product_pricing_ids')
(self - rental_products).display_price = ""
for product in (rental_products - rental_priced_products):
# No rental pricing defined, fallback on list price
product.display_price = self.env._(
"%(amount)s (fixed)",
amount=format_amount(self.env, product.list_price, product.currency_id),
)
for product in rental_priced_products:
product.display_price = product.product_pricing_ids[0].description
def _get_qty_in_rent(self):
rentable = self.filtered('rent_ok')
not_rentable = self - rentable
not_rentable.update({'qty_in_rent': 0.0})
for template in rentable:
template.qty_in_rent = sum(template.mapped('product_variant_ids.qty_in_rent'))
@api.constrains('type', 'combo_ids', 'rent_ok')
def _check_rental_combo_ids(self):
for template in self:
if (
template.type == 'combo'
and template.rent_ok
and any(
not product.rent_ok for product in template.combo_ids.combo_item_ids.product_id
)
):
raise ValidationError(
self.env._("A rental combo product can only contain rental products.")
)
@api.model
def _get_incompatible_types(self):
return ['rent_ok'] + super()._get_incompatible_types()
def action_view_rentals(self):
"""Access Schedule view of rental order lines, filtered on variants of the current
templates."""
return self.product_variant_ids.action_view_rentals()
@api.depends('rent_ok')
@api.depends_context('show_rental_tag')
def _compute_display_name(self):
super()._compute_display_name()
if not self.env.context.get('show_rental_tag'):
return
for template in self:
if template.rent_ok:
template.display_name = self.env._("%s (Rental)", template.display_name)
def _get_best_pricing_rule(self, product=False, start_date=False, end_date=False, **kwargs):
""" Return the best pricing rule for the given duration.
:param ProductProduct product: a product recordset (containing at most one record)
:param datetime start_date: start date of leasing period
:param datetime end_date: end date of leasing period
:return: least expensive pricing rule for given duration
"""
self.ensure_one()
best_pricing_rule = self.env['product.pricing']
if not self.product_pricing_ids or not (start_date and end_date):
return best_pricing_rule
pricelist = kwargs.get('pricelist', self.env['product.pricelist'])
currency = kwargs.get('currency', self.currency_id)
company = kwargs.get('company', self.env.company)
duration_dict = self.env['product.pricing']._compute_duration_vals(start_date, end_date)
min_price = float("inf") # positive infinity
available_pricings = self.env['product.pricing']._get_suitable_pricings(
product or self, pricelist=pricelist
)
for pricing in available_pricings:
unit = pricing.recurrence_id.unit
price = pricing._compute_price(duration_dict[unit], unit)
if pricing.currency_id != currency:
price = pricing.currency_id._convert(
from_amount=price,
to_currency=currency,
company=company,
date=fields.Date.today(),
)
if price < min_price:
min_price, best_pricing_rule = price, pricing
return best_pricing_rule
def _get_contextual_price(self, product=None):
self.ensure_one()
if not (product or self).rent_ok:
return super()._get_contextual_price(product=product)
pricelist = self._get_contextual_pricelist()
quantity = self.env.context.get('quantity', 1.0)
uom = self.env['uom.uom'].browse(self.env.context.get('uom'))
date = self.env.context.get('date')
start_date = self.env.context.get('start_date')
end_date = self.env.context.get('end_date')
return pricelist._get_product_price(
product or self, quantity, uom=uom, date=date, start_date=start_date, end_date=end_date
)
def copy(self, default=None):
copied_tmpls = self.env['product.template']
for record in self:
copied_tmpl = super(ProductTemplate, record).copy(default)
copied_tmpls += copied_tmpl
for pricing in record.product_pricing_ids:
copied_variant_ids = []
for product in pricing.product_variant_ids:
pav_ids = product\
.product_template_variant_value_ids\
.product_attribute_value_id\
.ids
copied_variant_ids.extend(
copied_tmpl.product_variant_ids.filtered(
lambda p: p
.product_template_variant_value_ids
.product_attribute_value_id
.ids == pav_ids
).ids
)
pricing.copy({
'product_template_id': copied_tmpl.id,
'product_variant_ids': copied_variant_ids,
})
return copied_tmpls
@api.model
def _get_additional_configurator_data(
self,
product_or_template,
date,
currency,
pricelist,
*,
start_date=None,
end_date=None,
**kwargs,
):
""" Override of `sale` to append rental data.
:param product.product|product.template product_or_template: The product for which to get
additional data.
:param datetime date: The date to use to compute prices.
:param res.currency currency: The currency to use to compute prices.
:param product.pricelist pricelist: The pricelist to use to compute prices.
:param datetime|None start_date: The rental start date, to compute the rental duration.
:param datetime|None end_date: The rental end date, to compute the rental duration.
:param dict kwargs: Locally unused data passed to `super`.
:rtype: dict
:return: A dict containing additional data about the specified product.
"""
data = super()._get_additional_configurator_data(
product_or_template,
date,
currency,
pricelist,
start_date=start_date,
end_date=end_date,
**kwargs,
)
if product_or_template.rent_ok and start_date and end_date:
pricing = product_or_template._get_best_pricing_rule(
start_date=start_date, end_date=end_date, pricelist=pricelist, currency=currency
)
if pricing:
rental_duration = self.env['product.pricing']._compute_duration_vals(
start_date, end_date
)[pricing.recurrence_id.unit]
# Some locales might swap the duration and the unit, so we need to use the
# translation function.
duration, label = pricing.recurrence_id._get_converted_duration_and_label(
rental_duration
)
data['price_info'] = self.env._(
"/ %(duration)s %(unit)s",
duration=ceil(duration),
unit=label,
)
return data
def _has_multiple_uoms(self):
# multi-uoms doesn't work with rental (for now)
if self.rent_ok:
return False
return super()._has_multiple_uoms()