Пример #1
0
def test_custom_rules_business(event):
    tr = TaxRule(event=event,
                 rate=Decimal('10.00'),
                 price_includes_tax=False,
                 custom_rules=json.dumps([
                     {
                         'country': 'ZZ',
                         'address_type': 'business',
                         'action': 'no'
                     },
                 ]))
    ia = InvoiceAddress(is_business=True, country=Country('AT'))
    assert not tr.is_reverse_charge(ia)
    assert not tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('0.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('100.00'),
        net=Decimal('100.00'),
        tax=Decimal('0.00'),
        rate=Decimal('0.00'),
        name='',
    )

    ia = InvoiceAddress(is_business=False, country=Country('DE'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('100.00'),
        tax=Decimal('10.00'),
        rate=Decimal('10.00'),
        name='',
    )
Пример #2
0
    def add_position(self, item: Item, variation: ItemVariation, price: Decimal, addon_to: Order = None,
                     subevent: SubEvent = None):
        if price is None:
            price = get_price(item, variation, subevent=subevent, invoice_address=self._invoice_address)
        else:
            if item.tax_rule.tax_applicable(self._invoice_address):
                price = item.tax(price, base_price_is='gross')
            else:
                price = TaxedPrice(gross=price, net=price, tax=Decimal('0.00'), rate=Decimal('0.00'), name='')

        if price is None:
            raise OrderError(self.error_messages['product_invalid'])
        if not addon_to and item.category and item.category.is_addon:
            raise OrderError(self.error_messages['addon_to_required'])
        if addon_to:
            if not item.category or item.category_id not in addon_to.item.addons.values_list('addon_category', flat=True):
                raise OrderError(self.error_messages['addon_invalid'])
        if self.order.event.has_subevents and not subevent:
            raise OrderError(self.error_messages['subevent_required'])

        new_quotas = (variation.quotas.filter(subevent=subevent)
                      if variation else item.quotas.filter(subevent=subevent))
        if not new_quotas:
            raise OrderError(self.error_messages['quota_missing'])

        if self.order.event.settings.invoice_include_free or price.gross != Decimal('0.00'):
            self._invoice_dirty = True

        self._totaldiff += price.gross
        self._quotadiff.update(new_quotas)
        self._operations.append(self.AddOperation(item, variation, price, addon_to, subevent))
Пример #3
0
    def recalculate_taxes(self):
        positions = self.order.positions.select_related(
            'item', 'item__tax_rule')
        ia = self._invoice_address
        for pos in positions:
            if not pos.item.tax_rule:
                continue
            if not pos.price:
                continue

            charge_tax = pos.item.tax_rule.tax_applicable(ia)
            if pos.tax_value and not charge_tax:
                net_price = pos.price - pos.tax_value
                price = TaxedPrice(gross=net_price,
                                   net=net_price,
                                   tax=Decimal('0.00'),
                                   rate=Decimal('0.00'),
                                   name='')
                if price.gross != pos.price:
                    self._totaldiff += price.gross - pos.price
                    self._operations.append(self.PriceOperation(pos, price))
            elif charge_tax and not pos.tax_value:
                price = pos.item.tax(pos.price, base_price_is='net')
                if price.gross != pos.price:
                    self._totaldiff += price.gross - pos.price
                    self._operations.append(self.PriceOperation(pos, price))
Пример #4
0
def test_custom_rules_country_rate_subtract_from_gross(event):
    tr = TaxRule(event=event,
                 rate=Decimal('10.00'),
                 price_includes_tax=False,
                 custom_rules=json.dumps([
                     {
                         'country': 'EU',
                         'address_type': 'business_vat_id',
                         'action': 'vat',
                         'rate': '100.00'
                     },
                 ]))
    ia = InvoiceAddress(is_business=True,
                        country=Country('DE'),
                        vat_id='DE1234',
                        vat_id_validated=True)
    assert tr.tax_rate_for(ia) == Decimal('100.00')
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax(
        Decimal('100.00'),
        invoice_address=ia,
        subtract_from_gross=Decimal('20.00')) == TaxedPrice(
            gross=Decimal(
                '163.64'),  # ((100 * 1.1) - 20) / (1 + 10%) * (1 + 100%)
            net=Decimal('81.82'),
            tax=Decimal('81.82'),
            rate=Decimal('100.00'),
            name='',
        )
Пример #5
0
def test_custom_rules_override(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 home_country=Country('DE'),
                 rate=Decimal('10.00'),
                 price_includes_tax=False,
                 custom_rules=json.dumps([{
                     'country': 'ZZ',
                     'address_type': '',
                     'action': 'vat'
                 }]))
    ia = InvoiceAddress(is_business=True,
                        vat_id='AT12346',
                        vat_id_validated=True,
                        country=Country('AT'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('100.00'),
        tax=Decimal('10.00'),
        rate=Decimal('10.00'),
        name='',
    )
Пример #6
0
 def tax(self, price=None):
     price = price or self.price
     if not self.item.tax_rule:
         return TaxedPrice(gross=price,
                           net=price,
                           tax=Decimal('0.00'),
                           rate=Decimal('0.00'),
                           name='')
     return self.item.tax_rule.tax(price)
Пример #7
0
 def tax(self, price=None, base_price_is='auto'):
     price = price if price is not None else self.default_price
     if not self.tax_rule:
         return TaxedPrice(gross=price,
                           net=price,
                           tax=Decimal('0.00'),
                           rate=Decimal('0.00'),
                           name='')
     return self.tax_rule.tax(price, base_price_is=base_price_is)
Пример #8
0
    def extend_expired_positions(self):
        expired = self.positions.filter(
            expires__lte=self.now_dt).select_related(
                'item', 'variation',
                'voucher').prefetch_related('item__quotas',
                                            'variation__quotas')
        err = None
        for cp in expired:
            if not cp.includes_tax:
                price = self._get_price(cp.item,
                                        cp.variation,
                                        cp.voucher,
                                        cp.price,
                                        cp.subevent,
                                        cp_is_net=True)
                price = TaxedPrice(net=price.net,
                                   gross=price.net,
                                   rate=0,
                                   tax=0,
                                   name='')
            else:
                price = self._get_price(cp.item, cp.variation, cp.voucher,
                                        cp.price, cp.subevent)

            quotas = list(cp.quotas)
            if not quotas:
                self._operations.append(self.RemoveOperation(position=cp))
                continue
                err = error_messages['unavailable']

            if not cp.voucher or (not cp.voucher.allow_ignore_quota
                                  and not cp.voucher.block_quota):
                for quota in quotas:
                    self._quota_diff[quota] += 1
            else:
                quotas = []

            op = self.ExtendOperation(position=cp,
                                      item=cp.item,
                                      variation=cp.variation,
                                      voucher=cp.voucher,
                                      count=1,
                                      price=price,
                                      quotas=quotas,
                                      subevent=cp.subevent)
            self._check_item_constraints(op)

            if cp.voucher:
                self._voucher_use_diff[cp.voucher] += 1

            self._operations.append(op)
        return err
Пример #9
0
def test_custom_rules_country_rate_keep_gross_if_rate_changes(event):
    tr = TaxRule(event=event,
                 rate=Decimal('10.00'),
                 price_includes_tax=False,
                 keep_gross_if_rate_changes=True,
                 custom_rules=json.dumps([
                     {
                         'country': 'EU',
                         'address_type': 'business_vat_id',
                         'action': 'vat',
                         'rate': '100.00'
                     },
                 ]))
    ia = InvoiceAddress(is_business=True, country=Country('DE'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('100.00'),
        tax=Decimal('10.00'),
        rate=Decimal('10.00'),
        name='',
    )
    ia = InvoiceAddress(is_business=True,
                        country=Country('DE'),
                        vat_id='DE1234',
                        vat_id_validated=True)
    assert tr.tax_rate_for(ia) == Decimal('100.00')
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('55.00'),
        tax=Decimal('55.00'),
        rate=Decimal('100.00'),
        name='',
    )
Пример #10
0
def test_reverse_charge_no_country(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress()
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('100.00'),
        tax=Decimal('10.00'),
        rate=Decimal('10.00'),
        name='',
    )
Пример #11
0
def test_reverse_charge_individual_3rdc(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 home_country=Country('DE'),
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress(is_business=False, country=Country('US'))
    assert not tr.is_reverse_charge(ia)
    assert not tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('0.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('100.00'),
        net=Decimal('100.00'),
        tax=Decimal('0.00'),
        rate=Decimal('0.00'),
        name='',
    )
Пример #12
0
def test_reverse_charge_disabled(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=False,
                 home_country=Country('DE'),
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress(is_business=True,
                        vat_id='AT12346',
                        vat_id_validated=True,
                        country=Country('AT'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('100.00'),
        tax=Decimal('10.00'),
        rate=Decimal('10.00'),
        name='',
    )
Пример #13
0
    def forms(self):
        """
        A list of forms with one form for each cart position that can have add-ons.
        All forms have a custom prefix, so that they can all be submitted at once.
        """
        formset = []
        quota_cache = {}
        item_cache = {}
        for cartpos in get_cart(
                self.request).filter(addon_to__isnull=True).prefetch_related(
                    'item__addons',
                    'item__addons__addon_category',
                    'addons',
                    'addons__variation',
                ).order_by('pk'):
            formsetentry = {
                'cartpos': cartpos,
                'item': cartpos.item,
                'variation': cartpos.variation,
                'categories': []
            }
            formset.append(formsetentry)

            current_addon_products = defaultdict(list)
            for a in cartpos.addons.all():
                if not a.is_bundled:
                    current_addon_products[a.item_id, a.variation_id].append(a)

            for iao in cartpos.item.addons.all():
                ckey = '{}-{}'.format(
                    cartpos.subevent.pk if cartpos.subevent else 0,
                    iao.addon_category.pk)

                if ckey not in item_cache:
                    # Get all items to possibly show
                    items, _btn = get_grouped_items(
                        self.request.event,
                        subevent=cartpos.subevent,
                        voucher=None,
                        channel=self.request.sales_channel.identifier,
                        base_qs=iao.addon_category.items,
                        allow_addons=True,
                        quota_cache=quota_cache)
                    item_cache[ckey] = items
                else:
                    items = item_cache[ckey]

                for i in items:
                    i.allow_waitinglist = False

                    if i.has_variations:
                        for v in i.available_variations:
                            v.initial = len(current_addon_products[i.pk, v.pk])
                            if v.initial and i.free_price:
                                a = current_addon_products[i.pk, v.pk][0]
                                v.initial_price = TaxedPrice(
                                    net=a.price - a.tax_value,
                                    gross=a.price,
                                    tax=a.tax_value,
                                    name=a.item.tax_rule.name,
                                    rate=a.tax_rate,
                                )
                            else:
                                v.initial_price = v.display_price
                        i.expand = any(v.initial
                                       for v in i.available_variations)
                    else:
                        i.initial = len(current_addon_products[i.pk, None])
                        if i.initial and i.free_price:
                            a = current_addon_products[i.pk, None][0]
                            i.initial_price = TaxedPrice(
                                net=a.price - a.tax_value,
                                gross=a.price,
                                tax=a.tax_value,
                                name=a.item.tax_rule.name,
                                rate=a.tax_rate,
                            )
                        else:
                            i.initial_price = i.display_price

                if items:
                    formsetentry['categories'].append({
                        'category': iao.addon_category,
                        'price_included': iao.price_included,
                        'multi_allowed': iao.multi_allowed,
                        'min_count': iao.min_count,
                        'max_count': iao.max_count,
                        'iao': iao,
                        'items': items
                    })
        return formset
Пример #14
0
    def __init__(self, *args, **kwargs):
        instance = kwargs.pop('instance')
        invoice_address = kwargs.pop('invoice_address')
        initial = kwargs.get('initial', {})
        event = kwargs.pop('event')
        kwargs['initial'] = initial
        if instance.variation_id:
            initial['itemvar'] = f'{instance.item_id}-{instance.variation_id}'
        else:
            initial['itemvar'] = f'{instance.item_id}'

        super().__init__(*args, **kwargs)

        choices = []

        i = instance.item
        pname = str(i)
        variations = list(i.variations.all())

        if variations:
            current_quotas = instance.variation.quotas.all(
            ) if instance.variation else instance.item.quotas.all()
            qa = QuotaAvailability()
            for v in variations:
                qa.queue(*v.quotas.all())
            qa.compute()

            for v in variations:

                label = f'{i.name} – {v.value}'
                if instance.variation_id == v.id:
                    choices.append((f'{i.pk}-{v.pk}', label))
                    continue

                if not v.active:
                    continue

                q_res = [
                    qa.results[q][0] != Quota.AVAILABILITY_OK
                    for q in v.quotas.all() if q not in current_quotas
                ]
                if not v.quotas.all() or (q_res and any(q_res)):
                    continue

                new_price = get_price(i,
                                      v,
                                      voucher=instance.voucher,
                                      subevent=instance.subevent,
                                      invoice_address=invoice_address)
                current_price = TaxedPrice(
                    tax=instance.tax_value,
                    gross=instance.price,
                    net=instance.price - instance.tax_value,
                    name=instance.tax_rule.name if instance.tax_rule else '',
                    rate=instance.tax_rate)
                if new_price.gross < current_price.gross and event.settings.change_allow_user_price == 'gte':
                    continue
                if new_price.gross <= current_price.gross and event.settings.change_allow_user_price == 'gt':
                    continue
                if new_price.gross != current_price.gross and event.settings.change_allow_user_price == 'eq':
                    continue

                if new_price.gross < current_price.gross:
                    if event.settings.display_net_prices:
                        label += ' (- {} {})'.format(
                            money_filter(current_price.gross - new_price.gross,
                                         event.currency), _('plus taxes'))
                    else:
                        label += ' (- {})'.format(
                            money_filter(current_price.gross - new_price.gross,
                                         event.currency))
                elif current_price.gross < new_price.gross:
                    if event.settings.display_net_prices:
                        label += ' ({}{} {})'.format(
                            '+ '
                            if current_price.gross != Decimal('0.00') else '',
                            money_filter(new_price.gross - current_price.gross,
                                         event.currency), _('plus taxes'))
                    else:
                        label += ' ({}{})'.format(
                            '+ '
                            if current_price.gross != Decimal('0.00') else '',
                            money_filter(new_price.gross - current_price.gross,
                                         event.currency))

                choices.append((f'{i.pk}-{v.pk}', label))

            if not choices:
                self.fields['itemvar'].widget.attrs['disabled'] = True
                self.fields['itemvar'].help_text = _(
                    'No other variation of this product is currently available for you.'
                )
        else:
            choices.append((str(i.pk), '%s' % pname))
            self.fields['itemvar'].widget.attrs['disabled'] = True
            self.fields['itemvar'].help_text = _(
                'No other variations of this product exist.')

        self.fields['itemvar'].choices = choices
Пример #15
0
def get_price(item: Item,
              variation: ItemVariation = None,
              voucher: Voucher = None,
              custom_price: Decimal = None,
              subevent: SubEvent = None,
              custom_price_is_net: bool = False,
              addon_to: AbstractPosition = None,
              invoice_address: InvoiceAddress = None,
              force_custom_price: bool = False,
              bundled_sum: Decimal = Decimal('0.00'),
              max_discount: Decimal = None) -> TaxedPrice:
    if addon_to:
        try:
            iao = addon_to.item.addons.get(addon_category_id=item.category_id)
            if iao.price_included:
                return TAXED_ZERO
        except ItemAddOn.DoesNotExist:
            pass

    price = item.default_price
    if subevent and item.pk in subevent.item_price_overrides:
        price = subevent.item_price_overrides[item.pk]

    if variation is not None:
        if variation.default_price is not None:
            price = variation.default_price
        if subevent and variation.pk in subevent.var_price_overrides:
            price = subevent.var_price_overrides[variation.pk]

    if voucher:
        price = voucher.calculate_price(price, max_discount=max_discount)

    if item.tax_rule:
        tax_rule = item.tax_rule
    else:
        tax_rule = TaxRule(
            name='',
            rate=Decimal('0.00'),
            price_includes_tax=True,
            eu_reverse_charge=False,
        )
    price = tax_rule.tax(price)

    if force_custom_price and custom_price is not None and custom_price != "":
        if custom_price_is_net:
            price = tax_rule.tax(custom_price, base_price_is='net')
        else:
            price = tax_rule.tax(custom_price, base_price_is='gross')
    if item.free_price and custom_price is not None and custom_price != "":
        if not isinstance(custom_price, Decimal):
            custom_price = Decimal(str(custom_price).replace(",", "."))
        if custom_price > 100000000:
            raise ValueError('price_too_high')
        if custom_price_is_net:
            price = tax_rule.tax(max(custom_price, price.net),
                                 base_price_is='net')
        else:
            price = tax_rule.tax(max(custom_price, price.gross),
                                 base_price_is='gross')

    if bundled_sum:
        price = price - TaxedPrice(
            net=bundled_sum, gross=bundled_sum, rate=0, tax=0, name='')
        if price.gross < Decimal('0.00'):
            return TAXED_ZERO

    if invoice_address and not tax_rule.tax_applicable(invoice_address):
        price.tax = Decimal('0.00')
        price.rate = Decimal('0.00')
        price.gross = price.net
        price.name = ''

    price.gross = round_decimal(price.gross, item.event.currency)
    price.net = round_decimal(price.net, item.event.currency)
    price.tax = price.gross - price.net

    return price