示例#1
0
def test_units_match():
    class XxxMoney(int):
        currency = 'XXX'

    m1 = Money(1, 'EUR')
    m2 = Money(2, 'EUR')
    m3 = Money(3, 'XXX')
    m4 = XxxMoney(4)

    assert m1.unit_matches_with(m2)
    assert not m1.unit_matches_with(m3)
    assert m3.unit_matches_with(m4)
示例#2
0
def test_units_match():
    class XxxMoney(int):
        currency = "XXX"

    m1 = Money(1, "EUR")
    m2 = Money(2, "EUR")
    m3 = Money(3, "XXX")
    m4 = XxxMoney(4)

    assert m1.unit_matches_with(m2)
    assert not m1.unit_matches_with(m3)
    assert m3.unit_matches_with(m4)
示例#3
0
def test_as_rounded_rounding_mode():
    set_precision_provider(babel_precision_provider.get_precision)

    prec2 = Decimal('0.01')
    m1 = Money('2.345', 'EUR')
    m2 = Money('2.344', 'EUR')

    assert m1.as_rounded(2).value == Decimal('2.34')
    assert m2.as_rounded(2).value == Decimal('2.34')

    from decimal import ROUND_HALF_DOWN, ROUND_HALF_UP, ROUND_FLOOR

    assert m1.as_rounded(2, rounding=ROUND_HALF_DOWN).value == Decimal('2.34')
    assert m2.as_rounded(2, rounding=ROUND_HALF_DOWN).value == Decimal('2.34')
    assert m1.as_rounded(2, rounding=ROUND_HALF_UP).value == Decimal('2.35')
    assert m2.as_rounded(2, rounding=ROUND_HALF_UP).value == Decimal('2.34')
    assert m1.as_rounded(2, rounding=ROUND_FLOOR).value == Decimal('2.34')
    assert m2.as_rounded(2, rounding=ROUND_FLOOR).value == Decimal('2.34')
示例#4
0
def test_as_rounded_rounding_mode():
    set_precision_provider(babel_precision_provider.get_precision)

    prec2 = Decimal("0.01")
    m1 = Money("2.345", "EUR")
    m2 = Money("2.344", "EUR")

    assert m1.as_rounded(2).value == Decimal("2.34")
    assert m2.as_rounded(2).value == Decimal("2.34")

    from decimal import ROUND_HALF_DOWN, ROUND_HALF_UP, ROUND_FLOOR

    assert m1.as_rounded(2, rounding=ROUND_HALF_DOWN).value == Decimal("2.34")
    assert m2.as_rounded(2, rounding=ROUND_HALF_DOWN).value == Decimal("2.34")
    assert m1.as_rounded(2, rounding=ROUND_HALF_UP).value == Decimal("2.35")
    assert m2.as_rounded(2, rounding=ROUND_HALF_UP).value == Decimal("2.34")
    assert m1.as_rounded(2, rounding=ROUND_FLOOR).value == Decimal("2.34")
    assert m2.as_rounded(2, rounding=ROUND_FLOOR).value == Decimal("2.34")
示例#5
0
def test_money_property_set_invalid_unit():
    w = get_wallet()
    with pytest.raises(UnitMixupError):
        w.amount = Money(3, 'USD')
示例#6
0
 def get_total_unpaid_amount(self):
     difference = self.taxful_total_price.amount - self.get_total_paid_amount(
     )
     return max(difference, Money(0, self.currency))
示例#7
0
 def get_total_refunded_amount(self):
     total = sum(
         [line.taxful_price.amount.value for line in self.lines.refunds()])
     return Money(-total, self.currency)
示例#8
0
def usd(value):
    """
    Get Money with USD currency for given value.
    """
    return Money(value, "USD")
示例#9
0
def test_format_money():
    assert format_money(Money("3.6", "EUR"), locale="fi") == "3,60\xa0\u20ac"
    assert format_money(Money("3.6", "EUR"), widen=2,
                        locale="fi") == "3,6000\xa0\u20ac"
    assert format_money(Money("3.6", "EUR"), digits=0,
                        locale="fi") == "4\xa0\u20ac"
