示例#1
0
    def _process_line_quantity_and_price(self, source, sline, sl_kwargs):
        quantity_val = sline.pop("quantity", None)
        try:
            sl_kwargs["quantity"] = parse_decimal_string(quantity_val)
        except Exception as exc:
            msg = _(
                "The quantity '%(quantity)s' (for line %(text)s) is invalid (%(error)s)."
            ) % {
                "text": sl_kwargs["text"],
                "quantity": quantity_val,
                "error": exc,
            }
            self.add_error(ValidationError(msg, code="invalid_quantity"))
            return False

        is_product = bool(sline.get("type") == "product")
        price_val = sline.pop("baseUnitPrice",
                              None) if is_product else sline.pop(
                                  "unitPrice", None)
        try:
            sl_kwargs["base_unit_price"] = source.create_price(
                parse_decimal_string(price_val))
        except Exception as exc:
            msg = _(
                "The price '%(price)s' (for line %(text)s) is invalid (%(error)s)."
            ) % {
                "text": sl_kwargs["text"],
                "price": price_val,
                "error": exc,
            }
            self.add_error(ValidationError(msg, code="invalid_price"))
            return False

        discount_val = sline.pop("discountAmount",
                                 parse_decimal_string(str("0.00")))
        try:
            sl_kwargs["discount_amount"] = source.create_price(
                parse_decimal_string(discount_val))
        except Exception as exc:
            msg = _(
                "The discount '%(discount)s' (for line %(text)s is invalid (%(error)s)."
            ) % {
                "discount": discount_val,
                "text": sl_kwargs["text"],
                "error": exc,
            }
            self.add_error(ValidationError(msg, code="invalid_discount"))

        return True
示例#2
0
    def update_quantity(self, line, value, **kwargs):
        new_quantity = int(parse_decimal_string(value))  # TODO: The quantity could be a non-integral value
        if new_quantity is None:
            return False

        if not (line and line["quantity"] != new_quantity):
            return False

        changed = False

        # Ensure sub-lines also get changed accordingly
        linked_lines = [line] + list(self.basket.find_lines_by_parent_line_id(line["line_id"]))
        for linked_line in linked_lines:
            errors = list(self._get_orderability_errors(linked_line, new_quantity))
            if errors:
                for error in errors:
                    error_texts = ", ".join(six.text_type(sub_error) for sub_error in error)
                    message = u"%s: %s" % (linked_line.get("text") or linked_line.get("name"), error_texts)
                    messages.warning(self.request, message)
                continue
            self.basket.update_line(linked_line, quantity=new_quantity)
            linked_line["quantity"] = new_quantity
            changed = True

        return changed
示例#3
0
    def best_selling(self, request):
        """
        Returns the top 20 (default) best selling products.
        To change the number of products, set the `limit` query param.
        """
        limit = int(parse_decimal_string(request.query_params.get("limit",
                                                                  20)))
        best_selling_products = get_best_selling_product_info(
            shop_ids=[request.shop.pk])
        combined_variation_products = defaultdict(int)

        for product_id, parent_id, qty in best_selling_products:
            if parent_id:
                combined_variation_products[parent_id] += qty
            else:
                combined_variation_products[product_id] += qty

        # take here the top `limit` records, because the filter_queryset below can mess with our work
        product_ids = [
            d[0] for d in sorted(six.iteritems(combined_variation_products),
                                 key=lambda i: i[1],
                                 reverse=True)[:limit]
        ]

        products_qs = Product.objects.filter(id__in=product_ids)
        products_qs = self.filter_queryset(products_qs).distinct()
        serializer = ProductSerializer(products_qs,
                                       many=True,
                                       context={"request": request})
        return Response(serializer.data)
示例#4
0
 def __init__(self, id, value, title, currency, **kwargs):
     self.currency = currency
     value = parse_decimal_string(value)
     value = format_currency(value,
                             currency=self.currency,
                             locale=get_current_babel_locale())
     super(DashboardMoneyBlock, self).__init__(id, value, title, **kwargs)
示例#5
0
    def get_stocks(self, product):
        stocks = []
        supplier_qs = Supplier.objects.filter(shop_products__product=product).distinct()

        # filtered by supplier
        supplier_id = int(parse_decimal_string(self.context["request"].query_params.get("supplier", 0)))
        if supplier_id:
            supplier_qs = supplier_qs.filter(pk=supplier_id)

        for supplier in supplier_qs:
            stock_status = supplier.get_stock_status(product.id)

            stocks.append({
                "id": supplier.id,
                "name": supplier.name,
                "type": supplier.type,
                "logical_count": stock_status.logical_count,
                "physical_count": stock_status.physical_count,
                "message": stock_status.message,
                "error": stock_status.error
            })

        return {
            "product": product.id,
            "sku": product.sku,
            "stocks": stocks
        }
示例#6
0
    def _set_numeric_value(self, new_value):
        if self.attribute.type == AttributeType.BOOLEAN and new_value is None:
            """
            Shuup uses `django.forms.fields.NullBooleanField` in admin.
            Which can read in the `None` value.

            Note: This is being handled separately due backwards compatibility.
            TODO (2.0): Boolean should not be a special case and handling `None` should be
            same for every "numeric" value.
            """
            self.numeric_value = None
            self.datetime_value = None
            self.untranslated_string_value = ""
            return

        if isinstance(new_value, datetime.timedelta):
            value = new_value.total_seconds()
            if value == int(value):
                value = int(value)
        else:
            value = parse_decimal_string(new_value or 0)

        if self.attribute.type == AttributeType.INTEGER:
            value = int(value)

        if self.attribute.type == AttributeType.BOOLEAN:
            value = int(bool(value))

        self.numeric_value = value
        self.datetime_value = None
        self.untranslated_string_value = str(self.numeric_value)
        return
