예제 #1
0
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')
예제 #2
0
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')
예제 #3
0
    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)
예제 #4
0
 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
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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
예제 #8
0
    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)
예제 #9
0
파일: orders.py 프로젝트: scperkins/pretix
    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))
예제 #10
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))
예제 #11
0
파일: orders.py 프로젝트: timfreund/pretix
 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)
예제 #12
0
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')
예제 #13
0
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')
예제 #14
0
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')
예제 #15
0
    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))
예제 #16
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)
        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))
예제 #17
0
 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
     )
예제 #18
0
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')
예제 #19
0
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')
예제 #20
0
파일: orders.py 프로젝트: NehemiasEC/pretix
    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)
예제 #21
0
파일: cart.py 프로젝트: FlaviaBastos/pretix
 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
예제 #22
0
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')
예제 #23
0
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')
예제 #24
0
    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
예제 #25
0
 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
예제 #26
0
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'))
예제 #27
0
def test_free_price_accepted(item):
    item.free_price = True
    assert get_price(item, custom_price=Decimal('42.00')).gross == Decimal('42.00')
예제 #28
0
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')
예제 #29
0
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')
예제 #30
0
파일: order.py 프로젝트: nkhanal0/pretix
    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
예제 #31
0
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')
예제 #32
0
def test_variation_with_specific_price(item, variation):
    variation.default_price = Decimal('24.00')
    assert get_price(item, variation=variation).gross == Decimal('24.00')
예제 #33
0
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)
예제 #34
0
def test_free_price_limit(item):
    item.free_price = True
    with pytest.raises(ValueError):
        get_price(item, custom_price=Decimal('200000000'))
예제 #35
0
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')
예제 #36
0
def test_base_item_subevent_no_entry(item, subevent):
    assert get_price(item, subevent=subevent).gross == Decimal('23.00')
예제 #37
0
def test_free_price_string(item):
    item.free_price = True
    assert get_price(item, custom_price='42,00').gross == Decimal('42.00')
예제 #38
0
파일: order.py 프로젝트: bsod85/pretix
    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,
            })
예제 #39
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
예제 #40
0
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')
예제 #41
0
 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))
예제 #42
0
    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
예제 #43
0
def test_free_price_float(item):
    item.free_price = True
    assert get_price(item, custom_price=42.00).gross == Decimal('42.00')
예제 #44
0
def test_base_item_subevent_no_entry(item, subevent):
    assert get_price(item, subevent=subevent).gross == Decimal('23.00')
예제 #45
0
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')
예제 #46
0
def test_variation_with_default_item_price(item, variation):
    assert get_price(item, variation=variation).gross == Decimal('23.00')
예제 #47
0
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')
예제 #48
0
def test_variation_with_default_item_price(item, variation):
    assert get_price(item, variation=variation).gross == Decimal('23.00')
예제 #49
0
def test_base_item_default(item):
    assert get_price(item).gross == Decimal('23.00')
예제 #50
0
def test_voucher_no_override(item, subevent, voucher):
    assert get_price(item, subevent=subevent, voucher=voucher).gross == Decimal('23.00')
예제 #51
0
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')
예제 #52
0
    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,
            })
예제 #53
0
def test_variation_with_specific_price(item, variation):
    variation.default_price = Decimal('24.00')
    assert get_price(item, variation=variation).gross == Decimal('24.00')
예제 #54
0
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')
예제 #55
0
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')
예제 #56
0
def test_free_price_ignored_if_disabled(item):
    assert get_price(item, custom_price=Decimal('42.00')).gross == Decimal('23.00')
예제 #57
0
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')
예제 #58
0
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')