def test_order_creator_account_manager(): company = create_random_company() shop = get_shop(identifier="random-shop", enabled=True) source = seed_source(create_random_user(), shop) source.customer = company source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) creator = OrderCreator() order = creator.create_order(source) assert order.account_manager is None # Company contact doesn't have account manager field person = create_random_person() person.account_manager = create_random_person() person.save() source = seed_source(create_random_user(), shop) source.customer = person source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) creator = OrderCreator() order = creator.create_order(source) assert order.account_manager is not None assert order.account_manager == person.account_manager with pytest.raises(ProtectedError): person.account_manager.delete()
def test_product_hightlight_plugin(rf, highlight_type, orderable): shop = get_default_shop() p1 = create_product("p1", shop, get_default_supplier(), "10") p2 = create_product("p2", shop, get_default_supplier(), "20") p3 = create_product("p3", shop, get_default_supplier(), "30") p4 = create_product("p4", shop, get_default_supplier(), "40") sp4 = p4.get_shop_instance(shop) sp4.purchasable = False sp4.save() context = get_context(rf) plugin = ProductHighlightPlugin({ "type": highlight_type, "count": 4, "orderable_only": orderable }) context_products = plugin.get_context_data(context)["products"] assert p1 in context_products assert p2 in context_products assert p3 in context_products if orderable: assert p4 not in context_products else: assert p4 in context_products
def get_frontend_order_state(contact, valid_lines=True): """ Get a dict structure mirroring what the frontend JavaScript would submit. :type contact: Contact|None """ translation.activate("en") shop = get_default_shop() tax = Tax.objects.create(code="test_code", rate=decimal.Decimal("0.20"), name="Default") tax_class = TaxClass.objects.create(identifier="test_tax_class", name="Default") rule = TaxRule.objects.create(tax=tax) rule.tax_classes.add(tax_class) rule.save() product = create_product( sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop ) product.tax_class = tax_class product.save() if valid_lines: lines = [ {"id": "x", "type": "product", "product": {"id": product.id}, "quantity": "32", "baseUnitPrice": 50}, {"id": "y", "type": "other", "sku": "hello", "text": "A greeting", "quantity": 1, "unitPrice": "5.5"}, {"id": "z", "type": "text", "text": "This was an order!", "quantity": 0}, ] else: unshopped_product = create_product(sku=printable_gibberish(), supplier=get_default_supplier()) not_visible_product = create_product( sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop ) not_visible_shop_product = not_visible_product.get_shop_instance(shop) not_visible_shop_product.visible = False not_visible_shop_product.save() lines = [ {"id": "x", "type": "product"}, # no product? {"id": "x", "type": "product", "product": {"id": unshopped_product.id}}, # not in this shop? {"id": "y", "type": "product", "product": {"id": -product.id}}, # invalid product? {"id": "z", "type": "other", "quantity": 1, "unitPrice": "q"}, # what's that price? {"id": "rr", "type": "product", "quantity": 1, "product": {"id": not_visible_product.id}} # not visible ] state = { "customer": {"id": contact.id if contact else None}, "lines": lines, "methods": { "shippingMethod": {"id": get_default_shipping_method().id}, "paymentMethod": {"id": get_default_payment_method().id}, }, "shop": { "selected": { "id": shop.id, "name": shop.name, "currency": shop.currency, "priceIncludeTaxes": shop.prices_include_tax } } } return state
def test_processor_orderability(admin_user): source = OrderSource(Shop()) processor = OrderProcessor() line = source.add_line( type=OrderLineType.PRODUCT, product=get_default_product(), supplier=get_default_supplier(), quantity=1, shop=get_default_shop(), base_unit_price=source.create_price(10), ) line.order = Order(shop=get_default_shop()) assert processor._check_orderability(line) is None unorderable_line = source.add_line( type=OrderLineType.PRODUCT, product=create_product("no-shop"), supplier=get_default_supplier(), quantity=1, shop=get_default_shop(), base_unit_price=source.create_price(20), ) unorderable_line.order = Order(shop=get_default_shop()) with pytest.raises(ValidationError) as exc: processor._check_orderability(unorderable_line) assert "Not available in" in exc.value.message
def test_get_orderable_variation_children(rf): supplier = get_default_supplier() shop = get_default_shop() variable_name = "Color" parent = create_product("test-sku-1", shop=shop) variation_variable = ProductVariationVariable.objects.create(product=parent, identifier="color", name=variable_name) red_value = ProductVariationVariableValue.objects.create(variable=variation_variable, identifier="red", value="Red") blue_value =ProductVariationVariableValue.objects.create(variable=variation_variable, identifier="blue", value="Blue") combinations = list(parent.get_all_available_combinations()) assert len(combinations) == 2 for combo in combinations: assert not combo["result_product_pk"] child = create_product("xyz-%s" % combo["sku_part"], shop=shop, supplier=get_default_supplier(), default_price=20) child.link_to_parent(parent, combination_hash=combo["hash"]) combinations = list(parent.get_all_available_combinations()) assert len(combinations) == 2 parent.refresh_from_db() assert parent.is_variation_parent() request = apply_request_middleware(rf.get("/")) cache.clear() for time in range(2): orderable_children, is_orderable = get_orderable_variation_children(parent, request, None) assert len(orderable_children) for var_variable, var_values in dict(orderable_children).items(): assert var_variable == variation_variable assert red_value in var_values assert blue_value in var_values
def test_shipping_table_behavior(admin_user): service = get_custom_carrier_service() component = ShippingTableByModeBehaviorComponent.objects.create( mode=FetchTableMode.LOWEST_DELIVERY_TIME ) service.behavior_components.add(component) source = get_source(admin_user, service) PRODUCT_WEIGHT = Decimal(700.0) # in grams PRODUCT_WIDTH = Decimal(340) PRODUCT_DEPTH = Decimal(320) PRODUCT_HEIGHT = Decimal(180) product = get_default_product() product.gross_weight = PRODUCT_WEIGHT product.width = PRODUCT_WIDTH product.depth = PRODUCT_DEPTH product.height = PRODUCT_HEIGHT product.save() source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(10), ) assert abs((component.get_source_weight(source) * KG_TO_G) - PRODUCT_WEIGHT) < Decimal(0.0001) # configure cubic usage component.use_cubic_weight = True component.cubic_weight_factor = Decimal(6000.0) component.cubic_weight_exemption = Decimal(3.000) component.save() # not using cubic weight because of weight exemption assert abs((component.get_source_weight(source) * KG_TO_G) - PRODUCT_WEIGHT) < Decimal(0.0001) source.add_line( type=OrderLineType.PRODUCT, product=product, supplier=get_default_supplier(), quantity=7, base_unit_price=source.create_price(10), ) cubic_weight = (PRODUCT_WIDTH * PRODUCT_DEPTH * (PRODUCT_HEIGHT * 8)) / component.cubic_weight_factor assert abs((component.get_source_weight(source) * KG_TO_G) - cubic_weight) < Decimal(0.0001) # set constraints component.max_package_width = Decimal(1000) component.max_package_height = Decimal(1000) component.max_package_length = Decimal(1000) component.max_package_edges_sum = Decimal(2000) component.max_package_weight = Decimal(10) component.save() cubic_weight = (PRODUCT_WIDTH * PRODUCT_DEPTH * (PRODUCT_HEIGHT * 8)) / component.cubic_weight_factor assert abs((component.get_source_weight(source) * KG_TO_G) - cubic_weight) < Decimal(0.0001)
def test_copy_order_to_basket(admin_user, settings): configure(settings) shop = factories.get_default_shop() basket = factories.get_basket() p1 = factories.create_product("test", shop=shop, supplier=factories.get_default_supplier()) order = factories.create_order_with_product(factories.get_default_product(), factories.get_default_supplier(), 2, 10, shop=shop) factories.add_product_to_order(order, factories.get_default_supplier(), p1, 2, 5) order.customer = get_person_contact(admin_user) order.save() client = _get_client(admin_user) payload = { "order": order.pk } response = client.post('/api/shuup/basket/{}-{}/add_from_order/'.format(shop.pk, basket.key), payload) assert response.status_code == status.HTTP_200_OK response_data = json.loads(response.content.decode("utf-8")) assert len(response_data["items"]) == 2 assert not response_data["validation_errors"] basket.refresh_from_db() assert len(basket.data["lines"]) == 2 # do it again, basket should clear first then read items payload = { "order": order.pk } response = client.post('/api/shuup/basket/{}-{}/add_from_order/'.format(shop.pk, basket.key), payload) assert response.status_code == status.HTTP_200_OK response_data = json.loads(response.content.decode("utf-8")) assert len(response_data["items"]) == 2 assert not response_data["validation_errors"] basket.refresh_from_db() assert len(basket.data["lines"]) == 2
def _create_order(shop, customer): p1 = factories.create_product("test", shop=shop, supplier=factories.get_default_supplier()) order = factories.create_order_with_product(factories.get_default_product(), factories.get_default_supplier(), 2, 10, shop=shop) factories.add_product_to_order(order, factories.get_default_supplier(), p1, 2, 5) order.customer = customer order.save() return order
def test_refunds(browser, admin_user, live_server, settings): order = create_order_with_product( get_default_product(), get_default_supplier(), 10, decimal.Decimal("10"), n_lines=10, shop=get_default_shop()) order2 = create_order_with_product( get_default_product(), get_default_supplier(), 10, decimal.Decimal("10"), n_lines=10, shop=get_default_shop()) order2.create_payment(order2.taxful_total_price) initialize_admin_browser_test(browser, live_server, settings) _test_toolbar_visibility(browser, live_server, order) _test_create_full_refund(browser, live_server, order) _test_refund_view(browser, live_server, order2)
def test_product_from_category_plugin(rf): shop = get_default_shop() category1 = get_default_category() category2 = CategoryFactory(status=CategoryStatus.VISIBLE) category1.shops.add(shop) category2.shops.add(shop) p1 = create_product("p1", shop, get_default_supplier(), "10") p2 = create_product("p2", shop, get_default_supplier(), "20") p3 = create_product("p3", shop, get_default_supplier(), "30") sp1 = p1.get_shop_instance(shop) sp2 = p2.get_shop_instance(shop) sp3 = p3.get_shop_instance(shop) sp1.categories.add(category1) sp2.categories.add(category1) sp3.categories.add(category2) context = get_context(rf) plugin = ProductsFromCategoryPlugin({ "category": category1.pk }) context_products = plugin.get_context_data(context)["products"] assert p1 in context_products assert p2 in context_products assert p3 not in context_products # test the plugin form with override_current_theme_class(None): theme = get_current_theme(get_default_shop()) cell = LayoutCell(theme, ProductsFromCategoryPlugin.identifier, sizes={"md": 8}) lcfg = LayoutCellFormGroup(layout_cell=cell, theme=theme, request=apply_request_middleware(rf.get("/"))) assert not lcfg.is_valid() lcfg = LayoutCellFormGroup( data={ "general-cell_width": "8", "general-cell_align": "pull-right", "plugin-count": 4, "plugin-category": category2.pk }, layout_cell=cell, theme=theme, request=apply_request_middleware(rf.get("/")) ) assert lcfg.is_valid() lcfg.save() assert cell.config["category"] == str(category2.pk)
def test_correios_delivery_time_2(rf, admin_user): with patch.object(CorreiosWS, 'get_preco_prazo', return_value=MOCKED_SUCCESS_RESULT): pac_carrier = get_correios_carrier_2() contact = get_person_contact(admin_user) p1 = create_product(sku='p1', supplier=get_default_supplier(), width=400, depth=400, height=400, gross_weight=1250) # P2 é pesado pacas - é empacotado em uma caixa separada # só para dar problema na metade da entrega p2 = create_product(sku='p2', supplier=get_default_supplier(), width=400, depth=400, height=400, gross_weight=31250) source = seed_source(admin_user) source.add_line( type=OrderLineType.PRODUCT, product=p1, supplier=get_default_supplier(), quantity=2, base_unit_price=source.create_price(10)) source.add_line( type=OrderLineType.PRODUCT, product=p2, supplier=get_default_supplier(), quantity=1, base_unit_price=source.create_price(20)) billing_address = get_address() shipping_address = get_address(name="My House", country='BR') shipping_address.postal_code = "89070210" source.billing_address = billing_address source.shipping_address = shipping_address source.customer = contact shipping = ShippingMethod.objects.filter(carrier=pac_carrier).first() bc = shipping.behavior_components.first() packages = bc._pack_source(source) assert len(packages) == 3 results = bc._get_correios_results(source, packages) assert len(results) == 3
def test_order_received(rf, regular_user): activate("en") get_default_product() get_default_supplier() get_test_script("test script", "order_received") template_data = STEP_DATA[0]["actions"][0]["template_data"] for lang in ["en", "fi"]: n_outbox_pre = len(mail.outbox) customer = create_random_person(locale=lang) create_random_order(customer) assert (len(mail.outbox) == n_outbox_pre + 1), "Sending email failed" latest_mail = mail.outbox[-1] assert latest_mail.subject == template_data[lang]["subject"], "Subject doesn't match" assert latest_mail.body == template_data[lang]["body"], "Body doesn't match"
def test_complex_import(): filename = "complex_import.xlsx" activate("en") shop = get_default_shop() get_default_tax_class() get_default_product_type() get_default_supplier() get_default_sales_unit() path = os.path.join(os.path.dirname(__file__), "data", "product", filename) transformed_data = transform_file(filename.split(".")[1], path) importer = ProductImporter(transformed_data, shop, "en") importer.process_data() assert len(importer.unmatched_fields) == 0 importer.do_import(ImportMode.CREATE_UPDATE) products = importer.new_objects assert len(products) == 6 assert ShopProduct.objects.count() == 6 assert Category.objects.count() == 11 assert Manufacturer.objects.count() == 4 for idx, product in enumerate(Product.objects.all().order_by("sku")): shop_product = product.get_shop_instance(shop) data = PRODUCT_DATA[idx] assert product.sku == data["sku"] assert product.name == data["name"] assert shop_product.default_price_value == Decimal(data["price"]) assert product.description == data["description"] if data.get("categories"): all_cats = set(data["categories"]) all_cats.add(data["category"]) for cat in shop_product.categories.all(): assert cat.name in all_cats assert shop_product.categories.count() == len(all_cats) # also add primary category if data.get("category"): assert shop_product.primary_category.name == data["category"] assert force_text(shop_product.visibility.label) == data["visibility"].lower() assert product.tax_class.name == data["tax_class"] if data.get("manufacturer"): assert product.manufacturer.name == data["manufacturer"]
def test_basket_shipping_error(rf): StoredBasket.objects.all().delete() shop = get_default_shop() supplier = get_default_supplier() shipped_product = create_product( printable_gibberish(), shop=shop, supplier=supplier, default_price=50, shipping_mode=ShippingMode.SHIPPED ) unshipped_product = create_product( printable_gibberish(), shop=shop, supplier=supplier, default_price=50, shipping_mode=ShippingMode.NOT_SHIPPED ) request = rf.get("/") request.session = {} request.shop = shop apply_request_middleware(request) basket = get_basket(request) # With a shipped product but no shipping methods, we oughta get an error basket.add_product(supplier=supplier, shop=shop, product=shipped_product, quantity=1) assert any(ve.code == "no_common_shipping" for ve in basket.get_validation_errors()) basket.clear_all() # But with an unshipped product, we should not basket.add_product(supplier=supplier, shop=shop, product=unshipped_product, quantity=1) assert not any(ve.code == "no_common_shipping" for ve in basket.get_validation_errors())
def test_min_amount_is_not_included(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() initial_group_count = person.groups.count() sales_ranges = [ ("silver", 0, 50), ("gold", 50, 100), ("diamond", 100, 1000), ("reverse_diamond", 1000, 100) ] for identifier, min, max in sales_ranges: create_sales_range(identifier, shop, min, max) payment = create_fully_paid_order(shop, person, supplier, "sku1", 50) assert get_total_sales(shop, person) == 50 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "gold"]) assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "diamond"]]) payment = create_fully_paid_order(shop, person, supplier, "sku2", 50) assert get_total_sales(shop, person) == 100 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "diamond"]) assert not bool([group for group in person.groups.all() if group.identifier == "reverse_diamond"]) assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "gold"]])
def test_protected_fields(): activate("en") shop = Shop.objects.create( name="testshop", identifier="testshop", status=ShopStatus.ENABLED, public_name="test shop", domain="derp", currency="EUR" ) get_currency("EUR") get_currency("USD") assert shop.name == "testshop" assert shop.currency == "EUR" assert not ConfigurationItem.objects.filter(shop=shop, key="languages").exists() shop_form = ShopBaseForm(instance=shop, languages=settings.LANGUAGES) assert not shop_form._get_protected_fields() # No protected fields just yet, right? data = get_form_data(shop_form, prepared=True) shop_form = ShopBaseForm(data=data, instance=shop, languages=settings.LANGUAGES) _test_cleanliness(shop_form) shop_form.save() # Now let's make it protected! create_product(printable_gibberish(), shop=shop, supplier=get_default_supplier()) order = create_random_order(customer=create_random_person(), shop=shop) assert order.shop == shop # And try again... data["currency"] = "USD" shop_form = ShopBaseForm(data=data, instance=shop, languages=settings.LANGUAGES) assert shop_form._get_protected_fields() # So protected! _test_cleanliness(shop_form) shop = shop_form.save() assert shop.currency == "EUR" # But the shop form ignored the change . . .
def test_sales_ranges_update_after_range_update(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() company = create_random_company() create_fully_paid_order(shop, person, supplier, "sku1", 50) create_fully_paid_order(shop, company, supplier, "sku2", 100) assert get_total_sales(shop, person) == 50 assert get_total_sales(shop, company) == 100 sales_range = create_sales_range("gold", shop, 10, 90) assert sales_range.group in person.groups.all() assert sales_range.group not in company.groups.all() sales_range.max_value = None sales_range.save() assert sales_range.group in person.groups.all() assert sales_range.group in company.groups.all() # Make sure customers is actually removed when range changes sales_range.max_value = 60 sales_range.save() assert sales_range.group in person.groups.all() assert sales_range.group not in company.groups.all() # Inactive ranges shouldn't update group members sales_range.min_value = None sales_range.save() assert sales_range.group in person.groups.all() assert sales_range.group not in company.groups.all()
def test_best_selling_products_with_multiple_orders(): context = get_jinja_context() supplier = get_default_supplier() shop = get_default_shop() n_products = 2 price = 10 product_1 = create_product("test-sku-1", supplier=supplier, shop=shop) product_2 = create_product("test-sku-2", supplier=supplier, shop=shop) create_order_with_product(product_1, supplier, quantity=1, taxless_base_unit_price=price, shop=shop) create_order_with_product(product_2, supplier, quantity=1, taxless_base_unit_price=price, shop=shop) cache.clear() # Two initial products sold assert product_1 in general.get_best_selling_products(context, n_products=n_products) assert product_2 in general.get_best_selling_products(context, n_products=n_products) product_3 = create_product("test-sku-3", supplier=supplier, shop=shop) create_order_with_product(product_3, supplier, quantity=2, taxless_base_unit_price=price, shop=shop) cache.clear() # Third product sold in greater quantity assert product_3 in general.get_best_selling_products(context, n_products=n_products) create_order_with_product(product_1, supplier, quantity=4, taxless_base_unit_price=price, shop=shop) create_order_with_product(product_2, supplier, quantity=4, taxless_base_unit_price=price, shop=shop) cache.clear() # Third product outsold by first two products assert product_3 not in general.get_best_selling_products(context, n_products=n_products) children = [create_product("SimpleVarChild-%d" % x, supplier=supplier, shop=shop) for x in range(5)] for child in children: child.link_to_parent(product_3) create_order_with_product(child, supplier, quantity=1, taxless_base_unit_price=price, shop=shop) cache.clear() # Third product now sold in greatest quantity assert product_3 == general.get_best_selling_products(context, n_products=n_products)[0]
def test_sales_between_ranges(): shop = get_default_shop() supplier = get_default_supplier() person = create_random_person() initial_group_count = person.groups.count() sales_ranges = [ ("wood", 15, 0), ("silver", 0, 50), ("diamond", 100, None) ] for identifier, min, max in sales_ranges: create_sales_range(identifier, shop, min, max) payment = create_fully_paid_order(shop, person, supplier, "sku1", 10) assert get_total_sales(shop, person) == 10 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "silver"]) assert not bool([group for group in person.groups.all() if group.identifier == "wood"]) payment = create_fully_paid_order(shop, person, supplier, "sku2", 50) assert get_total_sales(shop, person) == 60 update_customers_groups(Payment, payment) assert person.groups.count() == initial_group_count assert not bool([group for group in person.groups.all() if group.identifier in ["silver", "gold", "diamond"]]) payment = create_fully_paid_order(shop, person, supplier, "sku3", 200) assert get_total_sales(shop, person) == 260 update_customers_groups(Payment, payment) assert person.groups.count() == (initial_group_count + 1) assert bool([group for group in person.groups.all() if group.identifier == "diamond"])
def test_mass_edit_orders(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() contact1 = create_random_person() product1 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="50") product2 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="501") order = create_random_order(customer=contact1, products=[product1, product2], completion_probability=0) assert order.status.role != OrderStatusRole.CANCELED payload = { "action": CancelOrderAction().identifier, "values": [order.pk] } request = apply_request_middleware(rf.post( "/", user=admin_user, )) request._body = json.dumps(payload).encode("UTF-8") view = OrderListView.as_view() response = view(request=request) assert response.status_code == 200 for order in Order.objects.all(): assert order.status.role == OrderStatusRole.CANCELED
def test_mass_edit_orders3(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() contact1 = create_random_person() product1 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="50") product2 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price="501") order1 = create_random_order(customer=contact1, products=[product1, product2], completion_probability=0) order2 = create_random_order(customer=contact1, products=[product1, product2], completion_probability=0) assert order1.status.role != OrderStatusRole.CANCELED assert order2.status.role != OrderStatusRole.CANCELED payload = { "action": OrderConfirmationPdfAction().identifier, "values": [order1.pk, order2.pk] } request = apply_request_middleware(rf.post( "/", user=admin_user, )) request._body = json.dumps(payload).encode("UTF-8") view = OrderListView.as_view() response = view(request=request) assert response.status_code == 200 if weasyprint: assert response['Content-Disposition'] == 'attachment; filename=order_confirmation_pdf.zip' else: assert response["content-type"] == "application/json"
def test_complex_order_tax(include_taxes): tax = get_default_tax() quantities = [44, 23, 65] product = get_default_product() supplier = get_default_supplier() shop = get_default_shop() shop.prices_include_tax = include_taxes shop.save() order = create_empty_order(shop=shop) order.full_clean() order.save() pricing_context = get_pricing_module().get_context_from_data( shop=shop, customer=order.customer or AnonymousContact(), ) total_price = Decimal("0") price = Decimal("50") for quantity in quantities: total_price += quantity * price add_product_to_order(order, supplier, product, quantity, price, tax.rate, pricing_context) order.cache_prices() order.save() currency = "EUR" summary = order.get_tax_summary()[0] assert summary.tax_rate == tax.rate assert summary.based_on == Money(total_price, currency) assert summary.tax_amount == Money(total_price * tax.rate, currency) assert summary.taxful == summary.based_on + summary.tax_amount assert order.get_total_tax_amount() == Money(total_price * tax.rate, currency)
def get_package_product(): """ :rtype: shuup.core.models.Product """ shop = get_default_shop() supplier = get_default_supplier() return create_package_product("PackageParent", shop=shop, supplier=supplier)
def test_productfilter_works(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price product_price = "100" discount_percentage = "0.30" supplier = get_default_supplier() product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=product_price) shop_product = product.get_shop_instance(shop) # create catalog campaign catalog_filter = ProductFilter.objects.create() catalog_filter.products.add(product) assert catalog_filter.matches(shop_product) catalog_campaign = CatalogCampaign.objects.create(shop=shop, active=True, name="test") catalog_campaign.filters.add(catalog_filter) cdp = ProductDiscountPercentage.objects.create(campaign=catalog_campaign, discount_percentage=discount_percentage) # add product to basket basket = get_basket(request) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) basket.shipping_method = get_shipping_method(shop=shop) basket.save() expected_total = price(product_price) - (Decimal(discount_percentage) * price(product_price)) assert basket.total_price == expected_total
def test_edit_object_view_errors(rf, admin_user): shop = factories.get_default_shop() view = EditObjectView.as_view() # missing params response = view(apply_request_middleware(rf.get(reverse("shuup_admin:edit")), user=admin_user, shop=shop)) assert response.status_code == 400 assert "Invalid object" in response.content.decode("utf-8") # invalid model response = _get_edit_object_view(rf, view, ".", None, admin_user, shop) assert response.status_code == 400 assert "Invalid model" in response.content.decode("utf-8") # invalid object ID product = factories.create_product("p1", shop, factories.get_default_supplier()) model = ".".join(ContentType.objects.get_for_model(product).natural_key()) with pytest.raises(Http404) as error: _get_edit_object_view(rf, view, model, product.id + 10, admin_user, shop) assert "Object not found" in str(error) # object has no admin url from shuup.core.models import ConfigurationItem config = ConfigurationItem.objects.create(shop=shop, key="test", value={"value": 123}) model = ".".join(ContentType.objects.get_for_model(config).natural_key()) with pytest.raises(Http404) as error: _get_edit_object_view(rf, view, model, config.id, admin_user, shop) assert "Object not found" in str(error)
def test_discounted_price(rf): shop = factories.get_default_shop() supplier = factories.get_default_supplier() request = rf.get("/") request.shop = shop apply_request_middleware(request) assert request.shop == shop original_price = 10 product = factories.create_product("test1", shop=shop, supplier=supplier, default_price=original_price) shop_product = product.get_shop_instance(shop) # Set discount with discount amount for $2 discount_amount = 2 discount = Discount.objects.create( active=True, product=product, supplier=supplier, discount_amount_value=discount_amount) discount.shops = [shop] # Even though the supplier is matching with the product there is no discount # since the supplier is not in pricing context. assert not hasattr(request, "supplier") assert supplier in shop_product.suppliers.all() assert product.get_price_info(request).price == request.shop.create_price(10) setattr(request, "supplier", supplier) assert product.get_price_info(request).price == request.shop.create_price(8) # No discount once we change the discount supplier new_supplier = Supplier.objects.create(identifier="*") discount.supplier = new_supplier discount.save() assert product.get_price_info(request).price == request.shop.create_price(10)
def test_refunds_with_quantities(): shop = get_default_shop() supplier = get_default_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, stock_behavior=StockBehavior.STOCKED ) order = create_order_with_product(product, supplier, 3, 200, shop=shop) order.cache_prices() assert not order.lines.refunds() product_line = order.lines.first() refund_amount = Money(100, order.currency) order.create_refund([{"line": product_line, "quantity": 2, "amount": refund_amount}]) assert len(order.lines.refunds()) == 2 quantity_line = order.lines.refunds().filter(quantity=2).first() assert quantity_line amount_line = order.lines.refunds().filter(quantity=1).first() assert amount_line assert quantity_line.taxful_base_unit_price == -product_line.taxful_base_unit_price assert amount_line.taxful_price.amount == -refund_amount
def test_create_full_refund_view(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() product = create_product(sku="test-sku", shop=shop, supplier=supplier, default_price=3.33) order = create_order_with_product(product, supplier, quantity=1, taxless_base_unit_price=1, shop=shop) order.cache_prices() original_total_price = order.taxful_total_price assert not order.has_refunds() assert len(order.lines.all()) == 1 assert order.taxful_total_price.amount.value != 0 data = { "restock_products": "on", } request = apply_request_middleware(rf.post("/", data=data), user=admin_user) view = OrderCreateFullRefundView.as_view() response = view(request, pk=order.pk) assert response.status_code == 302 assert order.has_refunds() order.cache_prices() assert order.taxful_total_price.amount.value == 0 refund_line = order.lines.filter(type=OrderLineType.REFUND).last() assert refund_line assert refund_line.taxful_price == -original_total_price
def test_all_categories_view(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() category = get_default_category() product = get_default_product() request = apply_request_middleware(rf.get("/")) _check_product_count(request, 0) shop_product = product.get_shop_instance(shop) shop_product.categories.add(category) _check_product_count(request, 1) # Create few categories for better test results for i in range(10): cat = Category.objects.create(name=printable_gibberish()) cat.shops.add(shop) new_product_count = random.randint(1, 3) + 1 for i in range(1, new_product_count): product = create_product("sku-%s" % i, shop=shop, supplier=supplier, default_price=10) shop_product = product.get_shop_instance(shop) # Add random categories expect default category which we will make # hidden to make sure that products linked to hidden categories are # not listed shop_product.categories = Category.objects.exclude(id=category.pk).order_by("?")[:i] _check_product_count(request, new_product_count) category.status = CategoryStatus.INVISIBLE category.save() _check_product_count(request, new_product_count - 1)
def test_coupon_amount_limit(): coupon = Coupon.objects.create(code="TEST", active=True) get_default_campaign(coupon) contact = create_random_person() shop = get_default_shop() product = create_product("test", shop=shop, supplier=get_default_supplier(), default_price="12") order = create_random_order(customer=contact) for x in range(50): coupon.use(order) assert coupon.usages.count() == 50 coupon.increase_usage_limit_by(5) coupon.save() assert coupon.usage_limit == 55 assert coupon.can_use_code(contact) for x in range(5): coupon.use(order) assert coupon.usages.count() == 55 assert not Coupon.is_usable(coupon.code, order.customer) assert coupon.usages.count() == 55 # no change, limit met
def test_refunds(admin_user): shop = get_default_shop() supplier = get_default_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) tax_rate = Decimal("0.1") taxless_base_unit_price = shop.create_price(200) order = create_order_with_product(product, supplier, 3, taxless_base_unit_price, tax_rate, shop=shop) order.payment_status = PaymentStatus.DEFERRED order.cache_prices() order.save() assert len(order.lines.all()) == 1 assert order.can_create_refund() assert not order.has_refunds() client = _get_client(admin_user) # Refund first and the only order line in 3 parts refund_url = "/api/shuup/order/%s/create_refund/" % order.id product_line = order.lines.first() data = { "refund_lines": [{ "line": product_line.id, "quantity": 1, "amount": (product_line.taxful_price.amount.value / 3), "restock_products": False }] } # First refund response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_201_CREATED order.refresh_from_db() assert order.lines.count() == 2 assert order.has_refunds() # Second refund response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_201_CREATED order.refresh_from_db() assert order.lines.count() == 3 assert order.can_create_refund() # Third refund response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_201_CREATED order.refresh_from_db() assert order.lines.count() == 4 assert not order.can_create_refund() assert not order.taxful_total_price.amount assert order.get_total_tax_amount() == Money( (order.taxful_total_price_value - order.taxless_total_price_value), order.currency) # Test error message response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST error_msg = json.loads(response.content.decode("utf-8"))["error"] assert error_msg == "Order can not be refunded at the moment."
def test_bump_caches_signal(rf): """ Test that prices are bumped when discount objects changes """ initial_price = 10 discounted_price = 5 shop1 = factories.get_default_shop() shop2 = factories.get_shop(identifier="shop2", domain="shop2") product1 = factories.create_product( "product", shop=shop1, supplier=factories.get_default_supplier(), default_price=initial_price) product2 = factories.create_product( "product2", shop=shop2, supplier=factories.get_default_supplier(), default_price=20) now = datetime(2018, 1, 1, 9, 0, tzinfo=pytz.UTC) # 01/01/2018 09:00 AM with patch("django.utils.timezone.now", new=lambda: now): discount = Discount.objects.create( name="discount", active=True, start_datetime=now - timedelta(days=10), end_datetime=now + timedelta(days=10), discounted_price_value=discounted_price) request = apply_request_middleware(rf.get("/")) request_shop2 = apply_request_middleware( rf.get("/", HTTP_HOST=shop2.domain)) def assert_cache_product1(discounted=False): cache_price_info(request, product1, 1, product1.get_price_info(request)) if discounted: assert get_cached_price_info( request, product1, 1).price == shop1.create_price(discounted_price) else: assert get_cached_price_info( request, product1, 1).price == shop1.create_price(initial_price) def assert_product1_is_not_cached(): assert get_cached_price_info(request, product1) is None def assert_product2_is_cached(): assert get_cached_price_info(request_shop2, product2) is not None assert_product1_is_not_cached() assert_cache_product1() # cache bumped - the cache should be dropped - then, cache again discount.save() assert_product1_is_not_cached() assert_cache_product1() discount.shops.add(shop1) assert_product1_is_not_cached() assert_cache_product1(True) # cache product 2.. from now on, shop2 cache should never be bumped cache_price_info(request_shop2, product2, 1, product2.get_price_info(request_shop2)) assert_product2_is_cached() discount.product = product1 discount.save() assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() availability_exception = AvailabilityException.objects.create( name="ae1", start_datetime=now + timedelta(days=20), end_datetime=now + timedelta(days=30), ) availability_exception.discounts.add(discount) assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() availability_exception.save() assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() happy_hour = HappyHour.objects.create(name="hh 1") happy_hour.discounts.add(discount) assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() happy_hour.save() assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() time_range = TimeRange.objects.create( happy_hour=happy_hour, from_hour=(now - timedelta(hours=1)).time(), to_hour=(now + timedelta(hours=1)).time(), weekday=now.weekday()) assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() time_range.save() assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() time_range.delete() assert_product1_is_not_cached() assert_cache_product1(True) assert_product2_is_cached() with pytest.raises(DiscountM2MChangeError): handle_generic_m2m_changed("test", time_range)
user, shop, mode=None): data = {"model": model_name, "id": object_id} if mode: data["mode"] = mode request = apply_request_middleware(rf.get(reverse("shuup_admin:edit"), data), user=user, shop=shop) return view(request) @pytest.mark.parametrize("creator_fn", [ lambda: factories.create_product("sku", factories.get_default_shop(), factories.get_default_supplier()), lambda: factories.create_random_person(), lambda: factories.create_random_company(), lambda: factories.create_random_order( customer=factories.create_random_person(), products=[ factories.create_product("p", factories.get_default_shop(), factories.get_default_supplier()) ]), lambda: factories.create_random_user(), ]) @pytest.mark.django_db def test_edit_object_view(rf, admin_user, creator_fn): shop = factories.get_default_shop() view = EditObjectView.as_view() object_instance = creator_fn()
def test_serialize_data(): """ Test that contact data is serialized """ activate("en") shop = factories.get_default_shop() customer = factories.create_random_person("en") user = factories.create_random_user("en") customer.user = user customer.default_billing_address = factories.create_random_address() customer.default_shipping_address = factories.create_random_address() customer.save() company = factories.create_random_company() company.default_billing_address = factories.create_random_address() company.default_shipping_address = factories.create_random_address() company.save() company.members.add(customer) product = factories.create_product("p1", shop, factories.get_default_supplier()) orders = [] core_baskets = [] front_baskets = [] for basket_customer in [customer, company]: orders.extend([ factories.create_random_order(basket_customer, [product]) for order in range(3) ]) front_baskets.append( StoredBasket.objects.create( key=uuid4().hex, shop=shop, customer=basket_customer, orderer=customer, creator=customer.user, currency=shop.currency, data={"items": []}, prices_include_tax=shop.prices_include_tax)) core_baskets.append( Basket.objects.create(key=uuid4().hex, shop=shop, customer=basket_customer, orderer=customer, creator=customer.user, currency=shop.currency, data={"items": []}, prices_include_tax=shop.prices_include_tax)) person_data = GDPRPersonContactSerializer(customer).data assert person_data["name"] == customer.name assert person_data["phone"] == customer.phone assert person_data["default_billing_address"][ "street"] == customer.default_billing_address.street assert person_data["default_shipping_address"][ "street"] == customer.default_shipping_address.street assert person_data["user"]["id"] == customer.user.id assert person_data["user"]["username"] == customer.user.username assert person_data["company_memberships"][0]["name"] == company.name assert person_data["company_memberships"][0]["id"] == company.id assert len(person_data["orders"]) == 3 assert len(person_data["saved_baskets"]) == 1 assert len(person_data["baskets"]) == 1
def test_variation_product_price_more_complex(client): shop = get_default_shop() supplier = get_default_supplier() product_data = { "supplier-1": { "sizes": ["S", "M", "XL"], "colors": ["Black", "Yellow", "Blue"], "material": ["leather", "cotton"] }, "supplier-2": { "sizes": ["S", "XL", "XS", "XXL", "M"], "colors": ["Yellow", "Red"], "material": ["cotton"] }, } parent = create_product("ComplexVarParent", shop=shop) shop_parent_product = parent.get_shop_instance(shop) for key, data in six.iteritems(product_data): supplier = Supplier.objects.create(identifier=key) for size in data["sizes"]: for color in data["colors"]: for material in data["material"]: sku = "ComplexVarChild-%s-%s-%s" % (size, color, material) shop_product = ShopProduct.objects.filter( product__sku=sku).first() if shop_product: shop_product.suppliers.add(supplier) else: child = create_product(sku, shop=shop, supplier=supplier) child.link_to_parent(parent, variables={ "size": size, "color": color, "material": material }) assert parent.variation_children.count() == 25 # We have 6 different combinations but only 5 combinations # has product in them. assert len(list(parent.get_all_available_combinations())) == 40 # Lets test prices for suppliers for key, data in six.iteritems(product_data): supplier_id = Supplier.objects.get(identifier=key).id available_combinations = set() for size in data["sizes"]: for color in data["colors"]: for material in data["material"]: available_combinations.add( "color: %s, material: %s, size: %s" % (color, material, size)) expected_orderable_count = len(available_combinations) actual_orderable_count = 0 had_at_least_one_not_orderable_in_this_test = False for combination in parent.get_all_available_combinations(): product_result_pk = combination["result_product_pk"] if not product_result_pk: # we can skip combinations without products continue variable_string = "" for key, value in six.iteritems(combination["variable_to_value"]): variable_string += "var_%s=%s&" % (key.pk, value.pk) response = client.get( reverse('shuup:xtheme_extra_view', kwargs={'view': 'product_price'}) + "?id=%s&quantity=%s&%ssupplier=%s" % (parent.pk, 1, variable_string, supplier_id)) assert response.context_data["product"] == Product.objects.filter( id=product_result_pk).first() content = response.content.decode("utf-8") is_orderable = combination[ "text_description"] in available_combinations if is_orderable: actual_orderable_count += 1 assert "form" in content else: had_at_least_one_not_orderable_in_this_test = True assert "Combination not available" in content assert actual_orderable_count == expected_orderable_count assert had_at_least_one_not_orderable_in_this_test
def create_products(shop): supplier = get_default_supplier() for i in range(0, 200): sku = "sku-%d" % i create_product(sku, shop, supplier, default_price=i)
def test_view_availability(admin_user, rf): supplier = get_default_supplier() shop_one = get_shop(True, "USD", enabled=True, identifier="one", name="Shop One") supplier.shops.add(shop_one) simone = create_random_user(username="******") simone.is_staff = True simone.save() shop_one.staff_members.add(simone) peter = create_random_user(username="******") peter.is_staff = True peter.save() shop_one.staff_members.add(peter) shop_two = get_shop(True, "USD", enabled=True, identifier="two", name="Shop Two") assert shop_one.pk != shop_two.pk supplier.shops.add(shop_two) calle = create_random_user(username="******") calle.is_staff = True calle.save() shop_two.staff_members.add(calle) product = create_product("simple-test-product", shop_one) order = create_order_with_product(product, supplier, 6, 6, shop=shop_one) # Simone and Peter should access to this order. Calle should get 404 def test_view(view, order, shop, user, data=None): if data: request = apply_request_middleware(rf.post("/", data), user=user, shop=shop) else: request = apply_request_middleware(rf.get("/"), user=user, shop=shop) response = view.as_view()(request, pk=order.pk) # Gets for view in [ OrderDetailView, OrderSetStatusView, OrderCreatePaymentView, OrderSetPaidView, OrderAddressEditView ]: test_view(view, order, shop_one, simone) test_view(view, order, shop_one, peter) with pytest.raises(Http404): test_view(view, order, shop_two, calle) test_view(NewLogEntryView, order, shop_one, simone, {"message": "message here"}) test_view(NewLogEntryView, order, shop_one, peter, {"message": "message here"}) with pytest.raises(Http404): test_view(NewLogEntryView, order, shop_two, calle, {"message": "message here"}) test_view(UpdateAdminCommentView, order, shop_one, simone, {"comment": "comment here"}) test_view(UpdateAdminCommentView, order, shop_one, peter, {"comment": "comment here"}) with pytest.raises(Http404): test_view(UpdateAdminCommentView, order, shop_two, calle, {"comment": "comment here"}) def test_shipment_view(order, shop, supplier, user): request = apply_request_middleware(rf.get("/"), user=user, shop=shop) response = OrderCreateShipmentView.as_view()(request, pk=order.pk, supplier_pk=supplier.pk) test_shipment_view(order, shop_one, supplier, simone) test_shipment_view(order, shop_one, supplier, peter) with pytest.raises(Http404): test_shipment_view(order, shop_two, supplier, calle) # Create shipment to test delete shipment view shipment = order.create_shipment_of_all_products(supplier) def test_shipment_delete_view(shipment, shop, user): request = apply_request_middleware(rf.post("/"), user=user, shop=shop) response = ShipmentDeleteView.as_view()(request, pk=shipment.pk, supplier_pk=supplier.pk) test_shipment_delete_view(shipment, shop_one, simone) test_shipment_delete_view(shipment, shop_one, peter) with pytest.raises(Http404): test_shipment_delete_view(shipment, shop_two, calle) # Create payment to test refund and delete payment view order.create_payment(order.taxful_total_price) payment = order.payments.first() assert payment is not None for view in [OrderCreateRefundView, OrderCreateFullRefundView]: test_view(view, order, shop_one, simone) test_view(view, order, shop_one, peter) with pytest.raises(Http404): test_view(view, order, shop_two, calle) def test_payment_delete_view(payment, shop, user): request = apply_request_middleware(rf.post("/"), user=user, shop=shop) response = OrderDeletePaymentView.as_view()(request, pk=payment.pk) test_payment_delete_view(payment, shop_one, simone) test_payment_delete_view(payment, shop_one, peter) with pytest.raises(Http404): test_payment_delete_view(payment, shop_two, calle)
def test_broken_order(admin_user): """ """ quantities = [44, 23, 65] expected = sum(quantities) * 50 expected_based_on = expected / 1.5 # Shuup is calculating taxes per line so there will be some "errors" expected_based_on = ensure_decimal_places( Decimal("%s" % (expected_based_on + 0.01))) shop = get_default_shop() supplier = get_default_supplier() product1 = create_product("simple-test-product1", shop, supplier, 50) product2 = create_product("simple-test-product2", shop, supplier, 50) product3 = create_product("simple-test-product3", shop, supplier, 50) tax = get_default_tax() source = BasketishOrderSource(get_default_shop()) billing_address = get_address(country="US") shipping_address = get_address(name="Test street", country="US") source.status = get_initial_order_status() source.billing_address = billing_address source.shipping_address = shipping_address source.customer = create_random_person() source.payment_method = get_default_payment_method() source.shipping_method = get_default_shipping_method() source.add_line( type=OrderLineType.PRODUCT, product=product1, supplier=get_default_supplier(), quantity=quantities[0], base_unit_price=source.create_price(50), ) source.add_line( type=OrderLineType.PRODUCT, product=product2, supplier=get_default_supplier(), quantity=quantities[1], base_unit_price=source.create_price(50), ) source.add_line( type=OrderLineType.PRODUCT, product=product3, supplier=get_default_supplier(), quantity=quantities[2], base_unit_price=source.create_price(50), ) currency = "EUR" summary = source.get_tax_summary() assert len(summary) == 1 summary = summary[0] assert summary.taxful == Money(expected, "EUR") assert summary.based_on == Money(expected_based_on, "EUR") # originally non-rounded value assert bankers_round(source.get_total_tax_amount()) == summary.tax_amount assert source.taxless_total_price.value == expected_based_on assert summary.taxful.value == source.taxful_total_price.value assert summary.tax_amount == Money( bankers_round(source.taxful_total_price.value - source.taxless_total_price.value), currency) assert summary.taxful == summary.raw_based_on + summary.tax_amount assert summary.tax_rate == tax.rate assert summary.taxful.value == ( summary.based_on + summary.tax_amount).value - Decimal("%s" % 0.01) # create order from basket creator = OrderCreator() order = creator.create_order(source) assert order.taxless_total_price.value == expected_based_on # originally non-rounded value assert bankers_round(order.get_total_tax_amount()) == summary.tax_amount
def test_refunds(): shop = get_default_shop() supplier = get_default_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) tax_rate = Decimal("0.1") taxless_base_unit_price = shop.create_price(200) order = create_order_with_product(product, supplier, 3, taxless_base_unit_price, tax_rate, shop=shop) order.payment_status = PaymentStatus.DEFERRED order.cache_prices() order.save() assert order.get_total_refunded_amount().value == 0 assert order.get_total_unrefunded_amount( ).value == order.taxful_total_price.value assert not order.can_edit() assert len(order.lines.all()) == 1 product_line = order.lines.first() assert product_line.ordering == 0 assert order.can_create_refund() assert not order.has_refunds() order.create_refund([{ "line": product_line, "quantity": 1, "amount": (product_line.taxful_price.amount / 3) }]) assert len(order.lines.all()) == 2 assert order.lines.last().ordering == 1 assert order.has_refunds() # Confirm the value of the refund assert order.lines.last().taxful_price == -product_line.base_unit_price assert order.lines.last().tax_amount == -( product_line.taxless_base_unit_price * tax_rate).amount # Create a refund with a parent line and amount order.create_refund([{ "line": product_line, "quantity": 1, "amount": product_line.taxful_price.amount / 3 }]) assert len(order.lines.all()) == 3 assert order.lines.last().ordering == 2 assert order.lines.last( ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate) assert order.lines.last( ).tax_amount == -taxless_base_unit_price.amount * tax_rate assert order.taxless_total_price.amount == taxless_base_unit_price.amount assert order.taxful_total_price.amount == taxless_base_unit_price.amount * ( 1 + tax_rate) assert order.can_create_refund() assert order.get_total_tax_amount() == Money( (order.taxful_total_price_value - order.taxless_total_price_value), order.currency) # Try to refunding remaining amount without a parent line with pytest.raises(AssertionError): order.create_refund([{"amount": taxless_base_unit_price}]) # refund remaining amount order.create_refund([{ "line": product_line, "quantity": 1, "amount": product_line.taxful_price.amount / 3 }]) assert len(order.lines.all()) == 4 assert order.lines.last().ordering == 3 assert order.lines.last( ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate) assert not order.taxful_total_price.amount assert not order.can_create_refund() assert order.get_total_tax_amount() == Money( (order.taxful_total_price_value - order.taxless_total_price_value), order.currency) with pytest.raises(RefundExceedsAmountException): order.create_refund([{ "line": product_line, "quantity": 1, "amount": taxless_base_unit_price.amount }])
def test_multiple_campaigns_cheapest_price(): rf = RequestFactory() request, shop, group = initialize_test(rf, False) price = shop.create_price product_price = "100" discount_percentage = "0.30" discount_amount_value = "10" total_discount_amount = "50" expected_total = price(product_price) - (Decimal(discount_percentage) * price(product_price)) matching_expected_total = price(product_price) - price( total_discount_amount) category = get_default_category() supplier = get_default_supplier() product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=product_price) shop_product = product.get_shop_instance(shop) shop_product.categories.add(category) # create catalog campaign catalog_filter = ProductFilter.objects.create() catalog_filter.products.add(product) catalog_campaign = CatalogCampaign.objects.create(shop=shop, active=True, name="test") catalog_campaign.filters.add(catalog_filter) cdp = ProductDiscountPercentage.objects.create( campaign=catalog_campaign, discount_percentage=discount_percentage) # create basket campaign condition = CategoryProductsBasketCondition.objects.create( operator=ComparisonOperator.EQUALS, quantity=1) condition.categories.add(category) basket_campaign = BasketCampaign.objects.create(shop=shop, public_name="test", name="test", active=True) basket_campaign.conditions.add(condition) effect = DiscountFromProduct.objects.create( campaign=basket_campaign, discount_amount=discount_amount_value) effect.products.add(product) # add product to basket basket = get_basket(request) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=1) basket.shipping_method = get_shipping_method(shop=shop) final_lines = basket.get_final_lines() assert len(final_lines) == 2 assert basket.total_price == expected_total effect.discount_amount = total_discount_amount effect.save() basket.uncache() catalog_campaign.save() # save to bump caches basket_campaign.save() # save to bump caches assert basket.total_price == matching_expected_total # discount is now bigger than the original effect.delete() # remove effect basket.uncache() catalog_campaign.save() # save to bump caches basket_campaign.save() # save to bump caches assert BasketLineEffect.objects.count() == 0 assert basket.total_price == expected_total # add new effect effect = DiscountFromCategoryProducts.objects.create( category=category, campaign=basket_campaign, discount_amount=discount_amount_value) assert basket.total_price == expected_total effect.discount_amount = total_discount_amount effect.save() basket.uncache() catalog_campaign.save() # save to bump caches basket_campaign.save() # save to bump caches assert basket.total_price == matching_expected_total # discount is now bigger than the original
def test_undiscounted_effects(rf, include_tax): request, shop, _ = initialize_test(rf, include_tax) basket = get_basket(request) supplier = get_default_supplier() single_product_price = Decimal(50) discounted_product_quantity = 4 normal_priced_product_quantity = 2 discount_percentage = Decimal(0.2) # 20% discount_amount = basket.create_price(single_product_price * normal_priced_product_quantity * discount_percentage) category = CategoryFactory() discounted_product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) second_product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) ShopProduct.objects.get( shop=shop, product=discounted_product).categories.add(category) ShopProduct.objects.get(shop=shop, product=second_product).categories.add(category) basket.add_product(supplier=supplier, shop=shop, product=discounted_product, quantity=discounted_product_quantity) basket.add_product(supplier=supplier, shop=shop, product=second_product, quantity=normal_priced_product_quantity) basket.shipping_method = get_shipping_method(shop=shop) basket.save() # Store basket price before any campaigns exists original_price = basket.total_price # CatalogCampaign catalog_campaign = CatalogCampaign.objects.create(active=True, shop=shop, name="test", public_name="test") # Limit catalog campaign to "discounted_product" product_filter = ProductFilter.objects.create() product_filter.products.add(discounted_product) catalog_campaign.filters.add(product_filter) # BasketCampaign campaign = BasketCampaign.objects.create(active=True, shop=shop, name="test2", public_name="test2") final_lines = basket.get_final_lines() assert len(final_lines) == 3 # Discount based on undiscounted product values DiscountPercentageFromUndiscounted.objects.create( campaign=campaign, discount_percentage=discount_percentage) basket.uncache() final_lines = basket.get_final_lines() assert len(final_lines) == 4 discounted_basket_price = original_price - discount_amount assert basket.total_price.as_rounded( ) == discounted_basket_price.as_rounded()
def test_suppliers_deleted(): supplier = get_default_supplier() supplier.soft_delete() assert supplier.deleted is True
def test_discount_no_limits(rf, include_tax): # check whether is it possible to earn money buying from us # adding lots of effects request, shop, _ = initialize_test(rf, include_tax) basket = get_basket(request) supplier = get_default_supplier() single_product_price = Decimal(50) quantity = 4 discount_amount = (single_product_price * quantity * 2) discount_percentage = Decimal(1.9) # 190% second_product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) # the expected discount amount should not be greater than the products expected_discount_amount = basket.create_price( single_product_price) * quantity category = CategoryFactory() # create basket rule that requires 2 products in basket product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) ShopProduct.objects.get(shop=shop, product=product).categories.add(category) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=quantity) basket.shipping_method = get_shipping_method(shop=shop) basket.save() rule = ProductsInBasketCondition.objects.create(quantity=2) rule.products.add(product) rule.save() # BasketCampaign campaign = BasketCampaign.objects.create(active=True, shop=shop, name="test", public_name="test") campaign.conditions.add(rule) # effect 1 - categories from products DiscountFromCategoryProducts.objects.create( campaign=campaign, discount_percentage=discount_percentage, category=category) # effect 2 - discount from products effect2 = DiscountFromProduct.objects.create( campaign=campaign, discount_amount=discount_amount) effect2.products.add(product) # effect 3 - basket discount BasketDiscountAmount.objects.create(campaign=campaign, discount_amount=discount_amount) # effect 4 - basket discount percetage BasketDiscountPercentage.objects.create( campaign=campaign, discount_percentage=discount_percentage) basket.uncache() final_lines = basket.get_final_lines() assert len(final_lines) == 3 original_price = basket.create_price(single_product_price) * quantity line = final_lines[0] assert line.discount_amount == expected_discount_amount assert basket.total_price == original_price - expected_discount_amount # effect free - aaaww yes, it's free effect_free = FreeProductLine.objects.create(campaign=campaign, quantity=1) effect_free.products.add(second_product) basket.uncache() final_lines = basket.get_final_lines() assert len(final_lines) == 4 line = final_lines[0] assert line.discount_amount == expected_discount_amount assert basket.total_price == original_price - expected_discount_amount # CatalogCampaign catalog_campaign = CatalogCampaign.objects.create(active=True, shop=shop, name="test2", public_name="test") product_filter = ProductFilter.objects.create() product_filter.products.add(product) catalog_campaign.filters.add(product_filter) # effect 5 - ProductDiscountAmount ProductDiscountAmount.objects.create(campaign=catalog_campaign, discount_amount=discount_amount) # effct 6 - ProductDiscountPercentage ProductDiscountPercentage.objects.create( campaign=catalog_campaign, discount_percentage=discount_percentage) basket.uncache() final_lines = basket.get_final_lines() assert len(final_lines) == 4 line = final_lines[0] assert line.discount_amount == expected_discount_amount assert basket.total_price == original_price - expected_discount_amount
def test_order_received_variables(rf, with_shop_contact): activate("en") shop = get_shop(True) contact_address = get_address(**SHOP_ADDRESS_DATA) contact_address.save() shop.contact_address = contact_address shop.save() get_default_product() get_default_supplier(shop) STEP_DATA = [{ "cond_op": "all", "enabled": True, "next": "continue", "actions": [{ "template_data": { "en": { "body": "{{ customer_email }}", "content_type": "plain", "subject": "{{ customer_phone }}" } }, "identifier": "send_email", "language": { "constant": "en" }, "recipient": { "constant": "*****@*****.**" } }] }] if with_shop_contact: STEP_DATA[0]['actions'].insert(0, { "template_data": { "en": { "body": "{{ shop_email }}", "content_type": "plain", "subject": "{{ shop_phone }}" } }, "identifier": "send_email", "language": { "constant": "en" }, "recipient": { "constant": "*****@*****.**" } }) sc = Script.objects.create( name="variables script", event_identifier="order_received", enabled=True, shop=shop) sc.set_serialized_steps(STEP_DATA) sc.save() n_outbox_pre = len(mail.outbox) customer = create_random_person(locale='en') address = get_address(**DEFAULT_ADDRESS_DATA) address.save() customer.default_shipping_address = address customer.save() order = create_random_order(customer, shop=shop) assert (len(mail.outbox) == n_outbox_pre + (2 if with_shop_contact else 1)), "Sending email failed" latest_mail = mail.outbox[-1] assert latest_mail.subject == customer.default_shipping_address.phone assert latest_mail.body == customer.default_shipping_address.email if with_shop_contact: # shop email is sent first - we use insert(0, shop_step_data) penult_mail = mail.outbox[-2] assert penult_mail.subject == shop.contact_address.phone assert penult_mail.body == shop.contact_address.email
def test_product_total_sales_report(rf, admin_user, order_by): with override_provides("reports", [ "shuup.default_reports.reports.product_total_sales:ProductSalesReport" ]): shop = get_default_shop() supplier = get_default_supplier() product1 = create_product("product1", supplier=supplier, shop=shop) product2 = create_product("product2", supplier=supplier, shop=shop) p1_qtd, p1_price, p1_tr, p1_lines = Decimal(3), Decimal(5), Decimal( 0), 5 p2_qtd, p2_price, p2_tr, p2_lines = Decimal(4), Decimal(5), Decimal( 0.95), 3 order = create_order_with_product(product=product1, supplier=supplier, quantity=p1_qtd, taxless_base_unit_price=p1_price, tax_rate=p1_tr, n_lines=p1_lines, shop=shop) order.create_payment(order.taxful_total_price.amount) order2 = create_order_with_product(product=product2, supplier=supplier, quantity=p2_qtd, taxless_base_unit_price=p2_price, tax_rate=p2_tr, n_lines=p2_lines, shop=shop) order2.create_payment(order2.taxful_total_price.amount) data = { "report": ProductSalesReport.get_name(), "shop": shop.pk, "date_range": DateRangeChoices.ALL_TIME.value, "writer": "json", "force_download": 1, "order_by": order_by } view = ReportView.as_view() request = apply_request_middleware(rf.post("/", data=data), user=admin_user) response = view(request) if hasattr(response, "render"): response.render() assert response.status_code == 200 json_data = json.loads(response.content.decode("utf-8")) assert force_text(ProductSalesReport.title) in json_data.get("heading") data = json_data["tables"][0]["data"] assert len(data) == 2 p1_total_qtd = p1_qtd * p1_lines p1_taxless_total = p1_total_qtd * p1_price p1_taxful_total = p1_taxless_total * (1 + p1_tr) p2_total_qtd = p2_qtd * p2_lines p2_taxless_total = p2_total_qtd * p2_price p2_taxful_total = p2_taxless_total * (1 + p2_tr) if order_by == "quantity": p1 = data[0] p2 = data[1] elif order_by == "taxless_total": p1 = data[0] p2 = data[1] else: # order_by == "taxful_total": p1 = data[1] p2 = data[0] precision = Decimal('0.1')**2 assert p1["product"] == product1.name assert Decimal(p1["quantity"]) == p1_total_qtd assert Decimal( p1["taxless_total"]) == p1_taxless_total.quantize(precision) assert Decimal( p1["taxful_total"]) == p1_taxful_total.quantize(precision) assert p2["product"] == product2.name assert Decimal(p2["quantity"]) == p2_total_qtd assert Decimal( p2["taxless_total"]) == p2_taxless_total.quantize(precision) assert Decimal( p2["taxful_total"]) == p2_taxful_total.quantize(precision)
def get_frontend_order_state(contact, valid_lines=True): """ Get a dict structure mirroring what the frontend JavaScript would submit. :type contact: Contact|None """ translation.activate("en") shop = get_default_shop() tax = Tax.objects.create(code="test_code", rate=decimal.Decimal("0.20"), name="Default") tax_class = TaxClass.objects.create(identifier="test_tax_class", name="Default") rule = TaxRule.objects.create(tax=tax) rule.tax_classes.add(tax_class) rule.save() product = create_product(sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop) product.tax_class = tax_class product.save() if valid_lines: lines = [ { "id": "x", "type": "product", "product": { "id": product.id }, "quantity": "32", "baseUnitPrice": 50 }, { "id": "y", "type": "other", "sku": "hello", "text": "A greeting", "quantity": 1, "unitPrice": "5.5" }, { "id": "z", "type": "text", "text": "This was an order!", "quantity": 0 }, ] else: unshopped_product = create_product(sku=printable_gibberish(), supplier=get_default_supplier()) not_visible_product = create_product(sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop) not_visible_shop_product = not_visible_product.get_shop_instance(shop) not_visible_shop_product.visible = False not_visible_shop_product.save() lines = [ { "id": "x", "type": "product" }, # no product? { "id": "x", "type": "product", "product": { "id": unshopped_product.id } }, # not in this shop? { "id": "y", "type": "product", "product": { "id": -product.id } }, # invalid product? { "id": "z", "type": "other", "quantity": 1, "unitPrice": "q" }, # what's that price? { "id": "rr", "type": "product", "quantity": 1, "product": { "id": not_visible_product.id } } # not visible ] state = { "customer": { "id": contact.id if contact else None }, "lines": lines, "methods": { "shippingMethod": { "id": get_default_shipping_method().id }, "paymentMethod": { "id": get_default_payment_method().id }, }, "shop": { "selected": { "id": shop.id, "name": shop.name, "currency": shop.currency, "priceIncludeTaxes": shop.prices_include_tax } } } return state
def test_basket_free_product(rf): request, shop, _ = initialize_test(rf, False) basket = get_basket(request) supplier = get_default_supplier() single_product_price = "50" original_quantity = 2 # create basket rule that requires 2 products in basket product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=2) basket.shipping_method = get_shipping_method(shop=shop) basket.save() second_product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) # no shop third_product = create_product(printable_gibberish(), supplier=supplier) rule = BasketTotalProductAmountCondition.objects.create(value="2") campaign = BasketCampaign.objects.create(active=True, shop=shop, name="test", public_name="test") campaign.conditions.add(rule) effect = FreeProductLine.objects.create(campaign=campaign, quantity=2) effect.products.add(second_product) discount_lines_count = len(effect.get_discount_lines(basket, [])) assert discount_lines_count == 1 # do not affect as there is no shop product for the product effect.products.add(third_product) assert len(effect.get_discount_lines(basket, [])) == discount_lines_count basket.uncache() final_lines = basket.get_final_lines() assert len(final_lines) == 3 line_types = [l.type for l in final_lines] assert OrderLineType.DISCOUNT not in line_types for line in basket.get_final_lines(): assert line.type in [OrderLineType.PRODUCT, OrderLineType.SHIPPING] if line.type == OrderLineType.SHIPPING: continue if line.product != product: assert line.product == second_product assert line.line_source == LineSource.DISCOUNT_MODULE assert line.quantity == original_quantity else: assert line.line_source == LineSource.CUSTOMER
def test_product_catalog_cgp_with_variations(): shop = factories.get_default_shop() supplier = factories.get_default_supplier() contact = factories.create_random_person() group = PersonContact.get_default_group() contact.groups.add(group) parent = factories.create_product("p1", shop=shop, supplier=supplier, default_price=Decimal("10")) child1 = factories.create_product("p2", shop=shop, supplier=supplier, default_price=Decimal("20")) child2 = factories.create_product("p3", shop=shop, supplier=supplier, default_price=Decimal("40")) child3 = factories.create_product("p4", shop=shop, supplier=supplier, default_price=Decimal("50")) child1.link_to_parent(parent) child2.link_to_parent(parent) child3.link_to_parent(parent) # set a price for child2 CgpPrice.objects.create(shop=shop, product=child2, group=group, price_value=Decimal("5")) # create a discount for child3 CgpDiscount.objects.create(shop=shop, product=child3, group=group, discount_amount_value=Decimal("35")) catalog = ProductCatalog( context=ProductCatalogContext(purchasable_only=False, contact=contact)) ProductCatalog.index_product(parent) _assert_products_queryset( catalog, [ (child2.pk, Decimal("5"), None), (parent.pk, Decimal("10"), None), (child1.pk, Decimal("20"), None), (child3.pk, Decimal("50"), Decimal("15")), ], ) _assert_shop_products_queryset( catalog, [ (child2.get_shop_instance(shop).pk, Decimal("5"), None), (parent.get_shop_instance(shop).pk, Decimal("10"), None), (child1.get_shop_instance(shop).pk, Decimal("20"), None), (child3.get_shop_instance(shop).pk, Decimal("50"), Decimal("15")), ], ) # no customer _assert_price(parent, shop, Decimal("10"), Decimal("10")) _assert_price(child1, shop, Decimal("20"), Decimal("20")) _assert_price(child2, shop, Decimal("40"), Decimal("40")) _assert_price(child3, shop, Decimal("50"), Decimal("50")) # with the customer in the group _assert_price(parent, shop, Decimal("10"), Decimal("10"), customer=contact) _assert_price(child1, shop, Decimal("20"), Decimal("20"), customer=contact) _assert_price(child2, shop, Decimal("5"), Decimal("40"), customer=contact) _assert_price(child3, shop, Decimal("15"), Decimal("50"), customer=contact)
def test_product_category_discount_amount_with_minimum_price(rf): # Buy X amount of Y get Z discount from Y request, shop, _ = initialize_test(rf, False) basket = get_basket(request) supplier = get_default_supplier() single_product_price = Decimal("50") single_product_min_price = Decimal("40") discount_amount_value = Decimal("200") # will exceed the minimum price quantity = 2 # the expected discount amount should not be greater than the products expected_discount_amount = basket.create_price( single_product_price) * quantity category = CategoryFactory() # create basket rule that requires 2 products in basket product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=single_product_price) shop_product = ShopProduct.objects.get(shop=shop, product=product) shop_product.minimum_price_value = single_product_min_price shop_product.save() shop_product.categories.add(category) basket.add_product(supplier=supplier, shop=shop, product=product, quantity=quantity) basket.shipping_method = get_shipping_method(shop=shop) basket.save() rule = ProductsInBasketCondition.objects.create(quantity=2) rule.products.add(product) rule.save() campaign = BasketCampaign.objects.create(active=True, shop=shop, name="test", public_name="test") campaign.conditions.add(rule) DiscountFromCategoryProducts.objects.create( campaign=campaign, discount_amount=discount_amount_value, category=category) assert rule.matches(basket, []) basket.uncache() # the discount amount should not exceed the minimum price. as the configued discount # will exceed, it should limit the discount amount final_lines = basket.get_final_lines() expected_discount_amount = basket.create_price( (single_product_price - single_product_min_price) * quantity) original_price = basket.create_price(single_product_price) * quantity line = final_lines[0] assert line.discount_amount == expected_discount_amount assert basket.total_price == original_price - expected_discount_amount
def test_create_product_with_shop_product_and_attributes(admin_user): shop = get_default_shop() client = _get_client(admin_user) supplier = get_default_supplier() cat = Category.objects.create(status=CategoryStatus.VISIBLE, visibility=CategoryVisibility.VISIBLE_TO_ALL, identifier="test_category", name="Test") product_type = get_default_product_type() assert Attribute.objects.count() > 0 assert Product.objects.count() == 0 attributes_data = [] expected_values = { "untranslated_string_value": "test value", "numeric_value": 12, "boolean_value": True, "timedelta_value": "200", # seconds "datetime_value": "2017-01-01 01:00:00", "translated_string_value": "translated string value" } for spec in ATTR_SPECS: attr = Attribute.objects.get(identifier=spec["identifier"]) attr_data = { "numeric_value": None, "datetime_value": None, "untranslated_string_value": "", "attribute": attr.pk, # "product": product.pk } if attr.is_stringy: if attr.is_translated: attr_data["translations"] = { "en": { "translated_string_value": expected_values["translated_string_value"] } } else: attr_data["untranslated_string_value"] = expected_values[ "untranslated_string_value"] elif attr.is_numeric: if attr.type == AttributeType.BOOLEAN: attr_data["numeric_value"] = int( expected_values["boolean_value"]) elif attr.type == AttributeType.TIMEDELTA: attr_data["numeric_value"] = int( expected_values["timedelta_value"]) else: attr_data["numeric_value"] = expected_values["numeric_value"] elif attr.is_temporal: attr_data["datetime_value"] = expected_values["datetime_value"] attributes_data.append(attr_data) data = _get_product_sample_data() data["shop_products"] = _get_sample_shop_product_data(shop, cat, supplier) data["attributes"] = attributes_data response = client.post("/api/shuup/product/", content_type="application/json", data=json.dumps(data)) assert response.status_code == status.HTTP_201_CREATED # check all for lang in ("en", "pt-br"): activate(lang) product = Product.objects.first() _check_product_basic_data(product, data, lang) assert Product.objects.count() == 1 assert ShopProduct.objects.count() == 1 product = Product.objects.first() shop_product = ShopProduct.objects.first() assert product.get_shop_instance(shop) == shop_product assert supplier in shop_product.suppliers.all() assert cat in shop_product.categories.all() assert shop_product.primary_category == cat # validate attribute values for spec in ATTR_SPECS: attribute = Attribute.objects.get(identifier=spec["identifier"]) attr = ProductAttribute.objects.get(product=product, attribute=attribute) if attribute.is_stringy: if attribute.is_translated: attr.set_current_language("en") assert attr.value == expected_values["translated_string_value"] else: assert attr.value == expected_values[ "untranslated_string_value"] elif attribute.is_numeric: if attribute.type == AttributeType.BOOLEAN: assert attr.value == expected_values["boolean_value"] elif attribute.type == AttributeType.TIMEDELTA: assert attr.value == datetime.timedelta( seconds=int(expected_values["timedelta_value"])) else: assert attr.value == expected_values["numeric_value"] elif attribute.is_temporal: dt_value = expected_values["datetime_value"] parsed_dt = dt.strptime(dt_value, "%Y-%m-%d %H:%M:%S") assert attr.value.year == parsed_dt.year assert attr.value.month == parsed_dt.month assert attr.value.day == parsed_dt.day
def test_order_create_without_shipping_or_billing_method(admin_user): create_default_order_statuses() shop = get_default_shop() contact = create_random_person(locale="en_US", minimum_name_comp_len=5) product = create_product(sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop) assert not Order.objects.count() client = _get_client(admin_user) lines = [ { "type": "product", "product": product.id, "quantity": "1", "base_unit_price_value": "5.00" }, { "type": "product", "product": product.id, "quantity": "2", "base_unit_price_value": "1.00", "discount_amount_value": "0.50" }, { "type": "other", "sku": "hello", "text": "A greeting", "quantity": 1, "base_unit_price_value": "3.5" }, { "type": "text", "text": "This was an order!", "quantity": 0 }, ] response = client.post("/api/shuup/order/", content_type="application/json", data=json.dumps({ "shop": shop.pk, "customer": contact.pk, "lines": lines })) assert response.status_code == 201 assert Order.objects.count() == 1 order = Order.objects.first() assert order.shop == shop assert order.shipping_method is None assert order.payment_method is None assert order.customer == contact assert order.creator == admin_user assert order.billing_address == contact.default_billing_address.to_immutable( ) assert order.shipping_address == contact.default_shipping_address.to_immutable( ) assert order.payment_status == PaymentStatus.NOT_PAID assert order.shipping_status == ShippingStatus.NOT_SHIPPED assert order.status == OrderStatus.objects.get_default_initial() assert order.taxful_total_price_value == decimal.Decimal(10) assert order.lines.count() == 4 # 2 product lines, 2 other lines for idx, line in enumerate(order.lines.all()[:4]): assert line.quantity == decimal.Decimal(lines[idx].get("quantity")) assert line.base_unit_price_value == decimal.Decimal(lines[idx].get( "base_unit_price_value", 0)) assert line.discount_amount_value == decimal.Decimal(lines[idx].get( "discount_amount_value", 0))
def test_product_catalog_discounted_price(): shop = factories.get_default_shop() supplier = factories.get_default_supplier() contact = factories.create_random_person() group = PersonContact.get_default_group() contact.groups.add(group) product1 = factories.create_product("p1", shop=shop, supplier=supplier, default_price=Decimal("50")) product2 = factories.create_product("p2", shop=shop, supplier=supplier, default_price=Decimal("30")) # set price for product2 CgpPrice.objects.create(shop=shop, product=product2, group=group, price_value=Decimal(25)) # create a discount for product2 CgpDiscount.objects.create(shop=shop, product=product2, group=group, discount_amount_value=Decimal(7)) anon_catalog = ProductCatalog(context=ProductCatalogContext( purchasable_only=False)) customer_catalog = ProductCatalog( context=ProductCatalogContext(purchasable_only=False, contact=contact)) ProductCatalog.index_product(product1) ProductCatalog.index_product(product2) _assert_products_queryset( anon_catalog, [ (product2.pk, Decimal("30"), None), (product1.pk, Decimal("50"), None), ], ) _assert_products_queryset( customer_catalog, [ (product2.pk, Decimal("25"), Decimal("23")), (product1.pk, Decimal("50"), None), ], ) _assert_shop_products_queryset( anon_catalog, [ (product2.get_shop_instance(shop).pk, Decimal("30"), None), (product1.get_shop_instance(shop).pk, Decimal("50"), None), ], ) _assert_shop_products_queryset( customer_catalog, [ (product2.get_shop_instance(shop).pk, Decimal("25"), Decimal("23")), (product1.get_shop_instance(shop).pk, Decimal("50"), None), ], ) # no customer _assert_price(product1, shop, Decimal("50"), Decimal("50")) _assert_price(product2, shop, Decimal("30"), Decimal("30")) # with the customer in the group _assert_price(product1, shop, Decimal("50"), Decimal("50"), customer=contact) _assert_price(product2, shop, Decimal("18"), Decimal("30"), customer=contact)
def test_filter_caching(rf): request, shop, group = initialize_test(rf, False) price = shop.create_price product_price = "100" discount_percentage = "0.30" supplier = get_default_supplier() product = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=product_price) product2 = create_product(printable_gibberish(), shop=shop, supplier=supplier, default_price=product_price) assert product.pk != product2.pk # ensure they're different # create catalog campaign catalog_filter = ProductFilter.objects.create() catalog_filter.products.add(product) catalog_campaign = CatalogCampaign.objects.create(shop=shop, active=True, name="test") catalog_campaign.filters.add(catalog_filter) assert CatalogFilterCachedShopProduct.objects.count() == 1 catalog_campaign.save() assert CatalogFilterCachedShopProduct.objects.count() == 1 entry = CatalogFilterCachedShopProduct.objects.first() assert entry.pk == get_matching_catalog_filters( product.get_shop_instance(shop))[0] # create another campaign catalog_filter2 = ProductFilter.objects.create() catalog_filter2.products.add(product2) catalog_campaign2 = CatalogCampaign.objects.create(shop=shop, active=True, name="test") catalog_campaign2.filters.add(catalog_filter2) assert CatalogFilterCachedShopProduct.objects.count() == 2 catalog_campaign2.save() assert CatalogFilterCachedShopProduct.objects.count( ) == 2 # new cache for this product was created entry = CatalogFilterCachedShopProduct.objects.last() assert entry.pk == get_matching_catalog_filters( product2.get_shop_instance(shop))[0] # third campaign catalog_filter3 = ProductFilter.objects.create() catalog_filter3.products.add(product2) catalog_campaign3 = CatalogCampaign.objects.create(shop=shop, active=True, name="test") catalog_campaign3.filters.add(catalog_filter3) assert CatalogFilterCachedShopProduct.objects.count() == 3 catalog_campaign3.save() assert CatalogFilterCachedShopProduct.objects.count( ) == 3 # new one for this filter again expected = get_matching_catalog_filters(product2.get_shop_instance(shop)) for id in expected: assert id in [catalog_filter2.pk, catalog_filter3.pk]
def test_create_order(admin_user, currency): create_default_order_statuses() shop = get_default_shop() shop.currency = currency tax = get_default_tax() Currency.objects.get_or_create(code=currency, decimal_places=2) shop.save() sm = get_default_shipping_method() pm = get_default_payment_method() contact = create_random_person(locale="en_US", minimum_name_comp_len=5) default_group = get_default_customer_group() default_group.members.add(contact) account_manager = create_random_person(locale="en_US", minimum_name_comp_len=5) contact.account_manager = account_manager contact.save() product = create_product(sku=printable_gibberish(), supplier=get_default_supplier(), shop=shop) assert not Order.objects.count() client = _get_client(admin_user) lines = [ { "type": "product", "product": product.id, "quantity": "1", "base_unit_price_value": "5.00" }, { "type": "product", "product": product.id, "quantity": "2", "base_unit_price_value": "1.00", "discount_amount_value": "0.50" }, { "type": "other", "sku": "hello", "text": "A greeting", "quantity": 1, "base_unit_price_value": "3.5" }, { "type": "text", "text": "This was an order!", "quantity": 0 }, ] response = client.post("/api/shuup/order/", content_type="application/json", data=json.dumps({ "shop": shop.pk, "shipping_method": sm.pk, "payment_method": pm.pk, "customer": contact.pk, "lines": lines })) assert response.status_code == 201 assert Order.objects.count() == 1 order = Order.objects.first() assert order.shop == shop assert order.shipping_method == sm assert order.payment_method == pm assert order.customer == contact assert order.creator == admin_user assert order.billing_address == contact.default_billing_address.to_immutable( ) assert order.shipping_address == contact.default_shipping_address.to_immutable( ) assert order.payment_status == PaymentStatus.NOT_PAID assert order.shipping_status == ShippingStatus.NOT_SHIPPED assert order.status == OrderStatus.objects.get_default_initial() assert order.taxful_total_price_value == decimal.Decimal(10) assert order.lines.count( ) == 6 # shipping line, payment line, 2 product lines, 2 other lines assert order.currency == currency for idx, line in enumerate(order.lines.all()[:4]): assert line.quantity == decimal.Decimal(lines[idx].get("quantity")) assert line.base_unit_price_value == decimal.Decimal(lines[idx].get( "base_unit_price_value", 0)) assert line.discount_amount_value == decimal.Decimal(lines[idx].get( "discount_amount_value", 0)) # Test tax summary response_data = json.loads(response.content.decode("utf-8")) # Tax summary should not be present here assert "summary" not in response_data response = client.get('/api/shuup/order/{}/taxes/'.format(order.pk)) assert response.status_code == status.HTTP_200_OK response_data = json.loads(response.content.decode("utf-8")) assert "lines" in response_data assert "summary" in response_data line_summary = response_data["lines"] summary = response_data["summary"] first_tax_summary = summary[0] assert int(first_tax_summary["tax_id"]) == tax.id assert first_tax_summary["tax_rate"] == tax.rate first_line_summary = line_summary[0] assert "tax" in first_line_summary response = client.get("/api/shuup/order/%s/" % order.id) assert response.status_code == status.HTTP_200_OK order_data = json.loads(response.content.decode("utf-8")) assert order_data.get("id") == order.id assert "available_shipping_methods" in order_data assert "available_payment_methods" in order_data assert order_data["available_payment_methods"][0]["id"] == pm.id assert order_data["available_shipping_methods"][0]["id"] == sm.id assert order.account_manager == account_manager assert order.customer_groups.count() == contact.groups.count() for group in order.customer_groups.all(): assert contact.groups.filter(id=group.id).exists() assert order.tax_group is not None assert order.tax_group == contact.tax_group
def test_customer_sales_report(rf, order_by): shop = get_default_shop() supplier = get_default_supplier() product1 = create_product("p1", shop=shop, supplier=supplier) product2 = create_product("p2", shop=shop, supplier=supplier) product3 = create_product("p3", shop=shop, supplier=supplier) tax_rate = Decimal("0.3") # orders for person 1 person1 = create_random_person() order1 = create_order_with_product(product=product1, supplier=supplier, quantity=2, taxless_base_unit_price="5", tax_rate=tax_rate, n_lines=1, shop=shop) order1.customer = person1 order1.save() order2 = create_order_with_product(product=product2, supplier=supplier, quantity=1, taxless_base_unit_price="10", n_lines=1, shop=shop) order2.customer = person1 order2.save() person1_taxful_total_sales = (order1.taxful_total_price + order2.taxful_total_price) person1_taxless_total_sales = (order1.taxless_total_price + order2.taxless_total_price) person1_avg_sales = (person1_taxful_total_sales / Decimal(2.0)) # orders for person 2 person2 = create_random_person() order3 = create_order_with_product(product=product1, supplier=supplier, quantity=2, taxless_base_unit_price="5", tax_rate=tax_rate, n_lines=1, shop=shop) order3.customer = person2 order3.save() order4 = create_order_with_product(product=product2, supplier=supplier, quantity=2, taxless_base_unit_price="50", n_lines=1, shop=shop) order4.customer = person2 order4.save() order5 = create_order_with_product(product=product3, supplier=supplier, quantity=2, taxless_base_unit_price="20", tax_rate=tax_rate, n_lines=1, shop=shop) order5.customer = person2 order5.save() person2_taxful_total_sales = (order3.taxful_total_price + order4.taxful_total_price + order5.taxful_total_price) person2_taxless_total_sales = (order3.taxless_total_price + order4.taxless_total_price + order5.taxless_total_price) person2_avg_sales = (person2_taxful_total_sales / Decimal(3.0)).quantize( Decimal('0.01')) # pay orders [o.create_payment(o.taxful_total_price) for o in Order.objects.all()] data = { "report": CustomerSalesReport.get_name(), "shop": shop.pk, "date_range": DateRangeChoices.ALL_TIME, "writer": "json", "force_download": 1, "order_by": order_by } report = CustomerSalesReport(**data) writer = get_writer_instance(data["writer"]) response = writer.get_response(report=report) if hasattr(response, "render"): response.render() json_data = json.loads(response.content.decode("utf-8")) assert force_text(CustomerSalesReport.title) in json_data.get("heading") data = json_data.get("tables")[0].get("data") assert len(data) == 2 if order_by == "order_count": person1_data = data[1] person2_data = data[0] elif order_by == "average_sales": if person1_avg_sales > person2_avg_sales: person1_data = data[0] person2_data = data[1] else: person1_data = data[1] person2_data = data[0] elif order_by == "taxless_total": if person1_taxless_total_sales > person2_taxless_total_sales: person1_data = data[0] person2_data = data[1] else: person1_data = data[1] person2_data = data[0] elif order_by == "taxful_total": if person1_taxful_total_sales > person2_taxful_total_sales: person1_data = data[0] person2_data = data[1] else: person1_data = data[1] person2_data = data[0] assert person1_data["customer"] == person1.name assert person1_data["order_count"] == "2" assert person1_data["average_sales"] == str(person1_avg_sales.value) assert person1_data["taxless_total"] == str( person1_taxless_total_sales.value.quantize(Decimal("0.01"))) assert person1_data["taxful_total"] == str( person1_taxful_total_sales.value.quantize(Decimal("0.01"))) assert person2_data["customer"] == person2.name assert person2_data["order_count"] == "3" assert person2_data["average_sales"] == str(person2_avg_sales.value) assert person2_data["taxless_total"] == str( person2_taxless_total_sales.value.quantize(Decimal("0.01"))) assert person2_data["taxful_total"] == str( person2_taxful_total_sales.value.quantize(Decimal("0.01")))
def _create_total_sales(shop, day): product = create_product("test", shop=shop) supplier = get_default_supplier() order = create_order_with_product(product, supplier, 1, 10, shop=shop) order.order_date = day order.save()
def test_taxes_report(rf): shop = get_default_shop() supplier = get_default_supplier() product1 = create_product("p1", shop=shop, supplier=supplier) product2 = create_product("p2", shop=shop, supplier=supplier) create_product("p3", shop=shop, supplier=supplier) tax_rate1 = Decimal("0.3") tax_rate2 = Decimal("0.45") tax_rate1_instance = get_test_tax(tax_rate1) tax_rate2_instance = get_test_tax(tax_rate2) # orders for person 1 person1 = create_random_person() order1 = create_order_with_product(product=product1, supplier=supplier, quantity=2, taxless_base_unit_price="5", tax_rate=tax_rate1, n_lines=1, shop=shop) order1.customer = person1 order1.save() order2 = create_order_with_product(product=product2, supplier=supplier, quantity=1, taxless_base_unit_price="10", tax_rate=tax_rate1, n_lines=1, shop=shop) order2.customer = person1 order2.save() # orders for person 2 person2 = create_random_person() order3 = create_order_with_product(product=product1, supplier=supplier, quantity=1, taxless_base_unit_price="2", tax_rate=tax_rate2, n_lines=1, shop=shop) order3.customer = person2 order3.save() order4 = create_order_with_product(product=product2, supplier=supplier, quantity=2, taxless_base_unit_price="8", tax_rate=tax_rate1, n_lines=1, shop=shop) order4.customer = person2 order4.save() # pay orders [o.create_payment(o.taxful_total_price) for o in Order.objects.all()] data = { "report": TaxesReport.get_name(), "shop": shop.pk, "date_range": DateRangeChoices.ALL_TIME, "writer": "json", "force_download": 1, } report = TaxesReport(**data) writer = get_writer_instance(data["writer"]) response = writer.get_response(report=report) if hasattr(response, "render"): response.render() json_data = json.loads(response.content.decode("utf-8")) assert force_text(TaxesReport.title) in json_data.get("heading") data = json_data.get("tables")[0].get("data") assert len(data) == 2 tax1_rate1_total = ( (order1.taxful_total_price_value - order1.taxless_total_price_value) + (order2.taxful_total_price_value - order2.taxless_total_price_value) + (order4.taxful_total_price_value - order4.taxless_total_price_value)) tax1_pretax_total = (order1.taxless_total_price_value + order2.taxless_total_price_value + order4.taxless_total_price_value) tax1_total = (order1.taxful_total_price_value + order2.taxful_total_price_value + order4.taxful_total_price_value) tax2_rate2_total = (order3.taxful_total_price_value - order3.taxless_total_price_value) # the report data order is the total charged ascending expected_result = [{ "tax": tax_rate2_instance.name, "tax_rate": tax_rate2, "order_count": 1, "total_pretax_amount": order3.taxless_total_price_value, "total_tax_amount": tax2_rate2_total, "total": order3.taxful_total_price_value, }, { "tax": tax_rate1_instance.name, "tax_rate": tax_rate1, "order_count": 3, "total_pretax_amount": tax1_pretax_total, "total_tax_amount": tax1_rate1_total, "total": tax1_total, }] for ix, tax in enumerate(data): assert tax["tax"] == expected_result[ix]["tax"] assert Decimal(tax["tax_rate"] ) == expected_result[ix]["tax_rate"] * Decimal(100.0) assert tax["order_count"] == str(expected_result[ix]["order_count"]) assert tax["total_tax_amount"] == str( expected_result[ix]["total_tax_amount"]) assert tax["total_pretax_amount"] == str( expected_result[ix]["total_pretax_amount"]) assert tax["total"] == str(expected_result[ix]["total"])
def test_refund_errors(admin_user): shop = get_default_shop() supplier = get_default_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) tax_rate = Decimal("0.1") taxless_base_unit_price = shop.create_price(200) order = create_order_with_product(product, supplier, 3, taxless_base_unit_price, tax_rate, shop=shop) order.payment_status = PaymentStatus.DEFERRED order.cache_prices() order.save() assert len(order.lines.all()) == 1 assert order.can_create_refund() assert not order.has_refunds() client = _get_client(admin_user) refund_url = "/api/shuup/order/%s/create_refund/" % order.id product_line = order.lines.first() # error 1 - max refundable limit data = { "refund_lines": [{ "line": product_line.id, "quantity": 1000, "amount": 1, "restock_products": False }] } response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST assert "Refund exceeds quantity." in response.data # error 2 - max amount data = { "refund_lines": [{ "line": product_line.id, "quantity": 1, "amount": 100000000, "restock_products": False }] } response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST assert "Refund exceeds amount." in response.data # error 3 - invalid amount data = { "refund_lines": [{ "line": product_line.id, "quantity": 1, "amount": -10, "restock_products": False }] } response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST assert "Invalid refund amount." in response.data # create partial refund data = { "refund_lines": [{ "line": product_line.id, "quantity": 1, "amount": 1, "restock_products": False }] } response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_201_CREATED # error 4 - can't create full refund data = {"restock_products": False} response = client.post("/api/shuup/order/%s/create_full_refund/" % order.id, data, format="json") assert response.status_code == status.HTTP_400_BAD_REQUEST assert "It is not possible to create the refund." in response.data
def test_refunds_for_discounted_order_lines(): shop = get_default_shop() supplier = get_default_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) order = create_order_with_product(product, supplier, 2, 200, shop=shop) discount_line = OrderLine(order_id=order.id, type=OrderLineType.DISCOUNT, quantity=1, discount_amount_value=Decimal("0.54321")) discount_line.save() order.lines.add(discount_line) # Lines without quantity shouldn't affect refunds other_line = OrderLine(order=order, type=OrderLineType.OTHER, text="This random line for textual information", quantity=0) other_line.save() order.lines.add(other_line) product_line = order.lines.filter(type=OrderLineType.PRODUCT).first() product_line.discount_amount = TaxfulPrice(100, order.currency) product_line.save() taxful_price_with_discount = product_line.taxful_price order.cache_prices() order.save() assert product_line.base_price == TaxfulPrice(400, order.currency) assert taxful_price_with_discount == TaxfulPrice(300, order.currency) # try to refund only the product line - should fail since this would result in a negative total with pytest.raises(RefundExceedsAmountException): order.create_refund([{ "line": product_line, "quantity": 2, "amount": taxful_price_with_discount.amount }]) # try to refund the product line with a negative amount with pytest.raises(InvalidRefundAmountException): order.create_refund([{ "line": product_line, "quantity": 1, "amount": -taxful_price_with_discount.amount }]) # try to refund the discount line with a positive amount with pytest.raises(InvalidRefundAmountException): order.create_refund([{ "line": discount_line, "quantity": 1, "amount": -discount_line.taxful_price.amount }]) order.create_refund([ { "line": discount_line, "quantity": 1, "amount": discount_line.taxful_price.amount }, { "line": product_line, "quantity": 2, "amount": taxful_price_with_discount.amount }, ]) assert product_line.max_refundable_amount.value == 0 assert discount_line.max_refundable_amount.value == 0 assert order.taxful_total_price.value == 0 order = create_order_with_product(product, supplier, 2, 200, shop=shop) discount_line = OrderLine(order_id=order.id, type=OrderLineType.DISCOUNT, quantity=1, discount_amount_value=Decimal("0.54321")) discount_line.save() order.lines.add(discount_line) product_line = order.lines.filter(type=OrderLineType.PRODUCT).first() product_line.discount_amount = TaxfulPrice(100, order.currency) product_line.save() # Lines without quantity shouldn't affect refunds other_line = OrderLine(order=order, type=OrderLineType.OTHER, text="This random line for textual information", quantity=0) other_line.save() order.lines.add(other_line) order.cache_prices() order.save() order.create_full_refund(restock_products=False) assert order.taxful_total_price.value == 0
def test_anonymize_contact(): """ Test that contact are anonymized """ activate("en") shop = factories.get_default_shop() anonymizer = Anonymizer() customer = factories.create_random_person("en") user = factories.create_random_user("en") customer.user = user customer.default_billing_address = factories.create_random_address() customer.default_shipping_address = factories.create_random_address() customer.save() company = factories.create_random_company() company.default_billing_address = factories.create_random_address() company.default_shipping_address = factories.create_random_address() company.save() company.members.add(customer) product = factories.create_product("p1", shop, factories.get_default_supplier()) orders = [] core_baskets = [] front_baskets = [] for basket_customer in [customer, company]: orders.extend([ factories.create_random_order(basket_customer, [product]) for order in range(3) ]) front_baskets.append( StoredBasket.objects.create( key=uuid4().hex, shop=shop, customer=basket_customer, orderer=customer, creator=customer.user, currency=shop.currency, data={"items": []}, prices_include_tax=shop.prices_include_tax)) core_baskets.append( Basket.objects.create(key=uuid4().hex, shop=shop, customer=basket_customer, orderer=customer, creator=customer.user, currency=shop.currency, data={"items": []}, prices_include_tax=shop.prices_include_tax)) anonymized_person = PersonContact.objects.get(id=customer.id) anonymizer.anonymize_person(anonymized_person) anonymized_person.refresh_from_db() assert anonymized_person.first_name != customer.first_name assert anonymized_person.last_name != customer.last_name assert anonymized_person.email != customer.email assert anonymized_person.phone != customer.phone assert anonymized_person.default_billing_address.street != customer.default_billing_address.street assert anonymized_person.default_billing_address.city != customer.default_billing_address.city anonymized_company = CompanyContact.objects.get(id=company.id) anonymizer.anonymize_company(anonymized_company) anonymized_company.refresh_from_db() assert anonymized_company.tax_number != company.tax_number assert anonymized_company.email != company.email assert anonymized_company.phone != company.phone assert anonymized_company.default_billing_address.street != company.default_billing_address.street assert anonymized_company.default_billing_address.city != company.default_billing_address.city for created_order in orders: order = Order.objects.get(id=created_order.id) assert order.phone != created_order.phone assert order.ip_address != created_order.ip_address assert order.shipping_address.street != created_order.shipping_address.street assert order.billing_address.street != created_order.billing_address.street for front_basket in front_baskets: stored_basket = StoredBasket.objects.get(id=front_basket.id) assert stored_basket.data is None for core_basket in core_baskets: basket = Basket.objects.get(id=core_basket.id) assert basket.data is None anonymized_user = get_user_model().objects.get(id=user.id) anonymizer.anonymize_user(anonymized_user) anonymized_user.refresh_from_db() assert user.username != anonymized_user.username assert user.first_name != anonymized_user.first_name assert user.last_name != anonymized_user.last_name