示例#7
0
    def get_stocks(self, product):
        stocks = []
        supplier_qs = Supplier.objects.enabled().filter(shop_products__product=product).distinct()

        # filtered by supplier
        supplier_id = int(parse_decimal_string(self.context["request"].query_params.get("supplier", 0)))
        if supplier_id:
            supplier_qs = supplier_qs.filter(pk=supplier_id)

        for supplier in supplier_qs:
            stock_status = supplier.get_stock_status(product.id)

            stocks.append({
                "id": supplier.id,
                "name": supplier.name,
                "type": supplier.type,
                "logical_count": stock_status.logical_count,
                "physical_count": stock_status.physical_count,
                "message": stock_status.message,
                "error": stock_status.error
            })

        return {
            "product": product.id,
            "sku": product.sku,
            "stocks": stocks
        }
示例#8
0
    def _set_numeric_value(self, new_value):
        if self.attribute.type == AttributeType.BOOLEAN and new_value is None:
            """
            Shuup uses `django.forms.fields.NullBooleanField` in admin.
            Which can read in the `None` value.

            Note: This is being handled separately due backwards compatibility.
            TODO (2.0): Boolean should not be a special case and handling `None` should be
            same for every "numeric" value.
            """
            self.numeric_value = None
            self.datetime_value = None
            self.untranslated_string_value = ""
            return

        if isinstance(new_value, datetime.timedelta):
            value = new_value.total_seconds()
            if value == int(value):
                value = int(value)
        else:
            value = parse_decimal_string(new_value or 0)

        if self.attribute.type == AttributeType.INTEGER:
            value = int(value)

        if self.attribute.type == AttributeType.BOOLEAN:
            value = int(bool(value))

        self.numeric_value = value
        self.datetime_value = None
        self.untranslated_string_value = str(self.numeric_value)
        return
示例#9
0
    def stock(self, request, pk=None):
        """
        Retrieve or Update the current stocks of the Supplier.
        You can filter the stocks through `product` and `sku` parameters.
        """

        if request.method == 'POST':
            return self._adjust_stock(request, pk)

        supplier = self.get_object()
        products_qs = Product.objects.all_except_deleted().filter(shop_products__suppliers=supplier)

        # filter by id
        product_id = int(parse_decimal_string(request.query_params.get("product", 0)))
        if product_id:
            products_qs = products_qs.filter(pk=product_id)

        # filter by sku
        product_sku = request.query_params.get("sku")
        if product_sku:
            products_qs = products_qs.filter(sku=product_sku)

        page = self.paginate_queryset(products_qs)
        context = {'request': request, 'supplier': supplier}
        serializer = ProductStockSerializer((page or products_qs), many=True, context=context)
        return Response(serializer.data)
示例#10
0
    def update_quantity(self, line, value, **kwargs):
        new_quantity = int(parse_decimal_string(value))  # TODO: The quantity could be a non-integral value
        if new_quantity is None:
            return False

        if not (line and line["quantity"] != new_quantity):
            return False

        changed = False

        # Ensure sub-lines also get changed accordingly
        linked_lines = [line] + list(self.basket.find_lines_by_parent_line_id(line["line_id"]))
        for linked_line in linked_lines:
            errors = list(self._get_orderability_errors(linked_line, new_quantity))
            if errors:
                for error in errors:
                    error_texts = ", ".join(six.text_type(sub_error) for sub_error in error)
                    message = u"%s: %s" % (linked_line.get("text") or linked_line.get("name"), error_texts)
                    messages.warning(self.request, message)
                continue
            self.basket.update_line(linked_line, quantity=new_quantity)
            linked_line["quantity"] = new_quantity
            changed = True

        return changed
示例#11
0
def test_parse_decimal_string_with_dirty_input():
    assert parse_decimal_string('1e12') == Decimal(112)
    assert parse_decimal_string('foo1bar 1x2') == Decimal(112)
    assert parse_decimal_string('4a bc2def 8g.h5') == Decimal('428.5')
    assert parse_decimal_string(float('inf')) == Decimal('inf')
    assert parse_decimal_string(float('-inf')) == Decimal('-inf')
    assert str(parse_decimal_string(float('nan'))) == str(Decimal('nan'))
    assert parse_decimal_string('') == Decimal(0)
    assert parse_decimal_string(' ') == Decimal(0)
示例#12
0
def test_parse_decimal_string_with_dirty_input():
    assert parse_decimal_string("1e12") == Decimal(112)
    assert parse_decimal_string("foo1bar 1x2") == Decimal(112)
    assert parse_decimal_string("4a bc2def 8g.h5") == Decimal("428.5")
    assert parse_decimal_string(float("inf")) == Decimal("inf")
    assert parse_decimal_string(float("-inf")) == Decimal("-inf")
    assert str(parse_decimal_string(float("nan"))) == str(Decimal("nan"))
    assert parse_decimal_string("") == Decimal(0)
    assert parse_decimal_string(" ") == Decimal(0)
