forked from Mapan/odoo17e
150 lines
6.5 KiB
Python
150 lines
6.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.osv import expression
|
|
|
|
|
|
class ProductProduct(models.Model):
|
|
_inherit = 'product.product'
|
|
|
|
def _compute_show_qty_status_button(self):
|
|
super()._compute_show_qty_status_button()
|
|
for product in self:
|
|
if product.rent_ok and not product.sale_ok:
|
|
product.show_forecasted_qty_status_button = False
|
|
|
|
@api.depends('type', 'rent_ok', 'qty_available', 'qty_in_rent')
|
|
@api.depends_context('sale_stock_renting_show_total_qty', 'allowed_company_ids')
|
|
def _compute_display_name(self):
|
|
super()._compute_display_name()
|
|
if self.env.context.get('sale_stock_renting_show_total_qty'):
|
|
storable_rental_products = self.filtered(
|
|
lambda product: product.rent_ok and product.type == 'product'
|
|
)
|
|
if not storable_rental_products:
|
|
return
|
|
|
|
# Rental/Stock qties only have to be computed on the current date to know the current
|
|
# total qty (in stock + in rental)
|
|
ctxt = {
|
|
'from_date': self.env.cr.now(),
|
|
'to_date': self.env.cr.now(),
|
|
}
|
|
|
|
# Generate new display_name results
|
|
for product in storable_rental_products.with_context(**ctxt):
|
|
total_qty = product.qty_available + product.qty_in_rent
|
|
if int(total_qty) == total_qty:
|
|
# Display as integer if float has no decimal value
|
|
total_qty = int(total_qty)
|
|
if total_qty in (0, 1):
|
|
product.display_name = _(
|
|
"%(product)s (%(qty)s item)",
|
|
product=product.display_name,
|
|
qty=total_qty,
|
|
)
|
|
else:
|
|
product.display_name = _(
|
|
"%(product)s (%(qty)s items)",
|
|
product=product.display_name,
|
|
qty=total_qty,
|
|
)
|
|
|
|
def _get_domain_locations_new(self, location_ids):
|
|
domain_quant, domain_move_in_loc, domain_move_out_loc = super()._get_domain_locations_new(location_ids)
|
|
if self.env.context.get('ignore_rental_returns'):
|
|
rental_loc_ids = self.env.companies.rental_loc_id.ids
|
|
domain_move_in_loc = expression.AND([domain_move_in_loc, [('location_id', 'not in', rental_loc_ids)]])
|
|
return domain_quant, domain_move_in_loc, domain_move_out_loc
|
|
|
|
def _get_qty_in_rent_domain(self):
|
|
"""Allow precising the warehouse_id to get qty currently in rent."""
|
|
if self.env.context.get('warehouse_id', False):
|
|
return super()._get_qty_in_rent_domain() + [
|
|
('order_id.warehouse_id', '=', int(self.env.context.get('warehouse_id')))
|
|
]
|
|
else:
|
|
return super()._get_qty_in_rent_domain()
|
|
|
|
def _get_unavailable_qty(self, fro, to=None, **kwargs):
|
|
"""Return max qty of self (unique) unavailable between fro and to.
|
|
|
|
Doesn't count already returned quantities.
|
|
:param datetime fro:
|
|
:param datetime to:
|
|
:param dict kwargs: search domain restrictions (ignored_soline_id, warehouse_id)
|
|
"""
|
|
def unavailable_qty(so_line):
|
|
return so_line.product_uom_qty - so_line.qty_returned
|
|
|
|
begins_during_period, ends_during_period, covers_period = self._get_active_rental_lines(fro, to, **kwargs)
|
|
active_lines_in_period = begins_during_period + ends_during_period
|
|
max_qty_rented = 0
|
|
|
|
# TODO is it more efficient to filter the records active in period
|
|
# or to make another search on all the sale order lines???
|
|
if active_lines_in_period:
|
|
for date in (begins_during_period.mapped('reservation_begin') + [fro]):
|
|
# If no soline in begins_during_period, we need to check at period beginning
|
|
# how much products are rented.
|
|
active_lines_at_date = active_lines_in_period.filtered(
|
|
lambda line: line.reservation_begin and line.reservation_begin <= date and line.return_date and line.return_date >= date)
|
|
qty_rented_at_date = sum(active_lines_at_date.mapped(unavailable_qty))
|
|
if qty_rented_at_date > max_qty_rented:
|
|
max_qty_rented = qty_rented_at_date
|
|
|
|
qty_always_in_rent_during_period = sum(covers_period.mapped(unavailable_qty)) if covers_period else 0
|
|
|
|
return max_qty_rented + qty_always_in_rent_during_period
|
|
|
|
def _get_active_rental_lines(self, fro, to, **kwargs):
|
|
self.ensure_one()
|
|
|
|
Reservation = self.env['sale.order.line']
|
|
domain = [
|
|
('is_rental', '=', True),
|
|
('product_id', '=', self.id),
|
|
('state', '=', 'sale'),
|
|
]
|
|
|
|
ignored_soline_id = kwargs.get('ignored_soline_id', False)
|
|
if ignored_soline_id:
|
|
domain += [('id', '!=', ignored_soline_id)]
|
|
|
|
warehouse_id = kwargs.get('warehouse_id', False)
|
|
if warehouse_id:
|
|
domain += [('order_id.warehouse_id', '=', warehouse_id)]
|
|
|
|
if not to or fro == to:
|
|
active_lines_at_time_fro = Reservation.search(domain + [
|
|
('reservation_begin', '<=', fro),
|
|
('return_date', '>=', fro)
|
|
])
|
|
return Reservation, Reservation, active_lines_at_time_fro
|
|
else:
|
|
begins_during_period = Reservation.search(domain + [
|
|
('reservation_begin', '>', fro),
|
|
('reservation_begin', '<', to)])
|
|
ends_during_period = Reservation.search(domain + [
|
|
('return_date', '>', fro),
|
|
('return_date', '<', to),
|
|
('id', 'not in', begins_during_period.ids)])
|
|
covers_period = Reservation.search(domain + [
|
|
('reservation_begin', '<=', fro),
|
|
('return_date', '>=', to)])
|
|
return begins_during_period, ends_during_period, covers_period
|
|
|
|
"""
|
|
Products with tracking (by serial number)
|
|
"""
|
|
|
|
def _get_unavailable_lots(self, fro=fields.Datetime.now(), to=None, **kwargs):
|
|
begins_during_period, ends_during_period, covers_period = self._get_active_rental_lines(fro, to, **kwargs)
|
|
return (begins_during_period + ends_during_period + covers_period).mapped('unavailable_lot_ids')
|
|
|
|
def action_view_rentals(self):
|
|
result = super().action_view_rentals()
|
|
result['context'].update({'sale_stock_renting_show_total_qty': 1})
|
|
return result
|