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='', )
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))
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))
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='', )
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='', )
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)
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)
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
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='', )
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='', )
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='', )
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='', )
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
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
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