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

211 lines
8.6 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from pytz import timezone, UTC
from odoo import _, api, fields, models
from odoo.fields import Command
from odoo.tools import format_datetime, format_time
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
# Stored because a product could have been rent_ok when added to the SO but then updated
is_rental = fields.Boolean(compute='_compute_is_rental', store=True, precompute=True, readonly=False, copy=True)
qty_returned = fields.Float("Returned", default=0.0, copy=False)
start_date = fields.Datetime(related='order_id.rental_start_date')
return_date = fields.Datetime(related='order_id.rental_return_date')
reservation_begin = fields.Datetime(
string="Pickup date - padding time", compute='_compute_reservation_begin', store=True)
is_product_rentable = fields.Boolean(related='product_id.rent_ok', depends=['product_id'])
@api.depends('order_id.rental_start_date')
def _compute_reservation_begin(self):
lines = self.filtered('is_rental')
for line in lines:
line.reservation_begin = line.order_id.rental_start_date
(self - lines).reservation_begin = None
@api.onchange('qty_delivered')
def _onchange_qty_delivered(self):
"""When picking up more than reserved, reserved qty is updated"""
if self.qty_delivered > self.product_uom_qty:
self.product_uom_qty = self.qty_delivered
@api.depends('is_rental')
def _compute_qty_delivered_method(self):
"""Allow modification of delivered qty without depending on stock moves."""
rental_lines = self.filtered('is_rental')
super(SaleOrderLine, self - rental_lines)._compute_qty_delivered_method()
rental_lines.qty_delivered_method = 'manual'
@api.depends('is_rental')
def _compute_name(self):
"""Override to add the compute dependency.
The custom name logic can be found below in _get_sale_order_line_multiline_description_sale.
"""
super()._compute_name()
@api.depends('product_id')
def _compute_is_rental(self):
for line in self:
line.is_rental = line.is_product_rentable and line.env.context.get('in_rental_app')
@api.depends('is_rental')
def _compute_product_updatable(self):
rental_lines = self.filtered('is_rental')
super(SaleOrderLine, self - rental_lines)._compute_product_updatable()
rental_lines.product_updatable = True
def _compute_pricelist_item_id(self):
"""Discard pricelist item computation for rental lines.
This will disable the standard discount computation because no pricelist rule was found.
"""
rental_lines = self.filtered('is_rental')
super(SaleOrderLine, self - rental_lines)._compute_pricelist_item_id()
rental_lines.pricelist_item_id = False
_sql_constraints = [
('rental_stock_coherence',
"CHECK(NOT is_rental OR qty_returned <= qty_delivered)",
"You cannot return more than what has been picked up."),
]
def _get_sale_order_line_multiline_description_sale(self):
"""Add Rental information to the SaleOrderLine name."""
res = super()._get_sale_order_line_multiline_description_sale()
if self.is_rental:
self.order_id._rental_set_dates()
res += self._get_rental_order_line_description()
return res
def _get_rental_order_line_description(self):
tz = self._get_tz()
start_date = self.order_id.rental_start_date
return_date = self.order_id.rental_return_date
env = self.with_context(use_babel=True).env
if start_date and return_date\
and start_date.replace(tzinfo=UTC).astimezone(timezone(tz)).date()\
== return_date.replace(tzinfo=UTC).astimezone(timezone(tz)).date():
# If return day is the same as pickup day, don't display return_date Y/M/D in description.
return_date_part = format_time(env, return_date, tz=tz, time_format=False)
else:
return_date_part = format_datetime(env, return_date, tz=tz, dt_format=False)
start_date_part = format_datetime(env, start_date, tz=tz, dt_format=False)
return _(
"\n%(from_date)s to %(to_date)s", from_date=start_date_part, to_date=return_date_part
)
def _use_template_name(self):
""" Avoid the template line description in order to add the rental period on the SOL. """
if self.is_rental:
return False
return super()._use_template_name()
def _generate_delay_line(self, qty_returned):
"""Generate a sale order line representing the delay cost due to the late return.
:param float qty_returned: returned quantity
"""
self.ensure_one()
self = self.with_company(self.company_id)
duration = fields.Datetime.now() - self.return_date
delay_price = self.product_id._compute_delay_price(duration)
if delay_price <= 0.0:
return
# migrate to a function on res_company get_extra_product?
delay_product = self.company_id.extra_product
if not delay_product:
delay_product = self.env['product.product'].with_context(active_test=False).search(
[('default_code', '=', 'RENTAL'), ('type', '=', 'service')], limit=1)
if not delay_product:
delay_product = self.env['product.product'].create({
"name": "Rental Delay Cost",
"standard_price": 0.0,
"type": 'service',
"default_code": "RENTAL",
"purchase_ok": False,
})
# Not set to inactive to allow users to put it back in the settings
# In case they removed it.
self.company_id.extra_product = delay_product
if not delay_product.active:
return
delay_price = self._convert_to_sol_currency(delay_price, self.product_id.currency_id)
order_line_vals = self._prepare_delay_line_vals(delay_product, delay_price * qty_returned)
self.order_id.write({
'order_line': [Command.create(order_line_vals)],
})
def _prepare_delay_line_vals(self, delay_product, delay_price):
"""Prepare values of delay line.
:param product.product delay_product: Product used for the delay_line
:param float delay_price: Price of the delay line
:return: sale.order.line creation values
:rtype dict:
"""
delay_line_description = self._get_delay_line_description()
return {
'name': delay_line_description,
'product_id': delay_product.id,
'product_uom_qty': 1,
'qty_delivered': 1,
'price_unit': delay_price,
}
def _get_delay_line_description(self):
# Shouldn't tz be taken from self.order_id.user_id.tz ?
tz = self._get_tz()
env = self.with_context(use_babel=True).env
expected_date = format_datetime(env, self.return_date, tz=tz, dt_format=False)
now = format_datetime(env, fields.Datetime.now(), tz=tz, dt_format=False)
return "%s\n%s\n%s" % (
self.product_id.name,
_("Expected: %(date)s", date=expected_date),
_("Returned: %(date)s", date=now),
)
def _get_tz(self):
return self.env.context.get('tz') or self.env.user.tz or 'UTC'
def _get_pricelist_price(self):
""" Custom price computation for rental lines.
The displayed price will only be the price given by the product.pricing rules matching the
given line information (product, period, pricelist, ...).
"""
self.ensure_one()
if self.is_rental:
self.order_id._rental_set_dates()
return self.order_id.pricelist_id._get_product_price(
self.product_id.with_context(**self._get_product_price_context()),
self.product_uom_qty or 1.0,
currency=self.currency_id,
uom=self.product_uom,
date=self.order_id.date_order or fields.Date.today(),
start_date=self.start_date,
end_date=self.return_date,
)
return super()._get_pricelist_price()
# === PRICE COMPUTING HOOKS === #
def _lines_without_price_recomputation(self):
""" Override to filter out rental lines and allow the recomputation for these SOL. """
res = super()._lines_without_price_recomputation()
return res.filtered(lambda l: not l.is_rental)