# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import models, _ class SaleOrder(models.Model): _inherit = 'sale.order' def _get_warehouse_available(self): self.ensure_one() warehouse = self.website_id._get_warehouse_available() if not warehouse and self.user_id and self.company_id: warehouse = self.user_id.with_company(self.company_id.id)._get_default_warehouse_id() if not warehouse: warehouse = self.env.user._get_default_warehouse_id() return warehouse def _compute_warehouse_id(self): website_orders = self.filtered('website_id') super(SaleOrder, self - website_orders)._compute_warehouse_id() for order in website_orders: order.warehouse_id = order._get_warehouse_available() def _verify_updated_quantity(self, order_line, product_id, new_qty, **kwargs): self.ensure_one() product = self.env['product.product'].browse(product_id) if product.type == 'product' and not product.allow_out_of_stock_order: product_qty_in_cart, available_qty = self._get_cart_and_free_qty( line=order_line, product=product, **kwargs ) old_qty = order_line.product_uom_qty if order_line else 0 added_qty = new_qty - old_qty total_cart_qty = product_qty_in_cart + added_qty if available_qty < total_cart_qty: allowed_line_qty = available_qty - (product_qty_in_cart - old_qty) if allowed_line_qty > 0: if order_line: order_line._set_shop_warning_stock(total_cart_qty, available_qty) else: self._set_shop_warning_stock(total_cart_qty, available_qty) else: # 0 or negative allowed_qty # if existing line: it will be deleted # if no existing line: no line will be created self.shop_warning = _( "Some products became unavailable and your cart has been updated. We're sorry for the inconvenience.") return allowed_line_qty, order_line.shop_warning or self.shop_warning return super()._verify_updated_quantity(order_line, product_id, new_qty, **kwargs) def _get_cart_and_free_qty(self, line=None, product=None, **kwargs): """ Get cart quantity and free quantity for given product or line's product. Note: self.ensure_one() :param SaleOrderLine line: The optional line :param ProductProduct product: The optional product """ self.ensure_one() if not line and not product: return 0, 0 cart_qty = sum( self._get_common_product_lines(line, product, **kwargs).mapped('product_uom_qty') ) free_qty = (product or line.product_id).with_context(warehouse=self.warehouse_id.id).free_qty return cart_qty, free_qty def _get_common_product_lines(self, line=None, product=None, **kwargs): """ Get the lines with the same product or line's product :param SaleOrderLine line: The optional line :param ProductProduct product: The optional product """ if not line and not product: return self.env['sale.order.line'] product = product or line.product_id return self.order_line.filtered(lambda l: l.product_id == product) def _set_shop_warning_stock(self, desired_qty, new_qty): self.ensure_one() self.shop_warning = _( 'You ask for %(desired_qty)s products but only %(new_qty)s is available', desired_qty=desired_qty, new_qty=new_qty ) return self.shop_warning def _get_cache_key_for_line(self, line): return line.product_id def _get_context_for_line(self, line): return { 'website_sale_stock_get_quantity': True, } def _filter_can_send_abandoned_cart_mail(self): """ Filter sale orders on their product availability. """ self = super()._filter_can_send_abandoned_cart_mail() combination_info_cache = {} def _are_all_product_available_for_purchase(sale_order): for line in sale_order.order_line: product = line.product_id if product.type != 'product': continue cache_key = self._get_cache_key_for_line(line) combination_info = combination_info_cache.get(cache_key) if not combination_info: combination_info = product.with_context(**self._get_context_for_line(line), website_id=sale_order.website_id.id)._get_combination_info_variant(add_qty=line.product_uom_qty) combination_info_cache[cache_key] = combination_info if not product.allow_out_of_stock_order and combination_info['free_qty'] == 0: return False return True # If none of the products in the checkout are available for purchase (empty inventory, for example), # then the email won't be sent. return self.filtered(_are_all_product_available_for_purchase)