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)
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, stock_behavior=StockBehavior.STOCKED ) 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() 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() order.cache_prices() order.save() order.create_full_refund(restock_products=False) assert order.taxful_total_price.value == 0
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_get_best_selling_products(): context = get_jinja_context() cache.clear() # No products sold assert len(list(general.get_best_selling_products(context, n_products=2))) == 0 supplier = get_default_supplier() shop = get_default_shop() product = get_default_product() create_order_with_product(product, supplier, quantity=1, taxless_base_unit_price=10, shop=shop) cache.clear() # One product sold assert len(list(general.get_best_selling_products(context, n_products=2))) == 1
def test_report_writers(): """ Just check whether something breaks while writing differnt types of data """ shop = get_default_shop() product = get_default_product() supplier = get_default_supplier() order = create_order_with_product( product=product, supplier=supplier, quantity=1, taxless_base_unit_price=10, tax_rate=0, n_lines=2, shop=shop) order.create_payment(order.taxful_total_price.amount) data = { "report": SalesTestReport.get_name(), "shop": shop.pk, "date_range": DateRangeChoices.THIS_YEAR, "writer": "html", "force_download": 1, } report = SalesTestReport(**data) for writer_cls in [ExcelReportWriter, PDFReportWriter, PprintReportWriter, HTMLReportWriter, JSONReportWriter]: writer = writer_cls() report_data = [ { "date": order, "order_count": Decimal(2), "product_count": int(3), "taxless_total": lazy(lambda: order.taxless_total_price_value), "taxful_total": order.taxful_total_price, } ] writer.write_data_table(report, report_data) assert writer.get_rendered_output()
def _init_test_with_variations(): shop = get_default_shop() supplier = get_default_supplier() product_data = { "t-shirt": { "colors": ["black", "yellow"], }, "hoodie": { "colors": ["black"], } } for key, data in six.iteritems(product_data): parent = create_product(key, shop=shop) shop_parent_product = parent.get_shop_instance(shop) for color in data["colors"]: sku = "%s-%s" % (key, color) 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={"color": color}) assert Product.objects.count() == 5 black_t_shirt = Product.objects.filter(sku="t-shirt-black").first() black_hoodie = Product.objects.filter(sku="hoodie-black").first() order = create_order_with_product(black_t_shirt, supplier, quantity=1, taxless_base_unit_price=6, shop=shop) add_product_to_order(order, supplier, black_hoodie, quantity=1, taxless_base_unit_price=6) return black_t_shirt.variation_parent, black_hoodie.variation_parent
def test_refund_without_shipment(restock): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) # Start out with a supplier with quantity of 10 of a product supplier.adjust_stock(product.id, 10) check_stock_counts(supplier, product, physical=10, logical=10) order = create_order_with_product(product, supplier, 2, 200, shop=shop) order.cache_prices() check_stock_counts(supplier, product, physical=10, logical=8) # Restock value shouldn't matter if we don't have any shipments product_line = order.lines.first() order.create_refund([ {"line": product_line, "quantity": 2, "amount": Money(400, order.currency), "restock_products": restock}]) if restock: check_stock_counts(supplier, product, physical=10, logical=10) else: check_stock_counts(supplier, product, physical=10, logical=8) assert product_line.refunded_quantity == 2 assert order.get_total_tax_amount() == Money( order.taxful_total_price_value - order.taxless_total_price_value, order.currency)
def test_create_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() order.save() assert not order.has_refunds() assert len(order.lines.all()) == 1 product_line = order.lines.first() data = { "form-0-line_number": 0, "form-0-quantity": 1, "form-0-amount": 1, "form-0-restock_products": False, "form-INITIAL_FORMS": 0, "form-MAX_NUM_FORMS": 1000, "form-TOTAL_FORMS": 1, "form-MIN_NUM_FORMS": 0, } request = apply_request_middleware(rf.post("/", data=data), user=admin_user) view = OrderCreateRefundView.as_view() response = view(request, pk=order.pk) assert response.status_code == 302 assert order.has_refunds() assert len(order.lines.all()) == 2 refund_line = order.lines.filter(type=OrderLineType.REFUND).last() assert refund_line assert refund_line.taxful_price == -product_line.taxful_price
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 test_printouts_no_addresses(rf): try: import weasyprint except ImportError: pytest.skip() shop = get_default_shop() supplier = get_default_supplier() product = create_product("simple-test-product", shop) order = create_order_with_product(product, supplier, 6, 6, shop=shop) order.billing_address = None order.save() shipment = order.create_shipment_of_all_products(supplier) request = rf.get("/") response = get_delivery_pdf(request, shipment.id) assert response.status_code == 200 response = get_confirmation_pdf(request, order.id) assert response.status_code == 200 order.shipping_address = None order.save() response = get_delivery_pdf(request, shipment.id) assert response.status_code == 200 response = get_confirmation_pdf(request, order.id) assert response.status_code == 200
def test_extending_shipment_form_valid_hook(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) quantity = 1 order = create_order_with_product( product, supplier, quantity=quantity, taxless_base_unit_price=1, shop=shop) extend_form_class = "shuup_tests.admin.test_shipment_creator.ShipmentFormModifierTest" with override_provides(ShipmentForm.form_modifier_provide_key, [extend_form_class]): phone_number = "+358911" data = { "q_%s" % product.pk: 1, "supplier": supplier.pk, "phone": phone_number } request = apply_request_middleware(rf.post("/", data=data), user=admin_user) view = OrderCreateShipmentView.as_view() response = view(request, pk=order.pk) assert response.status_code == 302 # Order should now have shipment, but let's re fetch it first order = Order.objects.get(pk=order.pk) assert order.shipments.count() == 1 shipment = order.shipments.first() assert order.shipping_data.get(shipment.identifier).get("phone") == phone_number assert shipment.supplier_id == supplier.id assert shipment.products.count() == 1 assert shipment.products.first().product_id == product.id
def initialize_report_test(product_price, product_count, tax_rate, line_count): shop = get_default_shop() product = get_default_product() supplier = get_default_supplier() expected_taxless_total = product_count * product_price expected_taxful_total = product_count * product_price * (1 + tax_rate) order = create_order_with_product( product=product, supplier=supplier, quantity=product_count, taxless_base_unit_price=product_price, tax_rate=tax_rate, n_lines=line_count, shop=shop) order.create_payment(order.taxful_total_price.amount) order2 = create_order_with_product( product=product, supplier=supplier, quantity=product_count, taxless_base_unit_price=product_price, tax_rate=tax_rate, n_lines=line_count, shop=shop) order2.create_payment(order2.taxful_total_price.amount) order2.set_canceled() # Shouldn't affect reports return expected_taxful_total, expected_taxless_total, shop, order
def test_custom_payment_processor_cash_service(choice_identifier, expected_payment_status): shop = get_default_shop() product = get_default_product() supplier = get_default_supplier() processor = CustomPaymentProcessor.objects.create() payment_method = PaymentMethod.objects.create( shop=shop, payment_processor=processor, choice_identifier=choice_identifier, tax_class=get_default_tax_class()) order = create_order_with_product( product=product, supplier=supplier, quantity=1, taxless_base_unit_price=Decimal('5.55'), shop=shop) order.taxful_total_price = TaxfulPrice(Decimal('5.55'), u'EUR') order.payment_method = payment_method order.save() assert order.payment_status == PaymentStatus.NOT_PAID processor.process_payment_return_request(choice_identifier, order, None) assert order.payment_status == expected_payment_status processor.process_payment_return_request(choice_identifier, order, None) assert order.payment_status == expected_payment_status
def test_refund_entire_order_without_restock(admin_user): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) supplier.adjust_stock(product.id, 5) _check_stock_counts(supplier, product, 5, 5) order = create_order_with_product(product, supplier, 2, 200, Decimal("0.24"), shop=shop) order.payment_status = PaymentStatus.DEFERRED order.cache_prices() order.save() original_total_price = order.taxful_total_price _check_stock_counts(supplier, product, 5, 3) client = _get_client(admin_user) refund_url = "/api/shuup/order/%s/create_full_refund/" % order.id data = {"restock_products": False} response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_201_CREATED order.refresh_from_db() # Confirm the refund was created with correct amount assert order.taxless_total_price.amount.value == 0 assert order.taxful_total_price.amount.value == 0 refund_line = order.lines.order_by("ordering").last() assert refund_line.type == OrderLineType.REFUND assert refund_line.taxful_price == -original_total_price # Make sure logical count reflects refunded products _check_stock_counts(supplier, product, 5, 3)
def test_simple_supplier(rf): supplier = get_simple_supplier() shop = get_default_shop() product = create_product("simple-test-product", shop) ss = supplier.get_stock_status(product.pk) assert ss.product == product assert ss.logical_count == 0 num = random.randint(100, 500) supplier.adjust_stock(product.pk, +num) assert supplier.get_stock_status(product.pk).logical_count == num # Create order ... order = create_order_with_product(product, supplier, 10, 3, shop=shop) quantities = order.get_product_ids_and_quantities() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == num # Create shipment ... shipment = order.create_shipment_of_all_products(supplier) pss = supplier.get_stock_status(product.pk) assert pss.physical_count == (num - quantities[product.pk]) # Cancel order... order.set_canceled() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num) # physical stock still the same until shipment exists assert pss.physical_count == (num - quantities[product.pk]) shipment.soft_delete() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == num assert pss.physical_count == num
def test_simple_supplier(rf): supplier = get_simple_supplier() shop = get_default_shop() product = create_product("simple-test-product", shop) ss = supplier.get_stock_status(product.pk) assert ss.product == product assert ss.logical_count == 0 num = random.randint(100, 500) supplier.adjust_stock(product.pk, +num) assert supplier.get_stock_status(product.pk).logical_count == num # Create order order = create_order_with_product(product, supplier, 10, 3, shop=shop) quantities = order.get_product_ids_and_quantities() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == num # Create shipment shipment = order.create_shipment_of_all_products(supplier) assert isinstance(shipment, Shipment) pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == (num - quantities[product.pk]) # Delete shipment with pytest.raises(NotImplementedError): shipment.delete() shipment.soft_delete() assert shipment.is_deleted() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == (num)
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_refund_entire_order(): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, stock_behavior=StockBehavior.STOCKED ) supplier.adjust_stock(product.id, 5) assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 5 order = create_order_with_product(product, supplier, 2, 200, Decimal("0.1"), shop=shop) order.cache_prices() original_total_price = order.taxful_total_price assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3 # Create a full refund with `restock_products` set to False order.create_full_refund(restock_products=False) # Confirm the refund was created with correct amount assert order.taxless_total_price.amount.value == 0 assert order.taxful_total_price.amount.value == 0 refund_line = order.lines.order_by("ordering").last() assert refund_line.type == OrderLineType.REFUND assert refund_line.taxful_price == -original_total_price # Make sure stock status didn't change assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3
def test_tracking_codes(): product = get_default_product() supplier = get_default_supplier() order = create_order_with_product( product, supplier=supplier, quantity=1, taxless_base_unit_price=10, tax_rate=decimal.Decimal("0.5") ) _add_product_to_order(order, "duck-tape-1", 3, order.shop, supplier) _add_product_to_order(order, "water-1", 2, order.shop, supplier) order.cache_prices() order.check_all_verified() order.save() # Create shipment with tracking code for every product line. product_lines = order.lines.exclude(product_id=None) assert len(product_lines) == 3 for line in product_lines: shipment = order.create_shipment({line.product: line.quantity}, supplier=supplier) if line.quantity != 3: shipment.tracking_code = "123FI" shipment.save() tracking_codes = order.get_tracking_codes() code_count = (len(product_lines)-1) # We skipped that one assert len(tracking_codes) == code_count assert len([tracking_code for tracking_code in tracking_codes if tracking_code == "123FI"]) == code_count
def test_refund_entire_order(): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) supplier.adjust_stock(product.id, 5) check_stock_counts(supplier, product, 5, 5) order = create_order_with_product(product, supplier, 2, 200, Decimal("0.24"), shop=shop) order.cache_prices() original_total_price = order.taxful_total_price check_stock_counts(supplier, product, 5, 3) # Create a full refund with `restock_products` set to False order.create_full_refund(restock_products=False) # Confirm the refund was created with correct amount assert order.taxless_total_price.amount.value == 0 assert order.taxful_total_price.amount.value == 0 refund_line = order.lines.order_by("ordering").last() assert refund_line.type == OrderLineType.REFUND assert refund_line.taxful_price == -original_total_price # Make sure logical count reflects refunded products check_stock_counts(supplier, product, 5, 3)
def test_refund_entire_order_with_restock(admin_user): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) supplier.adjust_stock(product.id, 5) _check_stock_counts(supplier, product, 5, 5) order = create_order_with_product(product, supplier, 2, 200, shop=shop) order.payment_status = PaymentStatus.DEFERRED order.cache_prices() order.save() _check_stock_counts(supplier, product, 5, 3) assert not StockAdjustment.objects.filter(created_by=admin_user).exists() client = _get_client(admin_user) refund_url = "/api/shuup/order/%s/create_full_refund/" % order.id data = {"restock_products": True} response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_201_CREATED order.refresh_from_db() # restock logical count _check_stock_counts(supplier, product, 5, 5) assert StockAdjustment.objects.filter(created_by=admin_user).exists()
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_refund_with_product_restock(): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, stock_behavior=StockBehavior.STOCKED ) supplier.adjust_stock(product.id, 5) assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 5 order = create_order_with_product(product, supplier, 2, 200, shop=shop) order.cache_prices() assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3 # Create a refund with a parent line and quanity with `restock_products` set to False product_line = order.lines.first() order.create_refund([{"line": product_line, "quantity": 1, "restock_products": False}]) assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 3 assert order.lines.last().taxful_price == -product_line.base_unit_price assert order.get_total_unrefunded_amount() == product_line.base_unit_price.amount # Create a refund with a parent line and quanity with `restock_products` set to True product_line = order.lines.first() order.create_refund([{"line": product_line, "quantity": 1, "restock_products": True}]) assert supplier.get_stock_statuses([product.id])[product.id].logical_count == 4 assert not order.taxful_total_price
def create_fully_paid_order(shop, customer, supplier, product_sku, price_value): product = create_product(product_sku, shop=shop, supplier=supplier, default_price=price_value) order = create_order_with_product( product=product, supplier=supplier, quantity=1, taxless_base_unit_price=price_value, shop=get_default_shop()) order.customer = customer order.save() order.cache_prices() return order.create_payment(order.taxful_total_price)
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 _create_total_paid_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() order.create_payment(order.taxful_total_price) assert order.is_paid()
def test_can_create_payment(): 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, 1, 200, shop=shop) assert order.can_create_payment() order.cache_prices() # Partially paid orders can create payments payment_amount = (order.taxful_total_price.amount / 2) order.create_payment(payment_amount) assert order.can_create_payment() # But fully paid orders can't remaining_amount = order.taxful_total_price.amount - payment_amount order.create_payment(remaining_amount) assert not order.can_create_payment() order = create_order_with_product(product, supplier, 1, 200, shop=shop) order.cache_prices() assert order.can_create_payment() # Canceled orders can't create payments order.set_canceled() assert not order.can_create_payment() order = create_order_with_product(product, supplier, 2, 200, shop=shop) order.cache_prices() assert order.can_create_payment() # Partially refunded orders can create payments order.create_refund([ {"line": order.lines.first(), "quantity": 1, "amount": Money(200, order.currency), "restock": False}]) assert order.can_create_payment() # But fully refunded orders can't order.create_refund([ {"line": order.lines.first(), "quantity": 1, "amount": Money(200, order.currency), "restock": False}]) assert not order.can_create_payment()
def test_refunds(): 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) 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() # Create a refund with a parent line and quantity order.create_refund([{"line": product_line, "quantity": 1}]) 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 partial_refund_amount = order.taxful_total_price.amount / 2 remaining_amount = order.taxful_total_price.amount - partial_refund_amount # Create a refund with a parent line and amount order.create_refund([{"line": product_line, "amount": partial_refund_amount}]) assert len(order.lines.all()) == 3 assert order.lines.last().ordering == 2 assert order.lines.last().taxful_price.amount == -partial_refund_amount assert order.taxful_total_price.amount == remaining_amount assert order.can_create_refund() # Create a refund without parent line and remaining amount in order order.create_refund([{"amount": remaining_amount}]) assert len(order.lines.all()) == 4 assert order.lines.last().ordering == 3 assert order.lines.last().taxful_price.amount == -remaining_amount assert not order.taxful_total_price.amount assert not order.can_create_refund() with pytest.raises(RefundExceedsAmountException): order.create_refund([{"amount": remaining_amount}])
def initialize_report_test(product_price, product_count, tax_rate, line_count): shop = get_default_shop() product = get_default_product() supplier = get_default_supplier() expected_taxless_total = product_count * product_price expected_taxful_total = product_count * product_price * (1 + tax_rate) order = create_order_with_product( product=product, supplier=supplier, quantity=product_count, taxless_base_unit_price=product_price, tax_rate=tax_rate, n_lines=line_count, shop=shop) return expected_taxful_total, expected_taxless_total, shop, order
def test_edit_view_with_anonymous_contact(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() product = create_product(sku=printable_gibberish(), supplier=supplier, shop=shop) order = create_order_with_product(product, supplier, 1, 10, shop=shop) order.save() assert not order.customer request = apply_request_middleware(rf.get("/", user=admin_user)) response = OrderEditView.as_view()(request=request, pk=order.pk) assert response.status_code == 200
def test_refund_entire_order_with_product_restock(): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, stock_behavior=StockBehavior.STOCKED ) supplier.adjust_stock(product.id, 5) check_stock_counts(supplier, product, 5, 5) order = create_order_with_product(product, supplier, 2, 200, shop=shop) order.cache_prices() check_stock_counts(supplier, product, 5, 3) # Create a full refund with `restock_products` set to True order.create_full_refund(restock_products=True) # restock logical count check_stock_counts(supplier, product, 5, 5)
def test_extending_shipment_form_valid_hook(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) quantity = 1 order = create_order_with_product(product, supplier, quantity=quantity, taxless_base_unit_price=1, shop=shop) extend_form_class = "shuup_tests.admin.test_shipment_creator.ShipmentFormModifierTest" with override_provides(ShipmentForm.form_modifier_provide_key, [extend_form_class]): phone_number = "+358911" data = { "q_%s" % product.pk: 1, "supplier": supplier.pk, "phone": phone_number } request = apply_request_middleware(rf.post("/", data=data), user=admin_user) view = OrderCreateShipmentView.as_view() response = view(request, pk=order.pk) assert response.status_code == 302 # Order should now have shipment, but let's re fetch it first order = Order.objects.get(pk=order.pk) assert order.shipments.count() == 1 shipment = order.shipments.first() assert order.shipping_data.get( shipment.identifier).get("phone") == phone_number assert shipment.supplier_id == supplier.id assert shipment.products.count() == 1 assert shipment.products.first().product_id == product.id
def test_shipment_creating_with_no_shipping_address(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) # remove shipping address order.shipping_address = None order.save() with pytest.raises(NoShippingAddressException): order.create_shipment_of_all_products() # order should not have any shipments since it should have thrown an exception assert order.shipments.count() == 0
def test_refund_entire_order_without_restock(admin_user): shop = get_default_shop() supplier = get_simple_supplier() product = create_product("test-sku", shop=get_default_shop(), default_price=10, stock_behavior=StockBehavior.STOCKED) supplier.adjust_stock(product.id, 5) _check_stock_counts(supplier, product, 5, 5) order = create_order_with_product(product, supplier, 2, 200, Decimal("0.24"), shop=shop) order.payment_status = PaymentStatus.DEFERRED order.cache_prices() order.save() original_total_price = order.taxful_total_price _check_stock_counts(supplier, product, 5, 3) client = _get_client(admin_user) refund_url = "/api/shuup/order/%s/create_full_refund/" % order.id data = {"restock_products": False} response = client.post(refund_url, data, format="json") assert response.status_code == status.HTTP_201_CREATED order.refresh_from_db() # Confirm the refund was created with correct amount assert order.taxless_total_price.amount.value == 0 assert order.taxful_total_price.amount.value == 0 refund_line = order.lines.order_by("ordering").last() assert refund_line.type == OrderLineType.REFUND assert refund_line.taxful_price == -original_total_price # Make sure logical count reflects refunded products _check_stock_counts(supplier, product, 5, 3)
def test_computing_simple_product_relations(rf): shop = get_default_shop() supplier = get_default_supplier() product = create_product("simple-test-product", shop) related_product = create_product("simple-related-product", shop) quantities = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] for quantity in quantities: order = create_order_with_product(product, supplier, quantity=1, taxless_base_unit_price=6, shop=shop) add_product_to_order(order, supplier, related_product, quantity=quantity, taxless_base_unit_price=6) assert ProductCrossSell.objects.count() == 0 add_bought_with_relations_for_product(product.pk) assert ProductCrossSell.objects.count() == 1 cross_sell_product = ProductCrossSell.objects.filter(product1=product).first() assert cross_sell_product.product2 == related_product assert cross_sell_product.weight == sum(quantities) add_bought_with_relations_for_product(related_product.id) assert ProductCrossSell.objects.count() == 2 cross_sell_product = ProductCrossSell.objects.filter(product1=related_product).first() assert cross_sell_product.product2 == product assert cross_sell_product.weight == len(quantities)
def test_payments(): 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, 1, 200, shop=shop) order.cache_prices() assert order.get_total_paid_amount().value == 0 assert order.get_total_unpaid_amount().value == order.taxful_total_price.value assert order.payment_status == PaymentStatus.NOT_PAID assert order.can_edit() partial_payment_amount = order.taxful_total_price / 2 remaining_amount = order.taxful_total_price - partial_payment_amount order.create_payment(partial_payment_amount) assert order.payment_status == PaymentStatus.PARTIALLY_PAID assert not order.can_edit() order.create_payment(remaining_amount) assert order.payment_status == PaymentStatus.FULLY_PAID assert not order.can_edit()
def test_shipment_creating_view_get(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) quantity = 1 order = create_order_with_product(product, supplier, quantity=quantity, taxless_base_unit_price=1, shop=shop) request = apply_request_middleware(rf.get("/"), user=admin_user) view = OrderCreateShipmentView.as_view() response = view(request, pk=order.pk, supplier_pk=supplier.pk).render() assert response.status_code == 200 # Should contain supplier input and input for product soup = BeautifulSoup(response.content) assert soup.find("input", {"id": "id_q_%s" % product.pk})
def create_random_review_for_product(shop, product, reviewer=None, order=None, approved=True, rating=None, would_recommend=None, generate_comment=True): if reviewer is None: reviewer = factories.create_random_person("en") if order is None: order = factories.create_order_with_product( product=product, supplier=factories.get_default_supplier(), quantity=1, taxless_base_unit_price=10, shop=shop) if rating is None: rating = random.randint(1, 5) if would_recommend is None: would_recommend = random.choice([True, False]) comment = None if generate_comment: comment = Faker().text(100) return ProductReview.objects.create( shop=shop, product=product, reviewer=reviewer, order=order, rating=rating, comment=comment, would_recommend=would_recommend, status=(ReviewStatus.APPROVED if approved else ReviewStatus.PENDING))
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 product_line = order.lines.first() 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_product_relations_max_quantity(rf): shop = get_default_shop() supplier = get_default_supplier() product = create_product("simple-test-product", shop) quantities = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] for i, quantity in enumerate(quantities): order = create_order_with_product(product, supplier, quantity=1, taxless_base_unit_price=6, shop=shop) add_product_to_order(order, supplier, create_product("product-%s" % i, shop), quantity=quantity, taxless_base_unit_price=6) assert ProductCrossSell.objects.count() == 0 add_bought_with_relations_for_product(product.pk, max_quantity=5) assert ProductCrossSell.objects.count() == 5 # Test that ordering is ok assert not ProductCrossSell.objects.filter(weight=1).exists() assert ProductCrossSell.objects.filter(weight=11).exists()
def test_max_refundable_amount(): 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) order.cache_prices() assert len(order.lines.all()) == 1 line = order.lines.first() assert line.max_refundable_amount == line.taxful_price.amount partial_refund_amount = Money(10, order.currency) assert partial_refund_amount.value > 0 order.create_refund([{"line": line, "quantity": 1, "amount": partial_refund_amount}]) assert line.max_refundable_amount == line.taxful_price.amount - partial_refund_amount assert order.get_total_tax_amount() == Money( order.taxful_total_price_value - order.taxless_total_price_value, order.currency)
def test_can_create_refund(): 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, 2, 200, shop=shop) order.cache_prices() assert order.can_create_payment() # Partially refunded orders can create refunds order.create_refund([ {"line": order.lines.first(), "quantity": 1, "amount": Money(200, order.currency), "restock": False}]) assert order.can_create_refund() # But fully refunded orders can't order.create_refund([ {"line": order.lines.first(), "quantity": 1, "amount": Money(200, order.currency), "restock": False}]) assert not order.can_create_refund()
def test_refund_entire_order_with_product_restock(): if "shuup.simple_supplier" not in settings.INSTALLED_APPS: pytest.skip("Need shuup.simple_supplier in INSTALLED_APPS") from shuup_tests.simple_supplier.utils import get_simple_supplier shop = get_default_shop() supplier = get_simple_supplier() product = create_product("test-sku", shop=get_default_shop(), default_price=10, stock_behavior=StockBehavior.STOCKED) supplier.adjust_stock(product.id, 5) check_stock_counts(supplier, product, 5, 5) order = create_order_with_product(product, supplier, 2, 200, shop=shop) order.cache_prices() check_stock_counts(supplier, product, 5, 3) # Create a full refund with `restock_products` set to True order.create_full_refund(restock_products=True) # restock logical count check_stock_counts(supplier, product, 5, 5)
def test_simple_supplier_out_of_stock(rf, anonymous): supplier = get_simple_supplier() shop = get_default_shop() product = create_product("simple-test-product", shop, supplier, stock_behavior=StockBehavior.STOCKED) if anonymous: customer = AnonymousContact() else: customer = create_random_person() ss = supplier.get_stock_status(product.pk) assert ss.product == product assert ss.logical_count == 0 num = random.randint(100, 500) supplier.adjust_stock(product.pk, +num) assert supplier.get_stock_status(product.pk).logical_count == num shop_product = product.get_shop_instance(shop) assert shop_product.is_orderable(supplier, customer, 1) # Create order order = create_order_with_product(product, supplier, num, 3, shop=shop) order.get_product_ids_and_quantities() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == 0 assert pss.physical_count == num assert not shop_product.is_orderable(supplier, customer, 1) # Create shipment shipment = order.create_shipment_of_all_products(supplier) assert isinstance(shipment, Shipment) pss = supplier.get_stock_status(product.pk) assert pss.logical_count == 0 assert pss.physical_count == 0 assert not shop_product.is_orderable(supplier, customer, 1)
def test_get_best_selling_products_cache_bump(): supplier = get_default_supplier() shop = get_default_shop() shop2 = get_shop(identifier="shop2") product1 = create_product("product1", shop, supplier, 10) product2 = create_product("product2", shop, supplier, 20) product3 = create_product("product3", shop2, supplier, 20) shop1_product1 = product1.get_shop_instance(shop) shop2_product3 = product3.get_shop_instance(shop2) create_order_with_product(product1, supplier, quantity=1, taxless_base_unit_price=10, shop=shop) create_order_with_product(product2, supplier, quantity=2, taxless_base_unit_price=20, shop=shop) create_order_with_product(product3, supplier, quantity=2, taxless_base_unit_price=30, shop=shop2) cache.clear() from shuup.front.template_helpers import general context = get_jinja_context() set_cached_value_mock = mock.Mock(wraps=context_cache.set_cached_value) def set_cache_value(key, value, timeout=None): if "best_selling_products" in key: return set_cached_value_mock(key, value, timeout) with mock.patch.object(context_cache, "set_cached_value", new=set_cache_value): assert set_cached_value_mock.call_count == 0 assert general.get_best_selling_products(context, 2, orderable_only=False) assert set_cached_value_mock.call_count == 1 # call again, the cache should be returned instead and the set_cached_value shouldn't be called again assert general.get_best_selling_products(context, 2, orderable_only=False) assert set_cached_value_mock.call_count == 1 # save the shop2 product and see whether the cache is bumped shop2_product3.save() # neve SHOULD be changed and things should be cached assert general.get_best_selling_products(context, 2, orderable_only=False) assert set_cached_value_mock.call_count == 1 # now change shop1 product, it should bump the cache shop1_product1.save() assert general.get_best_selling_products(context, 2, orderable_only=False) assert set_cached_value_mock.call_count == 2
def test_simple_supplier(rf): supplier = get_simple_supplier() shop = get_default_shop() product = create_product("simple-test-product", shop) ss = supplier.get_stock_status(product.pk) assert ss.product == product assert ss.logical_count == 0 num = random.randint(100, 500) supplier.adjust_stock(product.pk, +num) assert supplier.get_stock_status(product.pk).logical_count == num # Create order ... order = create_order_with_product(product, supplier, 10, 3, shop=shop) quantities = order.get_product_ids_and_quantities() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == num # Create shipment ... order.create_shipment_of_all_products(supplier) pss = supplier.get_stock_status(product.pk) assert pss.physical_count == (num - quantities[product.pk]) # Cancel order... order.set_canceled() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num)
def test_refund_entire_order(): if "shuup.simple_supplier" not in settings.INSTALLED_APPS: pytest.skip("Need shuup.simple_supplier in INSTALLED_APPS") from shuup_tests.simple_supplier.utils import get_simple_supplier shop = get_default_shop() supplier = get_simple_supplier() product = create_product("test-sku", shop=get_default_shop(), default_price=10, stock_behavior=StockBehavior.STOCKED) supplier.adjust_stock(product.id, 5) check_stock_counts(supplier, product, 5, 5) order = create_order_with_product(product, supplier, 2, 200, Decimal("0.24"), shop=shop) order.cache_prices() original_total_price = order.taxful_total_price check_stock_counts(supplier, product, 5, 3) # Create a full refund with `restock_products` set to False order.create_full_refund(restock_products=False) # Confirm the refund was created with correct amount assert order.taxless_total_price.amount.value == 0 assert order.taxful_total_price.amount.value == 0 refund_line = order.lines.order_by("ordering").last() assert refund_line.type == OrderLineType.REFUND assert refund_line.taxful_price == -original_total_price # Make sure logical count reflects refunded products check_stock_counts(supplier, product, 5, 3)
def test_simple_supplier(rf): if "shuup.simple_supplier" not in settings.INSTALLED_APPS: pytest.skip("Need shuup.simple_supplier in INSTALLED_APPS") from shuup_tests.simple_supplier.utils import get_simple_supplier supplier = get_simple_supplier() shop = get_default_shop() product = create_product("simple-test-product", shop) ss = supplier.get_stock_status(product.pk) assert ss.product == product assert ss.logical_count == 0 num = random.randint(100, 500) supplier.adjust_stock(product.pk, +num) assert supplier.get_stock_status(product.pk).logical_count == num # Create order order = create_order_with_product(product, supplier, 10, 3, shop=shop) quantities = order.get_product_ids_and_quantities() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == num # Create shipment shipment = order.create_shipment_of_all_products(supplier) assert isinstance(shipment, Shipment) pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == (num - quantities[product.pk]) # Delete shipment with pytest.raises(NotImplementedError): shipment.delete() shipment.soft_delete() assert shipment.is_deleted() pss = supplier.get_stock_status(product.pk) assert pss.logical_count == (num - quantities[product.pk]) assert pss.physical_count == (num)
def test_adding_extra_fields_to_the_delivery(rf): try: import weasyprint except ImportError: pytest.skip() shop = get_default_shop() supplier = get_default_supplier() product = create_product("simple-test-product", shop) order = create_order_with_product(product, supplier, 6, 6, shop=shop) shipment = order.create_shipment_of_all_products(supplier) request = apply_request_middleware(rf.get("/"), user=get_default_staff_user()) with override_provides( "order_printouts_delivery_extra_fields", [ "shuup_tests.order_printouts.test_printouts:PrintoutTestDeliveryExtraFields", ], ): response = get_delivery_html(request, shipment.id) assert response.status_code == 200 assert "123456789" in response.content.decode() assert "Random" in response.content.decode()
def test_best_selling_products_with_multiple_orders(): from shuup.front.template_helpers import general 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, default_price=price) product_2 = create_product("test-sku-2", supplier=supplier, shop=shop, default_price=price) 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) # Two initial products sold for cache_test in range(2): 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, default_price=price) create_order_with_product(product_3, supplier, quantity=2, taxless_base_unit_price=price, shop=shop) # Third product sold in greater quantity cache.clear() 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 for cache_test in range(2): 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 for cache_test in range(2): assert product_3 == general.get_best_selling_products( context, n_products=n_products)[0] # add a new product with discounted amount product_4 = create_product("test-sku-4", supplier=supplier, shop=shop, default_price=price) create_order_with_product(product_4, supplier, quantity=2, taxless_base_unit_price=price, shop=shop) from shuup.customer_group_pricing.models import CgpDiscount CgpDiscount.objects.create(shop=shop, product=product_4, group=AnonymousContact.get_default_group(), discount_amount_value=(price * 0.1))
def test_get_best_selling_products_per_supplier(): from shuup.front.template_helpers import general context = get_jinja_context() # No products sold assert len(list(general.get_best_selling_products(context, n_products=3))) == 0 shop = get_default_shop() supplier = get_default_supplier() supplier2 = Supplier.objects.create(name="supplier2", enabled=True) supplier2.shops.add(shop) product1 = create_product("product1", shop, supplier, 10) product2 = create_product("product2", shop, supplier2, 20) create_order_with_product(product1, supplier, quantity=1, taxless_base_unit_price=10, shop=shop) create_order_with_product(product2, supplier2, quantity=2, taxless_base_unit_price=20, shop=shop) cache.clear() # Two products sold, but only one supplier for cache_test in range(2): best_selling_products = list( general.get_best_selling_products(context, n_products=3, supplier=supplier)) assert len(best_selling_products) == 1 assert product1 in best_selling_products assert product2 not in best_selling_products # Two products sold, but only one supplier for cache_test in range(2): best_selling_products = list( general.get_best_selling_products(context, n_products=3, supplier=supplier2)) assert len(best_selling_products) == 1 assert product1 not in best_selling_products assert product2 in best_selling_products # Make product 1 also sold by supplier2 shop_product = product1.get_shop_instance(shop) shop_product.suppliers.add(supplier2) cache.clear() for cache_test in range(2): best_selling_products = list( general.get_best_selling_products(context, n_products=3, supplier=supplier2)) assert len(best_selling_products ) == 1 # Since there isn't any orders yet for supplier 2 assert product2 in best_selling_products create_order_with_product(product1, supplier2, quantity=2, taxless_base_unit_price=20, shop=shop) cache.clear() for cache_test in range(2): best_selling_products = list( general.get_best_selling_products(context, n_products=3, supplier=supplier2)) assert len(best_selling_products) == 2 assert product1 in best_selling_products assert product2 in best_selling_products
def test_get_best_selling_products(): from shuup.front.template_helpers import general context = get_jinja_context() # No products sold assert len(list(general.get_best_selling_products(context, n_products=3))) == 0 shop = get_default_shop() supplier = get_default_supplier() supplier2 = Supplier.objects.create(name="supplier2", enabled=True) supplier3 = Supplier.objects.create(name="supplier3", enabled=True) supplier2.shops.add(shop) supplier3.shops.add(shop) product1 = create_product("product1", shop, supplier, 10) product2 = create_product("product2", shop, supplier, 20) create_order_with_product(product1, supplier, quantity=1, taxless_base_unit_price=10, shop=shop) create_order_with_product(product2, supplier, quantity=2, taxless_base_unit_price=20, shop=shop) cache.clear() # Two products sold for cache_test in range(2): best_selling_products = list( general.get_best_selling_products(context, n_products=3)) assert len(best_selling_products) == 2 assert product1 in best_selling_products assert product2 in best_selling_products # Make order unorderable shop_product = product1.get_shop_instance(shop) shop_product.visibility = ShopProductVisibility.NOT_VISIBLE shop_product.save() cache.clear() for cache_test in range(2): best_selling_products = list( general.get_best_selling_products(context, n_products=3)) assert len(best_selling_products) == 1 assert product1 not in best_selling_products assert product2 in best_selling_products # add a new product with discounted amount product3 = create_product("product3", supplier=supplier, shop=shop, default_price=30) create_order_with_product(product3, supplier, quantity=1, taxless_base_unit_price=30, shop=shop) from shuup.customer_group_pricing.models import CgpDiscount CgpDiscount.objects.create(shop=shop, product=product3, group=AnonymousContact.get_default_group(), discount_amount_value=5) cache.clear() for cache_test in range(2): best_selling_products = list( general.get_best_selling_products(context, n_products=3, orderable_only=True)) assert len(best_selling_products) == 2 assert product1 not in best_selling_products assert product2 in best_selling_products assert product3 in best_selling_products
def test_taxes_report(rf): shop = get_default_shop() supplier = get_default_supplier(shop) 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_product_summary(): shop = get_default_shop() supplier = get_simple_supplier() product = create_product( "test-sku", shop=get_default_shop(), default_price=10, ) supplier.adjust_stock(product.id, 5) # Order with 2 unshipped, non-refunded items and a shipping cost order = create_order_with_product(product, supplier, 2, 200, shop=shop) order.cache_prices() product_line = order.lines.first() shipping_line = order.lines.create(type=OrderLineType.SHIPPING, base_unit_price_value=5, quantity=1) # Make sure no invalid entries and check product quantities product_summary = order.get_product_summary() assert all(product_summary.keys()) summary = product_summary[product.id] assert_defaultdict_values(summary, ordered=2, shipped=0, refunded=0, unshipped=2) # Create a shipment for the other item, make sure status changes assert order.shipping_status == ShippingStatus.NOT_SHIPPED assert order.can_create_shipment() order.create_shipment(supplier=supplier, product_quantities={product: 1}) assert order.shipping_status == ShippingStatus.PARTIALLY_SHIPPED order.create_refund([{ "line": shipping_line, "quantity": 1, "amount": Money(5, order.currency), "restock": False }]) product_summary = order.get_product_summary() assert all(product_summary.keys()) summary = product_summary[product.id] assert_defaultdict_values(summary, ordered=2, shipped=1, refunded=0, unshipped=1) # Create a refund for 2 items, we should get no negative values order.create_refund([{ "line": product_line, "quantity": 2, "amount": Money(200, order.currency), "restock": False }]) product_summary = order.get_product_summary() assert all(product_summary.keys()) summary = product_summary[product.id] assert_defaultdict_values(summary, ordered=2, shipped=1, refunded=2, unshipped=0)
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_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 _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_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(shop) 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 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_customer_sales_report(rf, order_by): shop = get_default_shop() supplier = get_default_supplier(shop) 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")))