1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/website_sale_renting/models/sale_order.py
2024-12-10 09:04:09 +07:00

189 lines
8.3 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import timedelta
from pytz import timezone, UTC
from odoo import _, fields, models
from odoo.exceptions import ValidationError
from odoo.http import request
class SaleOrder(models.Model):
_inherit = 'sale.order'
def _is_cart_ready(self):
"""Whether the cart is valid and can be confirmed (and paid for)
:rtype: bool
"""
res = super()._is_cart_ready()
return res and (not self.is_rental_order or self._available_dates_for_renting())
def _check_cart_is_ready_to_be_paid(self):
self.ensure_one()
if not self._available_dates_for_renting():
raise ValidationError(_(
"Some of your rental products cannot be rented during the selected period and your"
" cart must be updated. We're sorry for the inconvenience."
))
return super()._check_cart_is_ready_to_be_paid()
def _verify_updated_quantity(self, order_line, product_id, new_qty, **kwargs):
new_qty, warning = super()._verify_updated_quantity(
order_line, product_id, new_qty, **kwargs
)
product = self.env['product.product'].browse(product_id)
if new_qty > 0 and product.rent_ok and not self._is_valid_renting_dates():
self.shop_warning = self._build_warning_renting(product)
return 0, self.shop_warning
return new_qty, warning
def _get_localized_renting_dates(self):
""" Return the rental dates localized in the user's timezone.
:return: The localized rental dates.
:rtype: tuple[Datetime, Datetime]
"""
self.ensure_one()
start_dt = self.rental_start_date
return_dt = self.rental_return_date
client_tz = UTC
if request and request.is_frontend and request.httprequest.cookies.get('tz'):
client_tz = timezone(request.httprequest.cookies['tz'])
elif self.env.user.tz:
client_tz = timezone(self.env.user.tz)
start_dt = UTC.localize(start_dt).astimezone(client_tz)
return_dt = UTC.localize(return_dt).astimezone(client_tz)
return start_dt, return_dt
def _is_valid_renting_dates(self):
""" Check if the pickup and return dates are valid.
:return: Whether the pickup and return dates are valid.
:rtype: bool
"""
self.ensure_one()
if not self.has_rented_products:
return True
days_forbidden = self.company_id._get_renting_forbidden_days()
# renting dates are in UTC, we need to convert them to the client's timezone
# to check the day of the week correctly or we might get a day off
start_dt, return_dt = self._get_localized_renting_dates()
return (
# 15 minutes of allowed time between adding the product to cart and paying it.
self.rental_start_date >= fields.Datetime.now() - timedelta(minutes=15)
and start_dt.isoweekday() not in days_forbidden
and return_dt.isoweekday() not in days_forbidden
and self._get_renting_duration() >= self.company_id.renting_minimal_time_duration
)
def _cart_update_order_line(self, *args, start_date=None, end_date=None, **kwargs):
"""Override to update rental order fields on the cart after line update."""
has_rental_dates = self.rental_start_date and self.rental_return_date
if not has_rental_dates and (start_date and end_date):
self.write({
'rental_start_date': start_date,
'rental_return_date': end_date,
})
has_rental_dates = True
# `in_rental_app` context makes sure rentable products added in cart becomes `is_rental`
# cart lines
self_ctx = self.with_context(in_rental_app=True)
res = super(SaleOrder, self_ctx)._cart_update_order_line(*args, **kwargs)
if self.is_rental_order and not self.has_rented_products:
self.write({
'is_rental_order': False,
'rental_start_date': False,
'rental_return_date': False,
})
return res
def _build_warning_renting(self, product):
""" Build the renting warning on SO to warn user a product cannot be rented on that period.
Note: self.ensure_one()
:param ProductProduct product: The product concerned by the warning
"""
self.ensure_one()
company = self.company_id
days_forbidden = company._get_renting_forbidden_days()
localized_start_date, localized_return_date = self._get_localized_renting_dates()
pickup_forbidden = localized_start_date.isoweekday() in days_forbidden
return_forbidden = localized_return_date.isoweekday() in days_forbidden
message = _("""
Some of your rental products (%(product)s) cannot be rented during the
selected period and your cart must be updated. We're sorry for the
inconvenience.
""", product=product.name)
if self.rental_start_date < fields.Datetime.now():
message += _("""Your rental product cannot be pickedup in the past.""")
elif pickup_forbidden and return_forbidden:
message += _("""
Your rental product had invalid dates of pickup (%(start_date)s) and
return (%(end_date)s). Unfortunately, we do not process pickups nor
returns on those weekdays.
""", start_date=localized_start_date, end_date=localized_return_date)
elif pickup_forbidden:
message += _("""
Your rental product had invalid date of pickup (%(start_date)s).
Unfortunately, we do not process pickups on that weekday.
""", start_date=localized_start_date)
elif return_forbidden:
message += _("""
Your rental product had invalid date of return (%(end_date)s).
Unfortunately, we do not process returns on that weekday.
""", end_date=localized_return_date)
minimal_duration = company.renting_minimal_time_duration
if self._get_renting_duration() < minimal_duration:
message += _("""
Your rental duration was too short. Unfortunately, we do not process
rentals that last less than %(duration)s %(unit)s.
""", duration=minimal_duration, unit=company.renting_minimal_time_unit)
return message
def _get_renting_duration(self):
""" Return the renting rounded-up duration. """
return self.env['product.pricing']._compute_duration_vals(
self.rental_start_date, self.rental_return_date
)[self.company_id.renting_minimal_time_unit]
def _is_renting_possible_in_hours(self):
""" Whether all products in the cart can be rented in a period computed in hours. """
rental_order_lines = self.order_line.filtered('is_rental')
return all('hour' in line.product_id.product_pricing_ids.mapped('recurrence_id.unit')
for line in rental_order_lines)
def _cart_update_renting_period(self, start_date, end_date):
self.ensure_one()
current_start_date = self.rental_start_date
current_end_date = self.rental_return_date
self.write({
'rental_start_date': start_date,
'rental_return_date': end_date,
})
if not self._available_dates_for_renting():
# shop_warning can be set by stock if invalid dates
self.shop_warning = self.shop_warning or _("""
The new period is not valid for some products of your cart.
Your changes on the rental period are not taken into account.
""")
self.write({
'rental_start_date': current_start_date,
'rental_return_date': current_end_date,
})
else:
rental_lines = self.order_line.filtered('is_rental')
self.env.add_to_compute(rental_lines._fields['name'], rental_lines)
self._recompute_rental_prices()
rental_lines = self.order_line.filtered('is_rental')
rental_lines._compute_name()
def _available_dates_for_renting(self):
"""Hook to override with the stock availability"""
return self._is_valid_renting_dates()