示例#13
0
def test_parse_decimal_string_with_dirty_input():
    assert parse_decimal_string('1e12') == Decimal(112)
    assert parse_decimal_string('foo1bar 1x2') == Decimal(112)
    assert parse_decimal_string('4a bc2def 8g.h5') == Decimal('428.5')
    assert parse_decimal_string(float('inf')) == Decimal('inf')
    assert parse_decimal_string(float('-inf')) == Decimal('-inf')
    assert str(parse_decimal_string(float('nan'))) == str(Decimal('nan'))
    assert parse_decimal_string('') == Decimal(0)
    assert parse_decimal_string(' ') == Decimal(0)
示例#14
0
 def newest(self, request):
     """
     Returns the top 20 (default) new products.
     To change the number of products, set the `limit` query param.
     """
     limit = int(parse_decimal_string(request.query_params.get("limit", 20)))
     product_qs = self.filter_queryset(self.get_queryset()).order_by("-id").distinct()
     serializer = ProductSerializer(product_qs[:limit], many=True, context={"request": request})
     return Response(serializer.data)
示例#15
0
    def _initialize_product_line_data(self, product, supplier, shop, quantity=0):
        if product.variation_children.filter(deleted=False).exists():
            raise ValueError("Error! Add a variation parent to the basket is not allowed.")

        return {
            "line_id": uuid4().hex,
            "product": product,
            "supplier": supplier,
            "shop": shop,
            "quantity": parse_decimal_string(quantity),
        }
示例#16
0
    def _initialize_product_line_data(product, supplier, shop, quantity=0):
        if product.variation_children.count():
            raise ValueError("Attempting to add variation parent to basket")

        return {
            "line_id": uuid.uuid4().int,
            "product": product,
            "supplier": supplier,
            "shop": shop,
            "quantity": parse_decimal_string(quantity),
        }
示例#17
0
    def _initialize_product_line_data(self, product, supplier, shop, quantity=0):
        if product.variation_children.count():
            raise ValueError("Attempting to add variation parent to basket")

        return {
            # TODO: FIXME: Make sure line_id's are unique (not random)
            "line_id": str(random.randint(0, 0x7FFFFFFF)),
            "product": product,
            "supplier": supplier,
            "shop": shop,
            "quantity": parse_decimal_string(quantity),
        }
示例#18
0
    def _import_product(self, sku, data):  # noqa
        product = create_from_datum(Product, sku, data, self.i18n_props, identifier_field="sku")
        price = parse_decimal_string(data.get("price", "0.00"))
        if not product:
            return
        assert isinstance(product, Product)
        product.type = self.product_type
        product.tax_class = self.tax_class
        product.sales_unit = self.sales_unit

        product.full_clean()
        product.save()
        try:
            shop_product = product.get_shop_instance(self.shop)
        except ShopProduct.DoesNotExist:
            shop_product = ShopProduct.objects.create(product=product, shop=self.shop, default_price_value=price)

        shop_product.suppliers.add(self.supplier)
        for limiter_name in ("limit_shipping_methods", "limit_payment_methods"):
            limiter_val = data.get(limiter_name, ())
            m2m_field = getattr(shop_product, limiter_name.replace("limit_", ""))
            if limiter_val:
                setattr(shop_product, limiter_name, True)

                for identifier in limiter_val:
                    m2m_field.add(m2m_field.model.objects.get(identifier=identifier))
            else:
                setattr(shop_product, limiter_name, False)
                m2m_field.clear()

        image_name = data.get("image")
        if image_name and self.include_images:
            self._attach_image_from_name(product, image_name, shop_product)

        category_identifier = data.get("category_identifier")
        if category_identifier:
            self._attach_category(product, shop_product, category_identifier, as_primary_category=True)

        additional_category_identifier = data.get("additional_category_identifier")
        if additional_category_identifier:
            self._attach_category(product, shop_product, additional_category_identifier)

        additional_category_identifiers = data.get("additional_category_identifiers")
        if additional_category_identifiers:
            for additional_category_identifier in additional_category_identifiers.split(","):
                self._attach_category(product, shop_product, additional_category_identifier)

        manufacturer_identifier = data.get("manufacturer_identifier")
        if manufacturer_identifier:
            self._attach_manufacturer(product, self.shop, manufacturer_identifier)

        shop_product.save()
        product.save()
示例#19
0
    def _initialize_product_line_data(self, product, supplier, shop, quantity=0):
        if product.variation_children.count():
            raise ValueError("Attempting to add variation parent to basket")

        return {
            # TODO: FIXME: Make sure line_id's are unique (not random)
            "line_id": str(random.randint(0, 0x7FFFFFFF)),
            "product": product,
            "supplier": supplier,
            "shop": shop,
            "quantity": parse_decimal_string(quantity),
        }
示例#20
0
 def newest(self, request):
     """
     Returns the top 20 (default) new products.
     To change the number of products, set the `limit` query param.
     """
     limit = int(parse_decimal_string(request.query_params.get("limit",
                                                               20)))
     product_qs = self.filter_queryset(
         self.get_queryset()).order_by("-id").distinct()
     serializer = ProductSerializer(product_qs[:limit],
                                    many=True,
                                    context={"request": request})
     return Response(serializer.data)
示例#21
0
 def update_display_quantity(self, line, value, **kwargs):
     if not line:
         return False
     new_display_quantity = parse_decimal_string(value)
     if new_display_quantity is None:
         return False
     basket_line = self.basket.get_basket_line(line['line_id'])
     if basket_line and basket_line.product:
         unit = basket_line.shop_product.unit
         new_quantity = unit.from_display(new_display_quantity)
     else:
         new_quantity = new_display_quantity
     return self._update_quantity(line, new_quantity)
