def clean(self): data = self.cleaned_data if not data.get('is_business'): data['company'] = '' data['vat_id'] = '' if data.get('is_business') and not is_eu_country(data.get('country')): data['vat_id'] = '' if self.event.settings.invoice_address_required: if data.get('is_business') and not data.get('company'): raise ValidationError(_('You need to provide a company name.')) if not data.get('is_business') and not data.get('name_parts'): raise ValidationError(_('You need to provide your name.')) if 'vat_id' in self.changed_data or not data.get('vat_id'): self.instance.vat_id_validated = False if data.get('city') and data.get('country') and str(data['country']) in COUNTRIES_WITH_STATE_IN_ADDRESS: if not data.get('state'): self.add_error('state', _('This field is required.')) self.instance.name_parts = data.get('name_parts') if all( not v for k, v in data.items() if k not in ('is_business', 'country', 'name_parts') ) and len(data.get('name_parts', {})) == 1: # Do not save the country if it is the only field set -- we don't know the user even checked it! self.cleaned_data['country'] = '' if self.validate_vat_id and self.instance.vat_id_validated and 'vat_id' not in self.changed_data: pass elif self.validate_vat_id and data.get('is_business') and is_eu_country(data.get('country')) and data.get('vat_id'): if data.get('vat_id')[:2] != cc_to_vat_prefix(str(data.get('country'))): raise ValidationError(_('Your VAT ID does not match the selected country.')) try: result = vat_moss.id.validate(data.get('vat_id')) if result: country_code, normalized_id, company_name = result self.instance.vat_id_validated = True self.instance.vat_id = normalized_id except (vat_moss.errors.InvalidError, ValueError): raise ValidationError(_('This VAT ID is not valid. Please re-check your input.')) except vat_moss.errors.WebServiceUnavailableError: logger.exception('VAT ID checking failed for country {}'.format(data.get('country'))) self.instance.vat_id_validated = False if self.request and self.vat_warning: messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of ' 'your country is currently not available. We will therefore ' 'need to charge VAT on your invoice. You can get the tax amount ' 'back via the VAT reimbursement process.')) except (vat_moss.errors.WebServiceError, HTTPError): logger.exception('VAT ID checking failed for country {}'.format(data.get('country'))) self.instance.vat_id_validated = False if self.request and self.vat_warning: messages.warning(self.request, _('Your VAT ID could not be checked, as the VAT checking service of ' 'your country returned an incorrect result. We will therefore ' 'need to charge VAT on your invoice. Please contact support to ' 'resolve this manually.')) else: self.instance.vat_id_validated = False
def validate_vat_id(vat_id, country_code): country_code = str(country_code) if is_eu_country(country_code): return _validate_vat_id_EU(vat_id, country_code) elif country_code == 'CH': return _validate_vat_id_CH(vat_id, country_code) raise VATIDTemporaryError( f'VAT ID should not be entered for country {country_code}')
def __init__(self, *args, **kwargs): self.event = event = kwargs.pop('event') self.request = kwargs.pop('request', None) self.validate_vat_id = kwargs.pop('validate_vat_id') self.all_optional = kwargs.pop('all_optional', False) kwargs.setdefault('initial', {}) if not kwargs.get('instance') or not kwargs['instance'].country: kwargs['initial']['country'] = guess_country(self.event) super().__init__(*args, **kwargs) if not event.settings.invoice_address_vatid: del self.fields['vat_id'] self.fields['country'].choices = CachedCountries() c = [('', pgettext_lazy('address', 'Select state'))] fprefix = self.prefix + '-' if self.prefix else '' cc = None if fprefix + 'country' in self.data: cc = str(self.data[fprefix + 'country']) elif 'country' in self.initial: cc = str(self.initial['country']) elif self.instance and self.instance.country: cc = str(self.instance.country) 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]) elif fprefix + 'state' in self.data: self.data = self.data.copy() del self.data[fprefix + 'state'] self.fields['state'] = forms.ChoiceField( label=pgettext_lazy('address', 'State'), required=False, choices=c, widget=forms.Select(attrs={ 'autocomplete': 'address-level1', }), ) self.fields['state'].widget.is_required = True # Without JavaScript the VAT ID field is not hidden, so we empty the field if a country outside the EU is selected. if cc and not is_eu_country(cc) and fprefix + 'vat_id' in self.data: self.data = self.data.copy() del self.data[fprefix + 'vat_id'] if not event.settings.invoice_address_required or self.all_optional: for k, f in self.fields.items(): f.required = False f.widget.is_required = False if 'required' in f.widget.attrs: del f.widget.attrs['required'] elif event.settings.invoice_address_company_required and not self.all_optional: self.initial['is_business'] = True self.fields['is_business'].widget = BusinessBooleanRadio( require_business=True) self.fields['company'].required = True self.fields['company'].widget.is_required = True self.fields['company'].widget.attrs['required'] = 'required' del self.fields['company'].widget.attrs['data-display-dependency'] self.fields['name_parts'] = NamePartsFormField( max_length=255, required=event.settings.invoice_name_required and not self.all_optional, scheme=event.settings.name_scheme, titles=event.settings.name_scheme_titles, label=_('Name'), initial=(self.instance.name_parts if self.instance else self.instance.name_parts), ) if event.settings.invoice_address_required and not event.settings.invoice_address_company_required and not self.all_optional: if not event.settings.invoice_name_required: self.fields['name_parts'].widget.attrs[ 'data-required-if'] = '#id_is_business_0' self.fields['name_parts'].widget.attrs[ 'data-no-required-attr'] = '1' self.fields['company'].widget.attrs[ 'data-required-if'] = '#id_is_business_1' if not event.settings.invoice_address_beneficiary: del self.fields['beneficiary'] if event.settings.invoice_address_custom_field: self.fields[ 'custom_field'].label = event.settings.invoice_address_custom_field else: del self.fields['custom_field'] for k, v in self.fields.items(): if v.widget.attrs.get('autocomplete') or k == 'name_parts': v.widget.attrs[ 'autocomplete'] = 'section-invoice billing ' + v.widget.attrs.get( 'autocomplete', '')
def build_invoice(invoice: Invoice) -> Invoice: invoice.locale = invoice.event.settings.get('invoice_language', invoice.event.settings.locale) if invoice.locale == '__user__': invoice.locale = invoice.order.locale or invoice.event.settings.locale lp = invoice.order.payments.last() with language(invoice.locale): invoice.invoice_from = invoice.event.settings.get( 'invoice_address_from') 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_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') introductory = invoice.event.settings.get('invoice_introductory_text', as_type=LazyI18nString) additional = invoice.event.settings.get('invoice_additional_text', as_type=LazyI18nString) footer = invoice.event.settings.get('invoice_footer_text', as_type=LazyI18nString) if lp and lp.payment_provider: if 'payment' in inspect.signature( lp.payment_provider.render_invoice_text).parameters: payment = str( lp.payment_provider.render_invoice_text(invoice.order, lp)) else: payment = str( lp.payment_provider.render_invoice_text(invoice.order)) else: payment = "" if invoice.event.settings.invoice_include_expire_date and invoice.order.status == Order.STATUS_PENDING: if payment: payment += "<br />" payment += pgettext( "invoice", "Please complete your payment before {expire_date}.").format( expire_date=date_format(invoice.order.expires, "SHORT_DATE_FORMAT")) invoice.introductory_text = str(introductory).replace('\n', '<br />') invoice.additional_text = str(additional).replace('\n', '<br />') invoice.footer_text = str(footer) 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} {i.zipcode} {i.city} {state} {country}""") invoice.invoice_to = "\n".join( a.strip() for a in addr_template.format( i=ia, country=ia.country.name if ia.country else ia.country_old, state=ia.state_for_address).split("\n") if a.strip()) invoice.internal_reference = ia.internal_reference invoice.custom_field = ia.custom_field invoice.invoice_to_company = ia.company invoice.invoice_to_name = ia.name invoice.invoice_to_street = ia.street invoice.invoice_to_zipcode = ia.zipcode invoice.invoice_to_city = ia.city invoice.invoice_to_country = ia.country invoice.invoice_to_state = ia.state invoice.invoice_to_beneficiary = ia.beneficiary if ia.vat_id: invoice.invoice_to += "\n" + pgettext("invoice", "VAT-ID: %s") % ia.vat_id invoice.invoice_to_vat_id = ia.vat_id cc = str(ia.country) if cc in EU_CURRENCIES and EU_CURRENCIES[ cc] != invoice.event.currency and invoice.event.settings.invoice_eu_currencies: 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 invoice.save() invoice.lines.all().delete() positions = list( invoice.order.positions.select_related( 'addon_to', 'item', 'tax_rule', 'subevent', 'variation').annotate( addon_c=Count('addons')).prefetch_related( 'answers', 'answers__question').order_by('positionid', 'id')) reverse_charge = False positions.sort(key=lambda p: p.sort_key) for i, p in enumerate(positions): if not invoice.event.settings.invoice_include_free and p.price == Decimal( '0.00') and not p.addon_c: continue desc = str(p.item.name) if p.variation: desc += " - " + str(p.variation.value) if p.addon_to_id: desc = " + " + desc if invoice.event.settings.invoice_attendee_name and p.attendee_name: desc += "<br />" + pgettext( "invoice", "Attendee: {name}").format(name=p.attendee_name) for recv, resp in invoice_line_text.send(sender=invoice.event, position=p): if resp: desc += "<br/>" + resp for answ in p.answers.all(): if not answ.question.print_on_invoice: continue desc += "<br />{}{} {}".format( answ.question.question, "" if str(answ.question.question).endswith("?") else ":", str(answ)) if invoice.event.has_subevents: desc += "<br />" + pgettext("subevent", "Date: {}").format( p.subevent) InvoiceLine.objects.create( position=i, invoice=invoice, description=desc, gross_value=p.price, tax_value=p.tax_value, subevent=p.subevent, event_date_from=(p.subevent.date_from if p.subevent else invoice.event.date_from), 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 />" if is_eu_country(invoice.invoice_to_country): 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.") else: invoice.additional_text += pgettext( "invoice", "VAT liability rests with the service recipient.") invoice.reverse_charge = True invoice.save() offset = len(positions) for i, fee in enumerate(invoice.order.fees.all()): if fee.fee_type == OrderFee.FEE_TYPE_OTHER and fee.description: fee_title = fee.description else: fee_title = _(fee.get_fee_type_display()) if fee.description: fee_title += " - " + fee.description InvoiceLine.objects.create( position=i + offset, invoice=invoice, description=fee_title, gross_value=fee.value, tax_value=fee.tax_value, tax_rate=fee.tax_rate, tax_name=fee.tax_rule.name if fee.tax_rule else '') return invoice