Tax rules and reverse charge (#559)

Tax rules and reverse charge
This commit is contained in:
Raphael Michel
2017-08-23 13:13:16 +03:00
committed by GitHub
parent b9ec5ea83c
commit 56338be13e
82 changed files with 2934 additions and 428 deletions

View File

@@ -1,19 +1,33 @@
import copy
from decimal import Decimal
import json
import logging
import urllib.error
from datetime import date, timedelta
from decimal import ROUND_HALF_UP, Decimal
import vat_moss.exchange_rates
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.serializers.json import DjangoJSONEncoder
from django.db import transaction
from django.db.models import Count
from django.dispatch import receiver
from django.utils import timezone
from django.utils.timezone import now
from django.utils.translation import pgettext, ugettext as _
from i18nfield.strings import LazyI18nString
from pretix.base.i18n import language
from pretix.base.models import Invoice, InvoiceAddress, InvoiceLine, Order
from pretix.base.models.tax import EU_CURRENCIES
from pretix.base.services.async import TransactionAwareTask
from pretix.base.settings import GlobalSettingsObject
from pretix.base.signals import periodic_task
from pretix.celery_app import app
from pretix.helpers.database import rolledback_transaction
logger = logging.getLogger(__name__)
@transaction.atomic
def build_invoice(invoice: Invoice) -> Invoice:
@@ -33,6 +47,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
invoice.payment_provider_text = str(payment).replace('\n', '<br />')
try:
ia = invoice.order.invoice_address
addr_template = pgettext("invoice", """{i.company}
{i.name}
{i.street}
@@ -44,7 +59,31 @@ def build_invoice(invoice: Invoice) -> Invoice:
).strip()
if invoice.order.invoice_address.vat_id:
invoice.invoice_to += "\n" + pgettext("invoice", "VAT-ID: %s") % invoice.order.invoice_address.vat_id
cc = str(invoice.order.invoice_address.country)
if cc in EU_CURRENCIES and EU_CURRENCIES[cc] != invoice.event.currency:
invoice.foreign_currency_display = EU_CURRENCIES[cc]
if settings.FETCH_ECB_RATES:
gs = GlobalSettingsObject()
rates_date = gs.settings.get('ecb_rates_date', as_type=date)
rates_dict = gs.settings.get('ecb_rates_dict', as_type=dict)
convert = (
rates_date and rates_dict and
rates_date > (now() - timedelta(days=7)).date() and
invoice.event.currency in rates_dict and
invoice.foreign_currency_display in rates_dict
)
if convert:
invoice.foreign_currency_rate = (
Decimal(rates_dict[invoice.foreign_currency_display])
/ Decimal(rates_dict[invoice.event.currency])
).quantize(Decimal('0.0001'), ROUND_HALF_UP)
invoice.foreign_currency_rate_date = rates_date
except InvoiceAddress.DoesNotExist:
ia = None
invoice.invoice_to = ""
invoice.file = None
@@ -52,10 +91,13 @@ def build_invoice(invoice: Invoice) -> Invoice:
invoice.lines.all().delete()
positions = list(
invoice.order.positions.select_related('addon_to', 'item', 'variation').annotate(
invoice.order.positions.select_related('addon_to', 'item', 'tax_rule', 'variation').annotate(
addon_c=Count('addons')
)
)
reverse_charge = False
positions.sort(key=lambda p: p.sort_key)
for p in positions:
if not invoice.event.settings.invoice_include_free and p.price == Decimal('0.00') and not p.addon_c:
@@ -69,15 +111,29 @@ def build_invoice(invoice: Invoice) -> Invoice:
InvoiceLine.objects.create(
invoice=invoice, description=desc,
gross_value=p.price, tax_value=p.tax_value,
tax_rate=p.tax_rate
tax_rate=p.tax_rate, tax_name=p.tax_rule.name if p.tax_rule else ''
)
if p.tax_rule and p.tax_rule.is_reverse_charge(ia) and p.price and not p.tax_value:
reverse_charge = True
if reverse_charge:
if invoice.additional_text:
invoice.additional_text += "<br /><br />"
invoice.additional_text += pgettext(
"invoice",
"Reverse Charge: According to Article 194, 196 of Council Directive 2006/112/EEC, VAT liability "
"rests with the service recipient."
)
invoice.save()
if invoice.order.payment_fee:
InvoiceLine.objects.create(
invoice=invoice,
description=_('Payment via {method}').format(method=str(payment_provider.verbose_name)),
gross_value=invoice.order.payment_fee, tax_value=invoice.order.payment_fee_tax_value,
tax_rate=invoice.order.payment_fee_tax_rate
tax_rate=invoice.order.payment_fee_tax_rate,
tax_name=invoice.order.payment_fee_tax_rule.name if invoice.order.payment_fee_tax_rule else ''
)
return invoice
@@ -200,3 +256,20 @@ def build_preview_invoice_pdf(event):
tax_rate=19
)
return event.invoice_renderer.generate(invoice)
@receiver(signal=periodic_task)
def fetch_ecb_rates(sender, **kwargs):
if not settings.FETCH_ECB_RATES:
return
gs = GlobalSettingsObject()
if gs.settings.ecb_rates_date == now().strftime("%Y-%m-%d"):
return
try:
date, rates = vat_moss.exchange_rates.fetch()
gs.settings.ecb_rates_date = date
gs.settings.ecb_rates_dict = json.dumps(rates, cls=DjangoJSONEncoder)
except urllib.error.URLError:
logger.exception('Could not retrieve rates from ECB')