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_order_refunds_with_other_lines(rf, admin_user): shop = get_default_shop() supplier = get_default_supplier() supplier.shops.add(shop) product = create_product("sku", shop=shop, default_price=10) shop_product = product.get_shop_instance(shop=shop) shop_product.suppliers = [supplier] order = create_empty_order(shop=shop) order.full_clean() order.save() add_product_to_order(order, supplier, product, 4, 5) # 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) # Lines with quantity again should be able to be refunded normally. other_line_with_quantity = OrderLine(order=order, type=OrderLineType.OTHER, text="Special service 100$/h", quantity=1, base_unit_price_value=100) other_line_with_quantity.save() order.lines.add(other_line_with_quantity) assert other_line_with_quantity.max_refundable_quantity == 1 assert other_line_with_quantity.max_refundable_amount.value == 100 order.cache_prices() order.create_payment(order.taxful_total_price) assert order.is_paid() assert order.taxful_total_price_value == 120 # 100 + 4 * 20 def get_refund_view_content(): request = apply_request_middleware(rf.get("/"), user=admin_user) view = OrderCreateRefundView.as_view() response = view(request, pk=order.pk) return BeautifulSoup(response.render().content) refund_soup = get_refund_view_content() refund_options = refund_soup.find( id="id_form-0-line_number").findAll("option") assert len( refund_options) == 4 # 1 empty line, 1 for arbitrary and 2 for lines assert len([ option for option in refund_options if "Special service 100$/h" in force_text(option) ]) == 1
def test_broken_order_lines(): with pytest.raises(ValidationError): OrderLine(type=OrderLineType.PRODUCT).save() with pytest.raises(ValidationError): OrderLine(product=get_default_product(), type=OrderLineType.PRODUCT, supplier=None).save() with pytest.raises(ValidationError): OrderLine(product=get_default_product(), type=OrderLineType.OTHER).save() with pytest.raises(ValidationError): OrderLine(product=get_default_product(), type=OrderLineType.OTHER, tax_rate=3).save()
def test_cannot_ship_basic_order_without_supplier_module(): PRODUCTS_TO_SEND = 10 product = get_default_product() supplier = get_default_supplier() supplier.supplier_modules.set([]) supplier.stock_managed = False supplier.save() order = create_order_with_product(product, supplier=supplier, quantity=PRODUCTS_TO_SEND, taxless_base_unit_price=10, tax_rate=Decimal("0.5")) assert order.shop.prices_include_tax is False price = order.shop.create_price currency = order.currency discount_order_line = OrderLine(order=order, quantity=1, type=OrderLineType.OTHER) discount_order_line.discount_amount = price(30) assert discount_order_line.price == price(-30) discount_order_line.save() order.cache_prices() order.check_all_verified() order.save() assert order.taxful_total_price == TaxfulPrice( PRODUCTS_TO_SEND * (10 + 5) - 30, currency) with pytest.raises(SupplierHasNoSupplierModules): shipment = order.create_shipment_of_all_products(supplier=supplier)
def add_product_to_order(order, supplier, product, quantity, taxless_base_unit_price, tax_rate=0, pricing_context=None): if not pricing_context: pricing_context = _get_pricing_context(order.shop, order.customer) product_order_line = OrderLine(order=order) update_order_line_from_product(pricing_context, order_line=product_order_line, product=product, quantity=quantity, supplier=supplier) base_unit_price = order.shop.create_price(taxless_base_unit_price) if order.prices_include_tax: base_unit_price *= (1 + tax_rate) product_order_line.base_unit_price = order.shop.create_price( base_unit_price) product_order_line.save() order_line_tax = OrderLineTax.from_tax( get_test_tax(tax_rate), Money(quantity * taxless_base_unit_price, order.currency), order_line=product_order_line, ) order_line_tax.save( ) # Save order line tax before linking to order_line.taxes product_order_line.taxes.add(order_line_tax)
def add_product_to_order(order, supplier, product, quantity, taxless_base_unit_price, tax_rate=0, pricing_context=None): if not pricing_context: pricing_context = _get_pricing_context(order.shop, order.customer) product_order_line = OrderLine(order=order) update_order_line_from_product(pricing_context, order_line=product_order_line, product=product, quantity=quantity, supplier=supplier) base_unit_price = order.shop.create_price(taxless_base_unit_price) if order.prices_include_tax: base_unit_price *= 1 + tax_rate product_order_line.base_unit_price = order.shop.create_price( base_unit_price) product_order_line.save() taxes = [get_test_tax(tax_rate)] price = quantity * base_unit_price taxed_price = stacked_value_added_taxes(price, taxes) order_line_tax = OrderLineTax.from_tax( taxes[0], taxed_price.taxless.amount, order_line=product_order_line, ) order_line_tax.save( ) # Save order line tax before linking to order_line.taxes product_order_line.taxes.add(order_line_tax)
def test_rounding(prices): expected = 0 for p in prices: expected += bankers_round(p, 2) order = create_empty_order(prices_include_tax=False) order.save() currency = order.shop.currency for x, price in enumerate(prices): ol = OrderLine(order=order, type=OrderLineType.OTHER, quantity=1, text="Thing", ordering=x, base_unit_price=order.shop.create_price(price)) ol.save() order.cache_prices() for x, order_line in enumerate(order.lines.all().order_by("ordering")): price = Decimal(prices[x]).quantize(Decimal(".1")**9) # make sure prices are in database with original precision assert order_line.base_unit_price == order.shop.create_price(price) # make sure the line taxless price is rounded assert order_line.taxless_price == order.shop.create_price( bankers_round(price, 2)) # make sure the line price is rounded assert order_line.price == order.shop.create_price(price) # make sure order total is rounded assert order.taxless_total_price == order.shop.create_price( bankers_round(expected, 2))
def source_line_to_order_lines(self, order, source_line): """ Convert a source line into one or more order lines. Normally each source line will yield just one order line, but package products will yield lines for both the parent and its children products. :type order: shuup.core.models.Order :param order: The order. :type source_line: shuup.core.order_creator.SourceLine :param source_line: The SourceLine. :rtype: Iterable[OrderLine] """ order_line = OrderLine(order=order) product = source_line.product quantity = Decimal(source_line.quantity) if product: order_line.product = product if product.sales_unit: quantized_quantity = bankers_round(quantity, product.sales_unit.decimals) if quantized_quantity != quantity: raise ValueError( "Error! Sales unit decimal conversion causes precision loss." ) else: order_line.product = None def text(value): return force_text(value) if value is not None else "" order_line.quantity = quantity order_line.supplier = source_line.supplier order_line.sku = text(source_line.sku) order_line.text = (text(source_line.text))[:192] if source_line.base_unit_price: order_line.base_unit_price = source_line.base_unit_price if source_line.discount_amount: order_line.discount_amount = source_line.discount_amount order_line.type = (source_line.type if source_line.type is not None else OrderLineType.OTHER) order_line.accounting_identifier = text( source_line.accounting_identifier) order_line.require_verification = bool( source_line.require_verification) order_line.verified = (not order_line.require_verification) order_line.source_line = source_line order_line.parent_source_line = source_line.parent_line extra_data = source_line.data.get("extra", {}) if hasattr( source_line, "data") else {} extra_data.update({"source_line_id": source_line.line_id}) order_line.extra_data = extra_data self._check_orderability(order_line) yield order_line for child_order_line in self.create_package_children(order_line): yield child_order_line
def test_basic_order_without_supplier_module(): PRODUCTS_TO_SEND = 10 product = get_default_product() supplier = get_default_supplier() supplier.supplier_modules.set([]) supplier.stock_managed = False supplier.save() order = create_order_with_product(product, supplier=supplier, quantity=PRODUCTS_TO_SEND, taxless_base_unit_price=10, tax_rate=Decimal("0.5")) assert order.shop.prices_include_tax is False price = order.shop.create_price currency = order.currency discount_order_line = OrderLine(order=order, quantity=1, type=OrderLineType.OTHER) discount_order_line.discount_amount = price(30) assert discount_order_line.price == price(-30) discount_order_line.save() order.cache_prices() order.check_all_verified() order.save() assert order.taxful_total_price == TaxfulPrice( PRODUCTS_TO_SEND * (10 + 5) - 30, currency) shipment = order.create_shipment_of_all_products(supplier=supplier) assert shipment.total_products == 0 assert shipment.weight == 0 assert PRODUCTS_TO_SEND == int( order.get_unshipped_products()[product.id]["unshipped"]) order.create_payment(order.taxful_total_price) assert order.payments.exists(), "A payment was created" with pytest.raises(NoPaymentToCreateException): order.create_payment(Money(6, currency)) assert order.is_paid(), "Order got paid" assert not order.can_set_complete(), "Finalization is possible" # Force to be complete order.change_status(next_status=OrderStatus.objects.get_default_complete(), save=False) assert order.is_complete(), "Finalization done" summary = order.get_tax_summary() assert len(summary) == 2 assert summary[0].tax_rate * 100 == 50 assert summary[0].based_on == Money(100, currency) assert summary[0].tax_amount == Money(50, currency) assert summary[0].taxful == summary[0].based_on + summary[0].tax_amount assert summary[1].tax_id is None assert summary[1].tax_code == "" assert summary[1].tax_amount == Money(0, currency) assert summary[1].tax_rate == 0 assert order.get_total_tax_amount() == Money(50, currency)
def test_order_refunds_with_other_lines(): shop = get_default_shop() supplier = Supplier.objects.create(identifier="1", name="supplier1") supplier.shops.add(shop) product = create_product("sku", shop=shop, default_price=10) shop_product = product.get_shop_instance(shop=shop) shop_product.suppliers.set([supplier]) order = create_empty_order(shop=shop) order.full_clean() order.save() add_product_to_order(order, supplier, product, 4, 5) # 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) # Lines with quantity again should be able to be refunded normally. other_line_with_quantity = OrderLine(order=order, type=OrderLineType.OTHER, text="Special service 100$/h", quantity=1, base_unit_price_value=100) other_line_with_quantity.save() order.lines.add(other_line_with_quantity) assert other_line_with_quantity.max_refundable_quantity == 1 assert other_line_with_quantity.max_refundable_amount.value == 100 order.cache_prices() order.create_payment(order.taxful_total_price) assert order.is_paid() assert order.taxful_total_price_value == 120 # 100 + 4 * 20 order.create_full_refund() assert order.taxful_total_price_value == 0
def _get_order_line(request): order = Order( shop=request.shop, currency=request.shop.currency, prices_include_tax=request.shop.prices_include_tax, ) pi = _get_price_info(request.shop, Product(sku='6.0745'), quantity=2) return OrderLine( order=order, base_unit_price=pi.base_unit_price, discount_amount=pi.discount_amount, quantity=pi.quantity, )
def test_basic_order(): PRODUCTS_TO_SEND = 10 product = get_default_product() supplier = get_default_supplier() order = create_order_with_product(product, supplier=supplier, quantity=PRODUCTS_TO_SEND, taxless_base_unit_price=10, tax_rate=Decimal("0.5")) assert order.shop.prices_include_tax is False price = order.shop.create_price currency = order.currency discount_order_line = OrderLine(order=order, quantity=1, type=OrderLineType.OTHER) discount_order_line.discount_amount = price(30) assert discount_order_line.price == price(-30) discount_order_line.save() order.cache_prices() order.check_all_verified() order.save() assert order.taxful_total_price == TaxfulPrice( PRODUCTS_TO_SEND * (10 + 5) - 30, currency) shipment = order.create_shipment_of_all_products(supplier=supplier) assert shipment.total_products == PRODUCTS_TO_SEND, "All products were shipped" assert shipment.weight == product.gross_weight * PRODUCTS_TO_SEND / 1000, "Gravity works" assert not order.get_unshipped_products( ), "Nothing was left in the warehouse" order.shipping_status = ShippingStatus.FULLY_SHIPPED order.create_payment(order.taxful_total_price) assert order.payments.exists(), "A payment was created" with pytest.raises(NoPaymentToCreateException): order.create_payment(Money(6, currency)) assert order.is_paid(), "Order got paid" assert order.can_set_complete(), "Finalization is possible" order.status = OrderStatus.objects.get_default_complete() assert order.is_complete(), "Finalization done" summary = order.get_tax_summary() assert len(summary) == 2 assert summary[0].tax_rate * 100 == 50 assert summary[0].based_on == Money(100, currency) assert summary[0].tax_amount == Money(50, currency) assert summary[0].taxful == summary[0].based_on + summary[0].tax_amount assert summary[1].tax_id is None assert summary[1].tax_code == '' assert summary[1].tax_amount == Money(0, currency) assert summary[1].tax_rate == 0 assert order.get_total_tax_amount() == Money(50, currency)
def _get_order_and_order_line(request): order = Order( shop=request.shop, currency=request.shop.currency, prices_include_tax=request.shop.prices_include_tax, ) order.taxful_total_price = TaxfulPrice("100", request.shop.currency) order.taxless_total_price = TaxlessPrice("50", request.shop.currency) pi = _get_price_info(request.shop, Product(sku='6.0745'), quantity=2) return (order, OrderLine( order=order, base_unit_price=pi.base_unit_price, discount_amount=pi.discount_amount, quantity=pi.quantity, ))
def test_line_discount_more(): order = create_empty_order() order.save() ol = OrderLine(order=order, type=OrderLineType.OTHER) ol.quantity = 5 ol.base_unit_price = order.shop.create_price(30) ol.discount_amount = order.shop.create_price(50) ol.save() currency = order.shop.currency assert ol.taxless_base_unit_price == TaxlessPrice(30, currency) assert ol.taxless_discount_amount == TaxlessPrice(50, currency) assert ol.taxless_price == TaxlessPrice(5 * 30 - 50, currency) ol.taxes.add(OrderLineTax.from_tax( get_default_tax(), ol.taxless_price.amount, order_line=ol)) assert ol.taxless_discount_amount == TaxlessPrice(50, currency) assert ol.taxful_discount_amount == TaxfulPrice(75, currency) assert ol.taxless_price == TaxlessPrice(100, currency) assert ol.taxful_price == TaxfulPrice(150, currency) assert ol.taxless_base_unit_price == TaxlessPrice(30, currency) assert ol.taxful_base_unit_price == TaxfulPrice(45, currency)
def test_line_discount(): order = create_empty_order(prices_include_tax=False) order.save() currency = order.shop.currency ol = OrderLine( order=order, type=OrderLineType.OTHER, quantity=5, text="Thing" ) ol.discount_amount = order.shop.create_price(50) ol.base_unit_price = order.shop.create_price(40) ol.save() ol.taxes.add(OrderLineTax.from_tax( get_default_tax(), ol.taxless_price.amount, order_line=ol)) assert ol.taxless_discount_amount == order.shop.create_price(50) assert ol.taxful_discount_amount == TaxfulPrice(75, currency) assert ol.taxless_price == order.shop.create_price(150) assert ol.taxful_price == TaxfulPrice(150 + 75, currency) assert ol.taxless_base_unit_price == order.shop.create_price(40) assert ol.taxful_base_unit_price == TaxfulPrice(60, currency) assert "Thing" in six.text_type(ol)
def create_package_children(self, order_line): order = order_line.order parent_product = order_line.product # :type parent_product: shuup.core.models.Product if not (parent_product and parent_product.is_package_parent()): return child_to_quantity = parent_product.get_package_child_to_quantity_map() for (child_product, child_quantity) in child_to_quantity.items(): child_order_line = OrderLine(order=order, parent_line=order_line) update_order_line_from_product( pricing_context=None, # Will use zero price order_line=child_order_line, product=child_product, quantity=(order_line.quantity * child_quantity), ) # Package children are free assert child_order_line.base_unit_price.value == 0 child_order_line.source_line = None child_order_line.parent_source_line = order_line.source_line child_order_line.supplier = order_line.supplier self._check_orderability(child_order_line) yield child_order_line
def add_product_to_order(order, supplier, product, quantity, taxless_base_unit_price, tax_rate=0, pricing_context=None): if not pricing_context: pricing_context = _get_pricing_context(order.shop, order.customer) product_order_line = OrderLine(order=order) update_order_line_from_product(pricing_context, order_line=product_order_line, product=product, quantity=quantity, supplier=supplier) product_order_line.base_unit_price = order.shop.create_price( taxless_base_unit_price) product_order_line.save() product_order_line.taxes.add( OrderLineTax.from_tax( get_test_tax(tax_rate), product_order_line.taxless_price.amount, order_line=product_order_line, ))
def create_order(request, creator, customer, product): billing_address = get_address().to_immutable() shipping_address = get_address(name="Shippy Doge").to_immutable() shipping_address.save() shop = request.shop order = Order( creator=creator, customer=customer, shop=shop, payment_method=get_default_payment_method(), shipping_method=get_default_shipping_method(), billing_address=billing_address, shipping_address=shipping_address, order_date=now(), status=get_initial_order_status(), currency=shop.currency, prices_include_tax=shop.prices_include_tax, ) order.full_clean() order.save() supplier = get_default_supplier() product_order_line = OrderLine(order=order) update_order_line_from_product(pricing_context=request, order_line=product_order_line, product=product, quantity=5, supplier=supplier) assert product_order_line.text == product.safe_translation_getter("name") product_order_line.base_unit_price = shop.create_price(100) assert product_order_line.price.value > 0 product_order_line.save() line_tax = get_line_taxes_for(product_order_line)[0] order_line_tax = OrderLineTax.from_tax( tax=line_tax.tax, base_amount=line_tax.base_amount, order_line=product_order_line, ) order_line_tax.save( ) # Save order_line_tax before linking to order_line.tax product_order_line.taxes.add(order_line_tax) discount_order_line = OrderLine(order=order, quantity=1, type=OrderLineType.OTHER) discount_order_line.discount_amount = shop.create_price(30) assert discount_order_line.discount_amount.value == 30 assert discount_order_line.price.value == -30 assert discount_order_line.base_unit_price.value == 0 discount_order_line.save() order.cache_prices() order.check_all_verified() order.save() assert not order.can_set_complete() base = 5 * shop.create_price(100).amount discount = shop.create_price(30).amount tax_value = line_tax.amount if not order.prices_include_tax: assert order.taxless_total_price.amount == base - discount assert order.taxful_total_price.amount == base + tax_value - discount else: assert_almost_equal(order.taxless_total_price.amount, base - tax_value - discount) assert_almost_equal(order.taxful_total_price.amount, base - discount) assert not order.is_fully_shipped() shipment = order.create_shipment_of_all_products(supplier=supplier) assert order.is_fully_shipped() assert shipment.total_products == 5, "All products were shipped" assert shipment.weight == product.gross_weight * 5 / 1000, "Gravity works" assert not order.get_unshipped_products( ), "Nothing was left in the warehouse" assert order.can_set_complete() order.create_payment(order.taxful_total_price) assert order.is_paid() assert Order.objects.paid().filter( pk=order.pk).exists(), "It was paid! Honestly!" assert order.has_products()
def test_order_arbitrary_refunds_with_multiple_suppliers(): shop = get_default_shop() supplier1 = Supplier.objects.create(identifier="1", name="supplier1") supplier1.shops.add(shop) supplier2 = Supplier.objects.create(identifier="2") supplier2.shops.add(shop) supplier3 = Supplier.objects.create(identifier="3", name="s") supplier3.shops.add(shop) product1 = create_product("sku1", shop=shop, default_price=10) shop_product1 = product1.get_shop_instance(shop=shop) shop_product1.suppliers.set([supplier1, supplier2, supplier3]) product2 = create_product("sku2", shop=shop, default_price=10) shop_product2 = product1.get_shop_instance(shop=shop) shop_product2.suppliers.set([supplier1, supplier2]) product3 = create_product("sku3", shop=shop, default_price=10, shipping_mode=ShippingMode.NOT_SHIPPED) shop_product3 = product1.get_shop_instance(shop=shop) shop_product3.suppliers.set([supplier3]) product_quantities = { supplier1: {product1: 5, product2: 6}, supplier2: {product1: 3, product2: 13}, supplier3: {product1: 1, product3: 50}, } def get_quantity(supplier, product): return product_quantities[supplier.pk][product.pk] order = create_empty_order(shop=shop) order.full_clean() order.save() for supplier, product_data in six.iteritems(product_quantities): for product, quantity in six.iteritems(product_data): add_product_to_order(order, supplier, product, quantity, 5) # 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.create_payment(order.taxful_total_price) assert order.is_paid() # All supplier should be able to refund the order assert order.can_create_refund() assert order.can_create_refund(supplier1) assert order.can_create_refund(supplier2) assert order.can_create_refund(supplier3) # Step by step refund lines for supplier1 assert order.can_create_refund() assert order.get_total_unrefunded_amount(supplier1).value == Decimal("55") # 11 * 5 assert order.get_total_unrefunded_amount().value == Decimal("390") # 55 + 80 + 255 proudct1_line_for_supplier1 = order.lines.filter(supplier=supplier1, product=product1).first() supplier1_refund_data = [ { "line": proudct1_line_for_supplier1, "quantity": proudct1_line_for_supplier1.quantity, "amount": order.shop.create_price(20).amount, # Line total is 5 * 5 = 25 "restock_products": True, } ] order.create_refund(supplier1_refund_data) assert order.get_total_unrefunded_amount(supplier1).value == Decimal("35") order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(30).amount}], supplier=supplier1 ) assert order.get_total_unrefunded_amount(supplier1).value == Decimal("5") order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(5).amount}], supplier=supplier1 ) assert order.get_total_unrefunded_amount(supplier1).value == Decimal("0") assert order.can_create_refund(supplier1) # Some quantity still left to refund proudct2_line_for_supplier1 = order.lines.filter(supplier=supplier1, product=product2).first() supplier1_restock_refund_data = [ { "line": proudct2_line_for_supplier1, "quantity": proudct2_line_for_supplier1.quantity, "amount": order.shop.create_price(0).amount, # Line total is 5 * 5 = 25 "restock_products": True, } ] order.create_refund(supplier1_restock_refund_data) assert not order.can_create_refund(supplier1) # Step by step refund lines for supplier2 assert order.can_create_refund() assert order.get_total_unrefunded_amount(supplier2).value == Decimal("80") # 16 * 5 assert order.get_total_unrefunded_amount().value == Decimal("335") # 80 + 255 proudct2_line_for_supplier2 = order.lines.filter(supplier=supplier2, product=product2).first() supplier2_refund_data = [ { "line": proudct2_line_for_supplier2, "quantity": 10, "amount": order.shop.create_price(50).amount, # Line total is 13 * 5 = 65 "restock_products": True, } ] order.create_refund(supplier2_refund_data) assert order.get_total_unrefunded_amount(supplier2).value == Decimal("30") order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(5).amount}], supplier=supplier2 ) assert order.get_total_unrefunded_amount(supplier2).value == Decimal("25") order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(25).amount}], supplier=supplier2 ) assert order.get_total_unrefunded_amount(supplier2).value == Decimal("0") assert order.can_create_refund(supplier2) # Some quantity still left to refund supplier2_restock_refund_data = [ { "line": proudct2_line_for_supplier2, "quantity": 3, "amount": order.shop.create_price(0).amount, # Line total is 5 * 5 = 25 "restock_products": True, } ] order.create_refund(supplier2_restock_refund_data) proudct1_line_for_supplier2 = order.lines.filter(supplier=supplier2, product=product1).first() supplier1_restock_refund_data = [ { "line": proudct1_line_for_supplier2, "quantity": proudct1_line_for_supplier2.quantity, "amount": order.shop.create_price(0).amount, # Line total is 5 * 5 = 25 "restock_products": True, } ] order.create_refund(supplier1_restock_refund_data) assert not order.can_create_refund(supplier2) # Step by step refund lines for supplier3 assert order.can_create_refund() assert order.get_total_unrefunded_amount(supplier3).value == Decimal("255") # 51 * 5 assert order.get_total_unrefunded_amount().value == Decimal("255") # 255 order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(55).amount}], supplier=supplier3 ) assert order.get_total_unrefunded_amount(supplier3).value == Decimal("200") proudct3_line_for_supplier3 = order.lines.filter(supplier=supplier3, product=product3).first() supplier3_refund_data = [ { "line": proudct3_line_for_supplier3, "quantity": 50, "amount": order.shop.create_price(200).amount, # Line total is 13 * 5 = 65 "restock_products": True, } ] order.create_refund(supplier3_refund_data) assert order.get_total_unrefunded_amount(supplier2).value == Decimal("0") assert order.get_total_unrefunded_amount().value == Decimal("0") assert order.can_create_refund(supplier3) # Some quantity still left to refund proudct1_line_for_supplier3 = order.lines.filter(supplier=supplier3, product=product1).first() supplier3_restock_refund_data = [ { "line": proudct1_line_for_supplier3, "quantity": proudct1_line_for_supplier3.quantity, "amount": order.shop.create_price(0).amount, # Line total is 5 * 5 = 25 "restock_products": True, } ] order.create_refund(supplier3_restock_refund_data) assert not order.can_create_refund(supplier3) assert not order.can_create_refund()
def test_order_refunds_with_multiple_suppliers(): shop = get_default_shop() supplier1 = Supplier.objects.create(identifier="1", name="supplier1") supplier1.shops.add(shop) supplier2 = Supplier.objects.create(identifier="2") supplier2.shops.add(shop) supplier3 = Supplier.objects.create(identifier="3", name="s") supplier3.shops.add(shop) product1 = create_product("sku1", shop=shop, default_price=10) shop_product1 = product1.get_shop_instance(shop=shop) shop_product1.suppliers.set([supplier1, supplier2, supplier3]) product2 = create_product("sku2", shop=shop, default_price=10) shop_product2 = product1.get_shop_instance(shop=shop) shop_product2.suppliers.set([supplier1, supplier2]) product3 = create_product("sku3", shop=shop, default_price=10, shipping_mode=ShippingMode.NOT_SHIPPED) shop_product3 = product1.get_shop_instance(shop=shop) shop_product3.suppliers.set([supplier3]) product_quantities = { supplier1: {product1: 5, product2: 6}, supplier2: {product1: 3, product2: 13}, supplier3: {product1: 1, product3: 50}, } def get_quantity(supplier, product): return product_quantities[supplier.pk][product.pk] order = create_empty_order(shop=shop) order.full_clean() order.save() for supplier, product_data in six.iteritems(product_quantities): for product, quantity in six.iteritems(product_data): add_product_to_order(order, supplier, product, quantity, 5) # 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.create_payment(order.taxful_total_price) assert order.is_paid() # All supplier should be able to refund the order assert order.can_create_refund() assert order.can_create_refund(supplier1) assert order.can_create_refund(supplier2) assert order.can_create_refund(supplier3) assert order.get_total_unrefunded_amount(supplier1).value == Decimal("55") # 11 * 5 assert order.get_total_unrefunded_quantity(supplier1) == Decimal("11") # 5 x product1 and 6 x product2 with pytest.raises(RefundExceedsAmountException): order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(60)}], supplier=supplier1 ) # Supplier 1 refunds the order order.create_refund(_get_refund_data(order, supplier1)) assert order.get_total_refunded_amount(supplier1).value == Decimal("55") # 11 * 5 assert order.get_total_unrefunded_amount(supplier1).value == Decimal("0") assert not order.can_create_refund(supplier1) assert order.can_create_refund() assert order.can_create_refund(supplier2) assert order.can_create_refund(supplier3) assert order.get_total_unrefunded_amount(supplier2).value == Decimal("80") # 16 * 5 assert order.get_total_unrefunded_quantity(supplier2) == Decimal("16") # 3 x product1 and 13 x product2 with pytest.raises(RefundExceedsAmountException): order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(81)}], supplier=supplier2 ) # Supplier 2 refunds the order order.create_refund(_get_refund_data(order, supplier2)) assert order.get_total_refunded_amount(supplier2).value == Decimal("80") # 11 * 5 assert order.get_total_unrefunded_amount(supplier2).value == Decimal("0") assert not order.can_create_refund(supplier1) assert not order.can_create_refund(supplier2) assert order.can_create_refund() assert order.can_create_refund(supplier3) assert order.get_total_unrefunded_amount(supplier3).value == Decimal("255") # 51 * 5 assert order.get_total_unrefunded_quantity(supplier3) == Decimal("51") # 3 x product1 and 13 x product2 with override_settings(SHUUP_ALLOW_ARBITRARY_REFUNDS=False): with pytest.raises(RefundArbitraryRefundsNotAllowedException): order.create_refund( [{"line": "amount", "quantity": 1, "amount": order.shop.create_price(200)}], supplier=supplier3 ) order.create_refund([{"line": "amount", "quantity": 1, "amount": order.shop.create_price(200)}], supplier=supplier3) assert OrderLine.objects.filter(order=order, supplier=supplier3, type=OrderLineType.REFUND).exists() # Supplier 3 refunds the order order.create_refund(_get_refund_data(order, supplier3)) assert order.get_total_refunded_amount(supplier3).value == Decimal("255") # 11 * 5 assert order.get_total_unrefunded_amount(supplier3).value == Decimal("0") assert not order.can_create_refund(supplier1) assert not order.can_create_refund(supplier2) assert not order.can_create_refund(supplier3) assert not order.can_create_refund()