Пример #1
0
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)
Пример #2
0
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)
Пример #3
0
 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
Пример #4
0
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
Пример #5
0
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
Пример #6
0
    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")
Пример #7
0
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
Пример #8
0
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
Пример #9
0
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)
Пример #10
0
 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
Пример #11
0
    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
Пример #12
0
    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
Пример #13
0
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
Пример #14
0
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)
Пример #15
0
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
Пример #16
0
    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")
Пример #17
0
    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
Пример #18
0
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")
Пример #19
0
    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
Пример #20
0
    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
Пример #21
0
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")