示例#10
0
def test_broken_order(admin_user):
    """
    """
    quantities = [44, 23, 65]
    expected = sum(quantities) * 50
    expected_based_on = expected / 1.5

    # Shuup is calculating taxes per line so there will be some "errors"
    expected_based_on = ensure_decimal_places(
        Decimal("%s" % (expected_based_on + 0.01)))

    shop = get_default_shop()

    supplier = get_default_supplier()
    product1 = create_product("simple-test-product1", shop, supplier, 50)
    product2 = create_product("simple-test-product2", shop, supplier, 50)
    product3 = create_product("simple-test-product3", shop, supplier, 50)

    tax = get_default_tax()

    source = BasketishOrderSource(get_default_shop())
    billing_address = get_address(country="US")
    shipping_address = get_address(name="Test street", country="US")
    source.status = get_initial_order_status()
    source.billing_address = billing_address
    source.shipping_address = shipping_address
    source.customer = create_random_person()
    source.payment_method = get_default_payment_method()
    source.shipping_method = get_default_shipping_method()

    source.add_line(
        type=OrderLineType.PRODUCT,
        product=product1,
        supplier=get_default_supplier(),
        quantity=quantities[0],
        base_unit_price=source.create_price(50),
    )

    source.add_line(
        type=OrderLineType.PRODUCT,
        product=product2,
        supplier=get_default_supplier(),
        quantity=quantities[1],
        base_unit_price=source.create_price(50),
    )

    source.add_line(
        type=OrderLineType.PRODUCT,
        product=product3,
        supplier=get_default_supplier(),
        quantity=quantities[2],
        base_unit_price=source.create_price(50),
    )

    currency = "EUR"
    summary = source.get_tax_summary()

    assert len(summary) == 1
    summary = summary[0]

    assert summary.taxful == Money(expected, "EUR")
    assert summary.based_on == Money(expected_based_on, "EUR")

    # originally non-rounded value
    assert bankers_round(source.get_total_tax_amount()) == summary.tax_amount

    assert source.taxless_total_price.value == expected_based_on
    assert summary.taxful.value == source.taxful_total_price.value

    assert summary.tax_amount == Money(
        bankers_round(source.taxful_total_price.value -
                      source.taxless_total_price.value), currency)
    assert summary.taxful == summary.raw_based_on + summary.tax_amount

    assert summary.tax_rate == tax.rate
    assert summary.taxful.value == (
        summary.based_on + summary.tax_amount).value - Decimal("%s" % 0.01)

    # create order from basket
    creator = OrderCreator()
    order = creator.create_order(source)
    assert order.taxless_total_price.value == expected_based_on

    # originally non-rounded value
    assert bankers_round(order.get_total_tax_amount()) == summary.tax_amount
示例#11
0
 def money_sum(iterable):
     return sum(iterable, Money(0, price.currency))
示例#12
0
def test_money_init_from_value_with_currency():
    class Dollar(int):
        currency = "USD"

    assert Money(Dollar(42)) == Money(42, "USD")
示例#13
0
def test_str():
    assert str(Money("42.25", "EUR")) == "42.25 EUR"
    assert str(Money("100", "USD")) == "100 USD"

    assert str(Money(42, "EUR")) == "42 EUR"
    assert str(Money("12.345", "EUR")) == "12.345 EUR"
示例#14
0
def test_money_property_get():
    w = get_wallet()
    assert w.amount == Money(42, 'EUR')
示例#15
0
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."
示例#16
0
def test_str():
    assert str(Money('42.25', 'EUR')) == '42.25 EUR'
    assert str(Money('100', 'USD')) == '100 USD'

    assert str(Money(42, 'EUR')) == '42 EUR'
    assert str(Money('12.345', 'EUR')) == '12.345 EUR'
示例#17
0
def test_repr():
    assert repr(Money(42, 'EUR')) == "Money('42', 'EUR')"
    assert repr(Money('42.123', 'EUR')) == "Money('42.123', 'EUR')"
    assert repr(Money('42.0', 'EUR')) == "Money('42.0', 'EUR')"
    assert repr(Money('42.123', 'EUR')) == "Money('42.123', 'EUR')"
    assert repr(Money('42.123', 'USD')) == "Money('42.123', 'USD')"