示例#22
0
    def _process_line_quantity_and_price(self, source, sline, sl_kwargs):
        quantity_val = sline.pop("quantity", None)
        try:
            sl_kwargs["quantity"] = parse_decimal_string(quantity_val)
        except Exception as exc:
            msg = _("The quantity '%(quantity)s' (for line %(text)s) is invalid (%(error)s)") % {
                "text": sl_kwargs["text"],
                "quantity": quantity_val,
                "error": exc,
            }
            self.add_error(ValidationError(msg, code="invalid_quantity"))
            return False

        is_product = bool(sline.get("type") == "product")
        price_val = sline.pop("baseUnitPrice", None) if is_product else sline.pop("unitPrice", None)
        try:
            sl_kwargs["base_unit_price"] = source.create_price(parse_decimal_string(price_val))
        except Exception as exc:
            msg = _("The price '%(price)s' (for line %(text)s) is invalid (%(error)s)") % {
                "text": sl_kwargs["text"],
                "price": price_val,
                "error": exc
            }
            self.add_error(ValidationError(msg, code="invalid_price"))
            return False

        discount_val = sline.pop("discountAmount", parse_decimal_string(str("0.00")))
        try:
            sl_kwargs["discount_amount"] = source.create_price(parse_decimal_string(discount_val))
        except Exception as exc:
            msg = _("The discount '%(discount)s' (for line %(text)s is invalid (%(error)s)") % {
                "discount": discount_val,
                "text": sl_kwargs["text"],
                "error": exc
            }
            self.add_error(ValidationError(msg, code="invalid_discount"))

        return True
示例#23
0
    def update_quantity(self, line, value, **kwargs):
        new_quantity = int(parse_decimal_string(
            value))  # TODO: The quantity could be a non-integral value
        if new_quantity is None:
            return False

        if not (line and line["quantity"] != new_quantity):
            return False

        changed = False

        # Ensure sub-lines also get changed accordingly
        linked_lines = [line] + list(
            self.basket.find_lines_by_parent_line_id(line["line_id"]))
        orderable_line_ids = [
            basket_line.line_id for basket_line in self.basket.get_lines()
        ]

        for linked_line in linked_lines:
            if linked_line["line_id"] not in orderable_line_ids:
                # Customer can change quantity in non-orderable lines regardless
                linked_line["quantity"] = new_quantity
                changed = True
            else:
                product = Product.objects.get(pk=linked_line["product_id"])
                supplier = Supplier.objects.filter(
                    pk=linked_line.get("supplier_id", 0)).first()

                # Basket quantities already contain current quantities for orderable lines
                quantity_delta = new_quantity - line["quantity"]
                errors = self._get_orderability_errors(product, supplier,
                                                       quantity_delta)
                if errors:
                    for error in errors:
                        error_texts = ", ".join(
                            six.text_type(sub_error) for sub_error in error)
                        message = u"%s: %s" % (linked_line.get("text")
                                               or linked_line.get("name"),
                                               error_texts)
                        messages.warning(self.request, message)
                    continue
                self.basket.update_line(linked_line, quantity=new_quantity)
                linked_line["quantity"] = new_quantity
                changed = True

        return changed
示例#24
0
    def _set_numeric_value(self, new_value):
        if isinstance(new_value, datetime.timedelta):
            value = new_value.total_seconds()
            if value == int(value):
                value = int(value)
        else:
            value = parse_decimal_string(new_value or 0)

        if self.attribute.type == AttributeType.INTEGER:
            value = int(value)

        if self.attribute.type == AttributeType.BOOLEAN:
            value = int(bool(value))

        self.numeric_value = value
        self.datetime_value = None
        self.untranslated_string_value = str(self.numeric_value)
        return
示例#25
0
    def update_quantity(self, line, value, **kwargs):
        new_quantity = int(parse_decimal_string(value))  # TODO: The quantity could be a non-integral value
        if new_quantity is None:
            return False

        if not (line and line["quantity"] != new_quantity):
            return False

        changed = False

        # Ensure sub-lines also get changed accordingly
        linked_lines = [line] + list(self.basket.find_lines_by_parent_line_id(line["line_id"]))
        orderable_line_ids = [basket_line.line_id for basket_line in self.basket.get_lines()]

        for linked_line in linked_lines:
            if linked_line["line_id"] not in orderable_line_ids:
                # Customer can change quantity in non-orderable lines regardless
                linked_line["quantity"] = new_quantity
                changed = True
            else:
                product = Product.objects.get(pk=linked_line["product_id"])
                supplier = Supplier.objects.filter(pk=linked_line.get("supplier_id", 0)).first()

                # Basket quantities already contain current quantities for orderable lines
                quantity_delta = new_quantity - line["quantity"]
                errors = self._get_orderability_errors(product, supplier, quantity_delta)
                if errors:
                    for error in errors:
                        error_texts = ", ".join(six.text_type(sub_error) for sub_error in error)
                        message = u"%s: %s" % (linked_line.get("text") or linked_line.get("name"), error_texts)
                        messages.warning(self.request, message)
                    continue
                self.basket.update_line(linked_line, quantity=new_quantity)
                linked_line["quantity"] = new_quantity
                changed = True

        return changed
