Пример #1
0
def get_cart_invoice_address(request):
    from pretix.presale.views.cart import cart_session

    if not hasattr(request, '_checkout_flow_invoice_address'):
        cs = cart_session(request)
        iapk = cs.get('invoice_address')
        if not iapk:
            request._checkout_flow_invoice_address = InvoiceAddress()
        else:
            try:
                with scopes_disabled():
                    request._checkout_flow_invoice_address = InvoiceAddress.objects.get(
                        pk=iapk, order__isnull=True)
            except InvoiceAddress.DoesNotExist:
                request._checkout_flow_invoice_address = InvoiceAddress()
    return request._checkout_flow_invoice_address
Пример #2
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')
Пример #3
0
def test_reverse_charge_no_country(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress()
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
Пример #4
0
def test_custom_rules_eu_country(event):
    tr = TaxRule(event=event,
                 rate=Decimal('10.00'),
                 price_includes_tax=False,
                 custom_rules=json.dumps([
                     {
                         'country': 'EU',
                         'address_type': '',
                         'action': 'no'
                     },
                 ]))
    ia = InvoiceAddress(is_business=True, country=Country('AT'))
    assert not tr.is_reverse_charge(ia)
    assert not tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('0.00')
    ia = InvoiceAddress(is_business=True, country=Country('US'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
Пример #5
0
def test_reverse_charge_individual_eu(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 home_country=Country('DE'),
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress(is_business=False, country=Country('AT'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
Пример #6
0
def cached_invoice_address(request):
    from .cart import cart_session

    if not hasattr(request, '_checkout_flow_invoice_address'):
        if not request.session.session_key:
            # do not create a session, if we don't have a session we also don't have an invoice address ;)
            request._checkout_flow_invoice_address = InvoiceAddress()
            return request._checkout_flow_invoice_address
        cs = cart_session(request)
        iapk = cs.get('invoice_address')
        if not iapk:
            request._checkout_flow_invoice_address = InvoiceAddress()
        else:
            try:
                with scopes_disabled():
                    request._checkout_flow_invoice_address = InvoiceAddress.objects.get(
                        pk=iapk, order__isnull=True)
            except InvoiceAddress.DoesNotExist:
                request._checkout_flow_invoice_address = InvoiceAddress()
    return request._checkout_flow_invoice_address
Пример #7
0
def test_reverse_charge_valid_vat_id_business_3rdc(event):
    tr = TaxRule(
        event=event, eu_reverse_charge=True, home_country=Country('DE'),
        rate=Decimal('10.00'), price_includes_tax=False
    )
    ia = InvoiceAddress(
        is_business=True,
        country=Country('US'),
        vat_id='US12346',
        vat_id_validated=True
    )
    assert not tr.is_reverse_charge(ia)
    assert not tr.tax_applicable(ia)
Пример #8
0
def test_reverse_charge_disabled(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=False,
                 home_country=Country('DE'),
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress(is_business=True,
                        vat_id='AT12346',
                        vat_id_validated=True,
                        country=Country('AT'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
Пример #9
0
def test_custom_rules_vat_id(event):
    tr = TaxRule(
        event=event,
        rate=Decimal('10.00'), price_includes_tax=False,
        custom_rules=json.dumps([
            {'country': 'EU', 'address_type': 'business_vat_id', 'action': 'reverse'},
        ])
    )
    ia = InvoiceAddress(
        is_business=True,
        country=Country('AT')
    )
    assert not tr.is_reverse_charge(ia)
    assert tr.tax_applicable(ia)
    ia = InvoiceAddress(
        is_business=True,
        country=Country('DE'),
        vat_id='DE1234',
        vat_id_validated=True
    )
    assert tr.is_reverse_charge(ia)
    assert not tr.tax_applicable(ia)
Пример #10
0
def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString,
               subevent: SubEvent, refund_amount: Decimal, user: User,
               positions: list):
    with language(order.locale, order.event.settings.region):
        try:
            ia = order.invoice_address
        except InvoiceAddress.DoesNotExist:
            ia = InvoiceAddress(order=order)

        email_context = get_email_context(event_or_subevent=subevent
                                          or order.event,
                                          refund_amount=refund_amount,
                                          order=order,
                                          position_or_address=ia,
                                          event=order.event)
        real_subject = str(subject).format_map(TolerantDict(email_context))
        try:
            order.send_mail(
                real_subject,
                message,
                email_context,
                'pretix.event.order.email.event_canceled',
                user,
            )
        except SendMailException:
            logger.exception('Order canceled email could not be sent')

        for p in positions:
            if subevent and p.subevent_id != subevent.id:
                continue

            if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email:
                real_subject = str(subject).format_map(
                    TolerantDict(email_context))
                email_context = get_email_context(event_or_subevent=p.subevent
                                                  or order.event,
                                                  event=order.event,
                                                  refund_amount=refund_amount,
                                                  position_or_address=p,
                                                  order=order,
                                                  position=p)
                try:
                    order.send_mail(real_subject,
                                    message,
                                    email_context,
                                    'pretix.event.order.email.event_canceled',
                                    position=p,
                                    user=user)
                except SendMailException:
                    logger.exception(
                        'Order canceled email could not be sent to attendee')
Пример #11
0
    def iterate_list(self, form_data):
        yield [
            _('Date'),
            _('Time'),
            _('Check-in list'),
            _('Scan type'),
            _('Order code'),
            _('Position ID'),
            _('Secret'),
            _('Product'),
            _('Name'),
            _('Device'),
            _('Offline override'),
            _('Automatically checked in'),
        ]

        qs = Checkin.objects.filter(
            position__order__event=self.event,
        )
        if form_data.get('list'):
            qs = qs.filter(list_id=form_data.get('list'))
        if form_data.get('items'):
            qs = qs.filter(position__item_id__in=form_data['items'])

        yield self.ProgressSetTotal(total=qs.count())

        qs = qs.select_related(
            'position__item', 'position__order', 'position__order__invoice_address', 'position', 'list', 'device'
        ).order_by(
            'datetime'
        )
        for ci in qs.iterator():
            try:
                ia = ci.position.order.invoice_address
            except InvoiceAddress.DoesNotExist:
                ia = InvoiceAddress()

            yield [
                date_format(ci.datetime, 'SHORT_DATE_FORMAT'),
                date_format(ci.datetime, 'TIME_FORMAT'),
                str(ci.list),
                ci.get_type_display(),
                ci.position.order.code,
                ci.position.positionid,
                ci.position.secret,
                str(ci.position.item),
                ci.position.attendee_name or ia.name,
                str(ci.device),
                _('Yes') if ci.forced else _('No'),
                _('Yes') if ci.auto_checked_in else _('No'),
            ]
Пример #12
0
def test_custom_rules_country_rate_keep_gross_if_rate_changes(event):
    tr = TaxRule(event=event,
                 rate=Decimal('10.00'),
                 price_includes_tax=False,
                 keep_gross_if_rate_changes=True,
                 custom_rules=json.dumps([
                     {
                         'country': 'EU',
                         'address_type': 'business_vat_id',
                         'action': 'vat',
                         'rate': '100.00'
                     },
                 ]))
    ia = InvoiceAddress(is_business=True, country=Country('DE'))
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('100.00'),
        tax=Decimal('10.00'),
        rate=Decimal('10.00'),
        name='',
    )
    ia = InvoiceAddress(is_business=True,
                        country=Country('DE'),
                        vat_id='DE1234',
                        vat_id_validated=True)
    assert tr.tax_rate_for(ia) == Decimal('100.00')
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('55.00'),
        tax=Decimal('55.00'),
        rate=Decimal('100.00'),
        name='',
    )
Пример #13
0
def test_custom_rules_override(event):
    tr = TaxRule(
        event=event, eu_reverse_charge=True, home_country=Country('DE'),
        rate=Decimal('10.00'), price_includes_tax=False,
        custom_rules=json.dumps([
            {'country': 'ZZ', 'address_type': '', 'action': 'vat'}
        ])
    )
    ia = InvoiceAddress(
        is_business=True,
        vat_id='AT12346',
        vat_id_validated=True,
        country=Country('AT')
    )
    assert not tr.is_reverse_charge(ia)
    assert tr.tax_applicable(ia)
Пример #14
0
def test_reverse_charge_no_country(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress()
    assert not tr.is_reverse_charge(ia)
    assert tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('10.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('110.00'),
        net=Decimal('100.00'),
        tax=Decimal('10.00'),
        rate=Decimal('10.00'),
        name='',
    )
Пример #15
0
def get_email_context(**kwargs):
    from pretix.base.models import InvoiceAddress

    event = kwargs['event']
    if 'order' in kwargs:
        try:
            kwargs['invoice_address'] = kwargs['order'].invoice_address
        except InvoiceAddress.DoesNotExist:
            kwargs['invoice_address'] = InvoiceAddress()
    ctx = {}
    for r, val in register_mail_placeholders.send(sender=event):
        if not isinstance(val, (list, tuple)):
            val = [val]
        for v in val:
            if all(rp in kwargs for rp in v.required_context):
                ctx[v.identifier] = v.render(kwargs)
    return ctx
Пример #16
0
def test_reverse_charge_individual_3rdc(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 home_country=Country('DE'),
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress(is_business=False, country=Country('US'))
    assert not tr.is_reverse_charge(ia)
    assert not tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('0.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('100.00'),
        net=Decimal('100.00'),
        tax=Decimal('0.00'),
        rate=Decimal('0.00'),
        name='',
    )
Пример #17
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')
Пример #18
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')
Пример #19
0
def test_reverse_charge_valid_vat_id_business_eu(event):
    tr = TaxRule(event=event,
                 eu_reverse_charge=True,
                 home_country=Country('DE'),
                 rate=Decimal('10.00'),
                 price_includes_tax=False)
    ia = InvoiceAddress(is_business=True,
                        vat_id='AT12346',
                        vat_id_validated=True,
                        country=Country('AT'))
    assert tr.is_reverse_charge(ia)
    assert not tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('0.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('100.00'),
        net=Decimal('100.00'),
        tax=Decimal('0.00'),
        rate=Decimal('0.00'),
        name='',
    )
Пример #20
0
def test_custom_rules_any_country(event):
    tr = TaxRule(event=event,
                 rate=Decimal('10.00'),
                 price_includes_tax=False,
                 custom_rules=json.dumps([
                     {
                         'country': 'ZZ',
                         'address_type': '',
                         'action': 'no'
                     },
                 ]))
    ia = InvoiceAddress(is_business=True, country=Country('AT'))
    assert not tr.is_reverse_charge(ia)
    assert not tr._tax_applicable(ia)
    assert tr.tax_rate_for(ia) == Decimal('0.00')
    assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice(
        gross=Decimal('100.00'),
        net=Decimal('100.00'),
        tax=Decimal('0.00'),
        rate=Decimal('0.00'),
        name='',
    )
Пример #21
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'))
Пример #22
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')
        payment_info = validated_data.pop('payment_info', '{}')

        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:
            quotadiff = Counter()

            consume_carts = validated_data.pop('consume_carts', [])
            delete_cps = []
            quota_avail_cache = {}
            if consume_carts:
                for cp in CartPosition.objects.filter(
                        event=self.context['event'],
                        cart_id__in=consume_carts):
                    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.expires > now_dt:
                        quotadiff.subtract(quotas)
                    delete_cps.append(cp)

            errs = [{} for p in positions_data]

            for i, pos_data in enumerate(positions_data):
                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)
                                ]

                quotadiff.update(new_quotas)

            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.total = sum([p['price'] for p in positions_data]) + sum(
                [f['value'] for f in fees_data], Decimal('0.00'))
            order.meta_info = "{}"
            order.save()

            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:
                order.payments.create(
                    amount=order.total,
                    provider=payment_provider,
                    info=payment_info,
                    payment_date=now(),
                    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)

            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
                pos._calculate_tax()
                if addon_to:
                    pos.addon_to = pos_map[addon_to]
                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()

        return order
Пример #23
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,
            })
Пример #24
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
Пример #25
0
def send_mails(event: Event,
               user: int,
               subject: dict,
               message: dict,
               orders: list,
               items: list,
               recipients: str,
               filter_checkins: bool,
               not_checked_in: bool,
               checkin_lists: list,
               attachments: list = None,
               attach_tickets: bool = False) -> None:
    failures = []
    user = User.objects.get(pk=user) if user else None
    orders = Order.objects.filter(pk__in=orders, event=event)
    subject = LazyI18nString(subject)
    message = LazyI18nString(message)

    for o in orders:
        send_to_order = recipients in ('both', 'orders')

        try:
            ia = o.invoice_address
        except InvoiceAddress.DoesNotExist:
            ia = InvoiceAddress(order=o)

        if recipients in ('both', 'attendees'):
            for p in o.positions.prefetch_related('addons'):
                if p.addon_to_id is not None:
                    continue

                if p.item_id not in items and not any(a.item_id in items
                                                      for a in p.addons.all()):
                    continue

                if filter_checkins:
                    checkins = list(p.checkins.all())
                    allowed = ((not_checked_in and not checkins)
                               or (any(c.list_id in checkin_lists
                                       for c in checkins)))
                    if not allowed:
                        continue

                if not p.attendee_email:
                    if recipients == 'attendees':
                        send_to_order = True
                    continue

                if p.attendee_email == o.email and send_to_order:
                    continue

                try:
                    with language(o.locale, event.settings.region):
                        email_context = get_email_context(
                            event=event,
                            order=o,
                            position_or_address=p,
                            position=p)
                        mail(p.attendee_email,
                             subject,
                             message,
                             email_context,
                             event,
                             locale=o.locale,
                             order=o,
                             position=p,
                             attach_tickets=attach_tickets,
                             attach_cached_files=attachments)
                        o.log_action(
                            'pretix.plugins.sendmail.order.email.sent.attendee',
                            user=user,
                            data={
                                'position':
                                p.positionid,
                                'subject':
                                subject.localize(
                                    o.locale).format_map(email_context),
                                'message':
                                message.localize(
                                    o.locale).format_map(email_context),
                                'recipient':
                                p.attendee_email
                            })
                except SendMailException:
                    failures.append(p.attendee_email)

        if send_to_order and o.email:
            try:
                with language(o.locale, event.settings.region):
                    email_context = get_email_context(event=event,
                                                      order=o,
                                                      position_or_address=ia)
                    mail(o.email,
                         subject,
                         message,
                         email_context,
                         event,
                         locale=o.locale,
                         order=o,
                         attach_tickets=attach_tickets,
                         attach_cached_files=attachments)
                    o.log_action('pretix.plugins.sendmail.order.email.sent',
                                 user=user,
                                 data={
                                     'subject':
                                     subject.localize(
                                         o.locale).format_map(email_context),
                                     'message':
                                     message.localize(
                                         o.locale).format_map(email_context),
                                     'recipient':
                                     o.email
                                 })
            except SendMailException:
                failures.append(o.email)
Пример #26
0
    def send(self):
        if self.state not in (ScheduledMail.STATE_SCHEDULED,
                              ScheduledMail.STATE_FAILED):
            raise ValueError("Should not be called in this state")

        e = self.event

        orders = e.orders.all()
        limit_products = self.rule.limit_products.values_list(
            'pk', flat=True) if not self.rule.all_products else None

        if self.subevent:
            orders = orders.filter(
                Exists(
                    OrderPosition.objects.filter(order=OuterRef('pk'),
                                                 subevent=self.subevent)))

        if not self.rule.all_products:
            orders = orders.filter(
                Exists(
                    OrderPosition.objects.filter(order=OuterRef('pk'),
                                                 item_id__in=limit_products)))

        status = [Order.STATUS_PENDING, Order.STATUS_PAID
                  ] if self.rule.include_pending else [Order.STATUS_PAID]

        if self.last_successful_order_id:
            orders = orders.filter(pk__gt=self.last_successful_order_id)

        orders = orders.filter(
            status__in=status,
            require_approval=False,
        ).order_by('pk').select_related('invoice_address').prefetch_related(
            'positions')

        send_to_orders = self.rule.send_to in (Rule.CUSTOMERS, Rule.BOTH)
        send_to_attendees = self.rule.send_to in (Rule.ATTENDEES, Rule.BOTH)

        for o in orders:
            positions = list(o.positions.all())
            o_sent = False

            try:
                ia = o.invoice_address
            except InvoiceAddress.DoesNotExist:
                ia = InvoiceAddress(order=o)

            if send_to_orders and o.email:
                email_ctx = get_email_context(event=e,
                                              order=o,
                                              position_or_address=ia)
                try:
                    o.send_mail(
                        self.rule.subject,
                        self.rule.template,
                        email_ctx,
                        log_entry_type=
                        'pretix.plugins.sendmail.rule.order.email.sent')
                    o_sent = True
                except SendMailException:
                    ...  # ¯\_(ツ)_/¯

            if send_to_attendees:
                if not self.rule.all_products:
                    positions = [
                        p for p in positions if p.item_id in limit_products
                    ]
                if self.subevent_id:
                    positions = [
                        p for p in positions
                        if p.subevent_id == self.subevent_id
                    ]

                for p in positions:
                    email_ctx = get_email_context(event=e,
                                                  order=o,
                                                  position_or_address=ia,
                                                  position=p)
                    try:
                        if p.attendee_email and (p.attendee_email != o.email
                                                 or not o_sent):
                            p.send_mail(
                                self.rule.subject,
                                self.rule.template,
                                email_ctx,
                                log_entry_type=
                                'pretix.plugins.sendmail.rule.order.position.email.sent'
                            )
                        elif not o_sent and o.email:
                            o.send_mail(
                                self.rule.subject,
                                self.rule.template,
                                email_ctx,
                                log_entry_type=
                                'pretix.plugins.sendmail.rule.order.email.sent'
                            )
                            o_sent = True
                    except SendMailException:
                        ...  # ¯\_(ツ)_/¯

            self.last_successful_order_id = o.pk
Пример #27
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 = {}
            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
Пример #28
0
    def _get_dataset(self, qs, questions):
        # Collect and store data in preferred output format
        coll = {}
        collected_product_columns = []
        collected_question_columns = []

        for op in qs:
            try:
                ia = op.order.invoice_address
            except InvoiceAddress.DoesNotExist:
                ia = InvoiceAddress()

            order_code = op.order.code
            attendee = op.attendee_name or (op.addon_to.attendee_name
                                            if op.addon_to else '')
            product = str(op.item.name) + (
                (' - ' + str(op.variation.value)) if op.variation else '')
            paid = _('Yes') if op.order.status == Order.STATUS_PAID else _(
                'No')
            email = op.attendee_email or (op.addon_to.attendee_email
                                          if op.addon_to else '')

            # Product will be added as new column
            if product not in collected_product_columns:
                collected_product_columns.append(product)

            # Check whether we need to modify an existing attendee
            new_row = {}
            if attendee in coll:
                new_row = coll[attendee]

            new_row['order_code'] = order_code

            # Attendee name parts
            name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme]

            if len(name_scheme['fields']) > 1:
                attendee_name_parts = []
                for k, label, w in name_scheme['fields']:
                    attendee_name_parts.append(
                        (op.attendee_name_parts
                         or (op.addon_to.attendee_name_parts if op.addon_to
                             else {}) or ia.name_parts).get(k, ''))

                new_row['attendee_name_parts'] = attendee_name_parts

            new_row['company'] = ia.company
            new_row['street'] = ia.street
            new_row['zipcode'] = ia.zipcode
            new_row['city'] = ia.city
            new_row['country'] = ia.country.name

            if not email:
                new_row['email'] = op.order.email
            else:
                new_row['email'] = email

            if op.voucher:
                new_row['voucher'] = op.voucher.code

            # Collect products
            if 'products' not in new_row:
                new_row['products'] = {}

            # Store the product
            # new_row['products'][product] = paid
            new_row['products'][product] = "yes"

            # Collect questions
            if 'questions' not in new_row:
                new_row['questions'] = {}

            acache = {}
            for answer in op.answers.all():
                acache[answer.question_id] = str(answer)

            for question in questions:
                question_str = str(
                    question.question)  # cast from LazyI18nString

                # We are grouping ticket + addons here, and if not all three of them answer the question, just
                # take the first one which provides one.
                if question_str in new_row['questions'] and new_row[
                        'questions'][question_str] != '':
                    continue

                # Question will be added as new column
                if question_str not in collected_question_columns:
                    collected_question_columns.append(question_str)

                new_row['questions'][question_str] = acache.get(
                    question.pk, '')

            # logger.error(new_row)

            # Pass back to collection
            coll[attendee] = new_row

            # logger.error(coll)

            # for loop end

        # return result set
        return coll, collected_product_columns, collected_question_columns
Пример #29
0
def import_orders(event: Event, fileid: str, settings: dict, locale: str,
                  user) -> None:
    # TODO: quotacheck?
    cf = CachedFile.objects.get(id=fileid)
    user = User.objects.get(pk=user)
    with language(locale, event.settings.region):
        cols = get_all_columns(event)
        parsed = parse_csv(cf.file)
        orders = []
        order = None
        data = []

        # Run validation
        for i, record in enumerate(parsed):
            if not any(record.values()):
                continue
            values = {}
            for c in cols:
                val = c.resolve(settings, record)
                if isinstance(val, str):
                    val = val.strip()
                try:
                    values[c.identifier] = c.clean(val, values)
                except ValidationError as e:
                    raise DataImportError(
                        _('Error while importing value "{value}" for column "{column}" in line "{line}": {message}'
                          ).format(value=val if val is not None else '',
                                   column=c.verbose_name,
                                   line=i + 1,
                                   message=e.message))
            data.append(values)

        # Prepare model objects. Yes, this might consume lots of RAM, but allows us to make the actual SQL transaction
        # shorter. We'll see what works better in reality…
        for i, record in enumerate(data):
            try:
                if order is None or settings['orders'] == 'many':
                    order = Order(
                        event=event,
                        testmode=settings['testmode'],
                    )
                    order.meta_info = {}
                    order._positions = []
                    order._address = InvoiceAddress()
                    order._address.name_parts = {
                        '_scheme': event.settings.name_scheme
                    }
                    orders.append(order)

                position = OrderPosition(positionid=len(order._positions) + 1)
                position.attendee_name_parts = {
                    '_scheme': event.settings.name_scheme
                }
                position.meta_info = {}
                order._positions.append(position)
                position.assign_pseudonymization_id()

                for c in cols:
                    c.assign(record.get(c.identifier), order, position,
                             order._address)

            except ImportError as e:
                raise ImportError(
                    _('Invalid data in row {row}: {message}').format(
                        row=i, message=str(e)))

        # quota check?
        with event.lock():
            with transaction.atomic():
                save_transactions = []
                for o in orders:
                    o.total = sum([c.price for c in o._positions
                                   ])  # currently no support for fees
                    if o.total == Decimal('0.00'):
                        o.status = Order.STATUS_PAID
                        o.save()
                        OrderPayment.objects.create(
                            local_id=1,
                            order=o,
                            amount=Decimal('0.00'),
                            provider='free',
                            info='{}',
                            payment_date=now(),
                            state=OrderPayment.PAYMENT_STATE_CONFIRMED)
                    elif settings['status'] == 'paid':
                        o.status = Order.STATUS_PAID
                        o.save()
                        OrderPayment.objects.create(
                            local_id=1,
                            order=o,
                            amount=o.total,
                            provider='manual',
                            info='{}',
                            payment_date=now(),
                            state=OrderPayment.PAYMENT_STATE_CONFIRMED)
                    else:
                        o.status = Order.STATUS_PENDING
                        o.save()
                    for p in o._positions:
                        p.order = o
                        p.save()
                    o._address.order = o
                    o._address.save()
                    for c in cols:
                        c.save(o)
                    o.log_action('pretix.event.order.placed',
                                 user=user,
                                 data={'source': 'import'})
                    save_transactions += o.create_transactions(
                        is_new=True,
                        fees=[],
                        positions=o._positions,
                        save=False)
                Transaction.objects.bulk_create(save_transactions)

            for o in orders:
                with language(o.locale, event.settings.region):
                    order_placed.send(event, order=o)
                    if o.status == Order.STATUS_PAID:
                        order_paid.send(event, order=o)

                    gen_invoice = invoice_qualified(o) and (
                        (event.settings.get('invoice_generate') == 'True') or
                        (event.settings.get('invoice_generate') == 'paid'
                         and o.status
                         == Order.STATUS_PAID)) and not o.invoices.last()
                    if gen_invoice:
                        generate_invoice(o, trigger_pdf=True)
    cf.delete()
Пример #30
0
    def iterate_list(self, form_data):
        cl = self.event.checkin_lists.get(pk=form_data['list'])

        questions = list(
            Question.objects.filter(event=self.event,
                                    id__in=form_data['questions']))

        qs = self._get_queryset(cl, form_data)

        name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme]
        headers = [
            _('Order code'),
            _('Attendee name'),
        ]
        if len(name_scheme['fields']) > 1:
            for k, label, w in name_scheme['fields']:
                headers.append(_('Attendee name: {part}').format(part=label))
        headers += [
            _('Product'),
            _('Price'),
            _('Checked in'),
            _('Automatically checked in')
        ]
        if not cl.include_pending:
            qs = qs.filter(order__status=Order.STATUS_PAID)
        else:
            qs = qs.filter(order__status__in=(Order.STATUS_PAID,
                                              Order.STATUS_PENDING))
            headers.append(_('Paid'))

        if form_data['secrets']:
            headers.append(_('Secret'))

        headers.append(_('E-mail'))

        if self.event.has_subevents:
            headers.append(pgettext('subevent', 'Date'))
            headers.append(_('Start date'))
            headers.append(_('End date'))

        for q in questions:
            headers.append(str(q.question))

        headers.append(_('Company'))
        headers.append(_('Voucher code'))
        headers.append(_('Order date'))
        headers.append(_('Requires special attention'))
        headers.append(_('Comment'))
        yield headers

        for op in qs:
            try:
                ia = op.order.invoice_address
            except InvoiceAddress.DoesNotExist:
                ia = InvoiceAddress()

            last_checked_in = None
            if isinstance(op.last_checked_in, str):  # SQLite
                last_checked_in = dateutil.parser.parse(op.last_checked_in)
            elif op.last_checked_in:
                last_checked_in = op.last_checked_in
            if last_checked_in and not is_aware(last_checked_in):
                last_checked_in = make_aware(last_checked_in, UTC)
            row = [
                op.order.code,
                op.attendee_name
                or (op.addon_to.attendee_name if op.addon_to else '')
                or ia.name,
            ]
            if len(name_scheme['fields']) > 1:
                for k, label, w in name_scheme['fields']:
                    row.append((op.attendee_name_parts
                                or (op.addon_to.attendee_name_parts
                                    if op.addon_to else {})
                                or ia.name_parts).get(k, ''))
            row += [
                str(op.item) +
                (" – " + str(op.variation.value) if op.variation else ""),
                op.price,
                date_format(last_checked_in.astimezone(self.event.timezone),
                            'SHORT_DATETIME_FORMAT')
                if last_checked_in else '',
                _('Yes') if op.auto_checked_in else _('No'),
            ]
            if cl.include_pending:
                row.append(
                    _('Yes') if op.order.status ==
                    Order.STATUS_PAID else _('No'))
            if form_data['secrets']:
                row.append(op.secret)
            row.append(op.attendee_email
                       or (op.addon_to.attendee_email if op.addon_to else '')
                       or op.order.email or '')
            if self.event.has_subevents:
                row.append(str(op.subevent.name))
                row.append(
                    date_format(
                        op.subevent.date_from.astimezone(self.event.timezone),
                        'SHORT_DATETIME_FORMAT'))
                if op.subevent.date_to:
                    row.append(
                        date_format(
                            op.subevent.date_to.astimezone(
                                self.event.timezone), 'SHORT_DATETIME_FORMAT'))
                else:
                    row.append('')
            acache = {}
            if op.addon_to:
                for a in op.addon_to.answers.all():
                    # We do not want to localize Date, Time and Datetime question answers, as those can lead
                    # to difficulties parsing the data (for example 2019-02-01 may become Février, 2019 01 in French).
                    if a.question.type in Question.UNLOCALIZED_TYPES:
                        acache[a.question_id] = a.answer
                    else:
                        acache[a.question_id] = str(a)
            for a in op.answers.all():
                # We do not want to localize Date, Time and Datetime question answers, as those can lead
                # to difficulties parsing the data (for example 2019-02-01 may become Février, 2019 01 in French).
                if a.question.type in Question.UNLOCALIZED_TYPES:
                    acache[a.question_id] = a.answer
                else:
                    acache[a.question_id] = str(a)
            for q in questions:
                row.append(acache.get(q.pk, ''))

            row.append(op.company or ia.company)
            row.append(op.voucher.code if op.voucher else "")
            row.append(
                op.order.datetime.astimezone(
                    self.event.timezone).strftime('%Y-%m-%d'))
            row.append(
                _('Yes') if op.order.checkin_attention
                or op.item.checkin_attention else _('No'))
            row.append(op.order.comment or "")
            yield row
Пример #31
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')
        payment_info = validated_data.pop('payment_info', '{}')
        payment_date = validated_data.pop('payment_date', now())
        force = validated_data.pop('force', 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:
            quotadiff = Counter()

            consume_carts = validated_data.pop('consume_carts', [])
            delete_cps = []
            quota_avail_cache = {}
            if consume_carts:
                for cp in CartPosition.objects.filter(event=self.context['event'], cart_id__in=consume_carts):
                    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.expires > now_dt:
                        quotadiff.subtract(quotas)
                    delete_cps.append(cp)

            errs = [{} for p in positions_data]

            if not force:
                for i, pos_data in enumerate(positions_data):
                    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
                                        )
                                    ]

                    quotadiff.update(new_quotas)

            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.total = sum([p['price'] for p in positions_data]) + sum([f['value'] for f in fees_data], Decimal('0.00'))
            order.meta_info = "{}"
            order.save()

            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:
                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
                )

            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
                pos._calculate_tax()
                if addon_to:
                    pos.addon_to = pos_map[addon_to]
                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()

        return order