示例#18
0
def test_money_init_from_money():
    assert Money(Money(123, 'GBP')) == Money(123, 'GBP')
示例#19
0
def test_repr():
    assert repr(Money(42, "EUR")) == "Money('42', 'EUR')"
    assert repr(Money("42.123", "EUR")) == "Money('42.123', 'EUR')"
    assert repr(Money("42.0", "EUR")) == "Money('42.0', 'EUR')"
    assert repr(Money("42.123", "EUR")) == "Money('42.123', 'EUR')"
    assert repr(Money("42.123", "USD")) == "Money('42.123', 'USD')"
示例#20
0
def test_order_creator(rf, admin_user):
    source = seed_source(admin_user)
    source.add_line(
        type=OrderLineType.PRODUCT,
        product=get_default_product(),
        supplier=get_default_supplier(),
        quantity=1,
        base_unit_price=source.create_price(10),
    )
    source.add_line(accounting_identifier="strawberries",
                    type=OrderLineType.OTHER,
                    quantity=1,
                    base_unit_price=source.create_price(10),
                    require_verification=True,
                    extra={"runner": "runner"})

    the_line = [
        sl for sl in source.get_lines()
        if sl.accounting_identifier == "strawberries"
    ]
    assert the_line[0].data["extra"]["runner"] == "runner"

    creator = OrderCreator()
    order = creator.create_order(source)
    zero = Money(0, order.currency)

    taxful_total_price = TaxfulPrice(-50, order.currency)
    last_price = order.taxful_total_price
    order.taxful_total_price = taxful_total_price
    order.save()
    assert not order.is_paid()
    assert not order.is_canceled()
    assert not order.get_total_unpaid_amount() > zero
    assert order.get_total_unpaid_amount() == zero
    assert not order.get_total_unpaid_amount() < zero
    assert not order.can_create_payment()
    order.taxful_total_price = last_price
    order.save()

    assert not order.is_paid()
    assert not order.is_canceled()
    assert order.get_total_unpaid_amount() > zero
    assert not order.get_total_unpaid_amount() == zero
    assert not order.get_total_unpaid_amount() < zero
    assert order.can_create_payment()

    order.set_canceled()
    assert not order.is_paid()
    assert order.is_canceled()
    assert order.get_total_unpaid_amount() > zero
    assert not order.get_total_unpaid_amount() == zero
    assert not order.get_total_unpaid_amount() < zero
    assert not order.can_create_payment()

    order.create_payment(order.get_total_unpaid_amount())
    assert order.is_paid()
    assert order.is_canceled()
    assert not order.get_total_unpaid_amount() > zero
    assert order.get_total_unpaid_amount() == zero
    assert not order.get_total_unpaid_amount() < zero
    assert not order.can_create_payment()

    with pytest.raises(NoPaymentToCreateException):
        order.create_payment(order.get_total_unpaid_amount())
        order.create_payment(order.get_total_unpaid_amount() +
                             Money(10, order.currency))
        order.create_payment(order.get_total_unpaid_amount() -
                             Money(10, order.currency))

    assert get_data_dict(source.billing_address) == get_data_dict(
        order.billing_address)
    assert get_data_dict(source.shipping_address) == get_data_dict(
        order.shipping_address)
    customer = source.customer
    assert customer == order.customer
    assert customer.groups.count() == 1
    assert customer.groups.first() == order.customer_groups.first()
    assert customer.tax_group is not None
    assert customer.tax_group == order.tax_group

    assert source.payment_method == order.payment_method
    assert source.shipping_method == order.shipping_method
    assert order.pk
    assert order.lines.filter(accounting_identifier="strawberries").first(
    ).extra_data["runner"] == "runner"
