feat: E-Rechnungs-Vorschau mit Versand, USt-IdNr.-Validierung und Icon-Buttons
- Rechnungsvorschau (CreateInvoiceView): ZUGFeRD-ZIP wird schon vor der Vorschau über das PDF Tool erzeugt und das signierte PDF daraus angezeigt; "Speichern" heißt jetzt "Senden" und versendet die Rechnung nach dem Speichern als E-Rechnung per E-Mail (Status "Versendet") - USt-IdNr.-Formatvalidierung (VatIdUtil, EU-Länderformate) in Profil- Rechnungsdaten sowie Kunde anlegen/bearbeiten, mit Unit-Tests - Rechnungsliste: Aktions-Buttons durch Icon-Buttons mit Tooltip ersetzt - Jobs-Seite: CSV-Export als Icon-Button, Filter greifen automatisch, "Filter anwenden"-Button entfernt - Neue Übersetzungsschlüssel in allen 10 Sprachdateien Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,7 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
ustId.setRequiredIndicatorVisible(true);
|
ustId.setRequiredIndicatorVisible(true);
|
||||||
ustId.setPlaceholder("DE123456789");
|
ustId.setPlaceholder("DE123456789");
|
||||||
ustId.setWidthFull();
|
ustId.setWidthFull();
|
||||||
ustId.addBlurListener(e -> validateField(ustId));
|
ustId.addBlurListener(e -> validateUstId());
|
||||||
|
|
||||||
// Anrede (Dropdown)
|
// Anrede (Dropdown)
|
||||||
title = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
title = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||||
@@ -184,8 +184,10 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
binder.forField(taxNumber).asRequired(getTranslation("profile.validation.taxnumber.required"))
|
binder.forField(taxNumber).asRequired(getTranslation("profile.validation.taxnumber.required"))
|
||||||
.bind(Customer::getTaxNumber, Customer::setTaxNumber);
|
.bind(Customer::getTaxNumber, Customer::setTaxNumber);
|
||||||
|
|
||||||
binder.forField(ustId).asRequired(getTranslation("profile.validation.ustid.required")).bind(Customer::getUstId,
|
binder.forField(ustId).asRequired(getTranslation("profile.validation.ustid.required"))
|
||||||
Customer::setUstId);
|
.withValidator(de.assecutor.votianlt.util.VatIdUtil::isValid,
|
||||||
|
getTranslation("profile.validation.ustid.invalid"))
|
||||||
|
.bind(Customer::getUstId, Customer::setUstId);
|
||||||
|
|
||||||
binder.forField(title).bind(Customer::getTitle, Customer::setTitle);
|
binder.forField(title).bind(Customer::getTitle, Customer::setTitle);
|
||||||
|
|
||||||
@@ -262,6 +264,24 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft die USt-IdNr. auf Pflichtangabe und gültiges Format
|
||||||
|
* (Länderkennzeichen + länderspezifischer Aufbau, z. B. DE123456789).
|
||||||
|
*/
|
||||||
|
private void validateUstId() {
|
||||||
|
String value = ustId.getValue();
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
ustId.setInvalid(true);
|
||||||
|
ustId.setErrorMessage(getTranslation("profile.validation.ustid.required"));
|
||||||
|
} else if (!de.assecutor.votianlt.util.VatIdUtil.isValid(value)) {
|
||||||
|
ustId.setInvalid(true);
|
||||||
|
ustId.setErrorMessage(getTranslation("profile.validation.ustid.invalid"));
|
||||||
|
} else {
|
||||||
|
ustId.setInvalid(false);
|
||||||
|
ustId.setErrorMessage("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateField(TextField field) {
|
private void validateField(TextField field) {
|
||||||
String value = field.getValue();
|
String value = field.getValue();
|
||||||
if (value == null || value.trim().isEmpty()) {
|
if (value == null || value.trim().isEmpty()) {
|
||||||
@@ -286,7 +306,7 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
private boolean validateAllFields() {
|
private boolean validateAllFields() {
|
||||||
validateField(taxNumber);
|
validateField(taxNumber);
|
||||||
validateField(ustId);
|
validateUstId();
|
||||||
validateField(firstName);
|
validateField(firstName);
|
||||||
validateField(lastName);
|
validateField(lastName);
|
||||||
validateField(telephone);
|
validateField(telephone);
|
||||||
|
|||||||
@@ -33,9 +33,12 @@ import de.assecutor.votianlt.repository.ServiceRepository;
|
|||||||
import de.assecutor.votianlt.repository.UserRepository;
|
import de.assecutor.votianlt.repository.UserRepository;
|
||||||
import de.assecutor.votianlt.security.SecurityService;
|
import de.assecutor.votianlt.security.SecurityService;
|
||||||
import de.assecutor.votianlt.service.CustomerInvoiceService;
|
import de.assecutor.votianlt.service.CustomerInvoiceService;
|
||||||
|
import de.assecutor.votianlt.service.EmailService;
|
||||||
import de.assecutor.votianlt.service.InvoiceLifecycleException;
|
import de.assecutor.votianlt.service.InvoiceLifecycleException;
|
||||||
import de.assecutor.votianlt.service.InvoiceLifecycleService;
|
import de.assecutor.votianlt.service.InvoiceLifecycleService;
|
||||||
import de.assecutor.votianlt.service.InvoiceTemplateService;
|
import de.assecutor.votianlt.service.InvoiceTemplateService;
|
||||||
|
import de.assecutor.votianlt.service.PdfToolService;
|
||||||
|
import de.assecutor.votianlt.service.UserInvoiceZugferdService;
|
||||||
import jakarta.annotation.security.RolesAllowed;
|
import jakarta.annotation.security.RolesAllowed;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.bson.types.ObjectId;
|
import org.bson.types.ObjectId;
|
||||||
@@ -66,6 +69,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
private final UserInvoiceDataService userInvoiceDataService;
|
private final UserInvoiceDataService userInvoiceDataService;
|
||||||
private final CustomerService customerService;
|
private final CustomerService customerService;
|
||||||
private final InvoiceLifecycleService invoiceLifecycleService;
|
private final InvoiceLifecycleService invoiceLifecycleService;
|
||||||
|
private final UserInvoiceZugferdService userInvoiceZugferdService;
|
||||||
|
private final EmailService emailService;
|
||||||
private User currentUser;
|
private User currentUser;
|
||||||
private Job currentJob;
|
private Job currentJob;
|
||||||
private List<ServiceRow> gridRows = new ArrayList<>();
|
private List<ServiceRow> gridRows = new ArrayList<>();
|
||||||
@@ -120,7 +125,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
UserRepository userRepository, CustomerInvoiceService customerInvoiceService,
|
UserRepository userRepository, CustomerInvoiceService customerInvoiceService,
|
||||||
InvoiceTemplateService invoiceTemplateService, SecurityService securityService,
|
InvoiceTemplateService invoiceTemplateService, SecurityService securityService,
|
||||||
UserInvoiceDataService userInvoiceDataService, CustomerService customerService,
|
UserInvoiceDataService userInvoiceDataService, CustomerService customerService,
|
||||||
InvoiceLifecycleService invoiceLifecycleService) {
|
InvoiceLifecycleService invoiceLifecycleService, UserInvoiceZugferdService userInvoiceZugferdService,
|
||||||
|
EmailService emailService) {
|
||||||
this.jobRepository = jobRepository;
|
this.jobRepository = jobRepository;
|
||||||
this.serviceRepository = serviceRepository;
|
this.serviceRepository = serviceRepository;
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
@@ -130,6 +136,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
this.userInvoiceDataService = userInvoiceDataService;
|
this.userInvoiceDataService = userInvoiceDataService;
|
||||||
this.customerService = customerService;
|
this.customerService = customerService;
|
||||||
this.invoiceLifecycleService = invoiceLifecycleService;
|
this.invoiceLifecycleService = invoiceLifecycleService;
|
||||||
|
this.userInvoiceZugferdService = userInvoiceZugferdService;
|
||||||
|
this.emailService = emailService;
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
setPadding(true);
|
setPadding(true);
|
||||||
setSpacing(true);
|
setSpacing(true);
|
||||||
@@ -557,7 +565,19 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser,
|
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser,
|
||||||
getTranslation("createinvoice.preview.number"));
|
getTranslation("createinvoice.preview.number"));
|
||||||
|
|
||||||
showPdfPreviewDialog(pdfBytes, templateData, currentUser);
|
// Schon für die Vorschau das ZUGFeRD-ZIP über das PDF Tool erzeugen
|
||||||
|
// und das signierte PDF aus dem ZIP anzeigen — so sieht der Nutzer
|
||||||
|
// exakt das Dokument, das später versendet wird.
|
||||||
|
CustomerInvoice previewInvoice = buildInvoice(getTranslation("createinvoice.preview.number"), pdfBytes,
|
||||||
|
currentUser);
|
||||||
|
PdfToolService.SignedInvoice signed = userInvoiceZugferdService.sign(previewInvoice);
|
||||||
|
if (!signed.valid()) {
|
||||||
|
Notification.show(getTranslation("invoices.notification.zugferd.invalid"), 7000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
|
}
|
||||||
|
byte[] signedPdf = extractPdfFromZip(signed.zip());
|
||||||
|
|
||||||
|
showPdfPreviewDialog(signedPdf != null ? signedPdf : pdfBytes, templateData, currentUser);
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("Fehler beim Erstellen der Rechnungsvorschau", ex);
|
log.error("Fehler beim Erstellen der Rechnungsvorschau", ex);
|
||||||
@@ -566,17 +586,18 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveInvoice(String templateData, User user, Dialog previewDialog) {
|
/**
|
||||||
try {
|
* Baut die (noch nicht persistierte) Rechnung mit allen Pflichtangaben nach
|
||||||
String invoiceNumber = userInvoiceDataService.generateNextInvoiceNumber(user.getId());
|
* §14 UStG auf — auch Grundlage der ZUGFeRD-Metadaten für die E-Rechnung.
|
||||||
|
* Wird sowohl für die Vorschau (Platzhalter-Nummer, transient) als auch für
|
||||||
|
* das endgültige Speichern verwendet.
|
||||||
|
*/
|
||||||
|
private CustomerInvoice buildInvoice(String invoiceNumber, byte[] pdfBytes, User user) {
|
||||||
BigDecimal netAmount = calculateNetAmount();
|
BigDecimal netAmount = calculateNetAmount();
|
||||||
BigDecimal vatRate = effectiveVatRate();
|
BigDecimal vatRate = effectiveVatRate();
|
||||||
BigDecimal vatAmount = netAmount.multiply(vatRate);
|
BigDecimal vatAmount = netAmount.multiply(vatRate);
|
||||||
BigDecimal totalAmount = netAmount.add(vatAmount);
|
BigDecimal totalAmount = netAmount.add(vatAmount);
|
||||||
|
|
||||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, user, invoiceNumber);
|
|
||||||
|
|
||||||
CustomerInvoice invoice = new CustomerInvoice();
|
CustomerInvoice invoice = new CustomerInvoice();
|
||||||
invoice.setInvoiceNumber(invoiceNumber);
|
invoice.setInvoiceNumber(invoiceNumber);
|
||||||
invoice.setInvoiceDate(java.time.LocalDate.now());
|
invoice.setInvoiceDate(java.time.LocalDate.now());
|
||||||
@@ -587,8 +608,6 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
invoice.setVatAmount(vatAmount);
|
invoice.setVatAmount(vatAmount);
|
||||||
invoice.setTotalAmount(totalAmount);
|
invoice.setTotalAmount(totalAmount);
|
||||||
|
|
||||||
// Pflichtangaben nach §14 UStG: Leistungsdatum, Absender, Empfänger und
|
|
||||||
// Positionen — auch Grundlage der ZUGFeRD-Metadaten für die E-Rechnung.
|
|
||||||
invoice.setDeliveryDate(currentJob.getDeliveryDate() != null ? currentJob.getDeliveryDate()
|
invoice.setDeliveryDate(currentJob.getDeliveryDate() != null ? currentJob.getDeliveryDate()
|
||||||
: java.time.LocalDate.now());
|
: java.time.LocalDate.now());
|
||||||
applySenderData(invoice, user);
|
applySenderData(invoice, user);
|
||||||
@@ -596,6 +615,18 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
invoice.setItems(buildInvoiceItems(vatRate));
|
invoice.setItems(buildInvoiceItems(vatRate));
|
||||||
|
|
||||||
invoice.setPdfData(pdfBytes);
|
invoice.setPdfData(pdfBytes);
|
||||||
|
return invoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert die Rechnung endgültig und versendet sie anschließend als
|
||||||
|
* signierte ZUGFeRD-E-Rechnung (ZIP) per E-Mail an den Kunden.
|
||||||
|
*/
|
||||||
|
private void saveAndSendInvoice(String templateData, User user, Dialog previewDialog) {
|
||||||
|
try {
|
||||||
|
String invoiceNumber = userInvoiceDataService.generateNextInvoiceNumber(user.getId());
|
||||||
|
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, user, invoiceNumber);
|
||||||
|
CustomerInvoice invoice = buildInvoice(invoiceNumber, pdfBytes, user);
|
||||||
|
|
||||||
// Finalisierung mit Audit-Eintrag und Eindeutigkeitsprüfung der Rechnungsnummer (R-07/R-11/R-36).
|
// Finalisierung mit Audit-Eintrag und Eindeutigkeitsprüfung der Rechnungsnummer (R-07/R-11/R-36).
|
||||||
CustomerInvoice savedInvoice = invoiceLifecycleService.createAndIssue(invoice,
|
CustomerInvoice savedInvoice = invoiceLifecycleService.createAndIssue(invoice,
|
||||||
@@ -608,6 +639,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
Notification.show(getTranslation("createinvoice.notification.saved", invoiceNumber), 4000,
|
Notification.show(getTranslation("createinvoice.notification.saved", invoiceNumber), 4000,
|
||||||
Notification.Position.BOTTOM_END);
|
Notification.Position.BOTTOM_END);
|
||||||
|
|
||||||
|
sendInvoiceEmail(savedInvoice);
|
||||||
|
|
||||||
} catch (InvoiceLifecycleException lifecycleEx) {
|
} catch (InvoiceLifecycleException lifecycleEx) {
|
||||||
log.warn("Lifecycle-Verstoß beim Speichern der Rechnung: {}", lifecycleEx.getMessage());
|
log.warn("Lifecycle-Verstoß beim Speichern der Rechnung: {}", lifecycleEx.getMessage());
|
||||||
Notification.show(lifecycleEx.getMessage(), 5000, Notification.Position.MIDDLE);
|
Notification.show(lifecycleEx.getMessage(), 5000, Notification.Position.MIDDLE);
|
||||||
@@ -618,6 +651,66 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Versendet die gespeicherte Rechnung als E-Rechnungs-ZIP per E-Mail.
|
||||||
|
* Besteht die Rechnung die Validierung nicht, wird der Versand abgebrochen —
|
||||||
|
* die Rechnung bleibt gespeichert und kann aus der Rechnungsliste erneut
|
||||||
|
* versendet werden.
|
||||||
|
*/
|
||||||
|
private void sendInvoiceEmail(CustomerInvoice savedInvoice) {
|
||||||
|
String recipientEmail = savedInvoice.getRecipientEmail();
|
||||||
|
if (recipientEmail == null || recipientEmail.isBlank()) {
|
||||||
|
Notification.show(getTranslation("invoices.notification.email.missing"), 6000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
PdfToolService.SignedInvoice signed = userInvoiceZugferdService.sign(savedInvoice);
|
||||||
|
if (!signed.valid()) {
|
||||||
|
log.warn("Versand der Rechnung {} abgebrochen, ZUGFeRD-Validierung fehlgeschlagen",
|
||||||
|
savedInvoice.getInvoiceNumber());
|
||||||
|
Notification.show(
|
||||||
|
getTranslation("invoices.notification.zugferd.invalidsend", savedInvoice.getInvoiceNumber()),
|
||||||
|
7000, Notification.Position.MIDDLE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emailService.sendEmailWithAttachment(recipientEmail,
|
||||||
|
getTranslation("invoices.mail.subject", savedInvoice.getInvoiceNumber()),
|
||||||
|
getTranslation("invoices.mail.body", savedInvoice.getInvoiceNumber(),
|
||||||
|
safe(savedInvoice.getSenderName())),
|
||||||
|
signed.zip(), signed.fileName());
|
||||||
|
|
||||||
|
invoiceLifecycleService.markAsSent(savedInvoice.getId(),
|
||||||
|
"Als E-Rechnung per E-Mail versendet an " + recipientEmail);
|
||||||
|
Notification.show(
|
||||||
|
getTranslation("createinvoice.notification.sent", savedInvoice.getInvoiceNumber(), recipientEmail),
|
||||||
|
5000, Notification.Position.BOTTOM_END);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("Fehler beim Versand der Rechnung {} an {}: {}", savedInvoice.getInvoiceNumber(), recipientEmail,
|
||||||
|
ex.getMessage(), ex);
|
||||||
|
Notification.show(getTranslation("invoices.notification.zugferd.error", ex.getMessage()), 7000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrahiert das signierte PDF (PDF/A-3) aus dem vom PDF Tool gelieferten
|
||||||
|
* ZIP; {@code null}, wenn kein PDF-Eintrag enthalten ist.
|
||||||
|
*/
|
||||||
|
private byte[] extractPdfFromZip(byte[] zipBytes) throws java.io.IOException {
|
||||||
|
try (java.util.zip.ZipInputStream zip = new java.util.zip.ZipInputStream(
|
||||||
|
new java.io.ByteArrayInputStream(zipBytes))) {
|
||||||
|
java.util.zip.ZipEntry entry;
|
||||||
|
while ((entry = zip.getNextEntry()) != null) {
|
||||||
|
if (!entry.isDirectory() && entry.getName().toLowerCase().endsWith(".pdf")) {
|
||||||
|
return zip.readAllBytes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Absenderdaten aus dem Nutzerprofil und den Rechnungs-Stammdaten
|
* Absenderdaten aus dem Nutzerprofil und den Rechnungs-Stammdaten
|
||||||
* (USt-IdNr./Steuernummer, Bankverbindung, Zahlungsbedingungen) übernehmen.
|
* (USt-IdNr./Steuernummer, Bankverbindung, Zahlungsbedingungen) übernehmen.
|
||||||
@@ -844,21 +937,21 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
|
Button closeButton = new Button(getTranslation("button.close"), e -> pdfDialog.close());
|
||||||
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
|
|
||||||
Button saveButton = new Button(getTranslation("createinvoice.button.save"), e -> {
|
Button sendButton = new Button(getTranslation("createinvoice.button.send"), e -> {
|
||||||
Dialog confirm = DialogStylingHelper.createConfirmationDialog(
|
Dialog confirm = DialogStylingHelper.createConfirmationDialog(
|
||||||
getTranslation("createinvoice.confirm.save.title"),
|
getTranslation("createinvoice.confirm.send.title"),
|
||||||
getTranslation("createinvoice.confirm.save.message"),
|
getTranslation("createinvoice.confirm.send.message"),
|
||||||
"560px",
|
"560px",
|
||||||
getTranslation("button.cancel"),
|
getTranslation("button.cancel"),
|
||||||
getTranslation("createinvoice.confirm.save.confirm"),
|
getTranslation("createinvoice.confirm.send.confirm"),
|
||||||
() -> saveInvoice(templateData, user, pdfDialog),
|
() -> saveAndSendInvoice(templateData, user, pdfDialog),
|
||||||
ButtonVariant.LUMO_PRIMARY);
|
ButtonVariant.LUMO_PRIMARY);
|
||||||
confirm.open();
|
confirm.open();
|
||||||
});
|
});
|
||||||
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
sendButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||||
|
|
||||||
pdfDialog.add(DialogStylingHelper.wrapContent(pdfFrame, true));
|
pdfDialog.add(DialogStylingHelper.wrapContent(pdfFrame, true));
|
||||||
pdfDialog.getFooter().add(closeButton, saveButton);
|
pdfDialog.getFooter().add(closeButton, sendButton);
|
||||||
pdfDialog.open();
|
pdfDialog.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,8 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
binder.forField(taxNumberField).asRequired(getTranslation("profile.validation.taxnumber.required"))
|
binder.forField(taxNumberField).asRequired(getTranslation("profile.validation.taxnumber.required"))
|
||||||
.bind(Customer::getTaxNumber, Customer::setTaxNumber);
|
.bind(Customer::getTaxNumber, Customer::setTaxNumber);
|
||||||
binder.forField(ustIdField).asRequired(getTranslation("profile.validation.ustid.required"))
|
binder.forField(ustIdField).asRequired(getTranslation("profile.validation.ustid.required"))
|
||||||
|
.withValidator(de.assecutor.votianlt.util.VatIdUtil::isValid,
|
||||||
|
getTranslation("profile.validation.ustid.invalid"))
|
||||||
.bind(Customer::getUstId, Customer::setUstId);
|
.bind(Customer::getUstId, Customer::setUstId);
|
||||||
binder.forField(firstnameField).bind(Customer::getFirstname, Customer::setFirstname);
|
binder.forField(firstnameField).bind(Customer::getFirstname, Customer::setFirstname);
|
||||||
binder.forField(lastNameField).bind(Customer::getLastName, Customer::setLastName);
|
binder.forField(lastNameField).bind(Customer::getLastName, Customer::setLastName);
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
ustIdField.setPlaceholder("DE123456789");
|
ustIdField.setPlaceholder("DE123456789");
|
||||||
ustIdField.setRequiredIndicatorVisible(true);
|
ustIdField.setRequiredIndicatorVisible(true);
|
||||||
ustIdField.addBlurListener(e -> {
|
ustIdField.addBlurListener(e -> {
|
||||||
validateField(ustIdField, getTranslation("profile.validation.ustid.required"));
|
validateUstIdField();
|
||||||
saveInvoiceData();
|
saveInvoiceData();
|
||||||
});
|
});
|
||||||
taxNumberField.setLabel(getTranslation("profile.billing.taxnumber"));
|
taxNumberField.setLabel(getTranslation("profile.billing.taxnumber"));
|
||||||
@@ -650,7 +650,7 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
// Steuernummer und USt-IdNr. sind Pflicht, sobald die Rechnungslegung
|
// Steuernummer und USt-IdNr. sind Pflicht, sobald die Rechnungslegung
|
||||||
// aktiviert ist
|
// aktiviert ist
|
||||||
if (billingEnabled.getValue()) {
|
if (billingEnabled.getValue()) {
|
||||||
validateField(ustIdField, getTranslation("profile.validation.ustid.required"));
|
validateUstIdField();
|
||||||
validateField(taxNumberField, getTranslation("profile.validation.taxnumber.required"));
|
validateField(taxNumberField, getTranslation("profile.validation.taxnumber.required"));
|
||||||
isValid = isValid && !ustIdField.isInvalid() && !taxNumberField.isInvalid();
|
isValid = isValid && !ustIdField.isInvalid() && !taxNumberField.isInvalid();
|
||||||
}
|
}
|
||||||
@@ -864,6 +864,24 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
return value != null ? value : "";
|
return value != null ? value : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft die USt-IdNr. auf Pflichtangabe und gültiges Format
|
||||||
|
* (Länderkennzeichen + länderspezifischer Aufbau, z. B. DE123456789).
|
||||||
|
*/
|
||||||
|
private void validateUstIdField() {
|
||||||
|
String value = ustIdField.getValue();
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
ustIdField.setInvalid(true);
|
||||||
|
ustIdField.setErrorMessage(getTranslation("profile.validation.ustid.required"));
|
||||||
|
} else if (!de.assecutor.votianlt.util.VatIdUtil.isValid(value)) {
|
||||||
|
ustIdField.setInvalid(true);
|
||||||
|
ustIdField.setErrorMessage(getTranslation("profile.validation.ustid.invalid"));
|
||||||
|
} else {
|
||||||
|
ustIdField.setInvalid(false);
|
||||||
|
ustIdField.setErrorMessage("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateField(TextField field, String errorMessage) {
|
private void validateField(TextField field, String errorMessage) {
|
||||||
String value = field.getValue();
|
String value = field.getValue();
|
||||||
if (value == null || value.trim().isEmpty()) {
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import com.vaadin.flow.component.Component;
|
|||||||
import com.vaadin.flow.component.UI;
|
import com.vaadin.flow.component.UI;
|
||||||
import com.vaadin.flow.component.button.Button;
|
import com.vaadin.flow.component.button.Button;
|
||||||
import com.vaadin.flow.component.button.ButtonVariant;
|
import com.vaadin.flow.component.button.ButtonVariant;
|
||||||
|
import com.vaadin.flow.component.icon.Icon;
|
||||||
|
import com.vaadin.flow.component.icon.VaadinIcon;
|
||||||
import com.vaadin.flow.component.dialog.Dialog;
|
import com.vaadin.flow.component.dialog.Dialog;
|
||||||
import com.vaadin.flow.component.grid.Grid;
|
import com.vaadin.flow.component.grid.Grid;
|
||||||
import com.vaadin.flow.component.html.Anchor;
|
import com.vaadin.flow.component.html.Anchor;
|
||||||
@@ -186,13 +188,15 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
actions.setSpacing(true);
|
actions.setSpacing(true);
|
||||||
actions.setPadding(false);
|
actions.setPadding(false);
|
||||||
|
|
||||||
Button viewBtn = new Button(getTranslation("invoices.action.view"), e -> downloadInvoicePdf(invoice));
|
Button viewBtn = actionIconButton(VaadinIcon.EYE, "invoices.action.view",
|
||||||
viewBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL);
|
e -> downloadInvoicePdf(invoice));
|
||||||
|
viewBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
viewBtn.setEnabled(invoice.getPdfData() != null && invoice.getPdfData().length > 0);
|
viewBtn.setEnabled(invoice.getPdfData() != null && invoice.getPdfData().length > 0);
|
||||||
actions.add(viewBtn);
|
actions.add(viewBtn);
|
||||||
|
|
||||||
Button historyBtn = new Button(getTranslation("invoices.action.history"), e -> openHistoryDialog(invoice));
|
Button historyBtn = actionIconButton(VaadinIcon.TIME_BACKWARD, "invoices.action.history",
|
||||||
historyBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL);
|
e -> openHistoryDialog(invoice));
|
||||||
|
historyBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
actions.add(historyBtn);
|
actions.add(historyBtn);
|
||||||
|
|
||||||
InvoiceStatus status = invoice.getStatus() != null ? invoice.getStatus() : InvoiceStatus.ISSUED;
|
InvoiceStatus status = invoice.getStatus() != null ? invoice.getStatus() : InvoiceStatus.ISSUED;
|
||||||
@@ -205,40 +209,38 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
|
|
||||||
if (type == InvoiceType.INVOICE && status == InvoiceStatus.ISSUED
|
if (type == InvoiceType.INVOICE && status == InvoiceStatus.ISSUED
|
||||||
&& invoicePermissionService.canMarkAsSent(currentUser)) {
|
&& invoicePermissionService.canMarkAsSent(currentUser)) {
|
||||||
Button sentBtn = new Button(getTranslation("invoices.action.marksent"),
|
Button sentBtn = actionIconButton(VaadinIcon.CHECK, "invoices.action.marksent",
|
||||||
e -> markAsSent(invoice));
|
e -> markAsSent(invoice));
|
||||||
sentBtn.addThemeVariants(ButtonVariant.LUMO_SMALL);
|
|
||||||
actions.add(sentBtn);
|
actions.add(sentBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// E-Rechnung (ZUGFeRD) über das externe PDF Tool — analog zu den
|
// E-Rechnung (ZUGFeRD) über das externe PDF Tool — analog zu den
|
||||||
// System-Rechnungen der Admin-Seite
|
// System-Rechnungen der Admin-Seite
|
||||||
if (type == InvoiceType.INVOICE && invoice.getPdfData() != null && invoice.getPdfData().length > 0) {
|
if (type == InvoiceType.INVOICE && invoice.getPdfData() != null && invoice.getPdfData().length > 0) {
|
||||||
Button zugferdBtn = new Button(getTranslation("invoices.action.zugferd"),
|
Button zugferdBtn = actionIconButton(VaadinIcon.FILE_ZIP, "invoices.action.zugferd",
|
||||||
e -> downloadZugferdZip(invoice));
|
e -> downloadZugferdZip(invoice));
|
||||||
zugferdBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL);
|
zugferdBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
actions.add(zugferdBtn);
|
actions.add(zugferdBtn);
|
||||||
|
|
||||||
if (isLiveInvoice && invoicePermissionService.canMarkAsSent(currentUser)) {
|
if (isLiveInvoice && invoicePermissionService.canMarkAsSent(currentUser)) {
|
||||||
Button sendBtn = new Button(getTranslation("invoices.action.sendinvoice"),
|
Button sendBtn = actionIconButton(VaadinIcon.PAPERPLANE, "invoices.action.sendinvoice",
|
||||||
e -> sendAsEInvoice(invoice));
|
e -> sendAsEInvoice(invoice));
|
||||||
sendBtn.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_PRIMARY);
|
sendBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
||||||
actions.add(sendBtn);
|
actions.add(sendBtn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLiveInvoice) {
|
if (isLiveInvoice) {
|
||||||
if (invoicePermissionService.canCorrect(currentUser)) {
|
if (invoicePermissionService.canCorrect(currentUser)) {
|
||||||
Button correctBtn = new Button(getTranslation("invoices.action.correct"),
|
Button correctBtn = actionIconButton(VaadinIcon.EDIT, "invoices.action.correct",
|
||||||
e -> openCorrectionDialog(invoice));
|
e -> openCorrectionDialog(invoice));
|
||||||
correctBtn.addThemeVariants(ButtonVariant.LUMO_SMALL);
|
|
||||||
actions.add(correctBtn);
|
actions.add(correctBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invoicePermissionService.canCancel(currentUser)) {
|
if (invoicePermissionService.canCancel(currentUser)) {
|
||||||
Button cancelBtn = new Button(getTranslation("invoices.action.cancel"),
|
Button cancelBtn = actionIconButton(VaadinIcon.BAN, "invoices.action.cancel",
|
||||||
e -> openCancellationDialog(invoice));
|
e -> openCancellationDialog(invoice));
|
||||||
cancelBtn.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_ERROR);
|
cancelBtn.addThemeVariants(ButtonVariant.LUMO_ERROR);
|
||||||
actions.add(cancelBtn);
|
actions.add(cancelBtn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -246,20 +248,33 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
// Zahlung erfassen: nur für reguläre Rechnungen (R-25)
|
// Zahlung erfassen: nur für reguläre Rechnungen (R-25)
|
||||||
if (type == InvoiceType.INVOICE && status != InvoiceStatus.DRAFT
|
if (type == InvoiceType.INVOICE && status != InvoiceStatus.DRAFT
|
||||||
&& invoicePermissionService.canRecordPayment(currentUser)) {
|
&& invoicePermissionService.canRecordPayment(currentUser)) {
|
||||||
Button payBtn = new Button(getTranslation("invoices.action.payment"),
|
Button payBtn = actionIconButton(VaadinIcon.EURO, "invoices.action.payment",
|
||||||
e -> openPaymentDialog(invoice));
|
e -> openPaymentDialog(invoice));
|
||||||
payBtn.addThemeVariants(ButtonVariant.LUMO_SMALL);
|
|
||||||
actions.add(payBtn);
|
actions.add(payBtn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Belegpaket exportieren (R-33/R-34)
|
// Belegpaket exportieren (R-33/R-34)
|
||||||
Button exportBtn = new Button(getTranslation("invoices.action.export"), e -> exportPackage(invoice));
|
Button exportBtn = actionIconButton(VaadinIcon.DOWNLOAD, "invoices.action.export",
|
||||||
exportBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY, ButtonVariant.LUMO_SMALL);
|
e -> exportPackage(invoice));
|
||||||
|
exportBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
actions.add(exportBtn);
|
actions.add(exportBtn);
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Icon-Button für die Aktionsspalte: die bisherige Button-Beschriftung
|
||||||
|
* bleibt als Tooltip (Bubblehelp) und Aria-Label erhalten.
|
||||||
|
*/
|
||||||
|
private Button actionIconButton(VaadinIcon icon, String labelKey,
|
||||||
|
com.vaadin.flow.component.ComponentEventListener<com.vaadin.flow.component.ClickEvent<Button>> listener) {
|
||||||
|
Button button = new Button(new Icon(icon), listener);
|
||||||
|
button.addThemeVariants(ButtonVariant.LUMO_SMALL, ButtonVariant.LUMO_ICON);
|
||||||
|
button.setTooltipText(getTranslation(labelKey));
|
||||||
|
button.setAriaLabel(getTranslation(labelKey));
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
private void openPaymentDialog(CustomerInvoice invoice) {
|
private void openPaymentDialog(CustomerInvoice invoice) {
|
||||||
Dialog dialog = DialogStylingHelper.createStyledDialog(
|
Dialog dialog = DialogStylingHelper.createStyledDialog(
|
||||||
getTranslation("invoices.payment.title", invoice.getInvoiceNumber()), "480px");
|
getTranslation("invoices.payment.title", invoice.getInvoiceNumber()), "480px");
|
||||||
|
|||||||
@@ -70,8 +70,10 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
endDate.setLabel(getTranslation("jobs.filter.enddate"));
|
endDate.setLabel(getTranslation("jobs.filter.enddate"));
|
||||||
searchField.setLabel(getTranslation("jobs.filter.search"));
|
searchField.setLabel(getTranslation("jobs.filter.search"));
|
||||||
statusFilter.setLabel(getTranslation("jobs.filter.status"));
|
statusFilter.setLabel(getTranslation("jobs.filter.status"));
|
||||||
Button exportButton = new Button(getTranslation("jobs.button.csvexport"));
|
Button exportButton = new Button(new Icon(VaadinIcon.DOWNLOAD));
|
||||||
exportButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
exportButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
|
exportButton.setTooltipText(getTranslation("jobs.button.csvexport"));
|
||||||
|
exportButton.setAriaLabel(getTranslation("jobs.button.csvexport"));
|
||||||
ViewToolbar toolbar = new ViewToolbar(getTranslation("nav.jobs"), exportButton);
|
ViewToolbar toolbar = new ViewToolbar(getTranslation("nav.jobs"), exportButton);
|
||||||
add(toolbar);
|
add(toolbar);
|
||||||
|
|
||||||
@@ -85,9 +87,7 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
searchField.setClearButtonVisible(true);
|
searchField.setClearButtonVisible(true);
|
||||||
|
|
||||||
// Filterleiste mit Export-Button am rechten Rand
|
// Filterleiste mit Export-Button am rechten Rand
|
||||||
Button applyFilter = new Button(getTranslation("jobs.filter.apply"));
|
HorizontalLayout leftFilters = new HorizontalLayout(startDate, endDate, searchField, statusFilter);
|
||||||
applyFilter.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
|
|
||||||
HorizontalLayout leftFilters = new HorizontalLayout(startDate, endDate, searchField, statusFilter, applyFilter);
|
|
||||||
leftFilters.setAlignItems(Alignment.END);
|
leftFilters.setAlignItems(Alignment.END);
|
||||||
leftFilters.getStyle().set("flex-wrap", "wrap");
|
leftFilters.getStyle().set("flex-wrap", "wrap");
|
||||||
|
|
||||||
@@ -105,7 +105,6 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
java.time.LocalDate today = java.time.LocalDate.now();
|
java.time.LocalDate today = java.time.LocalDate.now();
|
||||||
startDate.setValue(today.minusDays(30));
|
startDate.setValue(today.minusDays(30));
|
||||||
endDate.setValue(today);
|
endDate.setValue(today);
|
||||||
applyFilter.addClickListener(e -> loadData());
|
|
||||||
exportButton.addClickListener(e -> exportToCsv());
|
exportButton.addClickListener(e -> exportToCsv());
|
||||||
|
|
||||||
// Add real-time filtering
|
// Add real-time filtering
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package de.assecutor.votianlt.util;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formatvalidierung für Umsatzsteuer-Identifikationsnummern (USt-IdNr.).
|
||||||
|
*
|
||||||
|
* Prüft den Aufbau nach Länderkennzeichen gemäß den offiziellen Formaten der
|
||||||
|
* EU-Mitgliedstaaten (z. B. DE + 9 Ziffern). Für Länderkennzeichen ohne
|
||||||
|
* hinterlegtes Muster wird ein generisches Format (2 Buchstaben + 2–12
|
||||||
|
* alphanumerische Zeichen) akzeptiert. Es findet keine Online-Prüfung (VIES)
|
||||||
|
* statt — validiert wird ausschließlich das Format.
|
||||||
|
*/
|
||||||
|
public final class VatIdUtil {
|
||||||
|
|
||||||
|
/** Offizielle USt-IdNr.-Formate der EU-Mitgliedstaaten (ohne Länderpräfix). */
|
||||||
|
private static final Map<String, Pattern> COUNTRY_PATTERNS = Map.ofEntries(
|
||||||
|
Map.entry("AT", Pattern.compile("U\\d{8}")),
|
||||||
|
Map.entry("BE", Pattern.compile("[01]\\d{9}")),
|
||||||
|
Map.entry("BG", Pattern.compile("\\d{9,10}")),
|
||||||
|
Map.entry("CY", Pattern.compile("\\d{8}[A-Z]")),
|
||||||
|
Map.entry("CZ", Pattern.compile("\\d{8,10}")),
|
||||||
|
Map.entry("DE", Pattern.compile("\\d{9}")),
|
||||||
|
Map.entry("DK", Pattern.compile("\\d{8}")),
|
||||||
|
Map.entry("EE", Pattern.compile("\\d{9}")),
|
||||||
|
Map.entry("EL", Pattern.compile("\\d{9}")),
|
||||||
|
Map.entry("ES", Pattern.compile("[A-Z0-9]\\d{7}[A-Z0-9]")),
|
||||||
|
Map.entry("FI", Pattern.compile("\\d{8}")),
|
||||||
|
Map.entry("FR", Pattern.compile("[A-Z0-9]{2}\\d{9}")),
|
||||||
|
Map.entry("HR", Pattern.compile("\\d{11}")),
|
||||||
|
Map.entry("HU", Pattern.compile("\\d{8}")),
|
||||||
|
Map.entry("IE", Pattern.compile("\\d{7}[A-Z]{1,2}|\\d[A-Z0-9+*]\\d{5}[A-Z]")),
|
||||||
|
Map.entry("IT", Pattern.compile("\\d{11}")),
|
||||||
|
Map.entry("LT", Pattern.compile("(\\d{9}|\\d{12})")),
|
||||||
|
Map.entry("LU", Pattern.compile("\\d{8}")),
|
||||||
|
Map.entry("LV", Pattern.compile("\\d{11}")),
|
||||||
|
Map.entry("MT", Pattern.compile("\\d{8}")),
|
||||||
|
Map.entry("NL", Pattern.compile("\\d{9}B\\d{2}")),
|
||||||
|
Map.entry("PL", Pattern.compile("\\d{10}")),
|
||||||
|
Map.entry("PT", Pattern.compile("\\d{9}")),
|
||||||
|
Map.entry("RO", Pattern.compile("\\d{2,10}")),
|
||||||
|
Map.entry("SE", Pattern.compile("\\d{12}")),
|
||||||
|
Map.entry("SI", Pattern.compile("\\d{8}")),
|
||||||
|
Map.entry("SK", Pattern.compile("\\d{10}")));
|
||||||
|
|
||||||
|
/** Rückfallmuster für Länderkennzeichen ohne hinterlegtes Format. */
|
||||||
|
private static final Pattern GENERIC_PATTERN = Pattern.compile("[A-Z]{2}[A-Z0-9]{2,12}");
|
||||||
|
|
||||||
|
private VatIdUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob der Wert eine formal gültige USt-IdNr. ist. Leerzeichen,
|
||||||
|
* Punkte und Bindestriche werden vor der Prüfung entfernt,
|
||||||
|
* Kleinbuchstaben akzeptiert. {@code null} oder leer gilt als ungültig.
|
||||||
|
*/
|
||||||
|
public static boolean isValid(String vatId) {
|
||||||
|
String normalized = normalize(vatId);
|
||||||
|
if (normalized == null || normalized.length() < 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String countryCode = normalized.substring(0, 2);
|
||||||
|
if (!countryCode.chars().allMatch(Character::isLetter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Pattern pattern = COUNTRY_PATTERNS.get(countryCode);
|
||||||
|
if (pattern != null) {
|
||||||
|
return pattern.matcher(normalized.substring(2)).matches();
|
||||||
|
}
|
||||||
|
return GENERIC_PATTERN.matcher(normalized).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Entfernt Trennzeichen und wandelt in Großbuchstaben um. */
|
||||||
|
public static String normalize(String vatId) {
|
||||||
|
if (vatId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return vatId.replaceAll("[\\s.\\-]", "").toUpperCase(java.util.Locale.ROOT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,7 @@ profile.validation.zip.required=Postleitzahl ist erforderlich
|
|||||||
profile.validation.city.required=Stadt ist erforderlich
|
profile.validation.city.required=Stadt ist erforderlich
|
||||||
profile.validation.taxnumber.required=Steuernummer ist erforderlich
|
profile.validation.taxnumber.required=Steuernummer ist erforderlich
|
||||||
profile.validation.ustid.required=USt-IdNr. ist erforderlich
|
profile.validation.ustid.required=USt-IdNr. ist erforderlich
|
||||||
|
profile.validation.ustid.invalid=Ungültiges Format der USt-IdNr. (z. B. DE123456789)
|
||||||
profile.validation.firstname.required=Vorname ist erforderlich
|
profile.validation.firstname.required=Vorname ist erforderlich
|
||||||
profile.validation.lastname.required=Nachname ist erforderlich
|
profile.validation.lastname.required=Nachname ist erforderlich
|
||||||
profile.validation.phone.required=Telefonnummer ist erforderlich
|
profile.validation.phone.required=Telefonnummer ist erforderlich
|
||||||
@@ -699,6 +700,11 @@ createinvoice.button.save=Speichern
|
|||||||
createinvoice.confirm.save.title=Rechnung speichern
|
createinvoice.confirm.save.title=Rechnung speichern
|
||||||
createinvoice.confirm.save.message=Diese Rechnung wird dauerhaft gespeichert und kann anschlie\u00dfend nicht mehr ver\u00e4ndert werden. Fortfahren?
|
createinvoice.confirm.save.message=Diese Rechnung wird dauerhaft gespeichert und kann anschlie\u00dfend nicht mehr ver\u00e4ndert werden. Fortfahren?
|
||||||
createinvoice.confirm.save.confirm=Ja, speichern
|
createinvoice.confirm.save.confirm=Ja, speichern
|
||||||
|
createinvoice.button.send=Senden
|
||||||
|
createinvoice.confirm.send.title=Rechnung senden
|
||||||
|
createinvoice.confirm.send.message=Diese Rechnung wird dauerhaft gespeichert und als E-Rechnung (ZIP) per E-Mail an den Kunden versendet. Fortfahren?
|
||||||
|
createinvoice.confirm.send.confirm=Ja, senden
|
||||||
|
createinvoice.notification.sent=Rechnung {0} wurde als E-Rechnung an {1} versendet
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Rechnungen
|
invoices.title=Rechnungen
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ profile.validation.zip.required=Postiindeks on kohustuslik
|
|||||||
profile.validation.city.required=Linn on kohustuslik
|
profile.validation.city.required=Linn on kohustuslik
|
||||||
profile.validation.taxnumber.required=Maksunumber on kohustuslik
|
profile.validation.taxnumber.required=Maksunumber on kohustuslik
|
||||||
profile.validation.ustid.required=KMKR nr on kohustuslik
|
profile.validation.ustid.required=KMKR nr on kohustuslik
|
||||||
|
profile.validation.ustid.invalid=KMKR numbri vorming on vale (nt DE123456789)
|
||||||
profile.validation.firstname.required=Eesnimi on kohustuslik
|
profile.validation.firstname.required=Eesnimi on kohustuslik
|
||||||
profile.validation.lastname.required=Perekonnanimi on kohustuslik
|
profile.validation.lastname.required=Perekonnanimi on kohustuslik
|
||||||
profile.validation.phone.required=Telefoninumber on kohustuslik
|
profile.validation.phone.required=Telefoninumber on kohustuslik
|
||||||
@@ -639,6 +640,11 @@ createinvoice.button.save=Salvesta
|
|||||||
createinvoice.confirm.save.title=Arve salvestamine
|
createinvoice.confirm.save.title=Arve salvestamine
|
||||||
createinvoice.confirm.save.message=See arve salvestatakse p\u00fcsivalt ja seda ei saa hiljem muuta. Kas j\u00e4tkata?
|
createinvoice.confirm.save.message=See arve salvestatakse p\u00fcsivalt ja seda ei saa hiljem muuta. Kas j\u00e4tkata?
|
||||||
createinvoice.confirm.save.confirm=Jah, salvesta
|
createinvoice.confirm.save.confirm=Jah, salvesta
|
||||||
|
createinvoice.button.send=Saada
|
||||||
|
createinvoice.confirm.send.title=Saada arve
|
||||||
|
createinvoice.confirm.send.message=Arve salvestatakse jäädavalt ja saadetakse kliendile e-posti teel e-arvena (ZIP). Kas jätkata?
|
||||||
|
createinvoice.confirm.send.confirm=Jah, saada
|
||||||
|
createinvoice.notification.sent=Arve {0} saadeti e-arvena aadressile {1}
|
||||||
invoices.title=Arved
|
invoices.title=Arved
|
||||||
invoices.column.number=Number
|
invoices.column.number=Number
|
||||||
invoices.column.customer=Klient
|
invoices.column.customer=Klient
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ profile.validation.zip.required=Postal code is required
|
|||||||
profile.validation.city.required=City is required
|
profile.validation.city.required=City is required
|
||||||
profile.validation.taxnumber.required=Tax number is required
|
profile.validation.taxnumber.required=Tax number is required
|
||||||
profile.validation.ustid.required=VAT ID is required
|
profile.validation.ustid.required=VAT ID is required
|
||||||
|
profile.validation.ustid.invalid=Invalid VAT ID format (e.g. DE123456789)
|
||||||
profile.validation.firstname.required=First name is required
|
profile.validation.firstname.required=First name is required
|
||||||
profile.validation.lastname.required=Last name is required
|
profile.validation.lastname.required=Last name is required
|
||||||
profile.validation.phone.required=Phone number is required
|
profile.validation.phone.required=Phone number is required
|
||||||
@@ -699,6 +700,11 @@ createinvoice.button.save=Save
|
|||||||
createinvoice.confirm.save.title=Save Invoice
|
createinvoice.confirm.save.title=Save Invoice
|
||||||
createinvoice.confirm.save.message=This invoice will be permanently saved and cannot be modified afterwards. Continue?
|
createinvoice.confirm.save.message=This invoice will be permanently saved and cannot be modified afterwards. Continue?
|
||||||
createinvoice.confirm.save.confirm=Yes, save
|
createinvoice.confirm.save.confirm=Yes, save
|
||||||
|
createinvoice.button.send=Send
|
||||||
|
createinvoice.confirm.send.title=Send Invoice
|
||||||
|
createinvoice.confirm.send.message=This invoice will be permanently saved and sent to the customer by email as an e-invoice (ZIP). Continue?
|
||||||
|
createinvoice.confirm.send.confirm=Yes, send
|
||||||
|
createinvoice.notification.sent=Invoice {0} has been sent as an e-invoice to {1}
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Invoices
|
invoices.title=Invoices
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ profile.validation.zip.required=C\u00f3digo postal es obligatorio
|
|||||||
profile.validation.city.required=Ciudad es obligatorio
|
profile.validation.city.required=Ciudad es obligatorio
|
||||||
profile.validation.taxnumber.required=Número fiscal es obligatorio
|
profile.validation.taxnumber.required=Número fiscal es obligatorio
|
||||||
profile.validation.ustid.required=NIF-IVA es obligatorio
|
profile.validation.ustid.required=NIF-IVA es obligatorio
|
||||||
|
profile.validation.ustid.invalid=Formato de NIF-IVA no válido (p. ej. DE123456789)
|
||||||
profile.validation.firstname.required=Nombre es obligatorio
|
profile.validation.firstname.required=Nombre es obligatorio
|
||||||
profile.validation.lastname.required=Apellido es obligatorio
|
profile.validation.lastname.required=Apellido es obligatorio
|
||||||
profile.validation.phone.required=N\u00famero de tel\u00e9fono es obligatorio
|
profile.validation.phone.required=N\u00famero de tel\u00e9fono es obligatorio
|
||||||
@@ -696,6 +697,11 @@ createinvoice.button.save=Guardar
|
|||||||
createinvoice.confirm.save.title=Guardar factura
|
createinvoice.confirm.save.title=Guardar factura
|
||||||
createinvoice.confirm.save.message=Esta factura se guardar\u00e1 de forma permanente y no podr\u00e1 ser modificada posteriormente. \u00bfDesea continuar?
|
createinvoice.confirm.save.message=Esta factura se guardar\u00e1 de forma permanente y no podr\u00e1 ser modificada posteriormente. \u00bfDesea continuar?
|
||||||
createinvoice.confirm.save.confirm=S\u00ed, guardar
|
createinvoice.confirm.save.confirm=S\u00ed, guardar
|
||||||
|
createinvoice.button.send=Enviar
|
||||||
|
createinvoice.confirm.send.title=Enviar factura
|
||||||
|
createinvoice.confirm.send.message=La factura se guardará de forma permanente y se enviará al cliente por correo electrónico como factura electrónica (ZIP). ¿Continuar?
|
||||||
|
createinvoice.confirm.send.confirm=Sí, enviar
|
||||||
|
createinvoice.notification.sent=La factura {0} se ha enviado como factura electrónica a {1}
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Facturas
|
invoices.title=Facturas
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ profile.validation.zip.required=Le code postal est obligatoire
|
|||||||
profile.validation.city.required=La ville est obligatoire
|
profile.validation.city.required=La ville est obligatoire
|
||||||
profile.validation.taxnumber.required=Le numéro fiscal est obligatoire
|
profile.validation.taxnumber.required=Le numéro fiscal est obligatoire
|
||||||
profile.validation.ustid.required=Le n° TVA intracommunautaire est obligatoire
|
profile.validation.ustid.required=Le n° TVA intracommunautaire est obligatoire
|
||||||
|
profile.validation.ustid.invalid=Format de numéro de TVA invalide (p. ex. DE123456789)
|
||||||
profile.validation.firstname.required=Le pr\u00e9nom est obligatoire
|
profile.validation.firstname.required=Le pr\u00e9nom est obligatoire
|
||||||
profile.validation.lastname.required=Le nom est obligatoire
|
profile.validation.lastname.required=Le nom est obligatoire
|
||||||
profile.validation.phone.required=Le num\u00e9ro de t\u00e9l\u00e9phone est obligatoire
|
profile.validation.phone.required=Le num\u00e9ro de t\u00e9l\u00e9phone est obligatoire
|
||||||
@@ -696,6 +697,11 @@ createinvoice.button.save=Enregistrer
|
|||||||
createinvoice.confirm.save.title=Enregistrer la facture
|
createinvoice.confirm.save.title=Enregistrer la facture
|
||||||
createinvoice.confirm.save.message=Cette facture sera enregistr\u00e9e de mani\u00e8re permanente et ne pourra plus \u00eatre modifi\u00e9e par la suite. Continuer ?
|
createinvoice.confirm.save.message=Cette facture sera enregistr\u00e9e de mani\u00e8re permanente et ne pourra plus \u00eatre modifi\u00e9e par la suite. Continuer ?
|
||||||
createinvoice.confirm.save.confirm=Oui, enregistrer
|
createinvoice.confirm.save.confirm=Oui, enregistrer
|
||||||
|
createinvoice.button.send=Envoyer
|
||||||
|
createinvoice.confirm.send.title=Envoyer la facture
|
||||||
|
createinvoice.confirm.send.message=La facture sera enregistrée définitivement et envoyée au client par e-mail en tant que facture électronique (ZIP). Continuer ?
|
||||||
|
createinvoice.confirm.send.confirm=Oui, envoyer
|
||||||
|
createinvoice.notification.sent=La facture {0} a été envoyée en tant que facture électronique à {1}
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Factures
|
invoices.title=Factures
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ profile.validation.zip.required=Pašto kodas yra privalomas
|
|||||||
profile.validation.city.required=Miestas yra privalomas
|
profile.validation.city.required=Miestas yra privalomas
|
||||||
profile.validation.taxnumber.required=Mokesčių mokėtojo numeris yra privalomas
|
profile.validation.taxnumber.required=Mokesčių mokėtojo numeris yra privalomas
|
||||||
profile.validation.ustid.required=PVM mokėtojo kodas yra privalomas
|
profile.validation.ustid.required=PVM mokėtojo kodas yra privalomas
|
||||||
|
profile.validation.ustid.invalid=Neteisingas PVM mokėtojo kodo formatas (pvz., DE123456789)
|
||||||
profile.validation.firstname.required=Vardas yra privalomas
|
profile.validation.firstname.required=Vardas yra privalomas
|
||||||
profile.validation.lastname.required=Pavardė yra privaloma
|
profile.validation.lastname.required=Pavardė yra privaloma
|
||||||
profile.validation.phone.required=Telefono numeris yra privalomas
|
profile.validation.phone.required=Telefono numeris yra privalomas
|
||||||
@@ -698,6 +699,11 @@ createinvoice.button.save=Išsaugoti
|
|||||||
createinvoice.confirm.save.title=Išsaugoti sąskaitą
|
createinvoice.confirm.save.title=Išsaugoti sąskaitą
|
||||||
createinvoice.confirm.save.message=Ši sąskaita bus išsaugota visam laikui ir vėliau jos nebebus galima keisti. Tęsti?
|
createinvoice.confirm.save.message=Ši sąskaita bus išsaugota visam laikui ir vėliau jos nebebus galima keisti. Tęsti?
|
||||||
createinvoice.confirm.save.confirm=Taip, išsaugoti
|
createinvoice.confirm.save.confirm=Taip, išsaugoti
|
||||||
|
createinvoice.button.send=Siųsti
|
||||||
|
createinvoice.confirm.send.title=Siųsti sąskaitą
|
||||||
|
createinvoice.confirm.send.message=Sąskaita bus visam laikui išsaugota ir išsiųsta klientui el. paštu kaip e. sąskaita (ZIP). Tęsti?
|
||||||
|
createinvoice.confirm.send.confirm=Taip, siųsti
|
||||||
|
createinvoice.notification.sent=Sąskaita {0} išsiųsta kaip e. sąskaita adresu {1}
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Sąskaitos faktūros
|
invoices.title=Sąskaitos faktūros
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ profile.validation.zip.required=Pasta indekss ir obligāts
|
|||||||
profile.validation.city.required=Pilsēta ir obligāta
|
profile.validation.city.required=Pilsēta ir obligāta
|
||||||
profile.validation.taxnumber.required=Nodokļu maksātāja numurs ir obligāts
|
profile.validation.taxnumber.required=Nodokļu maksātāja numurs ir obligāts
|
||||||
profile.validation.ustid.required=PVN numurs ir obligāts
|
profile.validation.ustid.required=PVN numurs ir obligāts
|
||||||
|
profile.validation.ustid.invalid=Nederīgs PVN numura formāts (piem., DE123456789)
|
||||||
profile.validation.firstname.required=Vārds ir obligāts
|
profile.validation.firstname.required=Vārds ir obligāts
|
||||||
profile.validation.lastname.required=Uzvārds ir obligāts
|
profile.validation.lastname.required=Uzvārds ir obligāts
|
||||||
profile.validation.phone.required=Tālruņa numurs ir obligāts
|
profile.validation.phone.required=Tālruņa numurs ir obligāts
|
||||||
@@ -696,6 +697,11 @@ createinvoice.button.save=Saglabāt
|
|||||||
createinvoice.confirm.save.title=Saglabāt rēķinu
|
createinvoice.confirm.save.title=Saglabāt rēķinu
|
||||||
createinvoice.confirm.save.message=Šis rēķins tiks neatgriezeniski saglabāts un pēc tam to vairs nevarēs mainīt. Turpināt?
|
createinvoice.confirm.save.message=Šis rēķins tiks neatgriezeniski saglabāts un pēc tam to vairs nevarēs mainīt. Turpināt?
|
||||||
createinvoice.confirm.save.confirm=Jā, saglabāt
|
createinvoice.confirm.save.confirm=Jā, saglabāt
|
||||||
|
createinvoice.button.send=Sūtīt
|
||||||
|
createinvoice.confirm.send.title=Sūtīt rēķinu
|
||||||
|
createinvoice.confirm.send.message=Rēķins tiks neatgriezeniski saglabāts un nosūtīts klientam pa e-pastu kā e-rēķins (ZIP). Vai turpināt?
|
||||||
|
createinvoice.confirm.send.confirm=Jā, sūtīt
|
||||||
|
createinvoice.notification.sent=Rēķins {0} nosūtīts kā e-rēķins uz {1}
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Rēķini
|
invoices.title=Rēķini
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ profile.validation.zip.required=Kod pocztowy jest wymagany
|
|||||||
profile.validation.city.required=Miasto jest wymagane
|
profile.validation.city.required=Miasto jest wymagane
|
||||||
profile.validation.taxnumber.required=Numer podatkowy jest wymagany
|
profile.validation.taxnumber.required=Numer podatkowy jest wymagany
|
||||||
profile.validation.ustid.required=NIP UE jest wymagany
|
profile.validation.ustid.required=NIP UE jest wymagany
|
||||||
|
profile.validation.ustid.invalid=Nieprawidłowy format numeru VAT UE (np. DE123456789)
|
||||||
profile.validation.firstname.required=Imi\u0119 jest wymagane
|
profile.validation.firstname.required=Imi\u0119 jest wymagane
|
||||||
profile.validation.lastname.required=Nazwisko jest wymagane
|
profile.validation.lastname.required=Nazwisko jest wymagane
|
||||||
profile.validation.phone.required=Numer telefonu jest wymagany
|
profile.validation.phone.required=Numer telefonu jest wymagany
|
||||||
@@ -696,6 +697,11 @@ createinvoice.button.save=Zapisz
|
|||||||
createinvoice.confirm.save.title=Zapisz faktur\u0119
|
createinvoice.confirm.save.title=Zapisz faktur\u0119
|
||||||
createinvoice.confirm.save.message=Ta faktura zostanie trwale zapisana i nie b\u0119dzie mog\u0142a by\u0107 p\u00f3\u017aniej zmieniona. Kontynuowa\u0107?
|
createinvoice.confirm.save.message=Ta faktura zostanie trwale zapisana i nie b\u0119dzie mog\u0142a by\u0107 p\u00f3\u017aniej zmieniona. Kontynuowa\u0107?
|
||||||
createinvoice.confirm.save.confirm=Tak, zapisz
|
createinvoice.confirm.save.confirm=Tak, zapisz
|
||||||
|
createinvoice.button.send=Wyślij
|
||||||
|
createinvoice.confirm.send.title=Wyślij fakturę
|
||||||
|
createinvoice.confirm.send.message=Faktura zostanie trwale zapisana i wysłana do klienta e-mailem jako e-faktura (ZIP). Kontynuować?
|
||||||
|
createinvoice.confirm.send.confirm=Tak, wyślij
|
||||||
|
createinvoice.notification.sent=Faktura {0} została wysłana jako e-faktura na adres {1}
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Faktury
|
invoices.title=Faktury
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ profile.validation.zip.required=Почтовый индекс обязателе
|
|||||||
profile.validation.city.required=Город обязателен
|
profile.validation.city.required=Город обязателен
|
||||||
profile.validation.taxnumber.required=Налоговый номер обязателен
|
profile.validation.taxnumber.required=Налоговый номер обязателен
|
||||||
profile.validation.ustid.required=Идентификатор плательщика НДС обязателен
|
profile.validation.ustid.required=Идентификатор плательщика НДС обязателен
|
||||||
|
profile.validation.ustid.invalid=Неверный формат идентификационного номера НДС (напр. DE123456789)
|
||||||
profile.validation.firstname.required=Имя обязательно
|
profile.validation.firstname.required=Имя обязательно
|
||||||
profile.validation.lastname.required=Фамилия обязательна
|
profile.validation.lastname.required=Фамилия обязательна
|
||||||
profile.validation.phone.required=Номер телефона обязателен
|
profile.validation.phone.required=Номер телефона обязателен
|
||||||
@@ -696,6 +697,11 @@ createinvoice.button.save=Сохранить
|
|||||||
createinvoice.confirm.save.title=Сохранить счёт
|
createinvoice.confirm.save.title=Сохранить счёт
|
||||||
createinvoice.confirm.save.message=Этот счёт будет сохранён навсегда и не может быть изменён впоследствии. Продолжить?
|
createinvoice.confirm.save.message=Этот счёт будет сохранён навсегда и не может быть изменён впоследствии. Продолжить?
|
||||||
createinvoice.confirm.save.confirm=Да, сохранить
|
createinvoice.confirm.save.confirm=Да, сохранить
|
||||||
|
createinvoice.button.send=Отправить
|
||||||
|
createinvoice.confirm.send.title=Отправить счёт
|
||||||
|
createinvoice.confirm.send.message=Счёт будет окончательно сохранён и отправлен клиенту по электронной почте как электронный счёт (ZIP). Продолжить?
|
||||||
|
createinvoice.confirm.send.confirm=Да, отправить
|
||||||
|
createinvoice.notification.sent=Счёт {0} отправлен как электронный счёт на {1}
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Счета
|
invoices.title=Счета
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ profile.validation.zip.required=Posta kodu gereklidir
|
|||||||
profile.validation.city.required=\u015eehir gereklidir
|
profile.validation.city.required=\u015eehir gereklidir
|
||||||
profile.validation.taxnumber.required=Vergi numarası gereklidir
|
profile.validation.taxnumber.required=Vergi numarası gereklidir
|
||||||
profile.validation.ustid.required=KDV kimlik no gereklidir
|
profile.validation.ustid.required=KDV kimlik no gereklidir
|
||||||
|
profile.validation.ustid.invalid=Geçersiz KDV kimlik numarası biçimi (örn. DE123456789)
|
||||||
profile.validation.firstname.required=Ad gereklidir
|
profile.validation.firstname.required=Ad gereklidir
|
||||||
profile.validation.lastname.required=Soyad gereklidir
|
profile.validation.lastname.required=Soyad gereklidir
|
||||||
profile.validation.phone.required=Telefon numaras\u0131 gereklidir
|
profile.validation.phone.required=Telefon numaras\u0131 gereklidir
|
||||||
@@ -696,6 +697,11 @@ createinvoice.button.save=Kaydet
|
|||||||
createinvoice.confirm.save.title=Faturay\u0131 Kaydet
|
createinvoice.confirm.save.title=Faturay\u0131 Kaydet
|
||||||
createinvoice.confirm.save.message=Bu fatura kal\u0131c\u0131 olarak kaydedilecek ve sonras\u0131nda de\u011fi\u015ftirilemeyecektir. Devam edilsin mi?
|
createinvoice.confirm.save.message=Bu fatura kal\u0131c\u0131 olarak kaydedilecek ve sonras\u0131nda de\u011fi\u015ftirilemeyecektir. Devam edilsin mi?
|
||||||
createinvoice.confirm.save.confirm=Evet, kaydet
|
createinvoice.confirm.save.confirm=Evet, kaydet
|
||||||
|
createinvoice.button.send=Gönder
|
||||||
|
createinvoice.confirm.send.title=Faturayı gönder
|
||||||
|
createinvoice.confirm.send.message=Fatura kalıcı olarak kaydedilecek ve müşteriye e-posta ile e-fatura (ZIP) olarak gönderilecektir. Devam edilsin mi?
|
||||||
|
createinvoice.confirm.send.confirm=Evet, gönder
|
||||||
|
createinvoice.notification.sent=Fatura {0}, e-fatura olarak {1} adresine gönderildi
|
||||||
|
|
||||||
# Invoices
|
# Invoices
|
||||||
invoices.title=Faturalar
|
invoices.title=Faturalar
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package de.assecutor.votianlt.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class VatIdUtilTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = { "DE123456789", "ATU12345678", "NL123456789B01", "FRXX123456789", "PL1234567890",
|
||||||
|
"IT12345678901", "BE0123456789", "SE123456789012", "IE1234567AB" })
|
||||||
|
void validVatIdsAreAccepted(String vatId) {
|
||||||
|
assertTrue(VatIdUtil.isValid(vatId), vatId + " sollte gültig sein");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void separatorsAndLowercaseAreNormalized() {
|
||||||
|
assertTrue(VatIdUtil.isValid("de 123.456-789"));
|
||||||
|
assertEquals("DE123456789", VatIdUtil.normalize("de 123.456-789"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = { "DE12345678", "DE1234567890", "DE12345678A", "ATU1234567", "NL123456789A01",
|
||||||
|
"123456789", "D123456789", "XX", "DE" })
|
||||||
|
void invalidVatIdsAreRejected(String vatId) {
|
||||||
|
assertFalse(VatIdUtil.isValid(vatId), vatId + " sollte ungültig sein");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void nullAndBlankAreInvalid() {
|
||||||
|
assertFalse(VatIdUtil.isValid(null));
|
||||||
|
assertFalse(VatIdUtil.isValid(""));
|
||||||
|
assertFalse(VatIdUtil.isValid(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unknownCountryFallsBackToGenericPattern() {
|
||||||
|
assertTrue(VatIdUtil.isValid("XI123456789"));
|
||||||
|
assertFalse(VatIdUtil.isValid("XI1"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user