def test_multivariable_variation(): parent = create_product("SuperComplexVarParent") color_var = ProductVariationVariable.objects.create(product=parent, identifier="color") size_var = ProductVariationVariable.objects.create(product=parent, identifier="size") for color in ("yellow", "blue", "brown"): ProductVariationVariableValue.objects.create(variable=color_var, identifier=color) for size in ("small", "medium", "large", "huge"): ProductVariationVariableValue.objects.create(variable=size_var, identifier=size) combinations = list(parent.get_all_available_combinations()) assert len(combinations) == (3 * 4) for combo in combinations: assert not combo["result_product_pk"] # Elide a combination (yellow/small) for testing: if combo["variable_to_value"][color_var].identifier == "yellow" and combo["variable_to_value"][size_var].identifier == "small": continue child = create_product("xyz-%s" % combo["sku_part"]) child.link_to_parent(parent, combo["variable_to_value"]) assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT # Elided product should not yield a result yellow_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="yellow") small_size_value = ProductVariationVariableValue.objects.get(variable=size_var, identifier="small") assert not ProductVariationResult.resolve(parent, {color_var: yellow_color_value, size_var: small_size_value}) # Anything else should brown_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="brown") result1 = ProductVariationResult.resolve(parent, {color_var: brown_color_value, size_var: small_size_value}) result2 = ProductVariationResult.resolve(parent, {color_var.pk: brown_color_value.pk, size_var.pk: small_size_value.pk}) assert result1 and result2 assert result1.pk == result2.pk assert len(parent.get_available_variation_results()) == (3 * 4 - 1)
def get_object(self, queryset=None): product = super(ProductPriceView, self).get_object(queryset) vars = self.get_variation_variables() if vars: return ProductVariationResult.resolve(product, vars) else: return product
def test_variable_variation(): parent = create_product("ComplexVarParent") sizes_and_children = [("%sL" % ("X" * x), create_product("ComplexVarChild-%d" % x)) for x in range(4)] for size, child in sizes_and_children: child.link_to_parent(parent, variables={"size": size}) assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT assert all(child.is_variation_child() for (size, child) in sizes_and_children) # Validation tests dummy = create_product("InvalidComplexVarChild") with pytest.raises(ValueError): dummy.link_to_parent(parent) with pytest.raises(ValueError): parent.link_to_parent(dummy) with pytest.raises(ValueError): dummy.link_to_parent(sizes_and_children[0][1]) # Variable tests size_attr = parent.variation_variables.get(identifier="size") for size, child in sizes_and_children: size_val = size_attr.values.get(identifier=size) result_product = ProductVariationResult.resolve(parent, {size_attr: size_val}) assert result_product == child
def get_orderable_variation_children(product, request, variation_variables): if not variation_variables: variation_variables = product.variation_variables.all().prefetch_related("values") key, val = context_cache.get_cached_value( identifier="orderable_variation_children", item=product, context=request, variation_variables=variation_variables) if val is not None: return val orderable_variation_children = OrderedDict() orderable = 0 for combo_data in product.get_all_available_combinations(): combo = combo_data["variable_to_value"] for k, v in six.iteritems(combo): if k not in orderable_variation_children: orderable_variation_children[k] = [] res = ProductVariationResult.resolve(product, combo) if res and res.get_shop_instance(request.shop).is_orderable( supplier=None, customer=request.customer, quantity=1 ): orderable += 1 for k, v in six.iteritems(combo): if v not in orderable_variation_children[k]: orderable_variation_children[k].append(v) values = (orderable_variation_children, orderable != 0) context_cache.set_cached_value(key, values) return values
def get_orderability_errors_for_variable_variation_parent( self, supplier, customer): from shuup.core.models import ProductVariationResult sellable = False for combo in self.product.get_all_available_combinations(): res = ProductVariationResult.resolve(self.product, combo["variable_to_value"]) if not res: continue try: child_shop_product = res.get_shop_instance(self.shop) except ShopProduct.DoesNotExist: continue if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False, ): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children."), code="no_sellable_children")
def get_orderable_variation_children(product, request, variation_variables): if not variation_variables: variation_variables = product.variation_variables.all( ).prefetch_related("values") key, val = context_cache.get_cached_value( identifier="orderable_variation_children", item=product, context=request, variation_variables=variation_variables) if val is not None: return val orderable_variation_children = OrderedDict() orderable = 0 for combo_data in product.get_all_available_combinations(): combo = combo_data["variable_to_value"] for k, v in six.iteritems(combo): if k not in orderable_variation_children: orderable_variation_children[k] = set() res = ProductVariationResult.resolve(product, combo) if res and res.get_shop_instance(request.shop).is_orderable( supplier=None, customer=request.customer, quantity=1): orderable += 1 for k, v in six.iteritems(combo): orderable_variation_children[k].add(v) values = (orderable_variation_children, orderable != 0) context_cache.set_cached_value(key, values) return values
def test_variable_variation(): parent = create_product("ComplexVarParent") sizes_and_children = [("%sL" % ("X" * x), create_product("ComplexVarChild-%d" % x)) for x in range(4)] for size, child in sizes_and_children: child.link_to_parent(parent, variables={"size": size}) assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT assert all(child.is_variation_child() for (size, child) in sizes_and_children) # Validation tests dummy = create_product("InvalidComplexVarChild") with pytest.raises(ValueError): dummy.link_to_parent(parent) with pytest.raises(ValueError): parent.link_to_parent(dummy) with pytest.raises(ValueError): dummy.link_to_parent(sizes_and_children[0][1]) # Variable tests size_attr = parent.variation_variables.get(identifier="size") for size, child in sizes_and_children: size_val = size_attr.values.get(identifier=size) result_product = ProductVariationResult.resolve( parent, {size_attr: size_val}) assert result_product == child
def handle_add_var(request, basket, product_id, quantity=1, unit_type='internal', **kwargs): """ Handle adding a complex variable product into the basket by resolving the combination variables. This actually uses `kwargs`, expecting `var_XXX=YYY` to exist there, where `XXX` is the PK of a ProductVariationVariable and YYY is the PK of a ProductVariationVariableValue. Confused yet? :param quantity: Quantity of the resolved variation to add. :param kwargs: Expected to contain `var_*` values, see above. """ # Resolve the combination... vars = dict((int(k.split("_")[-1]), int(v)) for (k, v) in six.iteritems(kwargs) if k.startswith("var_")) var_product = ProductVariationResult.resolve(product_id, combination=vars) if not var_product: raise ValidationError(_(u"Error! This variation is not available."), code="invalid_variation_combination") # and hand it off to handle_add like we're used to return handle_add(request=request, basket=basket, product_id=var_product.pk, quantity=quantity, unit_type=unit_type, **kwargs)
def get_context_data(self, **kwargs): context = super(ProductPriceView, self).get_context_data(**kwargs) vars = self.get_variation_variables() if vars: # complex variation variables detected context["product"] = ProductVariationResult.resolve(context["product"], vars) if not context["product"]: self.template_name = "shuup/front/product/detail_order_section_no_product.jinja" context["quantity"] = self.request.GET.get("quantity") if context["product"]: # Might be null from ProductVariationResult resolution context["quantity"] = context["product"].sales_unit.round(context["quantity"]) return context
def get_context_data(self, **kwargs): context = super(ProductPriceView, self).get_context_data(**kwargs) vars = self.get_variation_variables() if vars: # complex variation variables detected context["product"] = ProductVariationResult.resolve(context["product"], vars) if not context["product"]: self.template_name = "shuup/front/product/_detail_order_section_no_product.jinja" context["quantity"] = self.request.GET.get("quantity") if context["product"]: # Might be null from ProductVariationResult resolution context["quantity"] = context["product"].sales_unit.round(context["quantity"]) return context
def get_orderable_variation_children(product, request, variation_variables, supplier=None): # noqa (C901) if not variation_variables: variation_variables = product.variation_variables.all( ).prefetch_related("values") key, val = context_cache.get_cached_value( identifier="orderable_variation_children", item=product, context=request, variation_variables=variation_variables, supplier=supplier) if val is not None: return _unpack_orderable_variation_children_from_cache(val) orderable_variation_children = OrderedDict() orderable = 0 for combo_data in product.get_all_available_combinations(): combo = combo_data["variable_to_value"] for variable, values in six.iteritems(combo): if variable not in orderable_variation_children: orderable_variation_children[variable] = [] res = ProductVariationResult.resolve(product, combo) if not res: continue try: shop_product = res.get_shop_instance(request.shop) except ShopProduct.DoesNotExist: continue if res and shop_product.is_orderable( supplier=supplier, customer=request.customer, quantity=shop_product.minimum_purchase_quantity): orderable += 1 for variable, value in six.iteritems(combo): if value not in orderable_variation_children[variable]: orderable_variation_children[variable].append(value) orderable = (orderable > 0) values = (orderable_variation_children, orderable) context_cache.set_cached_value( key, _pack_orderable_variation_children_to_cache(*values)) return values
def handle_add_var(request, basket, product_id, quantity=1, **kwargs): """ Handle adding a complex variable product into the basket by resolving the combination variables. This actually uses `kwargs`, expecting `var_XXX=YYY` to exist there, where `XXX` is the PK of a ProductVariationVariable and YYY is the PK of a ProductVariationVariableValue. Confused yet? :param quantity: Quantity of the resolved variation to add. :param kwargs: Expected to contain `var_*` values, see above. """ # Resolve the combination... vars = dict((int(k.split("_")[-1]), int(v)) for (k, v) in six.iteritems(kwargs) if k.startswith("var_")) var_product = ProductVariationResult.resolve(product_id, combination=vars) if not var_product: raise ValidationError(_(u"This variation is not available."), code="invalid_variation_combination") # and hand it off to handle_add like we're used to return handle_add(request=request, basket=basket, product_id=var_product.pk, quantity=quantity)
def get_orderable_variation_children(product, request, variation_variables, supplier=None): # noqa (C901) if not variation_variables: variation_variables = product.variation_variables.all().prefetch_related("values") key, val = context_cache.get_cached_value( identifier="orderable_variation_children", item=product, context=request, variation_variables=variation_variables, supplier=supplier ) if val is not None: return _unpack_orderable_variation_children_from_cache(val) orderable_variation_children = OrderedDict() orderable = 0 for combo_data in product.get_all_available_combinations(): combo = combo_data["variable_to_value"] for variable, values in six.iteritems(combo): if variable not in orderable_variation_children: orderable_variation_children[variable] = [] res = ProductVariationResult.resolve(product, combo) if not res: continue try: shop_product = res.get_shop_instance(request.shop) except ShopProduct.DoesNotExist: continue if res and shop_product.is_orderable( supplier=supplier, customer=request.customer, quantity=shop_product.minimum_purchase_quantity): orderable += 1 for variable, value in six.iteritems(combo): if value not in orderable_variation_children[variable]: orderable_variation_children[variable].append(value) orderable = (orderable > 0) values = (orderable_variation_children, orderable) context_cache.set_cached_value(key, _pack_orderable_variation_children_to_cache(*values)) return values
def get_orderability_errors_for_variable_variation_parent(self, supplier, customer): from shuup.core.models import ProductVariationResult sellable = False for combo in self.product.get_all_available_combinations(): res = ProductVariationResult.resolve(self.product, combo["variable_to_value"]) if not res: continue try: child_shop_product = res.get_shop_instance(self.shop) except ShopProduct.DoesNotExist: continue if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False ): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children"), code="no_sellable_children")
def get_orderability_errors( # noqa (C901) self, supplier, quantity, customer, ignore_minimum=False): """ Yield ValidationErrors that would cause this product to not be orderable. :param supplier: Supplier to order this product from. May be None. :type supplier: shuup.core.models.Supplier :param quantity: Quantity to order. :type quantity: int|Decimal :param customer: Customer contact. :type customer: shuup.core.models.Contact :param ignore_minimum: Ignore any limitations caused by quantity minimums. :type ignore_minimum: bool :return: Iterable[ValidationError] """ for error in self.get_visibility_errors(customer): yield error if supplier is None and not self.suppliers.exists(): # `ShopProduct` must have at least one `Supplier`. # If supplier is not given and the `ShopProduct` itself # doesn't have suppliers we cannot sell this product. yield ValidationError(_('The product has no supplier.'), code="no_supplier") if not ignore_minimum and quantity < self.minimum_purchase_quantity: yield ValidationError(_( 'The purchase quantity needs to be at least %d for this product.' ) % self.minimum_purchase_quantity, code="purchase_quantity_not_met") if supplier and not self.suppliers.filter(pk=supplier.pk).exists(): yield ValidationError(_('The product is not supplied by %s.') % supplier, code="invalid_supplier") if self.product.mode == ProductMode.SIMPLE_VARIATION_PARENT: sellable = False for child_product in self.product.variation_children.all(): child_shop_product = child_product.get_shop_instance(self.shop) if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children"), code="no_sellable_children") elif self.product.mode == ProductMode.VARIABLE_VARIATION_PARENT: from shuup.core.models import ProductVariationResult sellable = False for combo in self.product.get_all_available_combinations(): res = ProductVariationResult.resolve( self.product, combo["variable_to_value"]) if not res: continue child_shop_product = res.get_shop_instance(self.shop) if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children"), code="no_sellable_children") if self.product.is_package_parent(): for child_product, child_quantity in six.iteritems( self.product.get_package_child_to_quantity_map()): try: child_shop_product = child_product.get_shop_instance( shop=self.shop, allow_cache=False) except ShopProduct.DoesNotExist: yield ValidationError("%s: Not available in %s" % (child_product, self.shop), code="invalid_shop") else: for error in child_shop_product.get_orderability_errors( supplier=supplier, quantity=(quantity * child_quantity), customer=customer, ignore_minimum=ignore_minimum): message = getattr(error, "message", "") code = getattr(error, "code", None) yield ValidationError("%s: %s" % (child_product, message), code=code) if supplier and self.product.stock_behavior == StockBehavior.STOCKED: for error in supplier.get_orderability_errors(self, quantity, customer=customer): yield error purchase_multiple = self.purchase_multiple if quantity > 0 and purchase_multiple > 0 and (quantity % purchase_multiple) != 0: p = (quantity // purchase_multiple) smaller_p = max(purchase_multiple, p * purchase_multiple) larger_p = max(purchase_multiple, (p + 1) * purchase_multiple) render_qty = self.unit.render_quantity if larger_p == smaller_p: message = _("The product can only be ordered in multiples of " "{package_size}, for example {amount}").format( package_size=render_qty(purchase_multiple), amount=render_qty(smaller_p)) else: message = _("The product can only be ordered in multiples of " "{package_size}, for example {smaller_amount} or " "{larger_amount}").format( package_size=render_qty(purchase_multiple), smaller_amount=render_qty(smaller_p), larger_amount=render_qty(larger_p)) yield ValidationError(message, code="invalid_purchase_multiple") for receiver, response in get_orderability_errors.send( ShopProduct, shop_product=self, customer=customer, supplier=supplier, quantity=quantity): for error in response: yield error
def test_complex_orderability(admin_user): shop = get_default_shop() fake_supplier = Supplier.objects.create(identifier="fake") admin_contact = get_person_contact(admin_user) parent = create_product("SuperComplexVarParent") shop_product = ShopProduct.objects.create( product=parent, shop=shop, visibility=ShopProductVisibility.ALWAYS_VISIBLE) shop_product.suppliers.add(fake_supplier) shop_product.visibility = ShopProductVisibility.ALWAYS_VISIBLE shop_product.save() color_var = ProductVariationVariable.objects.create(product=parent, identifier="color") size_var = ProductVariationVariable.objects.create(product=parent, identifier="size") for color in ("yellow", "blue", "brown"): ProductVariationVariableValue.objects.create(variable=color_var, identifier=color) for size in ("small", "medium", "large", "huge"): ProductVariationVariableValue.objects.create(variable=size_var, identifier=size) combinations = list(parent.get_all_available_combinations()) assert len(combinations) == (3 * 4) for combo in combinations: assert not combo["result_product_pk"] child = create_product("xyz-%s" % combo["sku_part"], shop=shop, supplier=fake_supplier) child.link_to_parent(parent, combo["variable_to_value"]) result_product = ProductVariationResult.resolve( parent, combo["variable_to_value"]) assert result_product == child assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT small_size_value = ProductVariationVariableValue.objects.get( variable=size_var, identifier="small") brown_color_value = ProductVariationVariableValue.objects.get( variable=color_var, identifier="brown") result1 = ProductVariationResult.resolve(parent, { color_var: brown_color_value, size_var: small_size_value }) result2 = ProductVariationResult.resolve(parent, { color_var.pk: brown_color_value.pk, size_var.pk: small_size_value.pk }) assert result1 and result2 assert result1.pk == result2.pk assert error_does_not_exist(shop_product.get_orderability_errors( supplier=fake_supplier, customer=admin_contact, quantity=1), code="no_sellable_children") # result 1 is no longer sellable sp = result1.get_shop_instance(shop) sp.visibility = ShopProductVisibility.NOT_VISIBLE sp.save() assert error_does_not_exist(shop_product.get_orderability_errors( supplier=fake_supplier, customer=admin_contact, quantity=1), code="no_sellable_children") # no sellable children for combo in combinations: result_product = ProductVariationResult.resolve( parent, combo["variable_to_value"]) sp = result_product.get_shop_instance(shop) sp.visibility = ShopProductVisibility.NOT_VISIBLE sp.save() assert error_exists(shop_product.get_orderability_errors( supplier=fake_supplier, customer=admin_contact, quantity=1), code="no_sellable_children") # no sellable children with no shop product for combo in combinations: result_product = ProductVariationResult.resolve( parent, combo["variable_to_value"]) sp = result_product.get_shop_instance(shop) sp.delete() sp.save() assert error_exists(shop_product.get_orderability_errors( supplier=fake_supplier, customer=admin_contact, quantity=1), code="no_sellable_children")
def get_orderability_errors( # noqa (C901) self, supplier, quantity, customer, ignore_minimum=False): """ Yield ValidationErrors that would cause this product to not be orderable. :param supplier: Supplier to order this product from. May be None. :type supplier: shuup.core.models.Supplier :param quantity: Quantity to order. :type quantity: int|Decimal :param customer: Customer contact. :type customer: shuup.core.models.Contact :param ignore_minimum: Ignore any limitations caused by quantity minimums. :type ignore_minimum: bool :return: Iterable[ValidationError] """ for error in self.get_visibility_errors(customer): yield error if supplier is None and not self.suppliers.exists(): # `ShopProduct` must have at least one `Supplier`. # If supplier is not given and the `ShopProduct` itself # doesn't have suppliers we cannot sell this product. yield ValidationError(_('The product has no supplier.'), code="no_supplier") if not ignore_minimum and quantity < self.minimum_purchase_quantity: yield ValidationError(_( 'The purchase quantity needs to be at least %d for this product.' ) % self.minimum_purchase_quantity, code="purchase_quantity_not_met") if supplier and not self.suppliers.filter(pk=supplier.pk).exists(): yield ValidationError(_('The product is not supplied by %s.') % supplier, code="invalid_supplier") if self.product.mode == ProductMode.SIMPLE_VARIATION_PARENT: sellable = False for child_product in self.product.variation_children.all(): try: child_shop_product = child_product.get_shop_instance( self.shop) except ShopProduct.DoesNotExist: continue if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children"), code="no_sellable_children") elif self.product.mode == ProductMode.VARIABLE_VARIATION_PARENT: from shuup.core.models import ProductVariationResult sellable = False for combo in self.product.get_all_available_combinations(): res = ProductVariationResult.resolve( self.product, combo["variable_to_value"]) if not res: continue try: child_shop_product = res.get_shop_instance(self.shop) except ShopProduct.DoesNotExist: continue if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children"), code="no_sellable_children") elif self.product.is_package_parent(): for child_product, child_quantity in six.iteritems( self.product.get_package_child_to_quantity_map()): try: child_shop_product = child_product.get_shop_instance( shop=self.shop, allow_cache=False) except ShopProduct.DoesNotExist: yield ValidationError("%s: Not available in %s" % (child_product, self.shop), code="invalid_shop") else: for error in child_shop_product.get_orderability_errors( supplier=supplier, quantity=(quantity * child_quantity), customer=customer, ignore_minimum=ignore_minimum): message = getattr(error, "message", "") code = getattr(error, "code", None) yield ValidationError("%s: %s" % (child_product, message), code=code) elif supplier: # Test supplier orderability only for variation children and normal products for error in supplier.get_orderability_errors(self, quantity, customer=customer): yield error for error in self.get_quantity_errors(quantity): yield error for receiver, response in get_orderability_errors.send( ShopProduct, shop_product=self, customer=customer, supplier=supplier, quantity=quantity): for error in response: yield error
def get_orderability_errors( # noqa (C901) self, supplier, quantity, customer, ignore_minimum=False): """ Yield ValidationErrors that would cause this product to not be orderable. :param supplier: Supplier to order this product from. May be None. :type supplier: shuup.core.models.Supplier :param quantity: Quantity to order. :type quantity: int|Decimal :param customer: Customer contact. :type customer: shuup.core.models.Contact :param ignore_minimum: Ignore any limitations caused by quantity minimums. :type ignore_minimum: bool :return: Iterable[ValidationError] """ for error in self.get_visibility_errors(customer): yield error if supplier is None and not self.suppliers.exists(): # `ShopProduct` must have at least one `Supplier`. # If supplier is not given and the `ShopProduct` itself # doesn't have suppliers we cannot sell this product. yield ValidationError( _('The product has no supplier.'), code="no_supplier" ) if not ignore_minimum and quantity < self.minimum_purchase_quantity: yield ValidationError( _('The purchase quantity needs to be at least %d for this product.') % self.minimum_purchase_quantity, code="purchase_quantity_not_met" ) if supplier and not self.suppliers.filter(pk=supplier.pk).exists(): yield ValidationError( _('The product is not supplied by %s.') % supplier, code="invalid_supplier" ) if self.product.mode == ProductMode.SIMPLE_VARIATION_PARENT: sellable = False for child_product in self.product.variation_children.all(): child_shop_product = child_product.get_shop_instance(self.shop) if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False ): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children"), code="no_sellable_children") elif self.product.mode == ProductMode.VARIABLE_VARIATION_PARENT: from shuup.core.models import ProductVariationResult sellable = False for combo in self.product.get_all_available_combinations(): res = ProductVariationResult.resolve(self.product, combo["variable_to_value"]) if not res: continue child_shop_product = res.get_shop_instance(self.shop) if child_shop_product.is_orderable( supplier=supplier, customer=customer, quantity=child_shop_product.minimum_purchase_quantity, allow_cache=False ): sellable = True break if not sellable: yield ValidationError(_("Product has no sellable children"), code="no_sellable_children") if self.product.is_package_parent(): for child_product, child_quantity in six.iteritems(self.product.get_package_child_to_quantity_map()): try: child_shop_product = child_product.get_shop_instance(shop=self.shop, allow_cache=False) except ShopProduct.DoesNotExist: yield ValidationError("%s: Not available in %s" % (child_product, self.shop), code="invalid_shop") else: for error in child_shop_product.get_orderability_errors( supplier=supplier, quantity=(quantity * child_quantity), customer=customer, ignore_minimum=ignore_minimum ): message = getattr(error, "message", "") code = getattr(error, "code", None) yield ValidationError("%s: %s" % (child_product, message), code=code) if supplier and self.product.stock_behavior == StockBehavior.STOCKED: for error in supplier.get_orderability_errors(self, quantity, customer=customer): yield error for error in self.get_quantity_errors(quantity): yield error for receiver, response in get_orderability_errors.send( ShopProduct, shop_product=self, customer=customer, supplier=supplier, quantity=quantity ): for error in response: yield error
def test_complex_orderability(admin_user): shop = get_default_shop() fake_supplier = Supplier.objects.create(identifier="fake") admin_contact = get_person_contact(admin_user) parent = create_product("SuperComplexVarParent") shop_product = ShopProduct.objects.create(product=parent, shop=shop, visibility=ShopProductVisibility.ALWAYS_VISIBLE) shop_product.suppliers.add(fake_supplier) shop_product.visibility = ShopProductVisibility.ALWAYS_VISIBLE shop_product.save() color_var = ProductVariationVariable.objects.create(product=parent, identifier="color") size_var = ProductVariationVariable.objects.create(product=parent, identifier="size") for color in ("yellow", "blue", "brown"): ProductVariationVariableValue.objects.create(variable=color_var, identifier=color) for size in ("small", "medium", "large", "huge"): ProductVariationVariableValue.objects.create(variable=size_var, identifier=size) combinations = list(parent.get_all_available_combinations()) assert len(combinations) == (3 * 4) for combo in combinations: assert not combo["result_product_pk"] child = create_product("xyz-%s" % combo["sku_part"], shop=shop, supplier=fake_supplier) child.link_to_parent(parent, combo["variable_to_value"]) result_product = ProductVariationResult.resolve(parent, combo["variable_to_value"]) assert result_product == child assert parent.mode == ProductMode.VARIABLE_VARIATION_PARENT small_size_value = ProductVariationVariableValue.objects.get(variable=size_var, identifier="small") brown_color_value = ProductVariationVariableValue.objects.get(variable=color_var, identifier="brown") result1 = ProductVariationResult.resolve(parent, {color_var: brown_color_value, size_var: small_size_value}) result2 = ProductVariationResult.resolve(parent, {color_var.pk: brown_color_value.pk, size_var.pk: small_size_value.pk}) assert result1 and result2 assert result1.pk == result2.pk assert error_does_not_exist( shop_product.get_orderability_errors(supplier=fake_supplier, customer=admin_contact, quantity=1), code="no_sellable_children") # result 1 is no longer sellable sp = result1.get_shop_instance(shop) sp.visibility = ShopProductVisibility.NOT_VISIBLE sp.save() assert error_does_not_exist( shop_product.get_orderability_errors(supplier=fake_supplier, customer=admin_contact, quantity=1), code="no_sellable_children") # no sellable children for combo in combinations: result_product = ProductVariationResult.resolve(parent, combo["variable_to_value"]) sp = result_product.get_shop_instance(shop) sp.visibility = ShopProductVisibility.NOT_VISIBLE sp.save() assert error_exists( shop_product.get_orderability_errors(supplier=fake_supplier, customer=admin_contact, quantity=1), code="no_sellable_children")