示例#21
0
def test_order_partial_refund_with_taxes(include_tax):
    tax_rate = Decimal(0.2)  # 20%
    product_price = 100
    discount_amount = 30
    random_line_price = 5
    refunded_amount = 15

    shop = factories.get_shop(include_tax)
    source = OrderSource(shop)
    source.status = factories.get_initial_order_status()
    supplier = factories.get_default_supplier()
    create_default_order_statuses()
    tax = factories.get_tax("sales-tax", "Sales Tax", tax_rate)
    factories.create_default_tax_rule(tax)

    product = factories.create_product("sku",
                                       shop=shop,
                                       supplier=supplier,
                                       default_price=product_price)

    line = source.add_line(
        line_id="product-line",
        type=OrderLineType.PRODUCT,
        product=product,
        supplier=supplier,
        quantity=1,
        shop=shop,
        base_unit_price=source.create_price(product_price),
    )
    discount_line = source.add_line(
        line_id="discount-line",
        type=OrderLineType.DISCOUNT,
        supplier=supplier,
        quantity=1,
        base_unit_price=source.create_price(0),
        discount_amount=source.create_price(discount_amount),
        parent_line_id=line.line_id)
    raw_total_price = Decimal(product_price - discount_amount)
    total_taxful = bround(source.taxful_total_price.value)
    total_taxless = bround(source.taxless_total_price.value)
    if include_tax:
        assert total_taxful == bround(raw_total_price)
        assert total_taxless == bround(raw_total_price / (1 + tax_rate))
    else:
        assert total_taxful == bround(raw_total_price * (1 + tax_rate))
        assert total_taxless == bround(raw_total_price)

    creator = OrderCreator()
    order = creator.create_order(source)
    assert order.taxful_total_price.value == total_taxful
    assert order.taxless_total_price.value == total_taxless

    order.create_payment(order.taxful_total_price)
    assert order.is_paid()

    refund_data = [
        dict(
            amount=Money(refunded_amount, shop.currency),
            quantity=1,
            line=order.lines.products().first(),
        )
    ]
    order.create_refund(refund_data)

    total_taxful = bround(order.taxful_total_price.value)
    total_taxless = bround(order.taxless_total_price.value)
    taxless_refunded_amount = (refunded_amount / (1 + tax_rate))

    if include_tax:
        raw_total_price = Decimal(product_price - discount_amount -
                                  refunded_amount)
        assert total_taxful == bround(raw_total_price)
        assert total_taxless == bround(raw_total_price / (1 + tax_rate))
    else:
        # the refunded amount it considered a taxful price internally
        raw_total_price = Decimal(product_price - discount_amount)
        assert total_taxful == bround((raw_total_price * (1 + tax_rate)) -
                                      refunded_amount)
        assert total_taxless == bround(raw_total_price -
                                       taxless_refunded_amount)

    refund_line = order.lines.refunds().filter(
        type=OrderLineType.REFUND).first()
    if include_tax:
        assert refund_line.taxful_price.value == -bround(refunded_amount)
        assert refund_line.taxless_price.value == -bround(
            taxless_refunded_amount)
    else:
        assert refund_line.taxful_price.value == -bround(refunded_amount)
        assert refund_line.taxless_price.value == -bround(
            taxless_refunded_amount)
示例#22
0
 def money_round(value):
     return Money(value, shop.currency).as_rounded(2)
示例#23
0
 def get_total_tax_amount(self):
     return sum((line.tax_amount for line in self.lines.all()),
                Money(0, self.currency))
