forked from CGM_Public/pretix_original
Invoice issuer address: Add state field (#5603)
* Invoice issuer address: Add state field * Update src/pretix/base/settings.py Co-authored-by: Richard Schreiber <schreiber@rami.io> * Update src/pretix/base/models/invoices.py Co-authored-by: Richard Schreiber <schreiber@rami.io> --------- Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
@@ -820,6 +820,7 @@ class EventSettingsSerializer(SettingsSerializer):
|
||||
'invoice_address_from',
|
||||
'invoice_address_from_zipcode',
|
||||
'invoice_address_from_city',
|
||||
'invoice_address_from_state',
|
||||
'invoice_address_from_country',
|
||||
'invoice_address_from_tax_id',
|
||||
'invoice_address_from_vat_id',
|
||||
@@ -952,6 +953,7 @@ class DeviceEventSettingsSerializer(EventSettingsSerializer):
|
||||
'invoice_address_from',
|
||||
'invoice_address_from_zipcode',
|
||||
'invoice_address_from_city',
|
||||
'invoice_address_from_state',
|
||||
'invoice_address_from_country',
|
||||
'invoice_address_from_tax_id',
|
||||
'invoice_address_from_vat_id',
|
||||
|
||||
@@ -1831,7 +1831,7 @@ class InvoiceSerializer(I18nAwareModelSerializer):
|
||||
class Meta:
|
||||
model = Invoice
|
||||
fields = ('event', 'order', 'number', 'is_cancellation', 'invoice_from', 'invoice_from_name', 'invoice_from_zipcode',
|
||||
'invoice_from_city', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
|
||||
'invoice_from_city', 'invoice_from_state', 'invoice_from_country', 'invoice_from_tax_id', 'invoice_from_vat_id',
|
||||
'invoice_to', 'invoice_to_is_business', 'invoice_to_company', 'invoice_to_name', 'invoice_to_street',
|
||||
'invoice_to_zipcode', 'invoice_to_city', 'invoice_to_state', 'invoice_to_country', 'invoice_to_vat_id',
|
||||
'invoice_to_beneficiary', 'invoice_to_transmission_info', 'custom_field', 'date', 'refers', 'locale',
|
||||
|
||||
@@ -209,6 +209,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
||||
_('Invoice sender:') + ' ' + _('Address'),
|
||||
_('Invoice sender:') + ' ' + _('ZIP code'),
|
||||
_('Invoice sender:') + ' ' + _('City'),
|
||||
_('Invoice sender:') + ' ' + pgettext('address', 'State'),
|
||||
_('Invoice sender:') + ' ' + _('Country'),
|
||||
_('Invoice sender:') + ' ' + _('Tax ID'),
|
||||
_('Invoice sender:') + ' ' + _('VAT ID'),
|
||||
@@ -291,6 +292,7 @@ class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
|
||||
i.invoice_from,
|
||||
i.invoice_from_zipcode,
|
||||
i.invoice_from_city,
|
||||
i.invoice_from_state,
|
||||
i.invoice_from_country,
|
||||
i.invoice_from_tax_id,
|
||||
i.invoice_from_vat_id,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 4.2.24 on 2025-11-10 16:35
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pretixbase", "0295_user_is_verified"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="invoice",
|
||||
name="invoice_from_state",
|
||||
field=models.CharField(max_length=190, null=True),
|
||||
),
|
||||
]
|
||||
@@ -142,6 +142,7 @@ class Invoice(models.Model):
|
||||
invoice_from_name = models.CharField(max_length=190, null=True)
|
||||
invoice_from_zipcode = models.CharField(max_length=190, null=True)
|
||||
invoice_from_city = models.CharField(max_length=190, null=True)
|
||||
invoice_from_state = models.CharField(max_length=190, null=True)
|
||||
invoice_from_country = FastCountryField(null=True)
|
||||
invoice_from_tax_id = models.CharField(max_length=190, null=True)
|
||||
invoice_from_vat_id = models.CharField(max_length=190, null=True)
|
||||
@@ -218,10 +219,23 @@ class Invoice(models.Model):
|
||||
taxidrow = "ABN: %s" % self.invoice_from_tax_id
|
||||
else:
|
||||
taxidrow = pgettext("invoice", "Tax ID: %s") % self.invoice_from_tax_id
|
||||
|
||||
state_name = ""
|
||||
if self.invoice_from_state:
|
||||
state_name = self.invoice_from_state
|
||||
if str(self.invoice_from_country) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
if COUNTRIES_WITH_STATE_IN_ADDRESS[str(self.invoice_from_country)][1] == 'long':
|
||||
try:
|
||||
state_name = pycountry.subdivisions.get(
|
||||
code='{}-{}'.format(self.invoice_from_country, self.invoice_from_state)
|
||||
).name
|
||||
except:
|
||||
pass
|
||||
|
||||
parts = [
|
||||
self.invoice_from_name,
|
||||
self.invoice_from,
|
||||
(self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or ""),
|
||||
((self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or "") + " " + (state_name or "")).strip(),
|
||||
self.invoice_from_country.name if self.invoice_from_country else "",
|
||||
pgettext("invoice", "VAT-ID: %s") % self.invoice_from_vat_id if self.invoice_from_vat_id else "",
|
||||
taxidrow,
|
||||
@@ -230,10 +244,22 @@ class Invoice(models.Model):
|
||||
|
||||
@property
|
||||
def address_invoice_from(self):
|
||||
state_name = ""
|
||||
if self.invoice_from_state:
|
||||
state_name = self.invoice_from_state
|
||||
if str(self.invoice_from_country) in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
if COUNTRIES_WITH_STATE_IN_ADDRESS[str(self.invoice_from_country)][1] == 'long':
|
||||
try:
|
||||
state_name = pycountry.subdivisions.get(
|
||||
code='{}-{}'.format(self.invoice_from_country, self.invoice_from_state)
|
||||
).name
|
||||
except:
|
||||
pass
|
||||
|
||||
parts = [
|
||||
self.invoice_from_name,
|
||||
self.invoice_from,
|
||||
(self.invoice_from_zipcode or "") + " " + (self.invoice_from_city or ""),
|
||||
" ".join(s for s in [self.invoice_from_zipcode, self.invoice_from_city, state_name] if s)
|
||||
self.invoice_from_country.name if self.invoice_from_country else "",
|
||||
]
|
||||
return '\n'.join([p.strip() for p in parts if p and p.strip()])
|
||||
|
||||
@@ -93,6 +93,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
|
||||
invoice.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
|
||||
invoice.invoice_from_zipcode = invoice.event.settings.get('invoice_address_from_zipcode')
|
||||
invoice.invoice_from_city = invoice.event.settings.get('invoice_address_from_city')
|
||||
invoice.invoice_from_state = invoice.event.settings.get('invoice_address_from_state')
|
||||
invoice.invoice_from_country = invoice.event.settings.get('invoice_address_from_country')
|
||||
invoice.invoice_from_tax_id = invoice.event.settings.get('invoice_address_from_tax_id')
|
||||
invoice.invoice_from_vat_id = invoice.event.settings.get('invoice_address_from_vat_id')
|
||||
@@ -459,6 +460,7 @@ def generate_cancellation(invoice: Invoice, trigger_pdf=True):
|
||||
cancellation.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
|
||||
cancellation.invoice_from_zipcode = invoice.event.settings.get('invoice_address_from_zipcode')
|
||||
cancellation.invoice_from_city = invoice.event.settings.get('invoice_address_from_city')
|
||||
cancellation.invoice_from_state = invoice.event.settings.get('invoice_address_from_state')
|
||||
cancellation.invoice_from_country = invoice.event.settings.get('invoice_address_from_country')
|
||||
cancellation.invoice_from_tax_id = invoice.event.settings.get('invoice_address_from_tax_id')
|
||||
cancellation.invoice_from_vat_id = invoice.event.settings.get('invoice_address_from_vat_id')
|
||||
@@ -562,6 +564,7 @@ def build_preview_invoice_pdf(event):
|
||||
invoice.invoice_from_name = invoice.event.settings.get('invoice_address_from_name')
|
||||
invoice.invoice_from_zipcode = invoice.event.settings.get('invoice_address_from_zipcode')
|
||||
invoice.invoice_from_city = invoice.event.settings.get('invoice_address_from_city')
|
||||
invoice.invoice_from_state = invoice.event.settings.get('invoice_address_from_state')
|
||||
invoice.invoice_from_country = invoice.event.settings.get('invoice_address_from_country')
|
||||
invoice.invoice_from_tax_id = invoice.event.settings.get('invoice_address_from_tax_id')
|
||||
invoice.invoice_from_vat_id = invoice.event.settings.get('invoice_address_from_vat_id')
|
||||
|
||||
@@ -40,6 +40,7 @@ from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
import pycountry
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
@@ -1229,13 +1230,32 @@ DEFAULTS = {
|
||||
label=_("City"),
|
||||
)
|
||||
},
|
||||
'invoice_address_from_state': {
|
||||
'default': '',
|
||||
'type': str,
|
||||
'form_class': forms.ChoiceField,
|
||||
'serializer_class': serializers.ChoiceField,
|
||||
'serializer_kwargs': {
|
||||
'choices': [('', '')],
|
||||
},
|
||||
'form_kwargs': {
|
||||
"label": pgettext_lazy('address', 'State'),
|
||||
'choices': [('', '')],
|
||||
},
|
||||
},
|
||||
'invoice_address_from_country': {
|
||||
'default': '',
|
||||
'type': str,
|
||||
'form_class': forms.ChoiceField,
|
||||
'serializer_class': serializers.ChoiceField,
|
||||
'serializer_kwargs': lambda: dict(**country_choice_kwargs()),
|
||||
'form_kwargs': lambda: dict(label=_('Country'), **country_choice_kwargs()),
|
||||
'form_kwargs': lambda: dict(
|
||||
label=_('Country'),
|
||||
widget=forms.Select(attrs={
|
||||
'data-trigger-address-info': 'on',
|
||||
}),
|
||||
**country_choice_kwargs()
|
||||
),
|
||||
},
|
||||
'invoice_address_from_tax_id': {
|
||||
'default': '',
|
||||
@@ -3971,6 +3991,16 @@ def validate_event_settings(event, settings_dict):
|
||||
raise ValidationError({
|
||||
'invoice_address_company_required': _('You have to require invoice addresses to require for company names.')
|
||||
})
|
||||
if settings_dict.get('invoice_address_from_state') and settings_dict.get('invoice_address_from_country'):
|
||||
cc = str(settings_dict.get('invoice_address_from_country'))
|
||||
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
raise ValidationError(
|
||||
{'invoice_address_from_state': ['States are not supported in country "{}".'.format(cc)]}
|
||||
)
|
||||
if not pycountry.subdivisions.get(code=cc + '-' + settings_dict.get('invoice_address_from_state')):
|
||||
raise ValidationError(
|
||||
{'invoice_address_from_state': ['"{}" is not a known subdivision of the country "{}".'.format(settings_dict.get('invoice_address_from_state'), cc)]}
|
||||
)
|
||||
|
||||
payment_term_last = settings_dict.get('payment_term_last')
|
||||
if payment_term_last and event.presale_end:
|
||||
|
||||
@@ -67,8 +67,9 @@ from pretix.base.models.tax import TAX_CODE_LISTS
|
||||
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
|
||||
from pretix.base.services.placeholders import FormPlaceholderMixin
|
||||
from pretix.base.settings import (
|
||||
COUNTRIES_WITH_STATE_IN_ADDRESS, DEFAULTS, PERSON_NAME_SCHEMES,
|
||||
PERSON_NAME_TITLE_GROUPS, ROUNDING_MODES, validate_event_settings,
|
||||
COUNTRIES_WITH_STATE_IN_ADDRESS, COUNTRY_STATE_LABEL, DEFAULTS,
|
||||
PERSON_NAME_SCHEMES, PERSON_NAME_TITLE_GROUPS, ROUNDING_MODES,
|
||||
validate_event_settings,
|
||||
)
|
||||
from pretix.base.validators import multimail_validate
|
||||
from pretix.control.forms import (
|
||||
@@ -945,6 +946,7 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
||||
'invoice_address_from',
|
||||
'invoice_address_from_zipcode',
|
||||
'invoice_address_from_city',
|
||||
'invoice_address_from_state',
|
||||
'invoice_address_from_country',
|
||||
'invoice_address_from_tax_id',
|
||||
'invoice_address_from_vat_id',
|
||||
@@ -1017,6 +1019,26 @@ class InvoiceSettingsForm(EventSettingsValidationMixin, SettingsForm):
|
||||
(a, a) for a in get_fonts(event, pdf_support_required=True).keys()
|
||||
]
|
||||
|
||||
if 'invoice_address_from_country' in self.data:
|
||||
cc = str(self.data['invoice_address_from_country'])
|
||||
elif 'invoice_address_from_country' in self.initial:
|
||||
cc = str(self.initial['invoice_address_from_country'])
|
||||
else:
|
||||
cc = self.obj.settings.invoice_address_from_country
|
||||
c = [('', '---')]
|
||||
state_label = pgettext_lazy('address', 'State')
|
||||
if cc and cc in COUNTRIES_WITH_STATE_IN_ADDRESS:
|
||||
types, form = COUNTRIES_WITH_STATE_IN_ADDRESS[cc]
|
||||
statelist = [s for s in pycountry.subdivisions.get(country_code=cc) if s.type in types]
|
||||
c += sorted([(s.code[3:], s.name) for s in statelist], key=lambda s: s[1])
|
||||
if cc in COUNTRY_STATE_LABEL:
|
||||
state_label = COUNTRY_STATE_LABEL[cc]
|
||||
elif 'invoice_address_from_state' in self.data:
|
||||
self.data = self.data.copy()
|
||||
del self.data['invoice_address_from_state']
|
||||
self.fields['invoice_address_from_state'].choices = c
|
||||
self.fields['invoice_address_from_state'].label = state_label
|
||||
|
||||
|
||||
def contains_web_channel_validate(val):
|
||||
if "web" not in val:
|
||||
|
||||
@@ -49,13 +49,14 @@
|
||||
{% bootstrap_field form.invoice_address_custom_field_helptext layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_explanation_text layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset data-address-information-url="{% url "js_helpers.address_form" %}">
|
||||
<legend>{% trans "Issuer details" %}</legend>
|
||||
{% bootstrap_field form.invoice_address_from_name layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_from layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_from_zipcode layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_from_city layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_from_country layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_from_state layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_from_tax_id layout="control" %}
|
||||
{% bootstrap_field form.invoice_address_from_vat_id layout="control" %}
|
||||
</fieldset>
|
||||
|
||||
@@ -5,6 +5,14 @@ $(function () {
|
||||
// to prevent fetching the same thing many times.
|
||||
var responseCache = {};
|
||||
|
||||
const cleanName = (name) => {
|
||||
// Remove form prefix
|
||||
name = name.split("-").pop();
|
||||
// Remove settings prefix
|
||||
name = name.replace(/^invoice_address_from_/, "");
|
||||
return name
|
||||
}
|
||||
|
||||
$("[data-address-information-url]").each(function () {
|
||||
let xhr;
|
||||
const form = $(this);
|
||||
@@ -22,7 +30,7 @@ $(function () {
|
||||
};
|
||||
|
||||
form.find("select[name*=transmission_], textarea[name*=transmission_], input[name*=transmission_]").each(function () {
|
||||
dependents[$(this).attr("name").split("-").pop()] = $(this)
|
||||
dependents[cleanName($(this).attr("name"))] = $(this)
|
||||
})
|
||||
|
||||
if (!Object.values(dependents).some((el) => el.length)) {
|
||||
@@ -109,7 +117,7 @@ $(function () {
|
||||
if (($(this).attr("type") === "radio" || $(this).attr("type") === "checkbox") && !$(this).prop("checked")) {
|
||||
return
|
||||
}
|
||||
url.searchParams.append($(this).attr("name").split("-").pop(), $(this).val());
|
||||
url.searchParams.append(cleanName($(this).attr("name")), $(this).val());
|
||||
})
|
||||
if (dependents.transmission_type) {
|
||||
url.searchParams.append("transmission_type_required", !dependents.transmission_type.find("option[value='-']").length);
|
||||
|
||||
@@ -197,6 +197,7 @@ TEST_INVOICE_RES = {
|
||||
"invoice_from": "",
|
||||
"invoice_from_zipcode": "",
|
||||
"invoice_from_city": "",
|
||||
"invoice_from_state": "",
|
||||
"invoice_from_country": None,
|
||||
"invoice_from_tax_id": "",
|
||||
"invoice_from_vat_id": "",
|
||||
|
||||
@@ -578,6 +578,7 @@ def test_order_create_invoice(token_client, organizer, event, order):
|
||||
"invoice_from": "",
|
||||
"invoice_from_zipcode": "",
|
||||
"invoice_from_city": "",
|
||||
"invoice_from_state": "",
|
||||
"invoice_from_country": None,
|
||||
"invoice_from_tax_id": "",
|
||||
"invoice_from_vat_id": "",
|
||||
|
||||
Reference in New Issue
Block a user