示例#26
0
    def best_selling(self, request):
        """
        Returns the top 20 (default) best selling products.
        To change the number of products, set the `limit` query param.
        """
        limit = int(parse_decimal_string(request.query_params.get("limit", 20)))
        best_selling_products = get_best_selling_product_info(shop_ids=[request.shop.pk])
        combined_variation_products = defaultdict(int)

        for product_id, parent_id, qty in best_selling_products:
            if parent_id:
                combined_variation_products[parent_id] += qty
            else:
                combined_variation_products[product_id] += qty

        # take here the top `limit` records, because the filter_queryset below can mess with our work
        product_ids = [
            d[0] for d in sorted(six.iteritems(combined_variation_products), key=lambda i: i[1], reverse=True)[:limit]
        ]

        products_qs = Product.objects.filter(id__in=product_ids)
        products_qs = self.filter_queryset(products_qs).distinct()
        serializer = ProductSerializer(products_qs, many=True, context={"request": request})
        return Response(serializer.data)
示例#27
0
def test_parse_decimal_string_with_unaccepted_input(value):
    with pytest.raises(decimal.InvalidOperation):
        parse_decimal_string(value)
示例#28
0
def test_parse_decimal_string_with_normal_input():
    assert parse_decimal_string("42") == Decimal(42)
    assert parse_decimal_string("0") == Decimal(0)
    assert parse_decimal_string(3.5) == Decimal("3.5")
    assert parse_decimal_string(-5) == Decimal(-5)
    assert parse_decimal_string("-5") == Decimal(-5)
示例#29
0
 def update_quantity(self, line, value, **kwargs):
     new_quantity = parse_decimal_string(value)
     if new_quantity is None:
         return False
     return self._update_quantity(line, new_quantity)
示例#30
0
def handle_add(request,
               basket,
               product_id,
               quantity=1,
               supplier_id=None,
               **kwargs):  # noqa (C901)
    """
    Handle adding a product to the basket.

    :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID)
    :param quantity: quantity of products to add
    :param child_product_id: child product ID to add (if truey)
    :param supplier_id: The supplier ID for the new line. If None, the first supplier is used.
    """
    product_id = int(product_id)

    product = get_object_or_404(Product, pk=product_id)
    shop_product = product.get_shop_instance(shop=request.shop)
    if not shop_product:
        raise ValidationError("Product not available in this shop",
                              code="product_not_available_in_shop")

    if supplier_id:
        supplier = shop_product.suppliers.filter(pk=supplier_id).first()
    else:
        supplier = shop_product.suppliers.first()

    if not supplier:
        raise ValidationError("Invalid supplier", code="invalid_supplier")

    try:
        quantity = parse_decimal_string(quantity)
        if not product.sales_unit.allow_fractions:
            if quantity % 1 != 0:
                msg = _("The quantity %f is not allowed. "
                        "Please use an integer value.") % quantity
                raise ValidationError(msg, code="invalid_quantity")
            quantity = int(quantity)
    except (ValueError, decimal.InvalidOperation):
        raise ValidationError(_(u"The quantity %s is not valid.") % quantity,
                              code="invalid_quantity")

    if quantity <= 0:
        raise ValidationError(_(u"The quantity %s is not valid.") % quantity,
                              code="invalid_quantity")

    product_ids_and_quantities = basket.get_product_ids_and_quantities()
    already_in_basket_qty = product_ids_and_quantities.get(product.id, 0)
    shop_product.raise_if_not_orderable(supplier=supplier,
                                        quantity=(already_in_basket_qty +
                                                  quantity),
                                        customer=basket.customer)

    # If the product is a package parent, also check child products
    if product.is_package_parent():
        for child_product, child_quantity in six.iteritems(
                product.get_package_child_to_quantity_map()):
            already_in_basket_qty = product_ids_and_quantities.get(
                child_product.id, 0)
            total_child_quantity = (quantity * child_quantity)
            sp = child_product.get_shop_instance(shop=request.shop)
            sp.raise_if_not_orderable(supplier=supplier,
                                      quantity=(already_in_basket_qty +
                                                total_child_quantity),
                                      customer=basket.customer)

    # TODO: Hook/extension point
    # if product.form:
    #     return {
    #         "error": u"Form required",
    #         "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity})
    #     }

    add_product_kwargs = {
        "product": product,
        "quantity": quantity,
        "supplier": supplier,
        "shop": request.shop,
    }

    basket.add_product(**add_product_kwargs)

    return {'ok': basket.product_count, 'added': quantity}
示例#31
0
def test_parse_decimal_string_with_normal_input():
    assert parse_decimal_string('42') == Decimal(42)
    assert parse_decimal_string('0') == Decimal(0)
    assert parse_decimal_string(3.5) == Decimal('3.5')
    assert parse_decimal_string(-5) == Decimal(-5)
    assert parse_decimal_string('-5') == Decimal(-5)
示例#32
0
def test_parse_decimal_string_with_float_input(input_val, expected_val):
    result = parse_decimal_string(input_val)
    assert result == expected_val
示例#33
0
def test_parse_decimal_string_with_float_input(input_val, expected_val):
    result = parse_decimal_string(input_val)
    assert result == expected_val
示例#34
0
def test_parse_decimal_string_with_normal_input():
    assert parse_decimal_string('42') == Decimal(42)
    assert parse_decimal_string('0') == Decimal(0)
    assert parse_decimal_string(3.5) == Decimal('3.5')
    assert parse_decimal_string(-5) == Decimal(-5)
    assert parse_decimal_string('-5') == Decimal(-5)