示例#24
0
def test_refunds():
    shop = get_default_shop()
    supplier = get_default_supplier()
    product = create_product(
        "test-sku",
        shop=get_default_shop(),
        default_price=10,
    )
    tax_rate = Decimal("0.1")
    taxless_base_unit_price = shop.create_price(200)
    order = create_order_with_product(product,
                                      supplier,
                                      3,
                                      taxless_base_unit_price,
                                      tax_rate,
                                      shop=shop)
    order.payment_status = PaymentStatus.DEFERRED
    order.cache_prices()
    order.save()

    assert order.get_total_refunded_amount().value == 0
    assert order.get_total_unrefunded_amount(
    ).value == order.taxful_total_price.value
    assert not order.can_edit()

    assert len(order.lines.all()) == 1

    product_line = order.lines.first()
    assert product_line.ordering == 0
    assert order.can_create_refund()
    assert not order.has_refunds()

    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": (product_line.taxful_price.amount / 3)
    }])
    assert len(order.lines.all()) == 2
    assert order.lines.last().ordering == 1
    assert order.has_refunds()

    # Confirm the value of the refund
    assert order.lines.last().taxful_price == -product_line.base_unit_price
    assert order.lines.last().tax_amount == -(
        product_line.taxless_base_unit_price * tax_rate).amount

    # Create a refund with a parent line and amount
    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": product_line.taxful_price.amount / 3
    }])
    assert len(order.lines.all()) == 3
    assert order.lines.last().ordering == 2

    assert order.lines.last(
    ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate)
    assert order.lines.last(
    ).tax_amount == -taxless_base_unit_price.amount * tax_rate

    assert order.taxless_total_price.amount == taxless_base_unit_price.amount
    assert order.taxful_total_price.amount == taxless_base_unit_price.amount * (
        1 + tax_rate)
    assert order.can_create_refund()
    assert order.get_total_tax_amount() == Money(
        (order.taxful_total_price_value - order.taxless_total_price_value),
        order.currency)

    # Try to refunding remaining amount without a parent line
    with pytest.raises(AssertionError):
        order.create_refund([{"amount": taxless_base_unit_price}])

    # refund remaining amount
    order.create_refund([{
        "line": product_line,
        "quantity": 1,
        "amount": product_line.taxful_price.amount / 3
    }])
    assert len(order.lines.all()) == 4
    assert order.lines.last().ordering == 3
    assert order.lines.last(
    ).taxful_price.amount == -taxless_base_unit_price.amount * (1 + tax_rate)

    assert not order.taxful_total_price.amount
    assert not order.can_create_refund()
    assert order.get_total_tax_amount() == Money(
        (order.taxful_total_price_value - order.taxless_total_price_value),
        order.currency)

    with pytest.raises(RefundExceedsAmountException):
        order.create_refund([{
            "line": product_line,
            "quantity": 1,
            "amount": taxless_base_unit_price.amount
        }])
示例#25
0
 def tax_amount(self):
     """
     :rtype: shuup.utils.money.Money
     """
     zero = Money(0, self.order.currency)
     return sum((x.amount for x in self.taxes.all()), zero)
示例#26
0
def test_product_summary():
    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)

    # 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)
示例#27
0
文件: _orders.py 项目: zarlant/shuup
 def get_total_refunded_amount(self):
     # rounding here to follow suit with cache_prices
     total = sum(_round_price(line.taxful_price.amount.value) for line in self.lines.refunds())
     return Money(-total, self.currency)
示例#28
0
def test_as_rounded_rounding_mode():
    set_precision_provider(babel_precision_provider.get_precision)

    prec2 = Decimal("0.01")
    m1 = Money("2.345", "EUR")
    m2 = Money("2.344", "EUR")

    assert m1.as_rounded(2).value == Decimal("2.34")
    assert m2.as_rounded(2).value == Decimal("2.34")

    from decimal import ROUND_FLOOR, ROUND_HALF_DOWN, ROUND_HALF_UP

    assert m1.as_rounded(2, rounding=ROUND_HALF_DOWN).value == Decimal("2.34")
    assert m2.as_rounded(2, rounding=ROUND_HALF_DOWN).value == Decimal("2.34")
    assert m1.as_rounded(2, rounding=ROUND_HALF_UP).value == Decimal("2.35")
    assert m2.as_rounded(2, rounding=ROUND_HALF_UP).value == Decimal("2.34")
    assert m1.as_rounded(2, rounding=ROUND_FLOOR).value == Decimal("2.34")
    assert m2.as_rounded(2, rounding=ROUND_FLOOR).value == Decimal("2.34")
示例#29
0
 def get_total_paid_amount(self):
     amounts = self.payments.values_list('amount_value', flat=True)
     return Money(sum(amounts, Decimal(0)), self.currency)
示例#30
0
def test_money_init_does_not_call_settings():
    def guarded_getattr(self, name):
        assert False, "nobody should read settings yet"

    with patch.object(type(settings), "__getattr__", guarded_getattr):
        Money(42, "EUR")
