def test_tax_added(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=False) assert get_price(item).gross == Decimal('119.00') assert get_price(item).net == Decimal('100.00') assert get_price(item).tax == Decimal('19.00') assert get_price(item).rate == Decimal('19.00')
def test_tax_added(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=False) assert get_price(item).gross == Decimal('119.00') assert get_price(item).net == Decimal('100.00') assert get_price(item).tax == Decimal('19.00') assert get_price(item).rate == Decimal('19.00')
def __init__(self, *args, **kwargs): self.items = kwargs.pop('items') order = kwargs.pop('order') super().__init__(*args, **kwargs) try: ia = order.invoice_address except InvoiceAddress.DoesNotExist: ia = None choices = [] for i in self.items: pname = str(i) if not i.is_available(): pname += ' ({})'.format(_('inactive')) variations = list(i.variations.all()) if i.tax_rule: # performance optimization i.tax_rule.event = order.event if variations: for v in variations: p = get_price(i, v, invoice_address=ia) choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s (%s)' % (pname, v.value, p.print(order.event.currency)))) else: p = get_price(i, invoice_address=ia) choices.append((str(i.pk), '%s (%s)' % (pname, p.print(order.event.currency)))) self.fields['itemvar'].choices = choices if order.event.cache.get_or_set( 'has_addon_products', default=lambda: ItemAddOn.objects.filter(base_item__event=order.event).exists(), timeout=300 ): self.fields['addon_to'].queryset = order.positions.filter(addon_to__isnull=True).select_related( 'item', 'variation' ) else: del self.fields['addon_to'] if order.event.has_subevents: self.fields['subevent'].queryset = order.event.subevents.all() self.fields['subevent'].widget = Select2( attrs={ 'data-model-select2': 'event', 'data-select2-url': reverse('control:event.subevents.select2', kwargs={ 'event': order.event.slug, 'organizer': order.event.organizer.slug, }), 'data-placeholder': pgettext_lazy('subevent', 'Date') } ) self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].required = True else: del self.fields['subevent'] change_decimal_field(self.fields['price'], order.event.currency)
def assign(self, value, order, position, invoice_address, **kwargs): if value is None: p = get_price(position.item, position.variation, position.voucher, subevent=position.subevent, invoice_address=invoice_address) else: p = get_price(position.item, position.variation, position.voucher, subevent=position.subevent, invoice_address=invoice_address, custom_price=value, force_custom_price=True) position.price = p.gross position.tax_rule = position.item.tax_rule position.tax_rate = p.rate position.tax_value = p.tax
def __init__(self, *args, **kwargs): instance = kwargs.pop('instance') initial = kwargs.get('initial', {}) try: ia = instance.order.invoice_address except InvoiceAddress.DoesNotExist: ia = None if instance: try: if instance.variation: initial['itemvar'] = '%d-%d' % (instance.item.pk, instance.variation.pk) elif instance.item: initial['itemvar'] = str(instance.item.pk) except Item.DoesNotExist: pass if instance.item.tax_rule and not instance.item.tax_rule.price_includes_tax: initial['price'] = instance.price - instance.tax_value else: initial['price'] = instance.price initial['subevent'] = instance.subevent kwargs['initial'] = initial super().__init__(*args, **kwargs) if instance.order.event.has_subevents: self.fields['subevent'].instance = instance self.fields['subevent'].queryset = instance.order.event.subevents.all() else: del self.fields['subevent'] choices = [] for i in instance.order.event.items.prefetch_related('variations').all(): pname = str(i) if not i.is_available(): pname += ' ({})'.format(_('inactive')) variations = list(i.variations.all()) if variations: for v in variations: p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent, invoice_address=ia) choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s (%s)' % (pname, v.value, p.print(instance.order.event.currency)))) else: p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent, invoice_address=ia) choices.append((str(i.pk), '%s (%s)' % (pname, p.print(instance.order.event.currency)))) self.fields['itemvar'].choices = choices change_decimal_field(self.fields['price'], instance.order.event.currency)
def __init__(self, *args, **kwargs): instance = kwargs.pop('instance') initial = kwargs.get('initial', {}) try: ia = instance.order.invoice_address except InvoiceAddress.DoesNotExist: ia = None if instance: try: if instance.variation: initial['itemvar'] = '%d-%d' % (instance.item.pk, instance.variation.pk) elif instance.item: initial['itemvar'] = str(instance.item.pk) except Item.DoesNotExist: pass if instance.item.tax_rule and not instance.item.tax_rule.price_includes_tax: initial['price'] = instance.price - instance.tax_value else: initial['price'] = instance.price initial['subevent'] = instance.subevent kwargs['initial'] = initial super().__init__(*args, **kwargs) if instance.order.event.has_subevents: self.fields['subevent'].instance = instance self.fields['subevent'].queryset = instance.order.event.subevents.all() else: del self.fields['subevent'] choices = [] for i in instance.order.event.items.prefetch_related('variations').all(): pname = str(i.name) if not i.is_available(): pname += ' ({})'.format(_('inactive')) variations = list(i.variations.all()) if variations: for v in variations: p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent, invoice_address=ia) choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s (%s)' % (pname, v.value, p.print(instance.order.event.currency)))) else: p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent, invoice_address=ia) choices.append((str(i.pk), '%s (%s)' % (pname, p.print(instance.order.event.currency)))) self.fields['itemvar'].choices = choices change_decimal_field(self.fields['price'], instance.order.event.currency)
def __init__(self, *args, **kwargs): instance = kwargs.pop('instance') initial = kwargs.get('initial', {}) if instance: try: if instance.variation: initial['itemvar'] = '%d-%d' % (instance.item.pk, instance.variation.pk) elif instance.item: initial['itemvar'] = str(instance.item.pk) except Item.DoesNotExist: pass initial['price'] = instance.price initial['subevent'] = instance.subevent kwargs['initial'] = initial super().__init__(*args, **kwargs) if instance.order.event.has_subevents: self.fields['subevent'].instance = instance self.fields[ 'subevent'].queryset = instance.order.event.subevents.all() else: del self.fields['subevent'] choices = [] for i in instance.order.event.items.prefetch_related( 'variations').all(): pname = str(i.name) if not i.is_available(): pname += ' ({})'.format(_('inactive')) variations = list(i.variations.all()) if variations: for v in variations: p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent) choices.append( ('%d-%d' % (i.pk, v.pk), '%s – %s (%s %s)' % (pname, v.value, localize(p), instance.order.event.currency))) else: p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent) choices.append( (str(i.pk), '%s (%s %s)' % (pname, localize(p), instance.order.event.currency))) self.fields['itemvar'].choices = choices
def __init__(self, *args, **kwargs): order = kwargs.pop('order') super().__init__(*args, **kwargs) try: ia = order.invoice_address except InvoiceAddress.DoesNotExist: ia = None choices = [] for i in order.event.items.prefetch_related('variations').all(): pname = str(i) if not i.is_available(): pname += ' ({})'.format(_('inactive')) variations = list(i.variations.all()) if variations: for v in variations: p = get_price(i, v, invoice_address=ia) choices.append(('%d-%d' % (i.pk, v.pk), '%s – %s (%s)' % (pname, v.value, p.print(order.event.currency)))) else: p = get_price(i, invoice_address=ia) choices.append((str(i.pk), '%s (%s)' % (pname, p.print(order.event.currency)))) self.fields['itemvar'].choices = choices if ItemAddOn.objects.filter(base_item__event=order.event).exists(): self.fields['addon_to'].queryset = order.positions.filter(addon_to__isnull=True).select_related( 'item', 'variation' ) else: del self.fields['addon_to'] if order.event.has_subevents: self.fields['subevent'].queryset = order.event.subevents.all() self.fields['subevent'].widget = Select2( attrs={ 'data-model-select2': 'event', 'data-select2-url': reverse('control:event.subevents.select2', kwargs={ 'event': order.event.slug, 'organizer': order.event.organizer.slug, }), 'data-placeholder': pgettext_lazy('subevent', 'Date') } ) self.fields['subevent'].widget.choices = self.fields['subevent'].choices self.fields['subevent'].required = True else: del self.fields['subevent'] change_decimal_field(self.fields['price'], order.event.currency)
def change_subevent(self, position: OrderPosition, subevent: SubEvent): price = get_price(position.item, position.variation, voucher=position.voucher, subevent=subevent, invoice_address=self._invoice_address) if price is None: # NOQA raise OrderError(self.error_messages['product_invalid']) new_quotas = (position.variation.quotas.filter(subevent=subevent) if position.variation else position.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') or position.price != Decimal('0.00'): self._invoice_dirty = True self._totaldiff += price.gross - position.price self._quotadiff.update(new_quotas) self._quotadiff.subtract(position.quotas) self._operations.append( self.SubeventOperation(position, subevent, price))
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 label_from_instance(self, obj): p = get_price(self.instance.item, self.instance.variation, voucher=self.instance.voucher, subevent=obj) return '{} – {} ({} {})'.format(obj.name, obj.get_date_range_display(), p, self.instance.order.event.currency)
def test_free_price_ignored_if_lower_than_subevent(item, subevent): item.free_price = True SubEventItem.objects.create(item=item, subevent=subevent, price=Decimal('50.00')) assert get_price(item, subevent=subevent, custom_price=Decimal('40.00')).gross == Decimal('50.00')
def test_variation_with_subevent_and_specific_price(item, subevent, variation): variation.default_price = Decimal('24.00') SubEventItemVariation.objects.create(variation=variation, subevent=subevent, price=Decimal('26.00')) assert get_price(item, variation=variation, subevent=subevent).gross == Decimal('26.00')
def test_variation_with_default_subevent_and_default_price( item, subevent, variation): SubEventItemVariation.objects.create(variation=variation, subevent=subevent, price=None) assert get_price(item, variation=variation, subevent=subevent).gross == Decimal('23.00')
def change_item(self, position: OrderPosition, item: Item, variation: Optional[ItemVariation]): if (not variation and item.has_variations) or (variation and variation.item_id != item.pk): raise OrderError(self.error_messages['product_without_variation']) price = get_price(item, variation, voucher=position.voucher, subevent=position.subevent) if price is None: # NOQA raise OrderError(self.error_messages['product_invalid']) new_quotas = (variation.quotas.filter( subevent=position.subevent) if variation else item.quotas.filter( subevent=position.subevent)) if not new_quotas: raise OrderError(self.error_messages['quota_missing']) if self.order.event.settings.invoice_include_free or price != Decimal( '0.00') or position.price != Decimal('0.00'): self._invoice_dirty = True self._totaldiff = price - position.price self._quotadiff.update(new_quotas) self._quotadiff.subtract(position.quotas) self._operations.append( self.ItemOperation(position, item, variation, price))
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) 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 != Decimal( '0.00'): self._invoice_dirty = True self._totaldiff = price self._quotadiff.update(new_quotas) self._operations.append( self.AddOperation(item, variation, price, addon_to, subevent))
def _get_price(self, item: Item, variation: Optional[ItemVariation], voucher: Optional[Voucher], custom_price: Optional[Decimal], subevent: Optional[SubEvent], cp_is_net: bool=None): return get_price( item, variation, voucher, custom_price, subevent, custom_price_is_net=cp_is_net if cp_is_net is not None else self.event.settings.display_net_prices, invoice_address=self.invoice_address )
def test_tax_reverse_charge_consumer(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=False, eu_reverse_charge=True, home_country=Country('DE')) ia = InvoiceAddress(is_business=False, country=Country('BE')) assert not item.tax_rule.is_reverse_charge(ia) assert get_price(item, invoice_address=ia).gross == Decimal('119.00')
def test_tax_reverse_charge_consumer(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create( rate=Decimal('19.00'), price_includes_tax=False, eu_reverse_charge=True, home_country=Country('DE') ) ia = InvoiceAddress( is_business=False, country=Country('BE') ) assert not item.tax_rule.is_reverse_charge(ia) assert get_price(item, invoice_address=ia).gross == Decimal('119.00')
def __init__(self, *args, **kwargs): order = kwargs.pop('order') super().__init__(*args, **kwargs) try: ia = order.invoice_address except InvoiceAddress.DoesNotExist: ia = None choices = [] for i in order.event.items.prefetch_related('variations').all(): pname = str(i.name) if not i.is_available(): pname += ' ({})'.format(_('inactive')) variations = list(i.variations.all()) if variations: for v in variations: p = get_price(i, v, invoice_address=ia) choices.append( ('%d-%d' % (i.pk, v.pk), '%s – %s (%s)' % (pname, v.value, p.print(order.event.currency)))) else: p = get_price(i, invoice_address=ia) choices.append( (str(i.pk), '%s (%s)' % (pname, p.print(order.event.currency)))) self.fields['itemvar'].choices = choices if ItemAddOn.objects.filter(base_item__event=order.event).exists(): self.fields['addon_to'].queryset = order.positions.filter( addon_to__isnull=True).select_related('item', 'variation') else: del self.fields['addon_to'] if order.event.has_subevents: self.fields['subevent'].queryset = order.event.subevents.all() else: del self.fields['subevent'] change_decimal_field(self.fields['price'], order.event.currency)
def _get_price(self, item: Item, variation: Optional[ItemVariation], voucher: Optional[Voucher], custom_price: Optional[Decimal], subevent: Optional[SubEvent], cp_is_net: bool=None): try: return get_price( item, variation, voucher, custom_price, subevent, custom_price_is_net=cp_is_net if cp_is_net is not None else self.event.settings.display_net_prices, invoice_address=self.invoice_address ) except ValueError as e: if str(e) == 'price_too_high': raise CartError(error_messages['price_too_high']) else: raise e
def test_country_specific_rule_gross_based(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=True, custom_rules=json.dumps([{ 'country': 'BE', 'address_type': '', 'action': 'vat', 'rate': '100.00' }])) ia = InvoiceAddress(is_business=True, vat_id="EU1234", vat_id_validated=True, country=Country('BE')) assert get_price(item, invoice_address=ia).gross == Decimal('168.06')
def test_country_specific_rule_net_based_but_keep_gross_if_rate_changes(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create( rate=Decimal('19.00'), price_includes_tax=False, keep_gross_if_rate_changes=True, custom_rules=json.dumps([{ 'country': 'BE', 'address_type': '', 'action': 'vat', 'rate': '100.00' }])) ia = InvoiceAddress(is_business=True, vat_id="EU1234", vat_id_validated=True, country=Country('BE')) p = get_price(item, invoice_address=ia) assert p.gross == Decimal('119.00') assert p.rate == Decimal('100.00') assert p.tax == Decimal('59.50')
def _process_change(self, ocm): try: ia = self.order.invoice_address except InvoiceAddress.DoesNotExist: ia = None for p in self.positions: if not p.form.is_valid(): return False try: change_item = None if p.form.cleaned_data['itemvar']: if '-' in p.form.cleaned_data['itemvar']: itemid, varid = p.form.cleaned_data['itemvar'].split('-') else: itemid, varid = p.form.cleaned_data['itemvar'], None item = self.request.event.items.get(pk=itemid) if varid: variation = item.variations.get(pk=varid) else: variation = None if item != p.item or variation != p.variation: change_item = (item, variation) if change_item is not None: ocm.change_item(p, *change_item) new_price = get_price(change_item[0], change_item[1], voucher=p.voucher, subevent=p.subevent, invoice_address=ia) if new_price.gross != p.price or new_price.rate != p.tax_rate: ocm.change_price(p, new_price.gross) if change_item[0].tax_rule != p.tax_rule or new_price.rate != p.tax_rate: ocm.change_tax_rule(p, change_item[0].tax_rule) except OrderError as e: p.custom_error = str(e) return False return True
def _get_price(self, item: Item, variation: Optional[ItemVariation], voucher: Optional[Voucher], custom_price: Optional[Decimal], subevent: Optional[SubEvent], cp_is_net: bool = None): try: return get_price( item, variation, voucher, custom_price, subevent, custom_price_is_net=cp_is_net if cp_is_net is not None else self.event.settings.display_net_prices, invoice_address=self.invoice_address) except ValueError as e: if str(e) == 'price_too_high': raise CartError(error_messages['price_too_high']) else: raise e
def test_country_specific_rule_net_based_subtract_bundled(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=False, custom_rules=json.dumps([{ 'country': 'BE', 'address_type': '', 'action': 'vat', 'rate': '100.00' }])) ia = InvoiceAddress(is_business=True, vat_id="EU1234", vat_id_validated=True, country=Country('BE')) assert get_price( item, invoice_address=ia, bundled_sum=Decimal('20.00')).gross == (round_decimal( (Decimal('119.00') - Decimal('20.00')) / Decimal('1.19')) * Decimal('2'))
def test_free_price_accepted(item): item.free_price = True assert get_price(item, custom_price=Decimal('42.00')).gross == Decimal('42.00')
def test_free_price_ignored_if_lower_than_subevent(item, subevent): item.free_price = True SubEventItem.objects.create(item=item, subevent=subevent, price=Decimal('50.00')) assert get_price(item, subevent=subevent, custom_price=Decimal('40.00')).gross == Decimal('50.00')
def test_free_price_ignored_if_lower_than_variation(item, variation): variation.default_price = Decimal('50.00') item.free_price = True assert get_price(item, variation=variation, custom_price=Decimal('40.00')).gross == Decimal('50.00')
def create(self, validated_data): fees_data = validated_data.pop( 'fees') if 'fees' in validated_data else [] positions_data = validated_data.pop( 'positions') if 'positions' in validated_data else [] payment_provider = validated_data.pop('payment_provider', None) payment_info = validated_data.pop('payment_info', '{}') payment_date = validated_data.pop('payment_date', now()) force = validated_data.pop('force', False) self._send_mail = validated_data.pop('send_mail', False) if 'invoice_address' in validated_data: iadata = validated_data.pop('invoice_address') name = iadata.pop('name', '') if name and not iadata.get('name_parts'): iadata['name_parts'] = {'_legacy': name} ia = InvoiceAddress(**iadata) else: ia = None with self.context['event'].lock() as now_dt: free_seats = set() seats_seen = set() consume_carts = validated_data.pop('consume_carts', []) delete_cps = [] quota_avail_cache = {} voucher_usage = Counter() if consume_carts: for cp in CartPosition.objects.filter( event=self.context['event'], cart_id__in=consume_carts, expires__gt=now()): quotas = (cp.variation.quotas.filter(subevent=cp.subevent) if cp.variation else cp.item.quotas.filter( subevent=cp.subevent)) for quota in quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] += 1 if cp.voucher: voucher_usage[cp.voucher] -= 1 if cp.expires > now_dt: if cp.seat: free_seats.add(cp.seat) delete_cps.append(cp) errs = [{} for p in positions_data] for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): v = pos_data['voucher'] if not v.applies_to(pos_data['item'], pos_data.get('variation')): errs[i]['voucher'] = [ error_messages['voucher_invalid_item'] ] continue if v.subevent_id and pos_data.get( 'subevent').pk != v.subevent_id: errs[i]['voucher'] = [ error_messages['voucher_invalid_subevent'] ] continue if v.valid_until is not None and v.valid_until < now_dt: errs[i]['voucher'] = [ error_messages['voucher_expired'] ] continue voucher_usage[v] += 1 if voucher_usage[v] > 0: redeemed_in_carts = CartPosition.objects.filter( Q(voucher=pos_data['voucher']) & Q(event=self.context['event']) & Q(expires__gte=now_dt)).exclude( pk__in=[cp.pk for cp in delete_cps]) v_avail = v.max_usages - v.redeemed - redeemed_in_carts.count( ) if v_avail < voucher_usage[v]: errs[i]['voucher'] = [ 'The voucher has already been used the maximum number of times.' ] seated = pos_data.get('item').seat_category_mappings.filter( subevent=pos_data.get('subevent')).exists() if pos_data.get('seat'): if not seated: errs[i]['seat'] = [ 'The specified product does not allow to choose a seat.' ] try: seat = self.context['event'].seats.get( seat_guid=pos_data['seat'], subevent=pos_data.get('subevent')) except Seat.DoesNotExist: errs[i]['seat'] = [ 'The specified seat does not exist.' ] else: pos_data['seat'] = seat if (seat not in free_seats and not seat.is_available()) or seat in seats_seen: errs[i]['seat'] = [ ugettext_lazy( 'The selected seat "{seat}" is not available.' ).format(seat=seat.name) ] seats_seen.add(seat) elif seated: errs[i]['seat'] = [ 'The specified product requires to choose a seat.' ] if not force: for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): if pos_data['voucher'].allow_ignore_quota or pos_data[ 'voucher'].block_quota: continue new_quotas = (pos_data.get('variation').quotas.filter( subevent=pos_data.get('subevent')) if pos_data.get('variation') else pos_data.get('item').quotas.filter( subevent=pos_data.get('subevent'))) if len(new_quotas) == 0: errs[i]['item'] = [ ugettext_lazy( 'The product "{}" is not assigned to a quota.' ).format(str(pos_data.get('item'))) ] else: for quota in new_quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] -= 1 if quota_avail_cache[quota][1] < 0: errs[i]['item'] = [ ugettext_lazy( 'There is not enough quota available on quota "{}" to perform the operation.' ).format(quota.name) ] if any(errs): raise ValidationError({'positions': errs}) if validated_data.get('locale', None) is None: validated_data['locale'] = self.context[ 'event'].settings.locale order = Order(event=self.context['event'], **validated_data) order.set_expires( subevents=[p.get('subevent') for p in positions_data]) order.meta_info = "{}" order.total = Decimal('0.00') order.save() if ia: ia.order = order ia.save() pos_map = {} for pos_data in positions_data: answers_data = pos_data.pop('answers', []) addon_to = pos_data.pop('addon_to', None) attendee_name = pos_data.pop('attendee_name', '') if attendee_name and not pos_data.get('attendee_name_parts'): pos_data['attendee_name_parts'] = { '_legacy': attendee_name } pos = OrderPosition(**pos_data) pos.order = order if addon_to: pos.addon_to = pos_map[addon_to] if pos.price is None: price = get_price( item=pos.item, variation=pos.variation, voucher=pos.voucher, custom_price=None, subevent=pos.subevent, addon_to=pos.addon_to, invoice_address=ia, ) pos.price = price.gross pos.tax_rate = price.rate pos.tax_value = price.tax pos.tax_rule = pos.item.tax_rule else: pos._calculate_tax() if pos.voucher: Voucher.objects.filter(pk=pos.voucher.pk).update( redeemed=F('redeemed') + 1) pos.save() pos_map[pos.positionid] = pos for answ_data in answers_data: options = answ_data.pop('options', []) answ = pos.answers.create(**answ_data) answ.options.add(*options) for cp in delete_cps: cp.delete() for fee_data in fees_data: f = OrderFee(**fee_data) f.order = order f._calculate_tax() f.save() order.total = sum([p.price for p in order.positions.all()]) + sum( [f.value for f in order.fees.all()]) order.save(update_fields=['total']) if order.total == Decimal( '0.00') and validated_data.get('status') != Order.STATUS_PAID: order.status = Order.STATUS_PAID order.save() order.payments.create(amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED, payment_date=now()) elif payment_provider == "free" and order.total != Decimal('0.00'): raise ValidationError( 'You cannot use the "free" payment provider for non-free orders.' ) elif validated_data.get('status') == Order.STATUS_PAID: if not payment_provider: raise ValidationError( 'You cannot create a paid order without a payment provider.' ) order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, payment_date=payment_date, state=OrderPayment.PAYMENT_STATE_CONFIRMED) elif payment_provider: order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, state=OrderPayment.PAYMENT_STATE_CREATED) return order
def test_free_price_ignored_if_lower_than_voucher(item, voucher): voucher.price_mode = 'set' voucher.value = Decimal('50.00') assert get_price(item, voucher=voucher, custom_price=Decimal('40.00')).gross == Decimal('50.00')
def test_variation_with_specific_price(item, variation): variation.default_price = Decimal('24.00') assert get_price(item, variation=variation).gross == Decimal('24.00')
def _check_positions(event: Event, now_dt: datetime, positions: List[CartPosition], address: InvoiceAddress=None): err = None errargs = None _check_date(event, now_dt) products_seen = Counter() for i, cp in enumerate(positions): if not cp.item.active or (cp.variation and not cp.variation.active): err = err or error_messages['unavailable'] cp.delete() continue quotas = list(cp.quotas) products_seen[cp.item] += 1 if cp.item.max_per_order and products_seen[cp.item] > cp.item.max_per_order: err = error_messages['max_items_per_product'] errargs = {'max': cp.item.max_per_order, 'product': cp.item.name} cp.delete() # Sorry! break if cp.voucher: redeemed_in_carts = CartPosition.objects.filter( Q(voucher=cp.voucher) & Q(event=event) & Q(expires__gte=now_dt) ).exclude(pk=cp.pk) v_avail = cp.voucher.max_usages - cp.voucher.redeemed - redeemed_in_carts.count() if v_avail < 1: err = err or error_messages['voucher_redeemed'] cp.delete() # Sorry! continue if cp.subevent and cp.subevent.presale_start and now_dt < cp.subevent.presale_start: err = err or error_messages['some_subevent_not_started'] cp.delete() break if cp.subevent and cp.subevent.presale_end and now_dt > cp.subevent.presale_end: err = err or error_messages['some_subevent_ended'] cp.delete() break if cp.item.require_voucher and cp.voucher is None: cp.delete() err = err or error_messages['voucher_required'] break if cp.item.hide_without_voucher and (cp.voucher is None or cp.voucher.item is None or cp.voucher.item.pk != cp.item.pk): cp.delete() err = error_messages['voucher_required'] break if cp.expires >= now_dt and not cp.voucher: # Other checks are not necessary continue price = get_price(cp.item, cp.variation, cp.voucher, cp.price, cp.subevent, custom_price_is_net=False, addon_to=cp.addon_to, invoice_address=address) if price is False or len(quotas) == 0: err = err or error_messages['unavailable'] cp.delete() continue if cp.voucher: if cp.voucher.valid_until and cp.voucher.valid_until < now_dt: err = err or error_messages['voucher_expired'] cp.delete() continue if price.gross != cp.price and not (cp.item.free_price and cp.price > price.gross): positions[i] = cp cp.price = price.gross cp.includes_tax = bool(price.rate) cp.save() err = err or error_messages['price_changed'] continue quota_ok = True ignore_all_quotas = cp.expires >= now_dt or ( cp.voucher and (cp.voucher.allow_ignore_quota or (cp.voucher.block_quota and cp.voucher.quota is None))) if not ignore_all_quotas: for quota in quotas: if cp.voucher and cp.voucher.block_quota and cp.voucher.quota_id == quota.pk: continue avail = quota.availability(now_dt) if avail[0] != Quota.AVAILABILITY_OK: # This quota is sold out/currently unavailable, so do not sell this at all err = err or error_messages['unavailable'] quota_ok = False break if quota_ok: positions[i] = cp cp.expires = now_dt + timedelta( minutes=event.settings.get('reservation_time', as_type=int)) cp.save() else: cp.delete() # Sorry! if err: raise OrderError(err, errargs)
def test_free_price_limit(item): item.free_price = True with pytest.raises(ValueError): get_price(item, custom_price=Decimal('200000000'))
def test_tax_none(item): item.default_price = Decimal('100.00') assert get_price(item).gross == Decimal('100.00') assert get_price(item).net == Decimal('100.00') assert get_price(item).tax == Decimal('0.00') assert get_price(item).rate == Decimal('0.00')
def test_base_item_subevent_no_entry(item, subevent): assert get_price(item, subevent=subevent).gross == Decimal('23.00')
def test_free_price_string(item): item.free_price = True assert get_price(item, custom_price='42,00').gross == Decimal('42.00')
def price_calc(self, request, *args, **kwargs): """ This calculates the price assuming a change of product or subevent. This endpoint is deliberately not documented and considered a private API, only to be used by pretix' web interface. Sample input: { "item": 2, "variation": null, "subevent": 3 } Sample output: { "gross": "2.34", "gross_formatted": "2,34", "net": "2.34", "tax": "0.00", "rate": "0.00", "name": "VAT" } """ serializer = PriceCalcSerializer(data=request.data, event=request.event) serializer.is_valid(raise_exception=True) data = serializer.validated_data pos = self.get_object() try: ia = pos.order.invoice_address except InvoiceAddress.DoesNotExist: ia = InvoiceAddress() kwargs = { 'item': pos.item, 'variation': pos.variation, 'voucher': pos.voucher, 'subevent': pos.subevent, 'addon_to': pos.addon_to, 'invoice_address': ia, } if data.get('item'): item = data.get('item') kwargs['item'] = item if item.has_variations: variation = data.get('variation') or pos.variation if not variation: raise ValidationError('No variation given') if variation.item != item: raise ValidationError('Variation does not belong to item') kwargs['variation'] = variation else: variation = None kwargs['variation'] = None if pos.voucher and not pos.voucher.applies_to(item, variation): kwargs['voucher'] = None if data.get('subevent'): kwargs['subevent'] = data.get('subevent') price = get_price(**kwargs) with language( data.get('locale') or self.request.event.settings.locale): return Response({ 'gross': price.gross, 'gross_formatted': money_filter(price.gross, self.request.event.currency, hide_currency=True), 'net': price.net, 'rate': price.rate, 'name': str(price.name), 'tax': price.tax, })
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 test_base_item_subevent_override(item, subevent): SubEventItem.objects.create(item=item, subevent=subevent, price=Decimal('24.00')) assert get_price(item, subevent=subevent).gross == Decimal('24.00')
def label_from_instance(self, obj): p = get_price(self.instance.item, self.instance.variation, voucher=self.instance.voucher, subevent=obj) return '{} – {} ({})'.format(obj.name, obj.get_date_range_display(), p.print(self.instance.order.event.currency))
def create(self, validated_data): fees_data = validated_data.pop( 'fees') if 'fees' in validated_data else [] positions_data = validated_data.pop( 'positions') if 'positions' in validated_data else [] payment_provider = validated_data.pop('payment_provider', None) payment_info = validated_data.pop('payment_info', '{}') payment_date = validated_data.pop('payment_date', now()) force = validated_data.pop('force', False) self._send_mail = validated_data.pop('send_mail', False) if 'invoice_address' in validated_data: iadata = validated_data.pop('invoice_address') name = iadata.pop('name', '') if name and not iadata.get('name_parts'): iadata['name_parts'] = {'_legacy': name} ia = InvoiceAddress(**iadata) else: ia = None with self.context['event'].lock() as now_dt: free_seats = set() seats_seen = set() consume_carts = validated_data.pop('consume_carts', []) delete_cps = [] quota_avail_cache = {} v_budget = {} voucher_usage = Counter() if consume_carts: for cp in CartPosition.objects.filter( event=self.context['event'], cart_id__in=consume_carts, expires__gt=now()): quotas = (cp.variation.quotas.filter(subevent=cp.subevent) if cp.variation else cp.item.quotas.filter( subevent=cp.subevent)) for quota in quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] += 1 if cp.voucher: voucher_usage[cp.voucher] -= 1 if cp.expires > now_dt: if cp.seat: free_seats.add(cp.seat) delete_cps.append(cp) errs = [{} for p in positions_data] for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): v = pos_data['voucher'] if pos_data.get('addon_to'): errs[i]['voucher'] = [ 'Vouchers are currently not supported for add-on products.' ] continue if not v.applies_to(pos_data['item'], pos_data.get('variation')): errs[i]['voucher'] = [ error_messages['voucher_invalid_item'] ] continue if v.subevent_id and pos_data.get( 'subevent').pk != v.subevent_id: errs[i]['voucher'] = [ error_messages['voucher_invalid_subevent'] ] continue if v.valid_until is not None and v.valid_until < now_dt: errs[i]['voucher'] = [ error_messages['voucher_expired'] ] continue voucher_usage[v] += 1 if voucher_usage[v] > 0: redeemed_in_carts = CartPosition.objects.filter( Q(voucher=pos_data['voucher']) & Q(event=self.context['event']) & Q(expires__gte=now_dt)).exclude( pk__in=[cp.pk for cp in delete_cps]) v_avail = v.max_usages - v.redeemed - redeemed_in_carts.count( ) if v_avail < voucher_usage[v]: errs[i]['voucher'] = [ 'The voucher has already been used the maximum number of times.' ] if v.budget is not None: price = pos_data.get('price') if price is None: price = get_price( item=pos_data.get('item'), variation=pos_data.get('variation'), voucher=v, custom_price=None, subevent=pos_data.get('subevent'), addon_to=pos_data.get('addon_to'), invoice_address=ia, ).gross pbv = get_price( item=pos_data['item'], variation=pos_data.get('variation'), voucher=None, custom_price=None, subevent=pos_data.get('subevent'), addon_to=pos_data.get('addon_to'), invoice_address=ia, ) if v not in v_budget: v_budget[v] = v.budget - v.budget_used() disc = pbv.gross - price if disc > v_budget[v]: new_disc = v_budget[v] v_budget[v] -= new_disc if new_disc == Decimal('0.00') or pos_data.get( 'price') is not None: errs[i]['voucher'] = [ 'The voucher has a remaining budget of {}, therefore a discount of {} can not be ' 'given.'.format(v_budget[v] + new_disc, disc) ] continue pos_data['price'] = price + (disc - new_disc) else: v_budget[v] -= disc seated = pos_data.get('item').seat_category_mappings.filter( subevent=pos_data.get('subevent')).exists() if pos_data.get('seat'): if not seated: errs[i]['seat'] = [ 'The specified product does not allow to choose a seat.' ] try: seat = self.context['event'].seats.get( seat_guid=pos_data['seat'], subevent=pos_data.get('subevent')) except Seat.DoesNotExist: errs[i]['seat'] = [ 'The specified seat does not exist.' ] else: pos_data['seat'] = seat if (seat not in free_seats and not seat.is_available( sales_channel=validated_data.get( 'sales_channel', 'web')) ) or seat in seats_seen: errs[i]['seat'] = [ ugettext_lazy( 'The selected seat "{seat}" is not available.' ).format(seat=seat.name) ] seats_seen.add(seat) elif seated: errs[i]['seat'] = [ 'The specified product requires to choose a seat.' ] if not force: for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): if pos_data['voucher'].allow_ignore_quota or pos_data[ 'voucher'].block_quota: continue new_quotas = (pos_data.get('variation').quotas.filter( subevent=pos_data.get('subevent')) if pos_data.get('variation') else pos_data.get('item').quotas.filter( subevent=pos_data.get('subevent'))) if len(new_quotas) == 0: errs[i]['item'] = [ ugettext_lazy( 'The product "{}" is not assigned to a quota.' ).format(str(pos_data.get('item'))) ] else: for quota in new_quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] -= 1 if quota_avail_cache[quota][1] < 0: errs[i]['item'] = [ ugettext_lazy( 'There is not enough quota available on quota "{}" to perform the operation.' ).format(quota.name) ] if any(errs): raise ValidationError({'positions': errs}) if validated_data.get('locale', None) is None: validated_data['locale'] = self.context[ 'event'].settings.locale order = Order(event=self.context['event'], **validated_data) order.set_expires( subevents=[p.get('subevent') for p in positions_data]) order.meta_info = "{}" order.total = Decimal('0.00') order.save() if ia: ia.order = order ia.save() pos_map = {} for pos_data in positions_data: answers_data = pos_data.pop('answers', []) addon_to = pos_data.pop('addon_to', None) attendee_name = pos_data.pop('attendee_name', '') if attendee_name and not pos_data.get('attendee_name_parts'): pos_data['attendee_name_parts'] = { '_legacy': attendee_name } pos = OrderPosition(**pos_data) pos.order = order if addon_to: pos.addon_to = pos_map[addon_to] if pos.price is None: price = get_price( item=pos.item, variation=pos.variation, voucher=pos.voucher, custom_price=None, subevent=pos.subevent, addon_to=pos.addon_to, invoice_address=ia, ) pos.price = price.gross pos.tax_rate = price.rate pos.tax_value = price.tax pos.tax_rule = pos.item.tax_rule else: pos._calculate_tax() pos.price_before_voucher = get_price( item=pos.item, variation=pos.variation, voucher=None, custom_price=None, subevent=pos.subevent, addon_to=pos.addon_to, invoice_address=ia, ).gross if pos.voucher: Voucher.objects.filter(pk=pos.voucher.pk).update( redeemed=F('redeemed') + 1) pos.save() pos_map[pos.positionid] = pos for answ_data in answers_data: options = answ_data.pop('options', []) answ = pos.answers.create(**answ_data) answ.options.add(*options) for cp in delete_cps: cp.delete() order.total = sum([p.price for p in order.positions.all()]) for fee_data in fees_data: is_percentage = fee_data.pop('_treat_value_as_percentage', False) if is_percentage: fee_data['value'] = round_decimal( order.total * (fee_data['value'] / Decimal('100.00')), self.context['event'].currency) is_split_taxes = fee_data.pop('_split_taxes_like_products', False) if is_split_taxes: d = defaultdict(lambda: Decimal('0.00')) trz = TaxRule.zero() for p in pos_map.values(): tr = p.tax_rule d[tr] += p.price - p.tax_value base_values = sorted([tuple(t) for t in d.items()], key=lambda t: (t[0] or trz).rate) sum_base = sum(t[1] for t in base_values) fee_values = [ (t[0], round_decimal(fee_data['value'] * t[1] / sum_base, self.context['event'].currency)) for t in base_values ] sum_fee = sum(t[1] for t in fee_values) # If there are rounding differences, we fix them up, but always leaning to the benefit of the tax # authorities if sum_fee > fee_data['value']: fee_values[0] = (fee_values[0][0], fee_values[0][1] + (fee_data['value'] - sum_fee)) elif sum_fee < fee_data['value']: fee_values[-1] = (fee_values[-1][0], fee_values[-1][1] + (fee_data['value'] - sum_fee)) for tr, val in fee_values: fee_data['tax_rule'] = tr fee_data['value'] = val f = OrderFee(**fee_data) f.order = order f._calculate_tax() f.save() else: f = OrderFee(**fee_data) f.order = order f._calculate_tax() f.save() order.total += sum([f.value for f in order.fees.all()]) order.save(update_fields=['total']) if order.total == Decimal('0.00') and validated_data.get( 'status') == Order.STATUS_PAID and not payment_provider: payment_provider = 'free' if order.total == Decimal( '0.00') and validated_data.get('status') != Order.STATUS_PAID: order.status = Order.STATUS_PAID order.save() order.payments.create(amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED, payment_date=now()) elif payment_provider == "free" and order.total != Decimal('0.00'): raise ValidationError( 'You cannot use the "free" payment provider for non-free orders.' ) elif validated_data.get('status') == Order.STATUS_PAID: if not payment_provider: raise ValidationError( 'You cannot create a paid order without a payment provider.' ) order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, payment_date=payment_date, state=OrderPayment.PAYMENT_STATE_CONFIRMED) elif payment_provider: order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, state=OrderPayment.PAYMENT_STATE_CREATED) return order
def test_free_price_float(item): item.free_price = True assert get_price(item, custom_price=42.00).gross == Decimal('42.00')
def test_base_item_subevent_no_entry(item, subevent): assert get_price(item, subevent=subevent).gross == Decimal('23.00')
def test_free_price_net(item): item.free_price = True item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00')) assert get_price(item, custom_price=Decimal('100.00'), custom_price_is_net=True).gross == Decimal('119.00')
def test_variation_with_default_item_price(item, variation): assert get_price(item, variation=variation).gross == Decimal('23.00')
def test_variation_with_no_subevent_and_specific_price(item, subevent, variation): variation.default_price = Decimal('24.00') assert get_price(item, variation=variation, subevent=subevent).gross == Decimal('24.00')
def test_variation_with_default_item_price(item, variation): assert get_price(item, variation=variation).gross == Decimal('23.00')
def test_base_item_default(item): assert get_price(item).gross == Decimal('23.00')
def test_voucher_no_override(item, subevent, voucher): assert get_price(item, subevent=subevent, voucher=voucher).gross == Decimal('23.00')
def test_base_item_subevent_override(item, subevent): SubEventItem.objects.create(item=item, subevent=subevent, price=Decimal('24.00')) assert get_price(item, subevent=subevent).gross == Decimal('24.00')
def price_calc(self, request, *args, **kwargs): """ This calculates the price assuming a change of product or subevent. This endpoint is deliberately not documented and considered a private API, only to be used by pretix' web interface. Sample input: { "item": 2, "variation": null, "subevent": 3 } Sample output: { "gross": "2.34", "gross_formatted": "2,34", "net": "2.34", "tax": "0.00", "rate": "0.00", "name": "VAT" } """ serializer = PriceCalcSerializer(data=request.data, event=request.event) serializer.is_valid(raise_exception=True) data = serializer.validated_data pos = self.get_object() try: ia = pos.order.invoice_address except InvoiceAddress.DoesNotExist: ia = InvoiceAddress() kwargs = { 'item': pos.item, 'variation': pos.variation, 'voucher': pos.voucher, 'subevent': pos.subevent, 'addon_to': pos.addon_to, 'invoice_address': ia, } if data.get('item'): item = data.get('item') kwargs['item'] = item if item.has_variations: variation = data.get('variation') or pos.variation if not variation: raise ValidationError('No variation given') if variation.item != item: raise ValidationError('Variation does not belong to item') kwargs['variation'] = variation else: variation = None kwargs['variation'] = None if pos.voucher and not pos.voucher.applies_to(item, variation): kwargs['voucher'] = None if data.get('subevent'): kwargs['subevent'] = data.get('subevent') price = get_price(**kwargs) with language(data.get('locale') or self.request.event.settings.locale): return Response({ 'gross': price.gross, 'gross_formatted': money_filter(price.gross, self.request.event.currency, hide_currency=True), 'net': price.net, 'rate': price.rate, 'name': str(price.name), 'tax': price.tax, })
def test_variation_with_specific_price(item, variation): variation.default_price = Decimal('24.00') assert get_price(item, variation=variation).gross == Decimal('24.00')
def test_voucher_percent(item, subevent, voucher): voucher.price_mode = 'percent' voucher.value = Decimal('10.00') assert get_price(item, subevent=subevent, voucher=voucher).gross == Decimal('20.70')
def test_variation_with_default_subevent_and_specific_price(item, subevent, variation): variation.default_price = Decimal('24.00') SubEventItemVariation.objects.create(variation=variation, subevent=subevent, price=None) assert get_price(item, variation=variation, subevent=subevent).gross == Decimal('24.00')
def test_free_price_ignored_if_disabled(item): assert get_price(item, custom_price=Decimal('42.00')).gross == Decimal('23.00')
def test_voucher_subtract(item, subevent, voucher): voucher.price_mode = 'subtract' voucher.value = Decimal('12.00') assert get_price(item, subevent=subevent, voucher=voucher).gross == Decimal('11.00')
def test_free_price_ignored_if_lower(item): item.free_price = True assert get_price(item, custom_price=Decimal('12.00')).gross == Decimal('23.00')