Telefon und Webseite des Rechnungsstellers: Adressbuch-Feld, Formularfelder, Belegung von masterdata.phone/website

- Adressbuch: neues Feld "Webseite" pro Unternehmen
- Rechnungsmaske: Telefon und Webseite beim Rechnungssteller manuell erfassbar, Vorbelegung aus dem Adressbuch
- masterdata.phone/masterdata.website werden aus den Formularfeldern belegt, mit Adressbuch-Fallback (bisher immer leer)
- Webseite wird in Vorbelegungen mitgespeichert, Telefon fließt über Party.phone in die ZUGFeRD-Daten

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-17 10:13:40 +02:00
co-authored by Claude Fable 5
parent 6838ce3b20
commit 28f62cee06
4 changed files with 66 additions and 5 deletions
@@ -25,6 +25,7 @@ public class Address {
private String vatId;
private String email;
private String phone;
private String website;
// Bankverbindung (v. a. für Rechnungssteller relevant)
private String bankName;
@@ -119,6 +120,14 @@ public class Address {
this.phone = phone;
}
public String getWebsite() {
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public String getBankName() {
return bankName;
}
@@ -27,6 +27,7 @@ public record InvoiceDraft(
String buyerReference,
String iban,
String bic,
String website,
InvoiceMetadata.Party sender,
InvoiceMetadata.Party recipient,
List<InvoiceMetadata.LineItem> items,
@@ -159,6 +159,9 @@ public class AddressBookView extends VerticalLayout {
});
TextField phone = new TextField("Telefon");
phone.setValue(target.getPhone() != null ? target.getPhone() : "");
TextField website = new TextField("Webseite");
website.setValue(target.getWebsite() != null ? target.getWebsite() : "");
website.setHelperText("z. B. www.assecutor.de");
// Bankverbindung (optional)
TextField bankName = new TextField("Bank");
@@ -170,7 +173,7 @@ public class AddressBookView extends VerticalLayout {
bic.setValue(target.getBic() != null ? target.getBic() : "");
FormLayout form = new FormLayout(type, name, street, zip, city, countryCode, vatId, email, phone,
bankName, iban, bic);
website, bankName, iban, bic);
form.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1), new FormLayout.ResponsiveStep("500px", 2));
form.setWidth("560px");
dialog.add(form);
@@ -231,6 +234,7 @@ public class AddressBookView extends VerticalLayout {
target.setVatId(vatId.getValue().isBlank() ? "" : VatIdValidator.normalize(vatId.getValue()));
target.setEmail(email.getValue().trim());
target.setPhone(phone.getValue().trim());
target.setWebsite(website.getValue().trim());
target.setBankName(bankName.getValue().trim());
target.setIban(iban.getValue().isBlank() ? "" : IbanValidator.normalize(iban.getValue()));
target.setBic(bic.getValue().trim().toUpperCase());
@@ -379,6 +379,7 @@ public class MainView extends VerticalLayout {
"",
sender.ibanNormalized(),
sender.bicNormalized(),
sender.websiteValue(),
sender.toParty(), recipient.toParty(),
List.copyOf(lineItems),
LocalDateTime.now());
@@ -410,6 +411,8 @@ public class MainView extends VerticalLayout {
currency.setValue(nullToEmpty(draft.currency()));
paymentTerms.setValue(nullToEmpty(draft.paymentTerms()));
applyParty(sender, draft.sender());
sender.prefillContact(draft.sender() != null ? nullToEmpty(draft.sender().phone()) : "",
nullToEmpty(draft.website()));
sender.prefillBank(nullToEmpty(draft.iban()), nullToEmpty(draft.bic()));
applyParty(recipient, draft.recipient());
lineItems.clear();
@@ -447,6 +450,7 @@ public class MainView extends VerticalLayout {
nullToEmpty(address.getZip()), nullToEmpty(address.getCity()),
nullToEmpty(address.getCountryCode()), nullToEmpty(address.getVatId()),
nullToEmpty(address.getEmail()));
form.prefillContact(nullToEmpty(address.getPhone()), nullToEmpty(address.getWebsite()));
form.prefillBank(nullToEmpty(address.getIban()), nullToEmpty(address.getBic()));
}
});
@@ -545,6 +549,7 @@ public class MainView extends VerticalLayout {
"",
sender.ibanNormalized(),
sender.bicNormalized(),
sender.websiteValue(),
sender.toParty(), recipient.toParty(),
List.copyOf(lineItems),
null);
@@ -661,12 +666,20 @@ public class MainView extends VerticalLayout {
private Map<String, String> buildTemplateVariables(InvoiceMetadata metadata) {
Map<String, String> variables = new LinkedHashMap<>();
InvoiceMetadata.Party senderParty = metadata.sender();
// Telefon und Webseite kommen aus den Formularfeldern des
// Rechnungsstellers; sind sie leer (z. B. alte Vorbelegung), springt
// der Adressbuch-Eintrag des Rechnungsstellers ein.
Optional<Address> senderAddress = findSenderAddress(senderParty.name());
String phone = !senderParty.phone().isBlank() ? senderParty.phone()
: senderAddress.map(address -> nullToEmpty(address.getPhone())).orElse("");
String website = !sender.websiteValue().isBlank() ? sender.websiteValue()
: senderAddress.map(address -> nullToEmpty(address.getWebsite())).orElse("");
variables.put("masterdata.company_name", senderParty.name());
variables.put("masterdata.street", senderParty.street());
variables.put("masterdata.city", senderParty.zip() + " " + senderParty.city());
variables.put("masterdata.phone", senderParty.phone());
variables.put("masterdata.phone", phone);
variables.put("masterdata.email", senderParty.email());
variables.put("masterdata.website", "");
variables.put("masterdata.website", website);
variables.put("masterdata.sender_line",
senderParty.name() + " · " + senderParty.street() + " · " + senderParty.zip() + " " + senderParty.city());
variables.put("masterdata.payment_terms", metadata.paymentTerms());
@@ -697,6 +710,22 @@ public class MainView extends VerticalLayout {
return variables;
}
/**
* @return der Adressbuch-Eintrag des Rechnungsstellers: die Auswahl in
* "Aus dem Adressbuch übernehmen", sonst der Namenstreffer unter
* den Rechnungssteller-Adressen (z. B. nach Laden einer Vorbelegung).
*/
private Optional<Address> findSenderAddress(String senderName) {
Address selected = senderAddressSelect.getValue();
if (selected != null) {
return Optional.of(selected);
}
String normalized = senderName == null ? "" : senderName.trim();
return addressRepository.findByType(AddressType.RECHNUNGSSTELLER).stream()
.filter(address -> normalized.equalsIgnoreCase(nullToEmpty(address.getName()).trim()))
.findFirst();
}
/** Wandelt das Quell-PDF in die ZUGFeRD-E-Rechnung um und bietet das ZIP zum Download an. */
private void convertAndOfferDownload(byte[] sourcePdf) {
try {
@@ -823,6 +852,10 @@ public class MainView extends VerticalLayout {
private final TextField vatId = new TextField("USt-IdNr.");
private final EmailField email = new EmailField("E-Mail");
// Kontaktangaben; nur beim Rechnungssteller sichtbar und optional
private final TextField phone = new TextField("Telefon");
private final TextField website = new TextField("Webseite");
// Bankverbindung; nur beim Rechnungssteller sichtbar und Pflicht
private final TextField iban = new TextField("IBAN");
private final TextField bic = new TextField("BIC");
@@ -849,11 +882,12 @@ public class MainView extends VerticalLayout {
email.addValueChangeListener(event -> checkEmail());
add(name, street, zip, city, countryCode, vatId, email);
if (vatRequired) {
website.setHelperText("z. B. www.assecutor.de");
iban.setRequired(true);
iban.setHelperText("Für die Zahlungsangaben (BG-16)");
bic.setRequired(true);
bic.setHelperText("8 oder 11 Stellen, z. B. NOLADE21RZB");
add(iban, bic);
add(phone, website, iban, bic);
}
setResponsiveSteps(new ResponsiveStep("0", 1), new ResponsiveStep("600px", 3));
}
@@ -864,6 +898,16 @@ public class MainView extends VerticalLayout {
bic.setValue(bicValue);
}
/** Belegt Telefon und Webseite vor, z. B. aus dem Adressbuch. */
void prefillContact(String phoneValue, String websiteValue) {
phone.setValue(phoneValue);
website.setValue(websiteValue);
}
String websiteValue() {
return website.getValue().trim();
}
/** IBAN ohne Leerzeichen in Großschreibung. */
String ibanNormalized() {
return iban.getValue().replaceAll("\\s", "").toUpperCase();
@@ -906,6 +950,8 @@ public class MainView extends VerticalLayout {
countryCode.addValueChangeListener(event -> listener.run());
vatId.addValueChangeListener(event -> listener.run());
email.addValueChangeListener(event -> listener.run());
phone.addValueChangeListener(event -> listener.run());
website.addValueChangeListener(event -> listener.run());
iban.addValueChangeListener(event -> listener.run());
bic.addValueChangeListener(event -> listener.run());
}
@@ -913,6 +959,7 @@ public class MainView extends VerticalLayout {
/** Leert alle Felder; das Land behält den Standardwert "DE". */
void clearFields() {
prefill("", "", "", "", "DE", "", "");
prefillContact("", "");
prefillBank("", "");
name.setInvalid(false);
street.setInvalid(false);
@@ -995,7 +1042,7 @@ public class MainView extends VerticalLayout {
countryCode.getValue().trim().toUpperCase(),
vatId.isEmpty() ? "" : VatIdValidator.normalize(vatId.getValue()),
email.getValue().trim(),
""
phone.getValue().trim()
);
}
}