示例#31
0
    def create_refund(self, refund_data, created_by=None):
        """
        Create a refund if passed a list of refund line data.

        Refund line data is simply a list of dictionaries where
        each dictionary contains data for a particular refund line.

        Additionally, if the parent line is of enum type
        `OrderLineType.PRODUCT` and the `restock_products` boolean
        flag is set to `True`, the products will be restocked with the
        order's supplier the exact amount of the value of the `quantity`
        field.

        :param refund_data: List of dicts containing refund data.
        :type refund_data: [dict]
        :param created_by: Refund creator's user instance, used for
                           adjusting supplier stock.
        :type created_by: django.contrib.auth.User|None
        """
        index = self.lines.all().aggregate(
            models.Max("ordering"))["ordering__max"]
        zero = Money(0, self.currency)
        refund_lines = []
        total_refund_amount = zero
        order_total = self.taxful_total_price.amount
        product_summary = self.get_product_summary()
        for refund in refund_data:
            index += 1
            amount = refund.get("amount", zero)
            quantity = refund.get("quantity", 0)
            parent_line = refund.get("line")
            restock_products = refund.get("restock_products")
            refund_line = None

            assert parent_line
            assert quantity

            # ensure the amount to refund and the order line amount have the same signs
            if ((amount > zero and parent_line.taxful_price.amount < zero) or
                (amount < zero and parent_line.taxful_price.amount > zero)):
                raise InvalidRefundAmountException

            if abs(amount) > abs(parent_line.max_refundable_amount):
                raise RefundExceedsAmountException

            # If restocking products, calculate quantity of products to restock
            product = parent_line.product
            if (restock_products and quantity and product
                    and (product.stock_behavior == StockBehavior.STOCKED)):
                from shuup.core.suppliers.enums import StockAdjustmentType
                # restock from the unshipped quantity first
                unshipped_quantity_to_restock = min(
                    quantity, product_summary[product.pk]["unshipped"])
                shipped_quantity_to_restock = min(
                    quantity - unshipped_quantity_to_restock,
                    product_summary[product.pk]["ordered"] -
                    product_summary[product.pk]["refunded"])

                if unshipped_quantity_to_restock > 0:
                    product_summary[product.pk][
                        "unshipped"] -= unshipped_quantity_to_restock
                    parent_line.supplier.adjust_stock(
                        product.id,
                        unshipped_quantity_to_restock,
                        created_by=created_by,
                        type=StockAdjustmentType.RESTOCK_LOGICAL)
                if shipped_quantity_to_restock > 0:
                    parent_line.supplier.adjust_stock(
                        product.id,
                        shipped_quantity_to_restock,
                        created_by=created_by,
                        type=StockAdjustmentType.RESTOCK)
                product_summary[product.pk]["refunded"] += quantity

            base_amount = amount if self.prices_include_tax else amount / (
                1 + parent_line.tax_rate)
            refund_line = OrderLine.objects.create(
                text=_("Refund for %s" % parent_line.text),
                order=self,
                type=OrderLineType.REFUND,
                parent_line=parent_line,
                ordering=index,
                base_unit_price_value=-(base_amount / quantity),
                quantity=quantity,
            )
            for line_tax in parent_line.taxes.all():
                tax_base_amount = amount / (1 + line_tax.tax.rate)
                tax_amount = tax_base_amount * line_tax.tax.rate
                refund_line.taxes.create(tax=line_tax.tax,
                                         name=_("Refund for %s" %
                                                line_tax.name),
                                         amount_value=-tax_amount,
                                         base_amount_value=-tax_base_amount,
                                         ordering=line_tax.ordering)

            total_refund_amount += refund_line.taxful_price.amount
            refund_lines.append(refund_line)
        if abs(total_refund_amount) > order_total:
            raise RefundExceedsAmountException
        self.cache_prices()
        self.save()
        self.update_shipping_status()
        refund_created.send(sender=type(self),
                            order=self,
                            refund_lines=refund_lines)
示例#32
0
def test_money_without_currency():
    with pytest.raises(TypeError):
        Money(42)
示例#33
0
 def get_total_unrefunded_amount(self):
     return max(self.taxful_total_price.amount, Money(0, self.currency))
示例#34
0
def test_money_init_from_money():
    assert Money(Money(123, "GBP")) == Money(123, "GBP")