示例#35
0
文件: commands.py 项目: ruqaiya/shuup
def handle_add(  # noqa (C901)
        request, basket, product_id,
        quantity=1, unit_type='internal',
        supplier_id=None, **kwargs):
    """
    Handle adding a product to the basket.

    :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID)
    :param quantity: quantity of products to add
    :param child_product_id: child product ID to add (if truey)
    :param supplier_id: The supplier ID for the new line. If None, the first supplier is used.
    """
    product_id = int(product_id)

    product = get_object_or_404(Product, pk=product_id)

    if product.mode in (ProductMode.SIMPLE_VARIATION_PARENT, ProductMode.VARIABLE_VARIATION_PARENT):
        raise ValidationError("Invalid product", code="invalid_product")

    try:
        shop_product = product.get_shop_instance(shop=request.shop)
    except ShopProduct.DoesNotExist:
        raise ValidationError("Product not available in this shop", code="product_not_available_in_shop")

    if supplier_id:
        supplier = shop_product.suppliers.enabled().filter(pk=supplier_id).first()
    else:
        supplier = shop_product.get_supplier(basket.customer, quantity, basket.shipping_address)

    if not supplier:
        raise ValidationError("Invalid supplier", code="invalid_supplier")

    try:
        quantity = parse_decimal_string(quantity)
        if unit_type == 'display':
            quantity = shop_product.unit.from_display(quantity)
        if not product.sales_unit.allow_fractions:
            if quantity % 1 != 0:
                msg = _(
                    "The quantity %f is not allowed. "
                    "Please use an integer value.") % quantity
                raise ValidationError(msg, code="invalid_quantity")
            quantity = int(quantity)
    except (ValueError, decimal.InvalidOperation):
        raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity")

    if quantity <= 0:
        raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity")

    product_ids_and_quantities = basket.get_product_ids_and_quantities()
    already_in_basket_qty = product_ids_and_quantities.get(product.id, 0)
    shop_product.raise_if_not_orderable(
        supplier=supplier,
        quantity=(already_in_basket_qty + quantity),
        customer=basket.customer
    )

    # If the product is a package parent, also check child products
    if product.is_package_parent():
        for child_product, child_quantity in six.iteritems(product.get_package_child_to_quantity_map()):
            already_in_basket_qty = product_ids_and_quantities.get(child_product.id, 0)
            total_child_quantity = (quantity * child_quantity)
            try:
                sp = child_product.get_shop_instance(shop=request.shop)
            except ShopProduct.DoesNotExist:
                raise ProductNotOrderableProblem("%s not available in %s" % (child_product, request.shop))

            sp.raise_if_not_orderable(
                supplier=supplier,
                quantity=(already_in_basket_qty + total_child_quantity),
                customer=basket.customer
            )

    # TODO: Hook/extension point
    # if product.form:
    #     return {
    #         "error": u"Form required",
    #         "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity})
    #     }

    add_product_kwargs = {
        "product": product,
        "quantity": quantity,
        "supplier": supplier,
        "shop": request.shop,
        "force_new_line": kwargs.get("force_new_line", False),
        "extra": kwargs.get("extra"),
        "parent_line": kwargs.get("parent_line")
    }
    line = basket.add_product(**add_product_kwargs)

    return {
        'ok': basket.smart_product_count,
        'line_id': line.line_id,
        'added': quantity
    }
示例#36
0
    def _import_product(self, sku, data):  # noqa
        product = create_from_datum(Product,
                                    sku,
                                    data,
                                    self.i18n_props,
                                    identifier_field="sku")
        price = parse_decimal_string(data.get("price", "0.00"))
        if not product:
            return
        assert isinstance(product, Product)
        product.type = self.product_type
        product.tax_class = self.tax_class
        product.sales_unit = self.sales_unit

        product.full_clean()
        product.save()
        try:
            shop_product = product.get_shop_instance(self.shop)
        except ShopProduct.DoesNotExist:
            shop_product = ShopProduct.objects.create(
                product=product, shop=self.shop, default_price_value=price)

        shop_product.suppliers.add(self.supplier)
        for limiter_name in ("limit_shipping_methods",
                             "limit_payment_methods"):
            limiter_val = data.get(limiter_name, ())
            m2m_field = getattr(shop_product,
                                limiter_name.replace("limit_", ""))
            if limiter_val:
                setattr(shop_product, limiter_name, True)

                for identifier in limiter_val:
                    m2m_field.add(
                        m2m_field.model.objects.get(identifier=identifier))
            else:
                setattr(shop_product, limiter_name, False)
                m2m_field.clear()

        image_name = data.get("image")
        if image_name and self.include_images:
            self._attach_image_from_name(product, image_name, shop_product)

        additional_images = (data.get("images") or "")
        for additional_image_name in additional_images.split(","):
            if not additional_image_name or image_name == additional_image_name:
                continue
            self._attach_image_from_name(product, additional_image_name,
                                         shop_product)

        category_identifier = data.get("category_identifier")
        if category_identifier:
            self._attach_category(product,
                                  shop_product,
                                  category_identifier,
                                  as_primary_category=True)

        additional_category_identifier = data.get(
            "additional_category_identifier")
        if additional_category_identifier:
            self._attach_category(product, shop_product,
                                  additional_category_identifier)

        additional_category_identifiers = data.get(
            "additional_category_identifiers")
        if additional_category_identifiers:
            for additional_category_identifier in additional_category_identifiers.split(
                    ","):
                self._attach_category(product, shop_product,
                                      additional_category_identifier)

        manufacturer_identifier = data.get("manufacturer_identifier")
        if manufacturer_identifier:
            self._attach_manufacturer(product, self.shop,
                                      manufacturer_identifier)

        attributes_data = data.get("attributes", {})
        for attribute_identifier, value in attributes_data.items():
            attribute = Attribute.objects.filter(
                identifier=attribute_identifier).first()
            if attribute:
                p_attribute, _ = ProductAttribute.objects.get_or_create(
                    attribute=attribute, product=product)
                p_attribute.value = value
                p_attribute.save()

        variation_parent_sku = data.get("variation_parent_sku")
        variation_variable_values = data.get("variation_variable_value",
                                             "").split("-")
        if variation_parent_sku and variation_variable_values and len(
                variation_variable_values) == 2:
            parent_product = Product.objects.filter(
                sku=variation_parent_sku).first()
            if parent_product:
                color = variation_variable_values[0].strip()
                size = variation_variable_values[1].strip()
                product.link_to_parent(parent_product,
                                       variables={
                                           "size": size,
                                           "color": color
                                       })

        shop_product.save()
        product.save()
