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 vatId;
private String email; private String email;
private String phone; private String phone;
private String website;
// Bankverbindung (v. a. für Rechnungssteller relevant) // Bankverbindung (v. a. für Rechnungssteller relevant)
private String bankName; private String bankName;
@@ -119,6 +120,14 @@ public class Address {
this.phone = phone; this.phone = phone;
} }
public String getWebsite() {
return website;
}
public void setWebsite(String website) {
this.website = website;
}
public String getBankName() { public String getBankName() {
return bankName; return bankName;
} }
@@ -27,6 +27,7 @@ public record InvoiceDraft(
String buyerReference, String buyerReference,
String iban, String iban,
String bic, String bic,
String website,
InvoiceMetadata.Party sender, InvoiceMetadata.Party sender,
InvoiceMetadata.Party recipient, InvoiceMetadata.Party recipient,
List<InvoiceMetadata.LineItem> items, List<InvoiceMetadata.LineItem> items,
@@ -159,6 +159,9 @@ public class AddressBookView extends VerticalLayout {
}); });
TextField phone = new TextField("Telefon"); TextField phone = new TextField("Telefon");
phone.setValue(target.getPhone() != null ? target.getPhone() : ""); 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) // Bankverbindung (optional)
TextField bankName = new TextField("Bank"); TextField bankName = new TextField("Bank");
@@ -170,7 +173,7 @@ public class AddressBookView extends VerticalLayout {
bic.setValue(target.getBic() != null ? target.getBic() : ""); bic.setValue(target.getBic() != null ? target.getBic() : "");
FormLayout form = new FormLayout(type, name, street, zip, city, countryCode, vatId, email, phone, 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.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1), new FormLayout.ResponsiveStep("500px", 2));
form.setWidth("560px"); form.setWidth("560px");
dialog.add(form); dialog.add(form);
@@ -231,6 +234,7 @@ public class AddressBookView extends VerticalLayout {
target.setVatId(vatId.getValue().isBlank() ? "" : VatIdValidator.normalize(vatId.getValue())); target.setVatId(vatId.getValue().isBlank() ? "" : VatIdValidator.normalize(vatId.getValue()));
target.setEmail(email.getValue().trim()); target.setEmail(email.getValue().trim());
target.setPhone(phone.getValue().trim()); target.setPhone(phone.getValue().trim());
target.setWebsite(website.getValue().trim());
target.setBankName(bankName.getValue().trim()); target.setBankName(bankName.getValue().trim());
target.setIban(iban.getValue().isBlank() ? "" : IbanValidator.normalize(iban.getValue())); target.setIban(iban.getValue().isBlank() ? "" : IbanValidator.normalize(iban.getValue()));
target.setBic(bic.getValue().trim().toUpperCase()); target.setBic(bic.getValue().trim().toUpperCase());
@@ -379,6 +379,7 @@ public class MainView extends VerticalLayout {
"", "",
sender.ibanNormalized(), sender.ibanNormalized(),
sender.bicNormalized(), sender.bicNormalized(),
sender.websiteValue(),
sender.toParty(), recipient.toParty(), sender.toParty(), recipient.toParty(),
List.copyOf(lineItems), List.copyOf(lineItems),
LocalDateTime.now()); LocalDateTime.now());
@@ -410,6 +411,8 @@ public class MainView extends VerticalLayout {
currency.setValue(nullToEmpty(draft.currency())); currency.setValue(nullToEmpty(draft.currency()));
paymentTerms.setValue(nullToEmpty(draft.paymentTerms())); paymentTerms.setValue(nullToEmpty(draft.paymentTerms()));
applyParty(sender, draft.sender()); applyParty(sender, draft.sender());
sender.prefillContact(draft.sender() != null ? nullToEmpty(draft.sender().phone()) : "",
nullToEmpty(draft.website()));
sender.prefillBank(nullToEmpty(draft.iban()), nullToEmpty(draft.bic())); sender.prefillBank(nullToEmpty(draft.iban()), nullToEmpty(draft.bic()));
applyParty(recipient, draft.recipient()); applyParty(recipient, draft.recipient());
lineItems.clear(); lineItems.clear();
@@ -447,6 +450,7 @@ public class MainView extends VerticalLayout {
nullToEmpty(address.getZip()), nullToEmpty(address.getCity()), nullToEmpty(address.getZip()), nullToEmpty(address.getCity()),
nullToEmpty(address.getCountryCode()), nullToEmpty(address.getVatId()), nullToEmpty(address.getCountryCode()), nullToEmpty(address.getVatId()),
nullToEmpty(address.getEmail())); nullToEmpty(address.getEmail()));
form.prefillContact(nullToEmpty(address.getPhone()), nullToEmpty(address.getWebsite()));
form.prefillBank(nullToEmpty(address.getIban()), nullToEmpty(address.getBic())); form.prefillBank(nullToEmpty(address.getIban()), nullToEmpty(address.getBic()));
} }
}); });
@@ -545,6 +549,7 @@ public class MainView extends VerticalLayout {
"", "",
sender.ibanNormalized(), sender.ibanNormalized(),
sender.bicNormalized(), sender.bicNormalized(),
sender.websiteValue(),
sender.toParty(), recipient.toParty(), sender.toParty(), recipient.toParty(),
List.copyOf(lineItems), List.copyOf(lineItems),
null); null);
@@ -661,12 +666,20 @@ public class MainView extends VerticalLayout {
private Map<String, String> buildTemplateVariables(InvoiceMetadata metadata) { private Map<String, String> buildTemplateVariables(InvoiceMetadata metadata) {
Map<String, String> variables = new LinkedHashMap<>(); Map<String, String> variables = new LinkedHashMap<>();
InvoiceMetadata.Party senderParty = metadata.sender(); 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.company_name", senderParty.name());
variables.put("masterdata.street", senderParty.street()); variables.put("masterdata.street", senderParty.street());
variables.put("masterdata.city", senderParty.zip() + " " + senderParty.city()); 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.email", senderParty.email());
variables.put("masterdata.website", ""); variables.put("masterdata.website", website);
variables.put("masterdata.sender_line", variables.put("masterdata.sender_line",
senderParty.name() + " · " + senderParty.street() + " · " + senderParty.zip() + " " + senderParty.city()); senderParty.name() + " · " + senderParty.street() + " · " + senderParty.zip() + " " + senderParty.city());
variables.put("masterdata.payment_terms", metadata.paymentTerms()); variables.put("masterdata.payment_terms", metadata.paymentTerms());
@@ -697,6 +710,22 @@ public class MainView extends VerticalLayout {
return variables; 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. */ /** Wandelt das Quell-PDF in die ZUGFeRD-E-Rechnung um und bietet das ZIP zum Download an. */
private void convertAndOfferDownload(byte[] sourcePdf) { private void convertAndOfferDownload(byte[] sourcePdf) {
try { try {
@@ -823,6 +852,10 @@ public class MainView extends VerticalLayout {
private final TextField vatId = new TextField("USt-IdNr."); private final TextField vatId = new TextField("USt-IdNr.");
private final EmailField email = new EmailField("E-Mail"); 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 // Bankverbindung; nur beim Rechnungssteller sichtbar und Pflicht
private final TextField iban = new TextField("IBAN"); private final TextField iban = new TextField("IBAN");
private final TextField bic = new TextField("BIC"); private final TextField bic = new TextField("BIC");
@@ -849,11 +882,12 @@ public class MainView extends VerticalLayout {
email.addValueChangeListener(event -> checkEmail()); email.addValueChangeListener(event -> checkEmail());
add(name, street, zip, city, countryCode, vatId, email); add(name, street, zip, city, countryCode, vatId, email);
if (vatRequired) { if (vatRequired) {
website.setHelperText("z. B. www.assecutor.de");
iban.setRequired(true); iban.setRequired(true);
iban.setHelperText("Für die Zahlungsangaben (BG-16)"); iban.setHelperText("Für die Zahlungsangaben (BG-16)");
bic.setRequired(true); bic.setRequired(true);
bic.setHelperText("8 oder 11 Stellen, z. B. NOLADE21RZB"); 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)); setResponsiveSteps(new ResponsiveStep("0", 1), new ResponsiveStep("600px", 3));
} }
@@ -864,6 +898,16 @@ public class MainView extends VerticalLayout {
bic.setValue(bicValue); 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. */ /** IBAN ohne Leerzeichen in Großschreibung. */
String ibanNormalized() { String ibanNormalized() {
return iban.getValue().replaceAll("\\s", "").toUpperCase(); return iban.getValue().replaceAll("\\s", "").toUpperCase();
@@ -906,6 +950,8 @@ public class MainView extends VerticalLayout {
countryCode.addValueChangeListener(event -> listener.run()); countryCode.addValueChangeListener(event -> listener.run());
vatId.addValueChangeListener(event -> listener.run()); vatId.addValueChangeListener(event -> listener.run());
email.addValueChangeListener(event -> listener.run()); email.addValueChangeListener(event -> listener.run());
phone.addValueChangeListener(event -> listener.run());
website.addValueChangeListener(event -> listener.run());
iban.addValueChangeListener(event -> listener.run()); iban.addValueChangeListener(event -> listener.run());
bic.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". */ /** Leert alle Felder; das Land behält den Standardwert "DE". */
void clearFields() { void clearFields() {
prefill("", "", "", "", "DE", "", ""); prefill("", "", "", "", "DE", "", "");
prefillContact("", "");
prefillBank("", ""); prefillBank("", "");
name.setInvalid(false); name.setInvalid(false);
street.setInvalid(false); street.setInvalid(false);
@@ -995,7 +1042,7 @@ public class MainView extends VerticalLayout {
countryCode.getValue().trim().toUpperCase(), countryCode.getValue().trim().toUpperCase(),
vatId.isEmpty() ? "" : VatIdValidator.normalize(vatId.getValue()), vatId.isEmpty() ? "" : VatIdValidator.normalize(vatId.getValue()),
email.getValue().trim(), email.getValue().trim(),
"" phone.getValue().trim()
); );
} }
} }