def tax(self, base_price, base_price_is='auto'): if self.rate == Decimal('0.00'): return TaxedPrice(net=base_price, gross=base_price, tax=Decimal('0.00'), rate=self.rate, name=self.name) if base_price_is == 'auto': if self.price_includes_tax: base_price_is = 'gross' else: base_price_is = 'net' if base_price_is == 'gross': gross = base_price net = round_decimal( gross - (base_price * (1 - 100 / (100 + self.rate))), self.event.currency if self.event else None) elif base_price_is == 'net': net = base_price gross = round_decimal((net * (1 + self.rate / 100)), self.event.currency if self.event else None) else: raise ValueError( 'Unknown base price type: {}'.format(base_price_is)) return TaxedPrice(net=net, gross=gross, tax=gross - net, rate=self.rate, name=self.name)
def tax(self, base_price, base_price_is='auto', currency=None): from .event import Event try: currency = currency or self.event.currency except Event.DoesNotExist: pass if self.rate == Decimal('0.00'): return TaxedPrice( net=base_price, gross=base_price, tax=Decimal('0.00'), rate=self.rate, name=self.name ) if base_price_is == 'auto': if self.price_includes_tax: base_price_is = 'gross' else: base_price_is = 'net' if base_price_is == 'gross': gross = base_price net = round_decimal(gross - (base_price * (1 - 100 / (100 + self.rate))), currency) elif base_price_is == 'net': net = base_price gross = round_decimal((net * (1 + self.rate / 100)), currency) else: raise ValueError('Unknown base price type: {}'.format(base_price_is)) return TaxedPrice( net=net, gross=gross, tax=gross - net, rate=self.rate, name=self.name )
def tax(self, base_price, base_price_is='auto'): if self.rate == Decimal('0.00'): return TaxedPrice( net=base_price, gross=base_price, tax=Decimal('0.00'), rate=self.rate, name=self.name ) if base_price_is == 'auto': if self.price_includes_tax: base_price_is = 'gross' else: base_price_is = 'net' if base_price_is == 'gross': gross = base_price net = round_decimal(gross - (base_price * (1 - 100 / (100 + self.rate))), self.event.currency if self.event else None) elif base_price_is == 'net': net = base_price gross = round_decimal((net * (1 + self.rate / 100)), self.event.currency if self.event else None) else: raise ValueError('Unknown base price type: {}'.format(base_price_is)) return TaxedPrice( net=net, gross=gross, tax=gross - net, rate=self.rate, name=self.name )
def tax(self, base_price, base_price_is='auto', currency=None, override_tax_rate=None, invoice_address=None, subtract_from_gross=Decimal('0.00'), gross_price_is_tax_rate: Decimal = None): from .event import Event try: currency = currency or self.event.currency except Event.DoesNotExist: pass rate = Decimal(self.rate) if override_tax_rate is not None: rate = override_tax_rate elif invoice_address: adjust_rate = self.tax_rate_for(invoice_address) if adjust_rate == gross_price_is_tax_rate and base_price_is == 'gross': rate = adjust_rate elif adjust_rate != rate: normal_price = self.tax(base_price, base_price_is, currency, subtract_from_gross=subtract_from_gross) base_price = normal_price.net base_price_is = 'net' subtract_from_gross = Decimal('0.00') rate = adjust_rate if rate == Decimal('0.00'): return TaxedPrice( net=base_price - subtract_from_gross, gross=base_price - subtract_from_gross, tax=Decimal('0.00'), rate=rate, name=self.name ) if base_price_is == 'auto': if self.price_includes_tax: base_price_is = 'gross' else: base_price_is = 'net' if base_price_is == 'gross': if base_price >= Decimal('0.00'): # For positive prices, make sure they don't go negative because of bundles gross = max(Decimal('0.00'), base_price - subtract_from_gross) else: # If the price is already negative, we don't really care any more gross = base_price - subtract_from_gross net = round_decimal(gross - (gross * (1 - 100 / (100 + rate))), currency) elif base_price_is == 'net': net = base_price gross = round_decimal((net * (1 + rate / 100)), currency) if subtract_from_gross: gross -= subtract_from_gross net = round_decimal(gross - (gross * (1 - 100 / (100 + rate))), currency) else: raise ValueError('Unknown base price type: {}'.format(base_price_is)) return TaxedPrice( net=net, gross=gross, tax=gross - net, rate=rate, name=self.name )
def get_price(item: Item, variation: ItemVariation = None, voucher: Voucher = None, custom_price: Decimal = None, subevent: SubEvent = None, custom_price_is_net: bool = False, addon_to: AbstractPosition = None, invoice_address: InvoiceAddress = None) -> TaxedPrice: if addon_to: try: iao = addon_to.item.addons.get(addon_category_id=item.category_id) if iao.price_included: return TAXED_ZERO except ItemAddOn.DoesNotExist: pass price = item.default_price if subevent and item.pk in subevent.item_price_overrides: price = subevent.item_price_overrides[item.pk] if variation is not None: if variation.default_price is not None: price = variation.default_price if subevent and variation.pk in subevent.var_price_overrides: price = subevent.var_price_overrides[variation.pk] if voucher: price = voucher.calculate_price(price) if item.tax_rule: tax_rule = item.tax_rule else: tax_rule = TaxRule( name='', rate=Decimal('0.00'), price_includes_tax=True, eu_reverse_charge=False, ) price = tax_rule.tax(price) if item.free_price and custom_price is not None and custom_price != "": if not isinstance(custom_price, Decimal): custom_price = Decimal(str(custom_price).replace(",", ".")) if custom_price > 100000000: raise ValueError('price_too_high') if custom_price_is_net: price = tax_rule.tax(max(custom_price, price.net), base_price_is='net') else: price = tax_rule.tax(max(custom_price, price.gross), base_price_is='gross') if invoice_address and not tax_rule.tax_applicable(invoice_address): price.tax = Decimal('0.00') price.rate = Decimal('0.00') price.gross = price.net price.name = '' price.gross = round_decimal(price.gross, item.event.currency) price.net = round_decimal(price.net, item.event.currency) price.tax = price.gross - price.net return price
def parse(file): data = file.read() try: import chardet charset = chardet.detect(data)['encoding'] except ImportError: charset = file.charset data = data.decode(charset or 'utf-8') mt = mt940.parse(io.StringIO(data.strip())) result = [] for t in mt: td = t.data.get('transaction_details', '') if len(td) >= 4 and td[3] == '?': # SEPA content transaction_details = parse_transaction_details( td.replace("\n", "")) payer = { 'name': transaction_details.get('accountholder', ''), 'iban': transaction_details.get('accountnumber', ''), } reference, eref = join_reference( transaction_details.get('reference', '').split('\n'), payer) if not eref: eref = transaction_details.get('eref', '') result.append({ 'amount': str(round_decimal(t.data['amount'].amount)), 'reference': reference + (' EREF: {}'.format(eref) if eref else ''), 'payer': payer['name'].strip(), 'date': t.data['date'].isoformat(), **{ k: payer[k].strip() for k in ("iban", "bic") if payer.get(k) } }) else: result.append({ 'reference': "\n".join([ t.data.get(f) for f in ('transaction_details', 'customer_reference', 'bank_reference', 'extra_details', 'non_swift_text') if t.data.get(f, '') ]), 'amount': str(round_decimal(t.data['amount'].amount)), 'date': t.data['date'].isoformat() }) return result
def format_price(self, value): return str( round_decimal( value, self.event.currency, { # PayPal behaves differently than Stripe in deciding what currencies have decimal places # Source https://developer.paypal.com/docs/classic/api/currency_codes/ 'HUF': 0, 'JPY': 0, 'MYR': 0, 'TWD': 0, # However, CLPs are not listed there while PayPal requires us not to send decimal places there. WTF. 'CLP': 0, # Let's just guess that the ones listed here are 0-based as well # https://developers.braintreepayments.com/reference/general/currencies 'BIF': 0, 'DJF': 0, 'GNF': 0, 'KMF': 0, 'KRW': 0, 'LAK': 0, 'PYG': 0, 'RWF': 0, 'UGX': 0, 'VND': 0, 'VUV': 0, 'XAF': 0, 'XOF': 0, 'XPF': 0, }))
def format_price(self, value): return str(round_decimal(value, self.event.currency, { # PayPal behaves differently than Stripe in deciding what currencies have decimal places # Source https://developer.paypal.com/docs/classic/api/currency_codes/ 'HUF': 0, 'JPY': 0, 'MYR': 0, 'TWD': 0, # However, CLPs are not listed there while PayPal requires us not to send decimal places there. WTF. 'CLP': 0, # Let's just guess that the ones listed here are 0-based as well # https://developers.braintreepayments.com/reference/general/currencies 'BIF': 0, 'DJF': 0, 'GNF': 0, 'KMF': 0, 'KRW': 0, 'LAK': 0, 'PYG': 0, 'RWF': 0, 'UGX': 0, 'VND': 0, 'VUV': 0, 'XAF': 0, 'XOF': 0, 'XPF': 0, }))
def calculate_fee(self, price: Decimal) -> Decimal: """ Calculate the fee for this payment provider which will be added to final price before fees (but after taxes). It should include any taxes. The default implementation makes use of the setting ``_fee_abs`` for an absolute fee and ``_fee_percent`` for a percentage. :param price: The total value without the payment method fee, after taxes. """ fee_abs = self.settings.get('_fee_abs', as_type=Decimal, default=0) fee_percent = self.settings.get('_fee_percent', as_type=Decimal, default=0) fee_reverse_calc = self.settings.get('_fee_reverse_calc', as_type=bool, default=True) if fee_reverse_calc: return round_decimal((price + fee_abs) * (1 / (1 - fee_percent / 100)) - price) else: return round_decimal(price * fee_percent / 100) + fee_abs
def parse(file): data = file.read() try: import chardet charset = chardet.detect(data)['encoding'] except ImportError: charset = file.charset data = data.decode(charset or 'utf-8') mt = mt940.parse(io.StringIO(data.strip())) result = [] for t in mt: result.append({ 'reference': "\n".join([ t.data.get(f) for f in ('transaction_details', 'customer_reference', 'bank_reference', 'extra_details', 'non_swift_text') if t.data.get(f, '') ]), 'amount': str(round_decimal(t.data['amount'].amount)), 'date': t.data['date'].isoformat() }) return result
def get_fees(event, total, invoice_address, mod='', request=None, positions=[]): if request is not None and not positions: positions = get_cart(request) positions = [ pos for pos in positions if not pos.addon_to and pos.price != Decimal('0.00') ] fee_per_ticket = event.settings.get('service_fee_per_ticket' + mod, as_type=Decimal) if mod and fee_per_ticket is None: fee_per_ticket = event.settings.get('service_fee_per_ticket', as_type=Decimal) fee_abs = event.settings.get('service_fee_abs' + mod, as_type=Decimal) if mod and fee_abs is None: fee_abs = event.settings.get('service_fee_abs', as_type=Decimal) fee_percent = event.settings.get('service_fee_percent' + mod, as_type=Decimal) if mod and fee_percent is None: fee_percent = event.settings.get('service_fee_percent', as_type=Decimal) fee_per_ticket = Decimal("0") if fee_per_ticket is None else fee_per_ticket fee_abs = Decimal("0") if fee_abs is None else fee_abs fee_percent = Decimal("0") if fee_percent is None else fee_percent if (fee_per_ticket or fee_abs or fee_percent) and total != Decimal('0.00'): fee = round_decimal( fee_abs + total * (fee_percent / 100) + len(positions) * fee_per_ticket, event.currency) tax_rule = event.settings.tax_rate_default or TaxRule.zero() if tax_rule.tax_applicable(invoice_address): tax = tax_rule.tax(fee) return [ OrderFee(fee_type=OrderFee.FEE_TYPE_SERVICE, internal_type='', value=fee, tax_rate=tax.rate, tax_value=tax.tax, tax_rule=tax_rule) ] else: return [ OrderFee(fee_type=OrderFee.FEE_TYPE_SERVICE, internal_type='', value=fee, tax_rate=Decimal('0.00'), tax_value=Decimal('0.00'), tax_rule=tax_rule) ] return []
def test_change_price_success(self): self.ocm.change_price(self.op1, Decimal('24.00')) self.ocm.commit() self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.ticket assert self.op1.price == Decimal('24.00') assert round_decimal(self.op1.price * (1 - 100 / (100 + self.op1.tax_rate))) == self.op1.tax_value assert self.order.total == self.op1.price + self.op2.price
def test_change_item_success(self): self.ocm.change_item(self.op1, self.shirt, None) self.ocm.commit() self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.shirt assert self.op1.price == self.shirt.default_price assert self.op1.tax_rate == self.shirt.tax_rule.rate assert round_decimal(self.op1.price * (1 - 100 / (100 + self.op1.tax_rate))) == self.op1.tax_value assert self.order.total == self.op1.price + self.op2.price
def test_change_item_success(self): self.ocm.change_item(self.op1, self.shirt, None) self.ocm.commit() self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.shirt assert self.op1.price == self.shirt.default_price assert self.op1.tax_rate == self.shirt.tax_rate assert round_decimal(self.op1.price * (1 - 100 / (100 + self.op1.tax_rate))) == self.op1.tax_value assert self.order.total == self.op1.price + self.op2.price
def test_payment_fee_calculation(self): self.event.settings.set('tax_rate_default', Decimal('19.00')) prov = self.ocm._get_payment_provider() prov.settings.set('_fee_abs', Decimal('0.30')) self.ocm.change_price(self.op1, Decimal('24.00')) self.ocm.commit() self.order.refresh_from_db() assert self.order.total == Decimal('47.30') assert self.order.payment_fee == prov.calculate_fee(self.order.total) assert self.order.payment_fee_tax_rate == Decimal('19.00') assert round_decimal(self.order.payment_fee * (1 - 100 / (100 + self.order.payment_fee_tax_rate))) == self.order.payment_fee_tax_value
def test_change_price_net_success(self): self.tr7.price_includes_tax = False self.tr7.save() self.ocm.change_price(self.op1, Decimal('10.00')) self.ocm.commit() self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.ticket assert self.op1.price == Decimal('10.70') assert round_decimal(self.op1.price * (1 - 100 / (100 + self.op1.tax_rate))) == self.op1.tax_value assert self.order.total == self.op1.price + self.op2.price
def test_add_item_custom_price(self): self.ocm.add_position(self.shirt, None, Decimal('13.00'), None) self.ocm.commit() self.order.refresh_from_db() assert self.order.positions.count() == 3 nop = self.order.positions.last() assert nop.item == self.shirt assert nop.price == Decimal('13.00') assert nop.tax_rate == self.shirt.tax_rule.rate assert round_decimal(nop.price * (1 - 100 / (100 + self.shirt.tax_rule.rate))) == nop.tax_value assert self.order.total == self.op1.price + self.op2.price + nop.price
def test_change_item_net_price_success(self): self.tr19.price_includes_tax = False self.tr19.save() self.ocm.change_item(self.op1, self.shirt, None) self.ocm.commit() self.op1.refresh_from_db() self.order.refresh_from_db() assert self.op1.item == self.shirt assert self.op1.price == Decimal('14.28') assert self.op1.tax_rate == self.shirt.tax_rule.rate assert round_decimal(self.op1.price * (1 - 100 / (100 + self.op1.tax_rate))) == self.op1.tax_value assert self.order.total == self.op1.price + self.op2.price
def test_add_item_success(self): self.ocm.add_position(self.shirt, None, None, None) self.ocm.commit() self.order.refresh_from_db() assert self.order.positions.count() == 3 nop = self.order.positions.last() assert nop.item == self.shirt assert nop.price == self.shirt.default_price assert nop.tax_rate == self.shirt.tax_rate assert round_decimal(nop.price * (1 - 100 / (100 + self.shirt.tax_rate))) == nop.tax_value assert self.order.total == self.op1.price + self.op2.price + nop.price assert nop.positionid == 3
def __mul__(self, other): newgross = self.gross * other newnet = round_decimal(newgross - (newgross * (1 - 100 / (100 + self.rate)))).quantize( Decimal('10') ** self.gross.as_tuple().exponent ) return TaxedPrice( gross=newgross, net=newnet, tax=newgross - newnet, rate=self.rate, name=self.name, )
def test_payment_fee_calculation(self): self.event.settings.set('tax_rate_default', self.tr19.pk) prov = self.ocm._get_payment_provider() prov.settings.set('_fee_abs', Decimal('0.30')) self.ocm.change_price(self.op1, Decimal('24.00')) self.ocm.commit() self.order.refresh_from_db() assert self.order.total == Decimal('47.30') fee = self.order.fees.get(fee_type=OrderFee.FEE_TYPE_PAYMENT) assert fee.value == prov.calculate_fee(self.order.total) assert fee.tax_rate == Decimal('19.00') assert round_decimal(fee.value * (1 - 100 / (100 + fee.tax_rate))) == fee.tax_value
def __mul__(self, other): newgross = self.gross * other newnet = round_decimal( newgross - (newgross * (1 - 100 / (100 + self.rate)))).quantize( Decimal('10')**self.gross.as_tuple().exponent) return TaxedPrice( gross=newgross, net=newnet, tax=newgross - newnet, rate=self.rate, name=self.name, )
def parse(file): data = file.read() try: import chardet charset = chardet.detect(data)['encoding'] except ImportError: charset = file.charset data = data.decode(charset or 'utf-8') mt = mt940.parse(io.StringIO(data.strip())) result = [] for t in mt: td = t.data.get('transaction_details', '') if len(td) >= 4 and td[3] == '?': # SEPA content transaction_details = parse_transaction_details(td.replace("\n", "")) payer = { 'name': transaction_details.get('accountholder', ''), 'iban': transaction_details.get('accountnumber', ''), } reference, eref = join_reference(transaction_details.get('reference', '').split('\n'), payer) if not eref: eref = transaction_details.get('eref', '') result.append({ 'amount': str(round_decimal(t.data['amount'].amount)), 'reference': reference + (' EREF: {}'.format(eref) if eref else ''), 'payer': (payer.get('name', '') + ' - ' + payer.get('iban', '')).strip(), 'date': t.data['date'].isoformat() }) else: result.append({ 'reference': "\n".join([ t.data.get(f) for f in ('transaction_details', 'customer_reference', 'bank_reference', 'extra_details', 'non_swift_text') if t.data.get(f, '')]), 'amount': str(round_decimal(t.data['amount'].amount)), 'date': t.data['date'].isoformat() }) return result
def test_add_item_net_price_success(self): self.tr19.price_includes_tax = False self.tr19.save() self.ocm.add_position(self.shirt, None, None, None) self.ocm.commit() self.order.refresh_from_db() assert self.order.positions.count() == 3 nop = self.order.positions.last() assert nop.item == self.shirt assert nop.price == Decimal('14.28') assert nop.tax_rate == self.shirt.tax_rule.rate assert round_decimal(nop.price * (1 - 100 / (100 + self.shirt.tax_rule.rate))) == nop.tax_value assert self.order.total == self.op1.price + self.op2.price + nop.price assert nop.positionid == 3
def _label(self, event, item_or_variation, avail, override_price=None): if isinstance(item_or_variation, ItemVariation): variation = item_or_variation item = item_or_variation.item price = variation.price price_net = variation.net_price label = variation.value else: item = item_or_variation price = item.default_price price_net = item.default_price_net label = item.name if override_price: price = override_price tax_value = round_decimal(price * (1 - 100 / (100 + item.tax_rate))) price_net = price - tax_value if self.price_included: price = Decimal('0.00') if not price: n = '{name}'.format(name=label) elif not item.tax_rate: n = _('{name} (+ {currency} {price})').format( name=label, currency=event.currency, price=number_format(price)) elif event.settings.display_net_prices: n = _('{name} (+ {currency} {price} plus {taxes}% taxes)').format( name=label, currency=event.currency, price=number_format(price_net), taxes=number_format(item.tax_rate)) else: n = _('{name} (+ {currency} {price} incl. {taxes}% taxes)').format( name=label, currency=event.currency, price=number_format(price), taxes=number_format(item.tax_rate)) if avail[0] < 20: n += ' – {}'.format(_('SOLD OUT')) elif avail[0] < 100: n += ' – {}'.format(_('Currently unavailable')) return n
def get_fee(event, total, invoice_address): payment_fee = round_decimal(Decimal('0.25') + Decimal('0.029') * total) payment_fee_tax_rule = event.settings.tax_rate_default or TaxRule.zero() if payment_fee_tax_rule.tax_applicable(invoice_address): payment_fee_tax = payment_fee_tax_rule.tax(payment_fee, base_price_is='gross') return OrderFee(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=payment_fee, tax_rate=payment_fee_tax.rate, tax_value=payment_fee_tax.tax, tax_rule=payment_fee_tax_rule) else: return OrderFee(fee_type=OrderFee.FEE_TYPE_PAYMENT, value=payment_fee, tax_rate=Decimal('0.00'), tax_value=Decimal('0.00'), tax_rule=payment_fee_tax_rule)
def _get_price(self, item: Item, variation: Optional[ItemVariation], voucher: Optional[Voucher], custom_price: Optional[Decimal]): price = item.default_price if variation is None else ( variation.default_price if variation.default_price is not None else item.default_price ) if voucher: price = voucher.calculate_price(price) if item.free_price and custom_price is not None and custom_price != "": if not isinstance(custom_price, Decimal): custom_price = Decimal(custom_price.replace(",", ".")) if custom_price > 100000000: raise CartError(error_messages['price_too_high']) if self.event.settings.display_net_prices: custom_price = round_decimal(custom_price * (100 + item.tax_rate) / 100) price = max(custom_price, price) return price
def _connect_kwargs(self, payment): d = {} if self.settings.connect_client_id and self.settings.connect_user_id: fee = Decimal('0.00') if self.settings.get('connect_app_fee_percent', as_type=Decimal): fee = round_decimal( self.settings.get('connect_app_fee_percent', as_type=Decimal) * payment.amount / Decimal('100.00'), self.event.currency) if self.settings.connect_app_fee_max: fee = min( fee, self.settings.get('connect_app_fee_max', as_type=Decimal)) if self.settings.get('connect_app_fee_min', as_type=Decimal): fee = max( fee, self.settings.get('connect_app_fee_min', as_type=Decimal)) if fee: d['application_fee_amount'] = self._decimal_to_int(fee) return d
def parse(file): data = file.read() try: import chardet charset = chardet.detect(data)['encoding'] except ImportError: charset = file.charset data = data.decode(charset or 'utf-8') mt = mt940.parse(io.StringIO(data.strip())) result = [] for t in mt: result.append({ 'reference': "\n".join([ t.data.get(f) for f in ('transaction_details', 'customer_reference', 'bank_reference', 'extra_details') if t.data.get(f, '')]), 'amount': str(round_decimal(t.data['amount'].amount)), 'date': t.data['date'].isoformat() }) return result
def get_price(item: Item, variation: ItemVariation = None, voucher: Voucher = None, custom_price: Decimal = None, subevent: SubEvent = None, custom_price_is_net: bool = False, addon_to: AbstractPosition = None): if addon_to: try: iao = addon_to.item.addons.get(addon_category_id=item.category_id) if iao.price_included: return Decimal('0.00') except ItemAddOn.DoesNotExist: pass price = item.default_price if subevent and item.pk in subevent.item_price_overrides: price = subevent.item_price_overrides[item.pk] if variation is not None: if variation.default_price is not None: price = variation.default_price if subevent and variation.pk in subevent.var_price_overrides: price = subevent.var_price_overrides[variation.pk] if voucher: price = voucher.calculate_price(price) if item.free_price and custom_price is not None and custom_price != "": if not isinstance(custom_price, Decimal): custom_price = Decimal(str(custom_price).replace(",", ".")) if custom_price > 100000000: raise ValueError('price_too_high') if custom_price_is_net: custom_price = round_decimal(custom_price * (100 + item.tax_rate) / 100) price = max(custom_price, price) return price
def test_country_specific_rule_gross_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=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, bundled_sum=Decimal('20.00')).gross == (round_decimal( (Decimal('100.00') - Decimal('20.00')) / Decimal('1.19')) * Decimal('2'))
def base_widgets(sender, subevent=None, **kwargs): prodc = Item.objects.filter( event=sender, active=True, ).filter( (Q(available_until__isnull=True) | Q(available_until__gte=now())) & (Q(available_from__isnull=True) | Q(available_from__lte=now())) ).count() if subevent: opqs = OrderPosition.objects.filter(subevent=subevent) else: opqs = OrderPosition.objects tickc = opqs.filter( order__event=sender, item__admission=True, order__status__in=(Order.STATUS_PAID, Order.STATUS_PENDING), ).count() paidc = opqs.filter( order__event=sender, item__admission=True, order__status=Order.STATUS_PAID, ).count() if subevent: rev = opqs.filter( order__event=sender, order__status=Order.STATUS_PAID ).aggregate( sum=Sum('price') )['sum'] or Decimal('0.00') else: rev = Order.objects.filter( event=sender, status=Order.STATUS_PAID ).aggregate(sum=Sum('total'))['sum'] or Decimal('0.00') return [ { 'content': NUM_WIDGET.format(num=tickc, text=_('Attendees (ordered)')), 'display_size': 'small', 'priority': 100, 'url': reverse('control:event.orders', kwargs={ 'event': sender.slug, 'organizer': sender.organizer.slug }) + ('?subevent={}'.format(subevent.pk) if subevent else '') }, { 'content': NUM_WIDGET.format(num=paidc, text=_('Attendees (paid)')), 'display_size': 'small', 'priority': 100, 'url': reverse('control:event.orders.overview', kwargs={ 'event': sender.slug, 'organizer': sender.organizer.slug }) + ('?subevent={}'.format(subevent.pk) if subevent else '') }, { 'content': NUM_WIDGET.format( num=formats.localize(round_decimal(rev, sender.currency)), text=_('Total revenue ({currency})').format(currency=sender.currency)), 'display_size': 'small', 'priority': 100, 'url': reverse('control:event.orders.overview', kwargs={ 'event': sender.slug, 'organizer': sender.organizer.slug }) + ('?subevent={}'.format(subevent.pk) if subevent else '') }, { 'content': NUM_WIDGET.format(num=prodc, text=_('Active products')), 'display_size': 'small', 'priority': 100, 'url': reverse('control:event.items', kwargs={ 'event': sender.slug, 'organizer': sender.organizer.slug }) }, ]
def iterate_orders(self, form_data): tz = self.event.timezone tax_rates = set( a for a in OrderFee.objects.filter( order__event=self.event ).values_list('tax_rate', flat=True).distinct().order_by() ) tax_rates |= set( a for a in OrderPosition.objects.filter(order__event=self.event).filter( order__status__in=form_data['status'] ).values_list('tax_rate', flat=True).distinct().order_by() ) tax_rates = sorted(tax_rates) headers = [ _('Order code'), _('Order date'), _('Company'), _('Name'), _('Country'), _('VAT ID'), _('Status'), _('Payment date'), _('Order total'), ] + sum(([str(t) + ' % ' + _('Gross'), str(t) + ' % ' + _('Tax')] for t in tax_rates), []) yield headers op_date = OrderPayment.objects.filter( order=OuterRef('order'), state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED), payment_date__isnull=False ).values('order').annotate( m=Max('payment_date') ).values( 'm' ).order_by() qs = self.filter_qs(OrderPosition.objects, form_data).filter( order__status__in=form_data['status'], order__event=self.event, ).annotate(payment_date=Subquery(op_date, output_field=models.DateTimeField())).values( 'order__code', 'order__datetime', 'payment_date', 'order__total', 'tax_rate', 'order__status', 'order__id', 'order__invoice_address__name_cached', 'order__invoice_address__company', 'order__invoice_address__country', 'order__invoice_address__vat_id' ).annotate(prices=Sum('price'), tax_values=Sum('tax_value')).order_by( 'order__datetime' if form_data['sort'] == 'datetime' else 'payment_date', 'order__datetime', 'order__code' ) fee_sum_cache = { (o['order__id'], o['tax_rate']): o for o in OrderFee.objects.values('tax_rate', 'order__id').order_by().annotate( taxsum=Sum('tax_value'), grosssum=Sum('value') ) } last_order_code = None tax_sums = defaultdict(Decimal) price_sums = defaultdict(Decimal) status_labels = dict(Order.STATUS_CHOICE) row = None for op in qs: if op['order__code'] != last_order_code: if row: yield row row = None row = [ op['order__code'], date_format(op['order__datetime'].astimezone(tz), "SHORT_DATE_FORMAT"), op['order__invoice_address__company'], op['order__invoice_address__name_cached'], op['order__invoice_address__country'], op['order__invoice_address__vat_id'], status_labels[op['order__status']], date_format(op['payment_date'], "SHORT_DATE_FORMAT") if op['payment_date'] else '', round_decimal(op['order__total'], self.event.currency), ] + sum(([Decimal('0.00'), Decimal('0.00')] for t in tax_rates), []) last_order_code = op['order__code'] for i, rate in enumerate(tax_rates): odata = fee_sum_cache.get((op['order__id'], rate)) if odata: row[9 + 2 * i] = odata['grosssum'] or 0 row[10 + 2 * i] = odata['taxsum'] or 0 tax_sums[rate] += odata['taxsum'] or 0 price_sums[rate] += odata['grosssum'] or 0 i = tax_rates.index(op['tax_rate']) row[9 + 2 * i] = round_decimal(row[9 + 2 * i] + op['prices'], self.event.currency) row[10 + 2 * i] = round_decimal(row[10 + 2 * i] + op['tax_values'], self.event.currency) tax_sums[op['tax_rate']] += op['tax_values'] price_sums[op['tax_rate']] += op['prices'] if row: yield row yield [ _('Total'), '', '', '', '', '', '', '', '' ] + sum(([ round_decimal(price_sums.get(t) or Decimal('0.00'), self.event.currency), round_decimal(tax_sums.get(t) or Decimal('0.00'), self.event.currency) ] for t in tax_rates), [])
def get_story(self, doc, form_data): from reportlab.lib.units import mm from reportlab.platypus import Paragraph, Spacer, Table, TableStyle headlinestyle = self.get_style() headlinestyle.fontSize = 15 headlinestyle.fontName = 'OpenSansBd' tz = pytz.timezone(self.event.settings.timezone) tax_rates = set( a for a in OrderFee.objects.filter( order__event=self.event ).values_list('tax_rate', flat=True).distinct().order_by() ) tax_rates |= set( a for a in OrderPosition.objects.filter(order__event=self.event).filter( order__status__in=self.form_data['status'] ).values_list('tax_rate', flat=True).distinct().order_by() ) tax_rates = sorted(tax_rates) # Cols: Order ID | Order date | Status | Payment Date | Total | {gross tax} for t in taxes colwidths = [a * doc.width for a in [0.12, 0.1, 0.10, 0.12, 0.08]] if tax_rates: colwidths += [0.48 / (len(tax_rates) * 2) * doc.width] * (len(tax_rates) * 2) tstyledata = [ # Alignment ('ALIGN', (0, 0), (3, 0), 'LEFT'), # Headlines ('ALIGN', (4, 0), (-1, 0), 'CENTER'), # Headlines ('ALIGN', (4, 1), (-1, -1), 'RIGHT'), # Money ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # Fonts ('FONTNAME', (0, 0), (-1, 0), 'OpenSansBd'), # Headlines ('FONTNAME', (0, -1), (-1, -1), 'OpenSansBd'), # Sums ] for i, rate in enumerate(tax_rates): tstyledata.append(('SPAN', (5 + 2 * i, 0), (6 + 2 * i, 0))) story = [ Paragraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle), Spacer(1, 5 * mm) ] tdata = [ [ _('Order code'), _('Order date'), _('Status'), _('Payment date'), _('Order total'), ] + sum(([localize(t) + ' %', ''] for t in tax_rates), []), [ '', '', '', '', '' ] + sum(([_('Gross'), _('Tax')] for t in tax_rates), []), ] op_date = OrderPayment.objects.filter( order=OuterRef('order'), state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED), payment_date__isnull=False ).values('order').annotate( m=Max('payment_date') ).values( 'm' ).order_by() qs = OrderPosition.objects.filter( order__status__in=self.form_data['status'], order__event=self.event, ).annotate(payment_date=Subquery(op_date, output_field=models.DateTimeField())).values( 'order__code', 'order__datetime', 'payment_date', 'order__total', 'tax_rate', 'order__status', 'order__id' ).annotate(prices=Sum('price'), tax_values=Sum('tax_value')).order_by( 'order__datetime' if self.form_data['sort'] == 'datetime' else 'payment_date', 'order__datetime', 'order__code' ) fee_sum_cache = { (o['order__id'], o['tax_rate']): o for o in OrderFee.objects.values('tax_rate', 'order__id').order_by().annotate( taxsum=Sum('tax_value'), grosssum=Sum('value') ) } last_order_code = None tax_sums = defaultdict(Decimal) price_sums = defaultdict(Decimal) status_labels = dict(Order.STATUS_CHOICE) for op in qs: if op['order__code'] != last_order_code: tdata.append( [ op['order__code'], date_format(op['order__datetime'].astimezone(tz), "SHORT_DATE_FORMAT"), status_labels[op['order__status']], date_format(op['payment_date'], "SHORT_DATE_FORMAT") if op['payment_date'] else '', op['order__total'] ] + sum((['', ''] for t in tax_rates), []), ) last_order_code = op['order__code'] for i, rate in enumerate(tax_rates): odata = fee_sum_cache.get((op['order__id'], rate)) if odata: tdata[-1][5 + 2 * i] = odata['grosssum'] or Decimal('0.00') tdata[-1][6 + 2 * i] = odata['taxsum'] or Decimal('0.00') tax_sums[rate] += odata['taxsum'] or Decimal('0.00') price_sums[rate] += odata['grosssum'] or Decimal('0.00') i = tax_rates.index(op['tax_rate']) tdata[-1][5 + 2 * i] = (tdata[-1][5 + 2 * i] or Decimal('0.00')) + op['prices'] tdata[-1][6 + 2 * i] = (tdata[-1][6 + 2 * i] or Decimal('0.00')) + op['tax_values'] tax_sums[op['tax_rate']] += op['tax_values'] price_sums[op['tax_rate']] += op['prices'] tdata.append( [ _('Total'), '', '', '', '' ] + sum(([ price_sums.get(t) or Decimal('0.00'), tax_sums.get(t) or Decimal('0.00') ] for t in tax_rates), []), ) tdata = [ [ localize(round_decimal(c, self.event.currency)) if isinstance(c, (Decimal, int, float)) else c for c in row ] for row in tdata ] table = Table(tdata, colWidths=colwidths, repeatRows=2) table.setStyle(TableStyle(tstyledata)) story.append(table) return story
def net_price(self): tax_value = round_decimal(self.price * (1 - 100 / (100 + self.item.tax_rate))) return self.price - tax_value
def default_price_net(self): tax_value = round_decimal(self.default_price * (1 - 100 / (100 + self.tax_rate))) return self.default_price - tax_value
def get_story(self, doc, form_data): from reportlab.platypus import Paragraph, Spacer, TableStyle, Table from reportlab.lib.units import mm headlinestyle = self.get_style() headlinestyle.fontSize = 15 headlinestyle.fontName = 'OpenSansBd' tz = pytz.timezone(self.event.settings.timezone) tax_rates = set( a for a in OrderFee.objects.filter( order__event=self.event ).values_list('tax_rate', flat=True).distinct().order_by() ) tax_rates |= set( a for a in OrderPosition.objects.filter(order__event=self.event).filter( order__status__in=self.form_data['status'] ).values_list('tax_rate', flat=True).distinct().order_by() ) tax_rates = sorted(tax_rates) # Cols: Order ID | Order date | Status | Payment Date | Total | {gross tax} for t in taxes colwidths = [a * doc.width for a in [0.12, 0.1, 0.10, 0.12, 0.08]] if tax_rates: colwidths += [0.48 / (len(tax_rates) * 2) * doc.width] * (len(tax_rates) * 2) tstyledata = [ # Alignment ('ALIGN', (0, 0), (3, 0), 'LEFT'), # Headlines ('ALIGN', (4, 0), (-1, 0), 'CENTER'), # Headlines ('ALIGN', (4, 1), (-1, -1), 'RIGHT'), # Money ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), # Fonts ('FONTNAME', (0, 0), (-1, 0), 'OpenSansBd'), # Headlines ('FONTNAME', (0, -1), (-1, -1), 'OpenSansBd'), # Sums ] for i, rate in enumerate(tax_rates): tstyledata.append(('SPAN', (5 + 2 * i, 0), (6 + 2 * i, 0))) story = [ Paragraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle), Spacer(1, 5 * mm) ] tdata = [ [ _('Order code'), _('Order date'), _('Status'), _('Payment date'), _('Order total'), ] + sum(([str(t) + ' %', ''] for t in tax_rates), []), [ '', '', '', '', '' ] + sum(([_('Gross'), _('Tax')] for t in tax_rates), []), ] op_date = OrderPayment.objects.filter( order=OuterRef('order'), state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED), payment_date__isnull=False ).values('order').annotate( m=Max('payment_date') ).values( 'm' ).order_by() qs = OrderPosition.objects.filter( order__status__in=self.form_data['status'], order__event=self.event, ).annotate(payment_date=Subquery(op_date, output_field=models.DateTimeField())).values( 'order__code', 'order__datetime', 'payment_date', 'order__total', 'tax_rate', 'order__status', 'order__id' ).annotate(prices=Sum('price'), tax_values=Sum('tax_value')).order_by( 'order__datetime' if self.form_data['sort'] == 'datetime' else 'payment_date', 'order__datetime', 'order__code' ) fee_sum_cache = { (o['order__id'], o['tax_rate']): o for o in OrderFee.objects.values('tax_rate', 'order__id').order_by().annotate( taxsum=Sum('tax_value'), grosssum=Sum('value') ) } last_order_code = None tax_sums = defaultdict(Decimal) price_sums = defaultdict(Decimal) status_labels = dict(Order.STATUS_CHOICE) for op in qs: if op['order__code'] != last_order_code: tdata.append( [ op['order__code'], date_format(op['order__datetime'].astimezone(tz), "SHORT_DATE_FORMAT"), status_labels[op['order__status']], date_format(op['payment_date'], "SHORT_DATE_FORMAT") if op['payment_date'] else '', localize(round_decimal(op['order__total'], self.event.currency)) ] + sum((['', ''] for t in tax_rates), []), ) last_order_code = op['order__code'] for i, rate in enumerate(tax_rates): odata = fee_sum_cache.get((op['order__id'], rate)) if odata: tdata[-1][5 + 2 * i] = str(odata['grosssum'] or 0) tdata[-1][6 + 2 * i] = str(odata['taxsum'] or 0) tax_sums[rate] += odata['taxsum'] or 0 price_sums[rate] += odata['grosssum'] or 0 i = tax_rates.index(op['tax_rate']) tdata[-1][5 + 2 * i] = localize( round_decimal(Decimal(tdata[-1][5 + 2 * i] or '0') + op['prices'], self.event.currency)) tdata[-1][6 + 2 * i] = localize( round_decimal(Decimal(tdata[-1][6 + 2 * i] or '0') + op['tax_values'], self.event.currency)) tax_sums[op['tax_rate']] += op['tax_values'] price_sums[op['tax_rate']] += op['prices'] tdata.append( [ _('Total'), '', '', '', '' ] + sum(([ localize(round_decimal(price_sums.get(t) or Decimal('0.00'), self.event.currency)), localize(round_decimal(tax_sums.get(t) or Decimal('0.00'), self.event.currency)) ] for t in tax_rates), []), ) table = Table(tdata, colWidths=colwidths, repeatRows=2) table.setStyle(TableStyle(tstyledata)) story.append(table) return story
def _get_story(self, doc): has_taxes = any(il.tax_value for il in self.invoice.lines.all()) story = [ NextPageTemplate('FirstPage'), Paragraph( ( pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU' else pgettext('invoice', 'Invoice') ) if not self.invoice.is_cancellation else pgettext('invoice', 'Cancellation'), self.stylesheet['Heading1'] ), Spacer(1, 5 * mm), NextPageTemplate('OtherPages'), ] story += self._get_intro() taxvalue_map = defaultdict(Decimal) grossvalue_map = defaultdict(Decimal) tstyledata = [ ('ALIGN', (1, 0), (-1, -1), 'RIGHT'), ('VALIGN', (0, 0), (-1, -1), 'TOP'), ('FONTNAME', (0, 0), (-1, 0), self.font_bold), ('FONTNAME', (0, -1), (-1, -1), self.font_bold), ('LEFTPADDING', (0, 0), (0, -1), 0), ('RIGHTPADDING', (-1, 0), (-1, -1), 0), ] if has_taxes: tdata = [( pgettext('invoice', 'Description'), pgettext('invoice', 'Qty'), pgettext('invoice', 'Tax rate'), pgettext('invoice', 'Net'), pgettext('invoice', 'Gross'), )] else: tdata = [( pgettext('invoice', 'Description'), pgettext('invoice', 'Qty'), pgettext('invoice', 'Amount'), )] total = Decimal('0.00') for line in self.invoice.lines.all(): if has_taxes: tdata.append(( Paragraph(line.description, self.stylesheet['Normal']), "1", localize(line.tax_rate) + " %", money_filter(line.net_value, self.invoice.event.currency), money_filter(line.gross_value, self.invoice.event.currency), )) else: tdata.append(( Paragraph(line.description, self.stylesheet['Normal']), "1", money_filter(line.gross_value, self.invoice.event.currency), )) taxvalue_map[line.tax_rate, line.tax_name] += line.tax_value grossvalue_map[line.tax_rate, line.tax_name] += line.gross_value total += line.gross_value if has_taxes: tdata.append([ pgettext('invoice', 'Invoice total'), '', '', '', money_filter(total, self.invoice.event.currency) ]) colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)] else: tdata.append([ pgettext('invoice', 'Invoice total'), '', money_filter(total, self.invoice.event.currency) ]) colwidths = [a * doc.width for a in (.65, .05, .30)] if self.invoice.event.settings.invoice_show_payments and not self.invoice.is_cancellation and \ self.invoice.order.status == Order.STATUS_PENDING: pending_sum = self.invoice.order.pending_sum if pending_sum != total: tdata.append([pgettext('invoice', 'Received payments')] + (['', '', ''] if has_taxes else ['']) + [ money_filter(pending_sum - total, self.invoice.event.currency) ]) tdata.append([pgettext('invoice', 'Outstanding payments')] + (['', '', ''] if has_taxes else ['']) + [ money_filter(pending_sum, self.invoice.event.currency) ]) tstyledata += [ ('FONTNAME', (0, len(tdata) - 3), (-1, len(tdata) - 3), self.font_bold), ] table = Table(tdata, colWidths=colwidths, repeatRows=1) table.setStyle(TableStyle(tstyledata)) story.append(table) story.append(Spacer(1, 15 * mm)) if self.invoice.payment_provider_text: story.append(Paragraph(self.invoice.payment_provider_text, self.stylesheet['Normal'])) if self.invoice.additional_text: story.append(Paragraph(self.invoice.additional_text, self.stylesheet['Normal'])) story.append(Spacer(1, 15 * mm)) tstyledata = [ ('ALIGN', (1, 0), (-1, -1), 'RIGHT'), ('LEFTPADDING', (0, 0), (0, -1), 0), ('RIGHTPADDING', (-1, 0), (-1, -1), 0), ('FONTSIZE', (0, 0), (-1, -1), 8), ('FONTNAME', (0, 0), (-1, -1), self.font_regular), ] thead = [ pgettext('invoice', 'Tax rate'), pgettext('invoice', 'Net value'), pgettext('invoice', 'Gross value'), pgettext('invoice', 'Tax'), '' ] tdata = [thead] for idx, gross in grossvalue_map.items(): rate, name = idx if rate == 0: continue tax = taxvalue_map[idx] tdata.append([ localize(rate) + " % " + name, money_filter(gross - tax, self.invoice.event.currency), money_filter(gross, self.invoice.event.currency), money_filter(tax, self.invoice.event.currency), '' ]) def fmt(val): try: return vat_moss.exchange_rates.format(val, self.invoice.foreign_currency_display) except ValueError: return localize(val) + ' ' + self.invoice.foreign_currency_display if len(tdata) > 1 and has_taxes: colwidths = [a * doc.width for a in (.25, .15, .15, .15, .3)] table = Table(tdata, colWidths=colwidths, repeatRows=2, hAlign=TA_LEFT) table.setStyle(TableStyle(tstyledata)) story.append(Spacer(5 * mm, 5 * mm)) story.append(KeepTogether([ Paragraph(pgettext('invoice', 'Included taxes'), self.stylesheet['FineprintHeading']), table ])) if self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate: tdata = [thead] for idx, gross in grossvalue_map.items(): rate, name = idx if rate == 0: continue tax = taxvalue_map[idx] gross = round_decimal(gross * self.invoice.foreign_currency_rate) tax = round_decimal(tax * self.invoice.foreign_currency_rate) net = gross - tax tdata.append([ localize(rate) + " % " + name, fmt(net), fmt(gross), fmt(tax), '' ]) table = Table(tdata, colWidths=colwidths, repeatRows=2, hAlign=TA_LEFT) table.setStyle(TableStyle(tstyledata)) story.append(KeepTogether([ Spacer(1, height=2 * mm), Paragraph( pgettext( 'invoice', 'Using the conversion rate of 1:{rate} as published by the European Central Bank on ' '{date}, this corresponds to:' ).format(rate=localize(self.invoice.foreign_currency_rate), date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT")), self.stylesheet['Fineprint'] ), Spacer(1, height=3 * mm), table ])) elif self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate: story.append(Spacer(1, 5 * mm)) story.append(Paragraph( pgettext( 'invoice', 'Using the conversion rate of 1:{rate} as published by the European Central Bank on ' '{date}, the invoice total corresponds to {total}.' ).format(rate=localize(self.invoice.foreign_currency_rate), date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT"), total=fmt(total)), self.stylesheet['Fineprint'] )) return story
def _amount_to_decimal(self, cents): places = settings.CURRENCY_PLACES.get(self.event.currency, 2) return round_decimal(float(cents) / (10 ** places), self.event.currency)
def _amount_to_decimal(self, cents): places = settings.CURRENCY_PLACES.get(self.event.currency, 2) return round_decimal(float(cents) / (10**places), self.event.currency)
def _get_story(self, doc): has_taxes = any(il.tax_value for il in self.invoice.lines.all()) story = [ NextPageTemplate('FirstPage'), Paragraph(pgettext('invoice', 'Invoice') if not self.invoice.is_cancellation else pgettext('invoice', 'Cancellation'), self.stylesheet['Heading1']), Spacer(1, 5 * mm), NextPageTemplate('OtherPages'), ] if self.invoice.internal_reference: story.append(Paragraph( pgettext('invoice', 'Customer reference: {reference}').format(reference=self.invoice.internal_reference), self.stylesheet['Normal'] )) if self.invoice.introductory_text: story.append(Paragraph(self.invoice.introductory_text, self.stylesheet['Normal'])) story.append(Spacer(1, 10 * mm)) taxvalue_map = defaultdict(Decimal) grossvalue_map = defaultdict(Decimal) tstyledata = [ ('ALIGN', (1, 0), (-1, -1), 'RIGHT'), ('VALIGN', (0, 0), (-1, -1), 'TOP'), ('FONTNAME', (0, 0), (-1, 0), 'OpenSansBd'), ('FONTNAME', (0, -1), (-1, -1), 'OpenSansBd'), ('LEFTPADDING', (0, 0), (0, -1), 0), ('RIGHTPADDING', (-1, 0), (-1, -1), 0), ] if has_taxes: tdata = [( pgettext('invoice', 'Description'), pgettext('invoice', 'Qty'), pgettext('invoice', 'Tax rate'), pgettext('invoice', 'Net'), pgettext('invoice', 'Gross'), )] else: tdata = [( pgettext('invoice', 'Description'), pgettext('invoice', 'Qty'), pgettext('invoice', 'Amount'), )] total = Decimal('0.00') for line in self.invoice.lines.all(): if has_taxes: tdata.append(( Paragraph(line.description, self.stylesheet['Normal']), "1", localize(line.tax_rate) + " %", money_filter(line.net_value, self.invoice.event.currency), money_filter(line.gross_value, self.invoice.event.currency), )) else: tdata.append(( Paragraph(line.description, self.stylesheet['Normal']), "1", money_filter(line.gross_value, self.invoice.event.currency), )) taxvalue_map[line.tax_rate, line.tax_name] += line.tax_value grossvalue_map[line.tax_rate, line.tax_name] += line.gross_value total += line.gross_value if has_taxes: tdata.append([ pgettext('invoice', 'Invoice total'), '', '', '', money_filter(total, self.invoice.event.currency) ]) colwidths = [a * doc.width for a in (.50, .05, .15, .15, .15)] else: tdata.append([ pgettext('invoice', 'Invoice total'), '', money_filter(total, self.invoice.event.currency) ]) colwidths = [a * doc.width for a in (.65, .05, .30)] table = Table(tdata, colWidths=colwidths, repeatRows=1) table.setStyle(TableStyle(tstyledata)) story.append(table) story.append(Spacer(1, 15 * mm)) if self.invoice.payment_provider_text: story.append(Paragraph(self.invoice.payment_provider_text, self.stylesheet['Normal'])) if self.invoice.additional_text: story.append(Paragraph(self.invoice.additional_text, self.stylesheet['Normal'])) story.append(Spacer(1, 15 * mm)) tstyledata = [ ('ALIGN', (1, 0), (-1, -1), 'RIGHT'), ('LEFTPADDING', (0, 0), (0, -1), 0), ('RIGHTPADDING', (-1, 0), (-1, -1), 0), ('FONTSIZE', (0, 0), (-1, -1), 8), ('FONTNAME', (0, 0), (-1, -1), 'OpenSans'), ] thead = [ pgettext('invoice', 'Tax rate'), pgettext('invoice', 'Net value'), pgettext('invoice', 'Gross value'), pgettext('invoice', 'Tax'), '' ] tdata = [thead] for idx, gross in grossvalue_map.items(): rate, name = idx if rate == 0: continue tax = taxvalue_map[idx] tdata.append([ localize(rate) + " % " + name, money_filter(gross - tax, self.invoice.event.currency), money_filter(gross, self.invoice.event.currency), money_filter(tax, self.invoice.event.currency), '' ]) def fmt(val): try: return vat_moss.exchange_rates.format(val, self.invoice.foreign_currency_display) except ValueError: return localize(val) + ' ' + self.invoice.foreign_currency_display if len(tdata) > 1 and has_taxes: colwidths = [a * doc.width for a in (.25, .15, .15, .15, .3)] table = Table(tdata, colWidths=colwidths, repeatRows=2, hAlign=TA_LEFT) table.setStyle(TableStyle(tstyledata)) story.append(KeepTogether([ Paragraph(pgettext('invoice', 'Included taxes'), self.stylesheet['FineprintHeading']), table ])) if self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate: tdata = [thead] for idx, gross in grossvalue_map.items(): rate, name = idx if rate == 0: continue tax = taxvalue_map[idx] gross = round_decimal(gross * self.invoice.foreign_currency_rate) tax = round_decimal(tax * self.invoice.foreign_currency_rate) net = gross - tax tdata.append([ localize(rate) + " % " + name, fmt(net), fmt(gross), fmt(tax), '' ]) table = Table(tdata, colWidths=colwidths, repeatRows=2, hAlign=TA_LEFT) table.setStyle(TableStyle(tstyledata)) story.append(KeepTogether([ Spacer(1, height=2 * mm), Paragraph( pgettext( 'invoice', 'Using the conversion rate of 1:{rate} as published by the European Central Bank on ' '{date}, this corresponds to:' ).format(rate=localize(self.invoice.foreign_currency_rate), date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT")), self.stylesheet['Fineprint'] ), Spacer(1, height=3 * mm), table ])) elif self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate: story.append(Spacer(1, 5 * mm)) story.append(Paragraph( pgettext( 'invoice', 'Using the conversion rate of 1:{rate} as published by the European Central Bank on ' '{date}, the invoice total corresponds to {total}.' ).format(rate=localize(self.invoice.foreign_currency_rate), date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT"), total=fmt(total)), self.stylesheet['Fineprint'] )) return story