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
def test_tax_reverse_charge_consumer(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=False, eu_reverse_charge=True, home_country=Country('DE')) ia = InvoiceAddress(is_business=False, country=Country('BE')) assert not item.tax_rule.is_reverse_charge(ia) assert get_price(item, invoice_address=ia).gross == Decimal('119.00')
def test_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')
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)
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')
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
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)
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')
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)
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')
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'), ]
def test_custom_rules_country_rate_keep_gross_if_rate_changes(event): tr = TaxRule(event=event, rate=Decimal('10.00'), price_includes_tax=False, keep_gross_if_rate_changes=True, custom_rules=json.dumps([ { 'country': 'EU', 'address_type': 'business_vat_id', 'action': 'vat', 'rate': '100.00' }, ])) ia = InvoiceAddress(is_business=True, country=Country('DE')) assert not tr.is_reverse_charge(ia) assert tr._tax_applicable(ia) assert tr.tax_rate_for(ia) == Decimal('10.00') assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice( gross=Decimal('110.00'), net=Decimal('100.00'), tax=Decimal('10.00'), rate=Decimal('10.00'), name='', ) ia = InvoiceAddress(is_business=True, country=Country('DE'), vat_id='DE1234', vat_id_validated=True) assert tr.tax_rate_for(ia) == Decimal('100.00') assert not tr.is_reverse_charge(ia) assert tr._tax_applicable(ia) assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice( gross=Decimal('110.00'), net=Decimal('55.00'), tax=Decimal('55.00'), rate=Decimal('100.00'), name='', )
def test_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)
def test_reverse_charge_no_country(event): tr = TaxRule(event=event, eu_reverse_charge=True, rate=Decimal('10.00'), price_includes_tax=False) ia = InvoiceAddress() assert not tr.is_reverse_charge(ia) assert tr._tax_applicable(ia) assert tr.tax_rate_for(ia) == Decimal('10.00') assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice( gross=Decimal('110.00'), net=Decimal('100.00'), tax=Decimal('10.00'), rate=Decimal('10.00'), name='', )
def 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
def test_reverse_charge_individual_3rdc(event): tr = TaxRule(event=event, eu_reverse_charge=True, home_country=Country('DE'), rate=Decimal('10.00'), price_includes_tax=False) ia = InvoiceAddress(is_business=False, country=Country('US')) assert not tr.is_reverse_charge(ia) assert not tr._tax_applicable(ia) assert tr.tax_rate_for(ia) == Decimal('0.00') assert tr.tax(Decimal('100.00'), invoice_address=ia) == TaxedPrice( gross=Decimal('100.00'), net=Decimal('100.00'), tax=Decimal('0.00'), rate=Decimal('0.00'), name='', )
def test_country_specific_rule_gross_based(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=True, custom_rules=json.dumps([{ 'country': 'BE', 'address_type': '', 'action': 'vat', 'rate': '100.00' }])) ia = InvoiceAddress(is_business=True, vat_id="EU1234", vat_id_validated=True, country=Country('BE')) assert get_price(item, invoice_address=ia).gross == Decimal('168.06')
def test_country_specific_rule_net_based_but_keep_gross_if_rate_changes(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create( rate=Decimal('19.00'), price_includes_tax=False, keep_gross_if_rate_changes=True, custom_rules=json.dumps([{ 'country': 'BE', 'address_type': '', 'action': 'vat', 'rate': '100.00' }])) ia = InvoiceAddress(is_business=True, vat_id="EU1234", vat_id_validated=True, country=Country('BE')) p = get_price(item, invoice_address=ia) assert p.gross == Decimal('119.00') assert p.rate == Decimal('100.00') assert p.tax == Decimal('59.50')
def 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='', )
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='', )
def test_country_specific_rule_net_based_subtract_bundled(item): item.default_price = Decimal('100.00') item.tax_rule = item.event.tax_rules.create(rate=Decimal('19.00'), price_includes_tax=False, custom_rules=json.dumps([{ 'country': 'BE', 'address_type': '', 'action': 'vat', 'rate': '100.00' }])) ia = InvoiceAddress(is_business=True, vat_id="EU1234", vat_id_validated=True, country=Country('BE')) assert get_price( item, invoice_address=ia, bundled_sum=Decimal('20.00')).gross == (round_decimal( (Decimal('119.00') - Decimal('20.00')) / Decimal('1.19')) * Decimal('2'))
def 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
def price_calc(self, request, *args, **kwargs): """ This calculates the price assuming a change of product or subevent. This endpoint is deliberately not documented and considered a private API, only to be used by pretix' web interface. Sample input: { "item": 2, "variation": null, "subevent": 3 } Sample output: { "gross": "2.34", "gross_formatted": "2,34", "net": "2.34", "tax": "0.00", "rate": "0.00", "name": "VAT" } """ serializer = PriceCalcSerializer(data=request.data, event=request.event) serializer.is_valid(raise_exception=True) data = serializer.validated_data pos = self.get_object() try: ia = pos.order.invoice_address except InvoiceAddress.DoesNotExist: ia = InvoiceAddress() kwargs = { 'item': pos.item, 'variation': pos.variation, 'voucher': pos.voucher, 'subevent': pos.subevent, 'addon_to': pos.addon_to, 'invoice_address': ia, } if data.get('item'): item = data.get('item') kwargs['item'] = item if item.has_variations: variation = data.get('variation') or pos.variation if not variation: raise ValidationError('No variation given') if variation.item != item: raise ValidationError('Variation does not belong to item') kwargs['variation'] = variation else: variation = None kwargs['variation'] = None if pos.voucher and not pos.voucher.applies_to(item, variation): kwargs['voucher'] = None if data.get('subevent'): kwargs['subevent'] = data.get('subevent') price = get_price(**kwargs) with language( data.get('locale') or self.request.event.settings.locale): return Response({ 'gross': price.gross, 'gross_formatted': money_filter(price.gross, self.request.event.currency, hide_currency=True), 'net': price.net, 'rate': price.rate, 'name': str(price.name), 'tax': price.tax, })
def create(self, validated_data): fees_data = validated_data.pop( 'fees') if 'fees' in validated_data else [] positions_data = validated_data.pop( 'positions') if 'positions' in validated_data else [] payment_provider = validated_data.pop('payment_provider', None) payment_info = validated_data.pop('payment_info', '{}') payment_date = validated_data.pop('payment_date', now()) force = validated_data.pop('force', False) self._send_mail = validated_data.pop('send_mail', False) if 'invoice_address' in validated_data: iadata = validated_data.pop('invoice_address') name = iadata.pop('name', '') if name and not iadata.get('name_parts'): iadata['name_parts'] = {'_legacy': name} ia = InvoiceAddress(**iadata) else: ia = None with self.context['event'].lock() as now_dt: free_seats = set() seats_seen = set() consume_carts = validated_data.pop('consume_carts', []) delete_cps = [] quota_avail_cache = {} v_budget = {} voucher_usage = Counter() if consume_carts: for cp in CartPosition.objects.filter( event=self.context['event'], cart_id__in=consume_carts, expires__gt=now()): quotas = (cp.variation.quotas.filter(subevent=cp.subevent) if cp.variation else cp.item.quotas.filter( subevent=cp.subevent)) for quota in quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] += 1 if cp.voucher: voucher_usage[cp.voucher] -= 1 if cp.expires > now_dt: if cp.seat: free_seats.add(cp.seat) delete_cps.append(cp) errs = [{} for p in positions_data] for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): v = pos_data['voucher'] if pos_data.get('addon_to'): errs[i]['voucher'] = [ 'Vouchers are currently not supported for add-on products.' ] continue if not v.applies_to(pos_data['item'], pos_data.get('variation')): errs[i]['voucher'] = [ error_messages['voucher_invalid_item'] ] continue if v.subevent_id and pos_data.get( 'subevent').pk != v.subevent_id: errs[i]['voucher'] = [ error_messages['voucher_invalid_subevent'] ] continue if v.valid_until is not None and v.valid_until < now_dt: errs[i]['voucher'] = [ error_messages['voucher_expired'] ] continue voucher_usage[v] += 1 if voucher_usage[v] > 0: redeemed_in_carts = CartPosition.objects.filter( Q(voucher=pos_data['voucher']) & Q(event=self.context['event']) & Q(expires__gte=now_dt)).exclude( pk__in=[cp.pk for cp in delete_cps]) v_avail = v.max_usages - v.redeemed - redeemed_in_carts.count( ) if v_avail < voucher_usage[v]: errs[i]['voucher'] = [ 'The voucher has already been used the maximum number of times.' ] if v.budget is not None: price = pos_data.get('price') if price is None: price = get_price( item=pos_data.get('item'), variation=pos_data.get('variation'), voucher=v, custom_price=None, subevent=pos_data.get('subevent'), addon_to=pos_data.get('addon_to'), invoice_address=ia, ).gross pbv = get_price( item=pos_data['item'], variation=pos_data.get('variation'), voucher=None, custom_price=None, subevent=pos_data.get('subevent'), addon_to=pos_data.get('addon_to'), invoice_address=ia, ) if v not in v_budget: v_budget[v] = v.budget - v.budget_used() disc = pbv.gross - price if disc > v_budget[v]: new_disc = v_budget[v] v_budget[v] -= new_disc if new_disc == Decimal('0.00') or pos_data.get( 'price') is not None: errs[i]['voucher'] = [ 'The voucher has a remaining budget of {}, therefore a discount of {} can not be ' 'given.'.format(v_budget[v] + new_disc, disc) ] continue pos_data['price'] = price + (disc - new_disc) else: v_budget[v] -= disc seated = pos_data.get('item').seat_category_mappings.filter( subevent=pos_data.get('subevent')).exists() if pos_data.get('seat'): if not seated: errs[i]['seat'] = [ 'The specified product does not allow to choose a seat.' ] try: seat = self.context['event'].seats.get( seat_guid=pos_data['seat'], subevent=pos_data.get('subevent')) except Seat.DoesNotExist: errs[i]['seat'] = [ 'The specified seat does not exist.' ] else: pos_data['seat'] = seat if (seat not in free_seats and not seat.is_available( sales_channel=validated_data.get( 'sales_channel', 'web')) ) or seat in seats_seen: errs[i]['seat'] = [ ugettext_lazy( 'The selected seat "{seat}" is not available.' ).format(seat=seat.name) ] seats_seen.add(seat) elif seated: errs[i]['seat'] = [ 'The specified product requires to choose a seat.' ] if not force: for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): if pos_data['voucher'].allow_ignore_quota or pos_data[ 'voucher'].block_quota: continue new_quotas = (pos_data.get('variation').quotas.filter( subevent=pos_data.get('subevent')) if pos_data.get('variation') else pos_data.get('item').quotas.filter( subevent=pos_data.get('subevent'))) if len(new_quotas) == 0: errs[i]['item'] = [ ugettext_lazy( 'The product "{}" is not assigned to a quota.' ).format(str(pos_data.get('item'))) ] else: for quota in new_quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] -= 1 if quota_avail_cache[quota][1] < 0: errs[i]['item'] = [ ugettext_lazy( 'There is not enough quota available on quota "{}" to perform the operation.' ).format(quota.name) ] if any(errs): raise ValidationError({'positions': errs}) if validated_data.get('locale', None) is None: validated_data['locale'] = self.context[ 'event'].settings.locale order = Order(event=self.context['event'], **validated_data) order.set_expires( subevents=[p.get('subevent') for p in positions_data]) order.meta_info = "{}" order.total = Decimal('0.00') order.save() if ia: ia.order = order ia.save() pos_map = {} for pos_data in positions_data: answers_data = pos_data.pop('answers', []) addon_to = pos_data.pop('addon_to', None) attendee_name = pos_data.pop('attendee_name', '') if attendee_name and not pos_data.get('attendee_name_parts'): pos_data['attendee_name_parts'] = { '_legacy': attendee_name } pos = OrderPosition(**pos_data) pos.order = order if addon_to: pos.addon_to = pos_map[addon_to] if pos.price is None: price = get_price( item=pos.item, variation=pos.variation, voucher=pos.voucher, custom_price=None, subevent=pos.subevent, addon_to=pos.addon_to, invoice_address=ia, ) pos.price = price.gross pos.tax_rate = price.rate pos.tax_value = price.tax pos.tax_rule = pos.item.tax_rule else: pos._calculate_tax() pos.price_before_voucher = get_price( item=pos.item, variation=pos.variation, voucher=None, custom_price=None, subevent=pos.subevent, addon_to=pos.addon_to, invoice_address=ia, ).gross if pos.voucher: Voucher.objects.filter(pk=pos.voucher.pk).update( redeemed=F('redeemed') + 1) pos.save() pos_map[pos.positionid] = pos for answ_data in answers_data: options = answ_data.pop('options', []) answ = pos.answers.create(**answ_data) answ.options.add(*options) for cp in delete_cps: cp.delete() order.total = sum([p.price for p in order.positions.all()]) for fee_data in fees_data: is_percentage = fee_data.pop('_treat_value_as_percentage', False) if is_percentage: fee_data['value'] = round_decimal( order.total * (fee_data['value'] / Decimal('100.00')), self.context['event'].currency) is_split_taxes = fee_data.pop('_split_taxes_like_products', False) if is_split_taxes: d = defaultdict(lambda: Decimal('0.00')) trz = TaxRule.zero() for p in pos_map.values(): tr = p.tax_rule d[tr] += p.price - p.tax_value base_values = sorted([tuple(t) for t in d.items()], key=lambda t: (t[0] or trz).rate) sum_base = sum(t[1] for t in base_values) fee_values = [ (t[0], round_decimal(fee_data['value'] * t[1] / sum_base, self.context['event'].currency)) for t in base_values ] sum_fee = sum(t[1] for t in fee_values) # If there are rounding differences, we fix them up, but always leaning to the benefit of the tax # authorities if sum_fee > fee_data['value']: fee_values[0] = (fee_values[0][0], fee_values[0][1] + (fee_data['value'] - sum_fee)) elif sum_fee < fee_data['value']: fee_values[-1] = (fee_values[-1][0], fee_values[-1][1] + (fee_data['value'] - sum_fee)) for tr, val in fee_values: fee_data['tax_rule'] = tr fee_data['value'] = val f = OrderFee(**fee_data) f.order = order f._calculate_tax() f.save() else: f = OrderFee(**fee_data) f.order = order f._calculate_tax() f.save() order.total += sum([f.value for f in order.fees.all()]) order.save(update_fields=['total']) if order.total == Decimal('0.00') and validated_data.get( 'status') == Order.STATUS_PAID and not payment_provider: payment_provider = 'free' if order.total == Decimal( '0.00') and validated_data.get('status') != Order.STATUS_PAID: order.status = Order.STATUS_PAID order.save() order.payments.create(amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED, payment_date=now()) elif payment_provider == "free" and order.total != Decimal('0.00'): raise ValidationError( 'You cannot use the "free" payment provider for non-free orders.' ) elif validated_data.get('status') == Order.STATUS_PAID: if not payment_provider: raise ValidationError( 'You cannot create a paid order without a payment provider.' ) order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, payment_date=payment_date, state=OrderPayment.PAYMENT_STATE_CONFIRMED) elif payment_provider: order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, state=OrderPayment.PAYMENT_STATE_CREATED) return order
def 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)
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
def create(self, validated_data): fees_data = validated_data.pop( 'fees') if 'fees' in validated_data else [] positions_data = validated_data.pop( 'positions') if 'positions' in validated_data else [] payment_provider = validated_data.pop('payment_provider', None) payment_info = validated_data.pop('payment_info', '{}') payment_date = validated_data.pop('payment_date', now()) force = validated_data.pop('force', False) self._send_mail = validated_data.pop('send_mail', False) if 'invoice_address' in validated_data: iadata = validated_data.pop('invoice_address') name = iadata.pop('name', '') if name and not iadata.get('name_parts'): iadata['name_parts'] = {'_legacy': name} ia = InvoiceAddress(**iadata) else: ia = None with self.context['event'].lock() as now_dt: free_seats = set() seats_seen = set() consume_carts = validated_data.pop('consume_carts', []) delete_cps = [] quota_avail_cache = {} voucher_usage = Counter() if consume_carts: for cp in CartPosition.objects.filter( event=self.context['event'], cart_id__in=consume_carts, expires__gt=now()): quotas = (cp.variation.quotas.filter(subevent=cp.subevent) if cp.variation else cp.item.quotas.filter( subevent=cp.subevent)) for quota in quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] += 1 if cp.voucher: voucher_usage[cp.voucher] -= 1 if cp.expires > now_dt: if cp.seat: free_seats.add(cp.seat) delete_cps.append(cp) errs = [{} for p in positions_data] for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): v = pos_data['voucher'] if not v.applies_to(pos_data['item'], pos_data.get('variation')): errs[i]['voucher'] = [ error_messages['voucher_invalid_item'] ] continue if v.subevent_id and pos_data.get( 'subevent').pk != v.subevent_id: errs[i]['voucher'] = [ error_messages['voucher_invalid_subevent'] ] continue if v.valid_until is not None and v.valid_until < now_dt: errs[i]['voucher'] = [ error_messages['voucher_expired'] ] continue voucher_usage[v] += 1 if voucher_usage[v] > 0: redeemed_in_carts = CartPosition.objects.filter( Q(voucher=pos_data['voucher']) & Q(event=self.context['event']) & Q(expires__gte=now_dt)).exclude( pk__in=[cp.pk for cp in delete_cps]) v_avail = v.max_usages - v.redeemed - redeemed_in_carts.count( ) if v_avail < voucher_usage[v]: errs[i]['voucher'] = [ 'The voucher has already been used the maximum number of times.' ] seated = pos_data.get('item').seat_category_mappings.filter( subevent=pos_data.get('subevent')).exists() if pos_data.get('seat'): if not seated: errs[i]['seat'] = [ 'The specified product does not allow to choose a seat.' ] try: seat = self.context['event'].seats.get( seat_guid=pos_data['seat'], subevent=pos_data.get('subevent')) except Seat.DoesNotExist: errs[i]['seat'] = [ 'The specified seat does not exist.' ] else: pos_data['seat'] = seat if (seat not in free_seats and not seat.is_available()) or seat in seats_seen: errs[i]['seat'] = [ ugettext_lazy( 'The selected seat "{seat}" is not available.' ).format(seat=seat.name) ] seats_seen.add(seat) elif seated: errs[i]['seat'] = [ 'The specified product requires to choose a seat.' ] if not force: for i, pos_data in enumerate(positions_data): if pos_data.get('voucher'): if pos_data['voucher'].allow_ignore_quota or pos_data[ 'voucher'].block_quota: continue new_quotas = (pos_data.get('variation').quotas.filter( subevent=pos_data.get('subevent')) if pos_data.get('variation') else pos_data.get('item').quotas.filter( subevent=pos_data.get('subevent'))) if len(new_quotas) == 0: errs[i]['item'] = [ ugettext_lazy( 'The product "{}" is not assigned to a quota.' ).format(str(pos_data.get('item'))) ] else: for quota in new_quotas: if quota not in quota_avail_cache: quota_avail_cache[quota] = list( quota.availability()) if quota_avail_cache[quota][1] is not None: quota_avail_cache[quota][1] -= 1 if quota_avail_cache[quota][1] < 0: errs[i]['item'] = [ ugettext_lazy( 'There is not enough quota available on quota "{}" to perform the operation.' ).format(quota.name) ] if any(errs): raise ValidationError({'positions': errs}) if validated_data.get('locale', None) is None: validated_data['locale'] = self.context[ 'event'].settings.locale order = Order(event=self.context['event'], **validated_data) order.set_expires( subevents=[p.get('subevent') for p in positions_data]) order.meta_info = "{}" order.total = Decimal('0.00') order.save() if ia: ia.order = order ia.save() pos_map = {} for pos_data in positions_data: answers_data = pos_data.pop('answers', []) addon_to = pos_data.pop('addon_to', None) attendee_name = pos_data.pop('attendee_name', '') if attendee_name and not pos_data.get('attendee_name_parts'): pos_data['attendee_name_parts'] = { '_legacy': attendee_name } pos = OrderPosition(**pos_data) pos.order = order if addon_to: pos.addon_to = pos_map[addon_to] if pos.price is None: price = get_price( item=pos.item, variation=pos.variation, voucher=pos.voucher, custom_price=None, subevent=pos.subevent, addon_to=pos.addon_to, invoice_address=ia, ) pos.price = price.gross pos.tax_rate = price.rate pos.tax_value = price.tax pos.tax_rule = pos.item.tax_rule else: pos._calculate_tax() if pos.voucher: Voucher.objects.filter(pk=pos.voucher.pk).update( redeemed=F('redeemed') + 1) pos.save() pos_map[pos.positionid] = pos for answ_data in answers_data: options = answ_data.pop('options', []) answ = pos.answers.create(**answ_data) answ.options.add(*options) for cp in delete_cps: cp.delete() for fee_data in fees_data: f = OrderFee(**fee_data) f.order = order f._calculate_tax() f.save() order.total = sum([p.price for p in order.positions.all()]) + sum( [f.value for f in order.fees.all()]) order.save(update_fields=['total']) if order.total == Decimal( '0.00') and validated_data.get('status') != Order.STATUS_PAID: order.status = Order.STATUS_PAID order.save() order.payments.create(amount=order.total, provider='free', state=OrderPayment.PAYMENT_STATE_CONFIRMED, payment_date=now()) elif payment_provider == "free" and order.total != Decimal('0.00'): raise ValidationError( 'You cannot use the "free" payment provider for non-free orders.' ) elif validated_data.get('status') == Order.STATUS_PAID: if not payment_provider: raise ValidationError( 'You cannot create a paid order without a payment provider.' ) order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, payment_date=payment_date, state=OrderPayment.PAYMENT_STATE_CONFIRMED) elif payment_provider: order.payments.create(amount=order.total, provider=payment_provider, info=payment_info, state=OrderPayment.PAYMENT_STATE_CREATED) return order
def _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
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()
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
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