示例#37
0
文件: commands.py 项目: suutari/shoop
def handle_add(request, basket, product_id, quantity=1, supplier_id=None, **kwargs):  # noqa (C901)
    """
    Handle adding a product to the basket.

    :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID)
    :param quantity: quantity of products to add
    :param child_product_id: child product ID to add (if truey)
    :param supplier_id: The supplier ID for the new line. If None, the first supplier is used.
    """
    product_id = int(product_id)

    product = get_object_or_404(Product, pk=product_id)
    shop_product = product.get_shop_instance(shop=request.shop)
    if not shop_product:
        raise ValidationError("Product not available in this shop", code="product_not_available_in_shop")

    if supplier_id:
        supplier = shop_product.suppliers.filter(pk=supplier_id).first()
    else:
        supplier = shop_product.suppliers.first()

    if not supplier:
        raise ValidationError("Invalid supplier", code="invalid_supplier")

    try:
        quantity = parse_decimal_string(quantity)
        if not product.sales_unit.allow_fractions:
            if quantity % 1 != 0:
                msg = _(
                    "The quantity %f is not allowed. "
                    "Please use an integer value.") % quantity
                raise ValidationError(msg, code="invalid_quantity")
            quantity = int(quantity)
    except (ValueError, decimal.InvalidOperation):
        raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity")

    if quantity <= 0:
        raise ValidationError(_(u"The quantity %s is not valid.") % quantity, code="invalid_quantity")

    product_ids_and_quantities = basket.get_product_ids_and_quantities()
    already_in_basket_qty = product_ids_and_quantities.get(product.id, 0)
    shop_product.raise_if_not_orderable(
        supplier=supplier,
        quantity=(already_in_basket_qty + quantity),
        customer=basket.customer
    )

    # If the product is a package parent, also check child products
    if product.is_package_parent():
        for child_product, child_quantity in six.iteritems(product.get_package_child_to_quantity_map()):
            already_in_basket_qty = product_ids_and_quantities.get(child_product.id, 0)
            total_child_quantity = (quantity * child_quantity)
            sp = child_product.get_shop_instance(shop=request.shop)
            sp.raise_if_not_orderable(
                supplier=supplier,
                quantity=(already_in_basket_qty + total_child_quantity),
                customer=basket.customer
            )

    # TODO: Hook/extension point
    # if product.form:
    #     return {
    #         "error": u"Form required",
    #         "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity})
    #     }

    add_product_kwargs = {
        "product": product,
        "quantity": quantity,
        "supplier": supplier,
        "shop": request.shop,
    }

    basket.add_product(**add_product_kwargs)

    return {
        'ok': basket.product_count,
        'added': quantity
    }
示例#38
0
文件: blocks.py 项目: gurch101/shuup
 def __init__(self, id, value, title, currency, **kwargs):
     self.currency = currency
     value = parse_decimal_string(value)
     value = format_currency(value, currency=self.currency, locale=get_current_babel_locale())
     super(DashboardMoneyBlock, self).__init__(id, value, title, **kwargs)
示例#39
0
 def round(self, value):
     return bankers_round(parse_decimal_string(value), self.decimals)
示例#40
0
def handle_add(  # noqa (C901)
        request,
        basket,
        product_id,
        quantity=1,
        unit_type='internal',
        supplier_id=None,
        **kwargs):
    """
    Handle adding a product to the basket.

    :param product_id: product ID to add (or if `child_product_id` is truey, the parent ID).
    :param quantity: quantity of products to add.
    :param child_product_id: child product ID to add (if truey).
    :param supplier_id: The supplier ID for the new line. If None, the first supplier is used.
    """
    product_id = int(product_id)

    product = get_object_or_404(Product, pk=product_id)

    if product.mode in (ProductMode.SIMPLE_VARIATION_PARENT,
                        ProductMode.VARIABLE_VARIATION_PARENT):
        raise ValidationError("Error! Invalid product.",
                              code="invalid_product")

    try:
        shop_product = product.get_shop_instance(shop=request.shop)
    except ShopProduct.DoesNotExist:
        raise ValidationError("Error! Product is not available in this shop.",
                              code="product_not_available_in_shop")

    if supplier_id:
        supplier = shop_product.suppliers.enabled(
            shop=shop_product.shop).filter(pk=supplier_id).first()
    else:
        supplier = shop_product.get_supplier(basket.customer, quantity,
                                             basket.shipping_address)

    if not supplier:
        raise ValidationError("Error! Invalid supplier.",
                              code="invalid_supplier")

    try:
        quantity = parse_decimal_string(quantity)
        if unit_type == 'display':
            quantity = shop_product.unit.from_display(quantity)
        if not product.sales_unit.allow_fractions:
            if quantity % 1 != 0:
                msg = _("Error! The quantity `%f` is not allowed. "
                        "Please use an integer value.") % quantity
                raise ValidationError(msg, code="invalid_quantity")
            quantity = int(quantity)
    except (ValueError, decimal.InvalidOperation):
        raise ValidationError(_(u"Error! The quantity `%s` is not valid.") %
                              quantity,
                              code="invalid_quantity")

    if quantity <= 0:
        raise ValidationError(_(u"Error! The quantity `%s` is not valid, "
                                "should be bigger than zero.") % quantity,
                              code="invalid_quantity")

    product_ids_and_quantities = basket.get_product_ids_and_quantities()
    already_in_basket_qty = product_ids_and_quantities.get(product.id, 0)
    shop_product.raise_if_not_orderable(supplier=supplier,
                                        quantity=(already_in_basket_qty +
                                                  quantity),
                                        customer=basket.customer)

    # If the product is a package parent, also check child products
    if product.is_package_parent():
        for child_product, child_quantity in six.iteritems(
                product.get_package_child_to_quantity_map()):
            already_in_basket_qty = product_ids_and_quantities.get(
                child_product.id, 0)
            total_child_quantity = (quantity * child_quantity)
            try:
                sp = child_product.get_shop_instance(shop=request.shop)
            except ShopProduct.DoesNotExist:
                raise ProductNotOrderableProblem(
                    "Error! Product %s is not available in shop %s." %
                    (child_product, request.shop))

            sp.raise_if_not_orderable(supplier=supplier,
                                      quantity=(already_in_basket_qty +
                                                total_child_quantity),
                                      customer=basket.customer)

    # TODO: Hook/extension point
    # if product.form:
    #     return {
    #         "error": u"Form required",
    #         "return": reverse_GET("product-form", kwargs={"pk": product.pk}, GET={"n": quantity})
    #     }

    add_product_kwargs = {
        "product": product,
        "quantity": quantity,
        "supplier": supplier,
        "shop": request.shop,
        "force_new_line": kwargs.get("force_new_line", False),
        "extra": kwargs.get("extra"),
        "parent_line": kwargs.get("parent_line")
    }
    line = basket.add_product(**add_product_kwargs)

    return {
        'ok': basket.smart_product_count,
        'line_id': line.line_id,
        'added': quantity
    }
示例#41
0
 def __init__(self, id, value, title, **kwargs):
     value = parse_decimal_string(value)
     if int(value) == value:
         value = int(value)
     value = format_number(value, locale=get_current_babel_locale())
     super(DashboardNumberBlock, self).__init__(id, value, title, **kwargs)
示例#42
0
文件: blocks.py 项目: gurch101/shuup
 def __init__(self, id, value, title, **kwargs):
     value = parse_decimal_string(value)
     if int(value) == value:
         value = int(value)
     value = format_number(value, locale=get_current_babel_locale())
     super(DashboardNumberBlock, self).__init__(id, value, title, **kwargs)
示例#43
0
def test_parse_decimal_string_with_unaccepted_input(value):
    with pytest.raises(decimal.InvalidOperation):
        parse_decimal_string(value)
示例#44
0
文件: _units.py 项目: rounak06/shuup
 def round(self, value):
     return bankers_round(parse_decimal_string(value), self.decimals)
示例#45
0
    def post(self, request, **kwargs):
        """
        A temporary basket is created and the product with quantity
        is added to it. Then the shipping methods can be calculated normally
        """

        shipping_simulator = cached_load("SHIPPING_SIMULATOR_CLASS_SPEC")()
        shipping_form = shipping_simulator.get_form(data=request.POST)

        if shipping_form.is_valid():
            # create temp basket
            tmp_basket = cached_load("SHUUP_BASKET_CLASS_SPEC")(request)

            # sets the shipping addres used to calculate the price and the delivery time
            tmp_basket.shipping_address = shipping_simulator.get_shipping_address(
                shipping_form)

            # from shuup/front/basket/commands.py:handle_add()
            # fetches the product object
            product_id = int(request.POST['product_id'])
            product = Product.objects.get(pk=product_id)
            shop_product = product.get_shop_instance(shop=request.shop)
            if not shop_product:
                logger.error(
                    _("Product ID {0} not available in {1} shop").format(
                        product_id, request.shop))
                return HttpResponseBadRequest()

            if request.POST.get('supplier_id'):
                supplier = shop_product.suppliers.filter(
                    pk=request.POST['supplier_id']).first()
            else:
                supplier = shop_product.suppliers.first()

            # validate and format the quantity
            try:
                quantity = parse_decimal_string(request.POST['quantity'])
                if not product.sales_unit.allow_fractions:
                    if quantity % 1 != 0:
                        logger.error(
                            _("The quantity %f is not allowed. Please use an integer value."
                              ) % quantity)
                        return HttpResponseBadRequest()

                    quantity = int(quantity)
            except (ValueError, decimal.InvalidOperation) as e:
                logger.exception(_("The quantity is not valid: {0}").format(e))
                return HttpResponseBadRequest()

            if quantity <= 0:
                logger.error(_("The quantity %s is not valid.") % quantity)
                return HttpResponseBadRequest()

            # create the dict to use in add_product
            add_product_kwargs = {
                "product": product,
                "quantity": quantity,
                "supplier": supplier,
                "shop": request.shop,
            }

            tmp_basket.add_product(**add_product_kwargs)
            methods = get_shipping_methods_from(tmp_basket)
            return JsonResponse({"methods": methods})

        return JsonResponse({})