Erweiterungen
This commit is contained in:
@@ -39,18 +39,18 @@ export function clearLanguageCookie(): void {
|
|||||||
* Maps Language enum values to locale strings
|
* Maps Language enum values to locale strings
|
||||||
*/
|
*/
|
||||||
export const languageToLocale: Record<string, string> = {
|
export const languageToLocale: Record<string, string> = {
|
||||||
'DE': 'de',
|
DE: 'de',
|
||||||
'EN': 'en',
|
EN: 'en',
|
||||||
'FR': 'fr',
|
FR: 'fr',
|
||||||
'ES': 'es'
|
ES: 'es',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps locale strings to Language enum values
|
* Maps locale strings to Language enum values
|
||||||
*/
|
*/
|
||||||
export const localeToLanguage: Record<string, string> = {
|
export const localeToLanguage: Record<string, string> = {
|
||||||
'de': 'DE',
|
de: 'DE',
|
||||||
'en': 'EN',
|
en: 'EN',
|
||||||
'fr': 'FR',
|
fr: 'FR',
|
||||||
'es': 'ES'
|
es: 'ES',
|
||||||
};
|
};
|
||||||
@@ -16,12 +16,13 @@ import java.util.Locale;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the user's preferred locale on the UI BEFORE any layout or view is
|
* Sets the user's preferred locale on the UI BEFORE any layout or view is
|
||||||
* constructed. Registered via {@code UIInitListener} → {@code BeforeEnterListener},
|
* constructed. Registered via {@code UIInitListener} →
|
||||||
* which fires prior to the router creating the layout component tree.
|
* {@code BeforeEnterListener}, which fires prior to the router creating the
|
||||||
|
* layout component tree.
|
||||||
*
|
*
|
||||||
* For authenticated users: Uses the language preference from the user profile.
|
* For authenticated users: Uses the language preference from the user profile.
|
||||||
* For anonymous users: Uses the language from the 'votianlt.language' cookie
|
* For anonymous users: Uses the language from the 'votianlt.language' cookie or
|
||||||
* or falls back to the browser's preferred locale.
|
* falls back to the browser's preferred locale.
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.vaadin.flow.i18n.I18NProvider;
|
|||||||
import de.assecutor.votianlt.model.Language;
|
import de.assecutor.votianlt.model.Language;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -22,20 +23,19 @@ public class TranslationProvider implements I18NProvider {
|
|||||||
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
|
public List<Locale> getCandidateLocales(String baseName, Locale locale) {
|
||||||
// Map Estonian "et" to "ee" file, Latvian "lv" to "lv", Lithuanian "lt" to "lt"
|
// Map Estonian "et" to "ee" file, Latvian "lv" to "lv", Lithuanian "lt" to "lt"
|
||||||
String language = locale.getLanguage();
|
String language = locale.getLanguage();
|
||||||
String country = locale.getCountry();
|
|
||||||
|
|
||||||
// Create a locale that matches our file naming convention
|
// Create a locale that matches our file naming convention
|
||||||
Locale mappedLocale = switch (language) {
|
Locale mappedLocale = switch (language) {
|
||||||
case "et" -> new Locale("ee"); // Estonian -> messages_ee.properties
|
case "et" -> Locale.of("ee"); // Estonian -> messages_ee.properties
|
||||||
case "lv" -> new Locale("lv"); // Latvian -> messages_lv.properties
|
case "lv" -> Locale.of("lv"); // Latvian -> messages_lv.properties
|
||||||
case "lt" -> new Locale("lt"); // Lithuanian -> messages_lt.properties
|
case "lt" -> Locale.of("lt"); // Lithuanian -> messages_lt.properties
|
||||||
case "ru" -> new Locale("ru"); // Russian -> messages_ru.properties
|
case "ru" -> Locale.of("ru"); // Russian -> messages_ru.properties
|
||||||
case "pl" -> new Locale("pl"); // Polish -> messages_pl.properties
|
case "pl" -> Locale.of("pl"); // Polish -> messages_pl.properties
|
||||||
case "tr" -> new Locale("tr"); // Turkish -> messages_tr.properties
|
case "tr" -> Locale.of("tr"); // Turkish -> messages_tr.properties
|
||||||
case "es" -> new Locale("es"); // Spanish -> messages_es.properties
|
case "es" -> Locale.of("es"); // Spanish -> messages_es.properties
|
||||||
case "fr" -> new Locale("fr"); // French -> messages_fr.properties
|
case "fr" -> Locale.of("fr"); // French -> messages_fr.properties
|
||||||
case "en" -> new Locale("en"); // English -> messages_en.properties
|
case "en" -> Locale.of("en"); // English -> messages_en.properties
|
||||||
case "de" -> new Locale("de"); // German -> messages.properties (default)
|
case "de" -> Locale.of("de"); // German -> messages.properties (default)
|
||||||
default -> locale;
|
default -> locale;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,18 +45,9 @@ public class TranslationProvider implements I18NProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Locale> getProvidedLocales() {
|
public List<Locale> getProvidedLocales() {
|
||||||
return Collections.unmodifiableList(Arrays.asList(
|
return Collections.unmodifiableList(Arrays.asList(Locale.GERMAN, Locale.ENGLISH, Locale.FRENCH,
|
||||||
Locale.GERMAN,
|
Locale.of("es", "ES"), Locale.of("tr", "TR"), Locale.of("pl", "PL"), Locale.of("ru", "RU"),
|
||||||
Locale.ENGLISH,
|
Locale.of("et", "EE"), Locale.of("lv", "LV"), Locale.of("lt", "LT")));
|
||||||
Locale.FRENCH,
|
|
||||||
Locale.of("es", "ES"),
|
|
||||||
Locale.of("tr", "TR"),
|
|
||||||
Locale.of("pl", "PL"),
|
|
||||||
Locale.of("ru", "RU"),
|
|
||||||
Locale.of("et", "EE"),
|
|
||||||
Locale.of("lv", "LV"),
|
|
||||||
Locale.of("lt", "LT")
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -70,7 +61,7 @@ public class TranslationProvider implements I18NProvider {
|
|||||||
String value = bundle.getString(key);
|
String value = bundle.getString(key);
|
||||||
|
|
||||||
if (params.length > 0) {
|
if (params.length > 0) {
|
||||||
value = String.format(value, params);
|
value = MessageFormat.format(value, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -111,8 +111,10 @@ public final class MainLayout extends AppLayout {
|
|||||||
TreeData<MenuTreeItem> treeData = new TreeData<>();
|
TreeData<MenuTreeItem> treeData = new TreeData<>();
|
||||||
|
|
||||||
// Root nodes
|
// Root nodes
|
||||||
MenuTreeItem auftragserstellungItem = new MenuTreeItem(getTranslation("nav.job.create"), "add_job", VaadinIcon.PLUS_CIRCLE);
|
MenuTreeItem auftragserstellungItem = new MenuTreeItem(getTranslation("nav.job.create"), "add_job",
|
||||||
MenuTreeItem nachrichtenItem = new MenuTreeItem(getTranslation("nav.messages"), "messages", VaadinIcon.ENVELOPE);
|
VaadinIcon.PLUS_CIRCLE);
|
||||||
|
MenuTreeItem nachrichtenItem = new MenuTreeItem(getTranslation("nav.messages"), "messages",
|
||||||
|
VaadinIcon.ENVELOPE);
|
||||||
MenuTreeItem verwaltungItem = new MenuTreeItem(getTranslation("nav.management"), null, VaadinIcon.COG);
|
MenuTreeItem verwaltungItem = new MenuTreeItem(getTranslation("nav.management"), null, VaadinIcon.COG);
|
||||||
MenuTreeItem benutzerItem = new MenuTreeItem(getTranslation("nav.users"), null, VaadinIcon.USER);
|
MenuTreeItem benutzerItem = new MenuTreeItem(getTranslation("nav.users"), null, VaadinIcon.USER);
|
||||||
|
|
||||||
@@ -126,19 +128,26 @@ public final class MainLayout extends AppLayout {
|
|||||||
treeData.addItem(null, benutzerItem);
|
treeData.addItem(null, benutzerItem);
|
||||||
|
|
||||||
// Add children to "Verwaltung"
|
// Add children to "Verwaltung"
|
||||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
|
treeData.addItem(verwaltungItem,
|
||||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.appusers"), "app-user", VaadinIcon.USERS));
|
new MenuTreeItem(getTranslation("nav.customers"), "customers", VaadinIcon.USERS));
|
||||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.statistics"), "statistics", VaadinIcon.BAR_CHART));
|
treeData.addItem(verwaltungItem,
|
||||||
|
new MenuTreeItem(getTranslation("nav.appusers"), "app-user", VaadinIcon.USERS));
|
||||||
|
treeData.addItem(verwaltungItem,
|
||||||
|
new MenuTreeItem(getTranslation("nav.statistics"), "statistics", VaadinIcon.BAR_CHART));
|
||||||
|
|
||||||
// Add invoices only if billing is enabled
|
// Add invoices only if billing is enabled
|
||||||
if (isBillingEnabledForCurrentUser()) {
|
if (isBillingEnabledForCurrentUser()) {
|
||||||
treeData.addItem(verwaltungItem, new MenuTreeItem(getTranslation("nav.invoices"), "invoices", VaadinIcon.FILE_TEXT));
|
treeData.addItem(verwaltungItem,
|
||||||
|
new MenuTreeItem(getTranslation("nav.invoices"), "invoices", VaadinIcon.FILE_TEXT));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add children to "Benutzer"
|
// Add children to "Benutzer"
|
||||||
treeData.addItem(benutzerItem, new MenuTreeItem(getTranslation("nav.profile"), "edit-profile", VaadinIcon.USER));
|
treeData.addItem(benutzerItem,
|
||||||
treeData.addItem(benutzerItem, new MenuTreeItem(getTranslation("nav.myinvoices"), "my-invoices", VaadinIcon.FILE_TEXT));
|
new MenuTreeItem(getTranslation("nav.profile"), "edit-profile", VaadinIcon.USER));
|
||||||
treeData.addItem(benutzerItem, new MenuTreeItem(getTranslation("nav.imprint"), "impressum", VaadinIcon.INFO_CIRCLE));
|
treeData.addItem(benutzerItem,
|
||||||
|
new MenuTreeItem(getTranslation("nav.myinvoices"), "my-invoices", VaadinIcon.FILE_TEXT));
|
||||||
|
treeData.addItem(benutzerItem,
|
||||||
|
new MenuTreeItem(getTranslation("nav.imprint"), "impressum", VaadinIcon.INFO_CIRCLE));
|
||||||
|
|
||||||
// Create Tree
|
// Create Tree
|
||||||
tree = new TreeGrid<>();
|
tree = new TreeGrid<>();
|
||||||
@@ -223,16 +232,10 @@ public final class MainLayout extends AppLayout {
|
|||||||
TreeData<MenuTreeItem> treeData = dataProvider.getTreeData();
|
TreeData<MenuTreeItem> treeData = dataProvider.getTreeData();
|
||||||
|
|
||||||
// Find and update the messages item with new badge count
|
// Find and update the messages item with new badge count
|
||||||
treeData.getChildren(null).stream()
|
treeData.getChildren(null).stream().filter(item -> "messages".equals(item.path())).findFirst()
|
||||||
.filter(item -> "messages".equals(item.path()))
|
|
||||||
.findFirst()
|
|
||||||
.ifPresent(oldItem -> {
|
.ifPresent(oldItem -> {
|
||||||
MenuTreeItem newItem = new MenuTreeItem(
|
MenuTreeItem newItem = new MenuTreeItem(getTranslation("nav.messages"), "messages",
|
||||||
getTranslation("nav.messages"),
|
VaadinIcon.ENVELOPE, unreadCount);
|
||||||
"messages",
|
|
||||||
VaadinIcon.ENVELOPE,
|
|
||||||
unreadCount
|
|
||||||
);
|
|
||||||
messagesTreeItem = newItem;
|
messagesTreeItem = newItem;
|
||||||
// Refresh to show updated badge
|
// Refresh to show updated badge
|
||||||
dataProvider.refreshAll();
|
dataProvider.refreshAll();
|
||||||
@@ -249,8 +252,10 @@ public final class MainLayout extends AppLayout {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o)
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
return true;
|
||||||
|
if (o == null || getClass() != o.getClass())
|
||||||
|
return false;
|
||||||
MenuTreeItem that = (MenuTreeItem) o;
|
MenuTreeItem that = (MenuTreeItem) o;
|
||||||
return Objects.equals(label, that.label) && Objects.equals(path, that.path);
|
return Objects.equals(label, that.label) && Objects.equals(path, that.path);
|
||||||
}
|
}
|
||||||
@@ -307,7 +312,8 @@ public final class MainLayout extends AppLayout {
|
|||||||
userMenuItem.add(userNameSpan);
|
userMenuItem.add(userNameSpan);
|
||||||
|
|
||||||
// Profil anzeigen mit Navigation
|
// Profil anzeigen mit Navigation
|
||||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.showprofile"), e -> UI.getCurrent().navigate(EditProfileView.class));
|
userMenuItem.getSubMenu().addItem(getTranslation("nav.showprofile"),
|
||||||
|
e -> UI.getCurrent().navigate(EditProfileView.class));
|
||||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.settings"));
|
userMenuItem.getSubMenu().addItem(getTranslation("nav.settings"));
|
||||||
userMenuItem.getSubMenu().addItem(getTranslation("nav.logout"), e -> securityService.logout());
|
userMenuItem.getSubMenu().addItem(getTranslation("nav.logout"), e -> securityService.logout());
|
||||||
|
|
||||||
@@ -359,10 +365,10 @@ public final class MainLayout extends AppLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the user's preferred language if it differs from the current UI locale.
|
* Applies the user's preferred language if it differs from the current UI
|
||||||
* The primary locale setup happens in {@code LocaleVaadinInitListener} before the
|
* locale. The primary locale setup happens in {@code LocaleVaadinInitListener}
|
||||||
* layout is constructed. This method handles edge cases such as language changes
|
* before the layout is constructed. This method handles edge cases such as
|
||||||
* within an active session.
|
* language changes within an active session.
|
||||||
*/
|
*/
|
||||||
private void applyUserLanguagePreference() {
|
private void applyUserLanguagePreference() {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -90,11 +90,13 @@ public class AddAppUserView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
designationField.setPlaceholder("(HH H 000)");
|
designationField.setPlaceholder("(HH H 000)");
|
||||||
designationField.setWidthFull();
|
designationField.setWidthFull();
|
||||||
designationField.setRequiredIndicatorVisible(true);
|
designationField.setRequiredIndicatorVisible(true);
|
||||||
designationField.addBlurListener(e -> validateField(designationField, getTranslation("addappuser.validation.designation")));
|
designationField.addBlurListener(
|
||||||
|
e -> validateField(designationField, getTranslation("addappuser.validation.designation")));
|
||||||
|
|
||||||
firstnameField.setWidthFull();
|
firstnameField.setWidthFull();
|
||||||
firstnameField.setRequiredIndicatorVisible(true);
|
firstnameField.setRequiredIndicatorVisible(true);
|
||||||
firstnameField.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
firstnameField
|
||||||
|
.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||||
|
|
||||||
lastnameField.setWidthFull();
|
lastnameField.setWidthFull();
|
||||||
lastnameField.setRequiredIndicatorVisible(true);
|
lastnameField.setRequiredIndicatorVisible(true);
|
||||||
@@ -162,8 +164,8 @@ public class AddAppUserView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
binder.forField(lastnameField).bind(AppUser::getNachname, AppUser::setNachname);
|
binder.forField(lastnameField).bind(AppUser::getNachname, AppUser::setNachname);
|
||||||
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
|
binder.forField(phoneField).bind(AppUser::getTelefon, AppUser::setTelefon);
|
||||||
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
|
binder.forField(emailField).bind(AppUser::getEmail, AppUser::setEmail);
|
||||||
binder.forField(passwordField).asRequired(getTranslation("addappuser.validation.password.required")).bind(AppUser::getPassword,
|
binder.forField(passwordField).asRequired(getTranslation("addappuser.validation.password.required"))
|
||||||
AppUser::setPassword);
|
.bind(AppUser::getPassword, AppUser::setPassword);
|
||||||
|
|
||||||
// Confirm password field validation
|
// Confirm password field validation
|
||||||
binder.forField(confirmPasswordField).asRequired(getTranslation("addappuser.validation.password.confirm"))
|
binder.forField(confirmPasswordField).asRequired(getTranslation("addappuser.validation.password.confirm"))
|
||||||
@@ -201,13 +203,13 @@ public class AddAppUserView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
} catch (org.springframework.dao.DuplicateKeyException e) {
|
} catch (org.springframework.dao.DuplicateKeyException e) {
|
||||||
// Handle duplicate email error
|
// Handle duplicate email error
|
||||||
if (e.getMessage().contains("email")) {
|
if (e.getMessage().contains("email")) {
|
||||||
Notification.show(getTranslation("addappuser.notification.email.duplicate"), 5000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("addappuser.notification.email.duplicate"), 5000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
emailField.focus();
|
emailField.focus();
|
||||||
emailField.setInvalid(true);
|
emailField.setInvalid(true);
|
||||||
emailField.setErrorMessage(getTranslation("addappuser.notification.email.duplicate"));
|
emailField.setErrorMessage(getTranslation("addappuser.notification.email.duplicate"));
|
||||||
} else {
|
} else {
|
||||||
Notification.show(getTranslation("addappuser.notification.check"), 5000,
|
Notification.show(getTranslation("addappuser.notification.check"), 5000, Notification.Position.MIDDLE);
|
||||||
Notification.Position.MIDDLE);
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Notification.show(getTranslation("addappuser.notification.error", e.getMessage()), 5000,
|
Notification.show(getTranslation("addappuser.notification.error", e.getMessage()), 5000,
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
// Anrede (Dropdown)
|
// Anrede (Dropdown)
|
||||||
title = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
title = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||||
title.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
title.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"),
|
||||||
|
getTranslation("addjob.salutation.other"));
|
||||||
title.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
title.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
||||||
title.setWidthFull();
|
title.setWidthFull();
|
||||||
|
|
||||||
@@ -160,16 +161,16 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void configureBinder() {
|
private void configureBinder() {
|
||||||
binder.forField(companyName).asRequired(getTranslation("profile.validation.company.required")).bind(Customer::getCompanyName,
|
binder.forField(companyName).asRequired(getTranslation("profile.validation.company.required"))
|
||||||
Customer::setCompanyName);
|
.bind(Customer::getCompanyName, Customer::setCompanyName);
|
||||||
|
|
||||||
binder.forField(title).bind(Customer::getTitle, Customer::setTitle);
|
binder.forField(title).bind(Customer::getTitle, Customer::setTitle);
|
||||||
|
|
||||||
binder.forField(firstName).asRequired(getTranslation("profile.validation.firstname.required")).bind(Customer::getFirstname,
|
binder.forField(firstName).asRequired(getTranslation("profile.validation.firstname.required"))
|
||||||
Customer::setFirstname);
|
.bind(Customer::getFirstname, Customer::setFirstname);
|
||||||
|
|
||||||
binder.forField(lastName).asRequired(getTranslation("profile.validation.lastname.required")).bind(Customer::getLastName,
|
binder.forField(lastName).asRequired(getTranslation("profile.validation.lastname.required"))
|
||||||
Customer::setLastName);
|
.bind(Customer::getLastName, Customer::setLastName);
|
||||||
|
|
||||||
binder.forField(telephone).asRequired(getTranslation("profile.validation.phone")).bind(Customer::getTelephone,
|
binder.forField(telephone).asRequired(getTranslation("profile.validation.phone")).bind(Customer::getTelephone,
|
||||||
Customer::setTelephone);
|
Customer::setTelephone);
|
||||||
@@ -180,16 +181,19 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
.withValidator(email -> email.contains("@"), getTranslation("profile.validation.email.invalid"))
|
.withValidator(email -> email.contains("@"), getTranslation("profile.validation.email.invalid"))
|
||||||
.bind(Customer::getMail, Customer::setMail);
|
.bind(Customer::getMail, Customer::setMail);
|
||||||
|
|
||||||
binder.forField(street).asRequired(getTranslation("profile.validation.street.required")).bind(Customer::getStreet, Customer::setStreet);
|
binder.forField(street).asRequired(getTranslation("profile.validation.street.required"))
|
||||||
|
.bind(Customer::getStreet, Customer::setStreet);
|
||||||
|
|
||||||
binder.forField(houseNumber).asRequired(getTranslation("profile.validation.housenr.required")).bind(Customer::getHouseNumber,
|
binder.forField(houseNumber).asRequired(getTranslation("profile.validation.housenr.required"))
|
||||||
Customer::setHouseNumber);
|
.bind(Customer::getHouseNumber, Customer::setHouseNumber);
|
||||||
|
|
||||||
binder.forField(addressAddition).bind(Customer::getAddressAddition, Customer::setAddressAddition);
|
binder.forField(addressAddition).bind(Customer::getAddressAddition, Customer::setAddressAddition);
|
||||||
|
|
||||||
binder.forField(zip).asRequired(getTranslation("profile.validation.zip.required")).bind(Customer::getZip, Customer::setZip);
|
binder.forField(zip).asRequired(getTranslation("profile.validation.zip.required")).bind(Customer::getZip,
|
||||||
|
Customer::setZip);
|
||||||
|
|
||||||
binder.forField(city).asRequired(getTranslation("profile.validation.city.required")).bind(Customer::getCity, Customer::setCity);
|
binder.forField(city).asRequired(getTranslation("profile.validation.city.required")).bind(Customer::getCity,
|
||||||
|
Customer::setCity);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTestData() {
|
private void setTestData() {
|
||||||
@@ -201,7 +205,8 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
boolean isValid = validateAllFields();
|
boolean isValid = validateAllFields();
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.validation"), 3000,
|
com.vaadin.flow.component.notification.Notification.show(
|
||||||
|
getTranslation("addcustomer.notification.validation"), 3000,
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -212,16 +217,17 @@ public class AddCustomerView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
addCustomerService.addCustomer(customer);
|
addCustomerService.addCustomer(customer);
|
||||||
|
|
||||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.success"), 3000,
|
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.success"),
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
3000, com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||||
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("customers"));
|
getUI().ifPresent(ui -> ui.navigate("customers"));
|
||||||
|
|
||||||
} catch (ValidationException e) {
|
} catch (ValidationException e) {
|
||||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.check"), 3000,
|
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.check"),
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
3000, com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
com.vaadin.flow.component.notification.Notification.show(getTranslation("addcustomer.notification.error", e.getMessage()), 5000,
|
com.vaadin.flow.component.notification.Notification.show(
|
||||||
|
getTranslation("addcustomer.notification.error", e.getMessage()), 5000,
|
||||||
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
com.vaadin.flow.component.notification.Notification.Position.TOP_CENTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
pickupCompany.setAllowCustomValue(true);
|
pickupCompany.setAllowCustomValue(true);
|
||||||
setupCompanyAutocomplete(pickupCompany, true); // true für Pickup
|
setupCompanyAutocomplete(pickupCompany, true); // true für Pickup
|
||||||
pickupSalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
pickupSalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||||
pickupSalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
pickupSalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"),
|
||||||
|
getTranslation("addjob.salutation.other"));
|
||||||
pickupSalutation.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
pickupSalutation.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
||||||
pickupFirstName = new TextField(getTranslation("profile.firstname"));
|
pickupFirstName = new TextField(getTranslation("profile.firstname"));
|
||||||
pickupFirstName.setPlaceholder(getTranslation("profile.firstname"));
|
pickupFirstName.setPlaceholder(getTranslation("profile.firstname"));
|
||||||
@@ -353,7 +354,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
deliveryCompany.setAllowCustomValue(true);
|
deliveryCompany.setAllowCustomValue(true);
|
||||||
setupCompanyAutocomplete(deliveryCompany, false); // false für Delivery
|
setupCompanyAutocomplete(deliveryCompany, false); // false für Delivery
|
||||||
deliverySalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
deliverySalutation = new ComboBox<>(getTranslation("addjob.address.salutation"));
|
||||||
deliverySalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"), getTranslation("addjob.salutation.other"));
|
deliverySalutation.setItems(getTranslation("addjob.salutation.mr"), getTranslation("addjob.salutation.ms"),
|
||||||
|
getTranslation("addjob.salutation.other"));
|
||||||
deliverySalutation.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
deliverySalutation.setPlaceholder(getTranslation("addjob.address.salutation.placeholder"));
|
||||||
deliveryFirstName = new TextField(getTranslation("profile.firstname"));
|
deliveryFirstName = new TextField(getTranslation("profile.firstname"));
|
||||||
deliveryFirstName.setPlaceholder(getTranslation("profile.firstname"));
|
deliveryFirstName.setPlaceholder(getTranslation("profile.firstname"));
|
||||||
@@ -678,7 +680,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
manualInputRow.setWidthFull();
|
manualInputRow.setWidthFull();
|
||||||
manualInputRow.setSpacing(true);
|
manualInputRow.setSpacing(true);
|
||||||
|
|
||||||
manualDistanceInput = new com.vaadin.flow.component.textfield.NumberField(getTranslation("addjob.route.distance.km"));
|
manualDistanceInput = new com.vaadin.flow.component.textfield.NumberField(
|
||||||
|
getTranslation("addjob.route.distance.km"));
|
||||||
manualDistanceInput.setWidthFull();
|
manualDistanceInput.setWidthFull();
|
||||||
manualDistanceInput.setPlaceholder(getTranslation("addjob.route.distance.placeholder"));
|
manualDistanceInput.setPlaceholder(getTranslation("addjob.route.distance.placeholder"));
|
||||||
manualDistanceInput.setMin(0);
|
manualDistanceInput.setMin(0);
|
||||||
@@ -692,7 +695,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
manualDurationInput = new com.vaadin.flow.component.textfield.IntegerField(getTranslation("addjob.route.duration.min"));
|
manualDurationInput = new com.vaadin.flow.component.textfield.IntegerField(
|
||||||
|
getTranslation("addjob.route.duration.min"));
|
||||||
manualDurationInput.setWidthFull();
|
manualDurationInput.setWidthFull();
|
||||||
manualDurationInput.setPlaceholder(getTranslation("addjob.route.duration.placeholder"));
|
manualDurationInput.setPlaceholder(getTranslation("addjob.route.duration.placeholder"));
|
||||||
manualDurationInput.setMin(0);
|
manualDurationInput.setMin(0);
|
||||||
@@ -740,7 +744,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
}
|
}
|
||||||
// Show price info if no route calculated yet
|
// Show price info if no route calculated yet
|
||||||
if (service.getCalculationBasis() == Service.CalculationBasis.DISTANCE && routeDistance == null) {
|
if (service.getCalculationBasis() == Service.CalculationBasis.DISTANCE && routeDistance == null) {
|
||||||
return service.getPricePerKilometer().setScale(2, RoundingMode.HALF_UP) + " €/km (" + getTranslation("addjob.services.route.missing") + ")";
|
return service.getPricePerKilometer().setScale(2, RoundingMode.HALF_UP) + " €/km ("
|
||||||
|
+ getTranslation("addjob.services.route.missing") + ")";
|
||||||
}
|
}
|
||||||
return service.getEffectivePrice() != null
|
return service.getEffectivePrice() != null
|
||||||
? service.getEffectivePrice().setScale(2, RoundingMode.HALF_UP) + " €"
|
? service.getEffectivePrice().setScale(2, RoundingMode.HALF_UP) + " €"
|
||||||
@@ -937,7 +942,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
|
if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
|
||||||
// Dauer in 15-Minuten-Einheiten umrechnen (aufrunden)
|
// Dauer in 15-Minuten-Einheiten umrechnen (aufrunden)
|
||||||
int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
|
int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
|
||||||
if (durationSeconds % 900 > 0) units++; // Aufrunden
|
if (durationSeconds % 900 > 0)
|
||||||
|
units++; // Aufrunden
|
||||||
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
|
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
|
||||||
}
|
}
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
@@ -1245,8 +1251,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
boolean digital = Boolean.TRUE.equals(digitalProcessing.getValue());
|
boolean digital = Boolean.TRUE.equals(digitalProcessing.getValue());
|
||||||
boolean hasUser = selectedUserId != null && !selectedUserId.trim().isEmpty();
|
boolean hasUser = selectedUserId != null && !selectedUserId.trim().isEmpty();
|
||||||
return !digital || hasUser;
|
return !digital || hasUser;
|
||||||
}, getTranslation("addjob.validation.appuser.required"))
|
}, getTranslation("addjob.validation.appuser.required")).bind(Job::getAppUser, Job::setAppUser);
|
||||||
.bind(Job::getAppUser, Job::setAppUser);
|
|
||||||
|
|
||||||
// Toggle required indicator and visibility for App-Nutzer based on
|
// Toggle required indicator and visibility for App-Nutzer based on
|
||||||
// digitalProcessing
|
// digitalProcessing
|
||||||
@@ -1525,14 +1530,15 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
// Manuelle Eingabe verwenden
|
// Manuelle Eingabe verwenden
|
||||||
job.setRouteDistanceKm(manualDistanceInput.getValue());
|
job.setRouteDistanceKm(manualDistanceInput.getValue());
|
||||||
if (manualDurationInput != null && manualDurationInput.getValue() != null) {
|
if (manualDurationInput != null && manualDurationInput.getValue() != null) {
|
||||||
job.setRouteDurationSeconds(manualDurationInput.getValue() * 60); // Minuten in Sekunden umrechnen
|
job.setRouteDurationSeconds(manualDurationInput.getValue() * 60); // Minuten in Sekunden
|
||||||
|
// umrechnen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Additional validation: If digital processing is enabled, app user must be
|
// Additional validation: If digital processing is enabled, app user must be
|
||||||
// selected
|
// selected
|
||||||
if (digitalProcessing.getValue() && appUser.getValue() == null) {
|
if (digitalProcessing.getValue() && appUser.getValue() == null) {
|
||||||
Notification errorNotification = Notification.show(
|
Notification errorNotification = Notification
|
||||||
getTranslation("addjob.validation.appuser.required"));
|
.show(getTranslation("addjob.validation.appuser.required"));
|
||||||
errorNotification.setDuration(5000);
|
errorNotification.setDuration(5000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1600,8 +1606,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
getUI().ifPresent(ui -> ui.navigate(JobSummaryView.class, savedJob.getId().toHexString()));
|
getUI().ifPresent(ui -> ui.navigate(JobSummaryView.class, savedJob.getId().toHexString()));
|
||||||
} else {
|
} else {
|
||||||
// Validation failed, show error message
|
// Validation failed, show error message
|
||||||
Notification errorNotification = Notification
|
Notification errorNotification = Notification.show(getTranslation("addjob.validation.required.fields"));
|
||||||
.show(getTranslation("addjob.validation.required.fields"));
|
|
||||||
errorNotification.setDuration(5000);
|
errorNotification.setDuration(5000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1612,7 +1617,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
cargoError.setVisible(false);
|
cargoError.setVisible(false);
|
||||||
if (cargoAreaContainer != null)
|
if (cargoAreaContainer != null)
|
||||||
cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
|
cargoAreaContainer.getStyle().set("border", "1px solid var(--lumo-contrast-20pct)");
|
||||||
Notification errorNotification = Notification.show(getTranslation("addjob.notification.error", e.getMessage()));
|
Notification errorNotification = Notification
|
||||||
|
.show(getTranslation("addjob.notification.error", e.getMessage()));
|
||||||
errorNotification.setDuration(5000);
|
errorNotification.setDuration(5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1659,8 +1665,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
loadJobIntoForm(draft);
|
loadJobIntoForm(draft);
|
||||||
|
|
||||||
// Benutzer informieren
|
// Benutzer informieren
|
||||||
Notification notification = Notification
|
Notification notification = Notification.show(getTranslation("addjob.notification.draft.restored"));
|
||||||
.show(getTranslation("addjob.notification.draft.restored"));
|
|
||||||
notification.setDuration(4000);
|
notification.setDuration(4000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1712,7 +1717,9 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
row.setAlignItems(FlexComponent.Alignment.END);
|
row.setAlignItems(FlexComponent.Alignment.END);
|
||||||
|
|
||||||
ComboBox<String> desc = new ComboBox<>(getTranslation("addjob.cargo.description"));
|
ComboBox<String> desc = new ComboBox<>(getTranslation("addjob.cargo.description"));
|
||||||
desc.setItems(getTranslation("addjob.cargo.europalette"), getTranslation("addjob.cargo.disposablepalette"), getTranslation("addjob.cargo.dusseldorfpalette"), getTranslation("addjob.cargo.gridboxpalette"), getTranslation("addjob.cargo.gridcart"), getTranslation("addjob.cargo.parcel"));
|
desc.setItems(getTranslation("addjob.cargo.europalette"), getTranslation("addjob.cargo.disposablepalette"),
|
||||||
|
getTranslation("addjob.cargo.dusseldorfpalette"), getTranslation("addjob.cargo.gridboxpalette"),
|
||||||
|
getTranslation("addjob.cargo.gridcart"), getTranslation("addjob.cargo.parcel"));
|
||||||
desc.setAllowCustomValue(true);
|
desc.setAllowCustomValue(true);
|
||||||
desc.setPlaceholder(getTranslation("addjob.cargo.description.placeholder"));
|
desc.setPlaceholder(getTranslation("addjob.cargo.description.placeholder"));
|
||||||
desc.setWidth("40%");
|
desc.setWidth("40%");
|
||||||
@@ -2510,7 +2517,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
try {
|
try {
|
||||||
// Check if there are any tasks to save
|
// Check if there are any tasks to save
|
||||||
if (tasksState.isEmpty()) {
|
if (tasksState.isEmpty()) {
|
||||||
Notification.show(getTranslation("addjob.tasks.template.no.tasks"), 3000, Notification.Position.BOTTOM_END);
|
Notification.show(getTranslation("addjob.tasks.template.no.tasks"), 3000,
|
||||||
|
Notification.Position.BOTTOM_END);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2577,7 +2585,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error opening save template dialog", e);
|
log.error("Error opening save template dialog", e);
|
||||||
Notification.show(getTranslation("addjob.tasks.template.dialog.error", e.getMessage()), 4000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("addjob.tasks.template.dialog.error", e.getMessage()), 4000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2644,7 +2653,8 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
templateComboBox.setItems(templates);
|
templateComboBox.setItems(templates);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error loading templates", e);
|
log.error("Error loading templates", e);
|
||||||
Notification.show(getTranslation("addjob.tasks.template.load.templates.error", e.getMessage()), 4000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("addjob.tasks.template.load.templates.error", e.getMessage()), 4000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3075,10 +3085,12 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
// Abholadresse anzeigen
|
// Abholadresse anzeigen
|
||||||
if (pickupResult != null) {
|
if (pickupResult != null) {
|
||||||
if (pickupResult.isValid()) {
|
if (pickupResult.isValid()) {
|
||||||
pickupResultLabel.setText("✓ " + getTranslation("addjob.validation.pickup.address") + ": " + pickupResult.getFormattedAddress());
|
pickupResultLabel.setText("✓ " + getTranslation("addjob.validation.pickup.address") + ": "
|
||||||
|
+ pickupResult.getFormattedAddress());
|
||||||
pickupResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
pickupResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||||
} else {
|
} else {
|
||||||
pickupResultLabel.setText("⚠ " + getTranslation("addjob.validation.pickup.address") + ": " + pickupResult.getValidationMessage());
|
pickupResultLabel.setText("⚠ " + getTranslation("addjob.validation.pickup.address") + ": "
|
||||||
|
+ pickupResult.getValidationMessage());
|
||||||
pickupResultLabel.getStyle().set("color", "var(--lumo-error-text-color)");
|
pickupResultLabel.getStyle().set("color", "var(--lumo-error-text-color)");
|
||||||
hasInvalidAddress = true;
|
hasInvalidAddress = true;
|
||||||
bothAddressesValid = false;
|
bothAddressesValid = false;
|
||||||
@@ -3090,10 +3102,12 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
// Lieferadresse anzeigen
|
// Lieferadresse anzeigen
|
||||||
if (deliveryResult != null) {
|
if (deliveryResult != null) {
|
||||||
if (deliveryResult.isValid()) {
|
if (deliveryResult.isValid()) {
|
||||||
deliveryResultLabel.setText("✓ " + getTranslation("addjob.validation.delivery.address") + ": " + deliveryResult.getFormattedAddress());
|
deliveryResultLabel.setText("✓ " + getTranslation("addjob.validation.delivery.address") + ": "
|
||||||
|
+ deliveryResult.getFormattedAddress());
|
||||||
deliveryResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
deliveryResultLabel.getStyle().set("color", "var(--lumo-success-text-color)");
|
||||||
} else {
|
} else {
|
||||||
deliveryResultLabel.setText("⚠ " + getTranslation("addjob.validation.delivery.address") + ": " + deliveryResult.getValidationMessage());
|
deliveryResultLabel.setText("⚠ " + getTranslation("addjob.validation.delivery.address") + ": "
|
||||||
|
+ deliveryResult.getValidationMessage());
|
||||||
deliveryResultLabel.getStyle().set("color", "var(--lumo-error-text-color)");
|
deliveryResultLabel.getStyle().set("color", "var(--lumo-error-text-color)");
|
||||||
hasInvalidAddress = true;
|
hasInvalidAddress = true;
|
||||||
bothAddressesValid = false;
|
bothAddressesValid = false;
|
||||||
@@ -3104,8 +3118,10 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
// Route anzeigen, wenn beide Adressen gültig sind
|
// Route anzeigen, wenn beide Adressen gültig sind
|
||||||
if (bothAddressesValid && routeCalculationResult != null && routeCalculationResult.isValid()) {
|
if (bothAddressesValid && routeCalculationResult != null && routeCalculationResult.isValid()) {
|
||||||
routeResultLabel.setText("🚛 " + getTranslation("addjob.validation.route") + ": " + String.format("%.1f km", routeCalculationResult.getDistanceKm())
|
routeResultLabel.setText("🚛 " + getTranslation("addjob.validation.route") + ": "
|
||||||
+ " (" + getTranslation("addjob.route.duration") + ": " + routeCalculationResult.getFormattedDurationLong() + ")");
|
+ String.format("%.1f km", routeCalculationResult.getDistanceKm()) + " ("
|
||||||
|
+ getTranslation("addjob.route.duration") + ": " + routeCalculationResult.getFormattedDurationLong()
|
||||||
|
+ ")");
|
||||||
routeResultLabel.getStyle().set("color", "var(--lumo-primary-text-color)");
|
routeResultLabel.getStyle().set("color", "var(--lumo-primary-text-color)");
|
||||||
routeResultLabel.setVisible(true);
|
routeResultLabel.setVisible(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -3234,9 +3250,11 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gibt die berechnete oder manuell eingegebene Entfernung zurück (für Preisberechnungen).
|
* Gibt die berechnete oder manuell eingegebene Entfernung zurück (für
|
||||||
|
* Preisberechnungen).
|
||||||
*
|
*
|
||||||
* @return Entfernung in km oder 0.0, wenn keine Berechnung oder Eingabe vorhanden ist
|
* @return Entfernung in km oder 0.0, wenn keine Berechnung oder Eingabe
|
||||||
|
* vorhanden ist
|
||||||
*/
|
*/
|
||||||
public double getRouteDistanceKm() {
|
public double getRouteDistanceKm() {
|
||||||
if (routeCalculationResult != null && routeCalculationResult.isValid()) {
|
if (routeCalculationResult != null && routeCalculationResult.isValid()) {
|
||||||
|
|||||||
@@ -148,19 +148,23 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
// Total jobs card
|
// Total jobs card
|
||||||
long totalJobs = jobRepository.count();
|
long totalJobs = jobRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.totaljobs"), String.valueOf(totalJobs), VaadinIcon.PACKAGE, "blue"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.totaljobs"), String.valueOf(totalJobs),
|
||||||
|
VaadinIcon.PACKAGE, "blue"));
|
||||||
|
|
||||||
// Total users card
|
// Total users card
|
||||||
long totalUsers = userRepository.count();
|
long totalUsers = userRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.users"), String.valueOf(totalUsers), VaadinIcon.USERS, "green"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.users"), String.valueOf(totalUsers),
|
||||||
|
VaadinIcon.USERS, "green"));
|
||||||
|
|
||||||
// Total app users card
|
// Total app users card
|
||||||
long totalAppUsers = appUserRepository.count();
|
long totalAppUsers = appUserRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.appusers"), String.valueOf(totalAppUsers), VaadinIcon.MOBILE, "purple"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.appusers"), String.valueOf(totalAppUsers),
|
||||||
|
VaadinIcon.MOBILE, "purple"));
|
||||||
|
|
||||||
// Current time
|
// Current time
|
||||||
String currentTime = DateTimeFormatUtil.formatDateTime(LocalDateTime.now());
|
String currentTime = DateTimeFormatUtil.formatDateTime(LocalDateTime.now());
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.lastupdated"), currentTime, VaadinIcon.CLOCK, "gray"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.lastupdated"), currentTime, VaadinIcon.CLOCK,
|
||||||
|
"gray"));
|
||||||
|
|
||||||
section.add(title, cards);
|
section.add(title, cards);
|
||||||
return section;
|
return section;
|
||||||
@@ -185,17 +189,22 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
|||||||
long inProgressJobs = jobRepository.countByStatus(JobStatus.IN_PROGRESS);
|
long inProgressJobs = jobRepository.countByStatus(JobStatus.IN_PROGRESS);
|
||||||
long completedJobs = jobRepository.countByStatus(JobStatus.COMPLETED);
|
long completedJobs = jobRepository.countByStatus(JobStatus.COMPLETED);
|
||||||
|
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.openjobs"), String.valueOf(openJobs), VaadinIcon.HOURGLASS_START, "orange"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.openjobs"), String.valueOf(openJobs),
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.inprogress"), String.valueOf(inProgressJobs), VaadinIcon.PLAY, "blue"));
|
VaadinIcon.HOURGLASS_START, "orange"));
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.completed"), String.valueOf(completedJobs), VaadinIcon.CHECK_CIRCLE, "green"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.inprogress"), String.valueOf(inProgressJobs),
|
||||||
|
VaadinIcon.PLAY, "blue"));
|
||||||
|
cards.add(createStatCard(getTranslation("admindashboard.stat.completed"), String.valueOf(completedJobs),
|
||||||
|
VaadinIcon.CHECK_CIRCLE, "green"));
|
||||||
|
|
||||||
// Total cargo items
|
// Total cargo items
|
||||||
long totalCargoItems = cargoItemRepository.count();
|
long totalCargoItems = cargoItemRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.cargo"), String.valueOf(totalCargoItems), VaadinIcon.CUBE, "purple"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.cargo"), String.valueOf(totalCargoItems),
|
||||||
|
VaadinIcon.CUBE, "purple"));
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Could not load job statistics by status", e);
|
log.warn("Could not load job statistics by status", e);
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.status.info"), getTranslation("admindashboard.stat.status.unavailable"), VaadinIcon.WARNING, "red"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.status.info"),
|
||||||
|
getTranslation("admindashboard.stat.status.unavailable"), VaadinIcon.WARNING, "red"));
|
||||||
}
|
}
|
||||||
|
|
||||||
section.add(title, cards);
|
section.add(title, cards);
|
||||||
@@ -217,20 +226,23 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
// Total tasks
|
// Total tasks
|
||||||
long totalTasks = taskRepository.count();
|
long totalTasks = taskRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.totaltasks"), String.valueOf(totalTasks), VaadinIcon.TASKS, "blue"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.totaltasks"), String.valueOf(totalTasks),
|
||||||
|
VaadinIcon.TASKS, "blue"));
|
||||||
|
|
||||||
// Completed tasks
|
// Completed tasks
|
||||||
long completedTasks = taskRepository.countByCompleted(true);
|
long completedTasks = taskRepository.countByCompleted(true);
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.completedtasks"), String.valueOf(completedTasks), VaadinIcon.CHECK, "green"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.completedtasks"), String.valueOf(completedTasks),
|
||||||
|
VaadinIcon.CHECK, "green"));
|
||||||
|
|
||||||
// Pending tasks
|
// Pending tasks
|
||||||
long pendingTasks = totalTasks - completedTasks;
|
long pendingTasks = totalTasks - completedTasks;
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.pendingtasks"), String.valueOf(pendingTasks), VaadinIcon.CLOCK, "orange"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.pendingtasks"), String.valueOf(pendingTasks),
|
||||||
|
VaadinIcon.CLOCK, "orange"));
|
||||||
|
|
||||||
// Completion rate
|
// Completion rate
|
||||||
double completionRate = totalTasks > 0 ? (completedTasks * 100.0 / totalTasks) : 0;
|
double completionRate = totalTasks > 0 ? (completedTasks * 100.0 / totalTasks) : 0;
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.successrate"), String.format("%.1f%%", completionRate), VaadinIcon.TRENDING_UP,
|
cards.add(createStatCard(getTranslation("admindashboard.stat.successrate"),
|
||||||
"purple"));
|
String.format("%.1f%%", completionRate), VaadinIcon.TRENDING_UP, "purple"));
|
||||||
|
|
||||||
section.add(title, cards);
|
section.add(title, cards);
|
||||||
return section;
|
return section;
|
||||||
@@ -251,16 +263,20 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
|||||||
|
|
||||||
// Content statistics
|
// Content statistics
|
||||||
long totalPhotos = photoRepository.count();
|
long totalPhotos = photoRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.photos"), String.valueOf(totalPhotos), VaadinIcon.CAMERA, "blue"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.photos"), String.valueOf(totalPhotos),
|
||||||
|
VaadinIcon.CAMERA, "blue"));
|
||||||
|
|
||||||
long totalBarcodes = barcodeRepository.count();
|
long totalBarcodes = barcodeRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.barcodes"), String.valueOf(totalBarcodes), VaadinIcon.BARCODE, "green"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.barcodes"), String.valueOf(totalBarcodes),
|
||||||
|
VaadinIcon.BARCODE, "green"));
|
||||||
|
|
||||||
long totalSignatures = signatureRepository.count();
|
long totalSignatures = signatureRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.signatures"), String.valueOf(totalSignatures), VaadinIcon.EDIT, "purple"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.signatures"), String.valueOf(totalSignatures),
|
||||||
|
VaadinIcon.EDIT, "purple"));
|
||||||
|
|
||||||
long totalComments = commentRepository.count();
|
long totalComments = commentRepository.count();
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.comments"), String.valueOf(totalComments), VaadinIcon.COMMENT, "orange"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.comments"), String.valueOf(totalComments),
|
||||||
|
VaadinIcon.COMMENT, "orange"));
|
||||||
|
|
||||||
section.add(title, cards);
|
section.add(title, cards);
|
||||||
return section;
|
return section;
|
||||||
@@ -282,16 +298,20 @@ public class AdminDashboardView extends Main implements HasDynamicTitle {
|
|||||||
// Database connection status
|
// Database connection status
|
||||||
try {
|
try {
|
||||||
userRepository.count(); // Test database connection
|
userRepository.count(); // Test database connection
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"), getTranslation("admindashboard.stat.database.connected"), VaadinIcon.DATABASE, "green"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.database"),
|
||||||
|
getTranslation("admindashboard.stat.database.connected"), VaadinIcon.DATABASE, "green"));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.database"), getTranslation("admindashboard.stat.database.error"), VaadinIcon.DATABASE, "red"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.database"),
|
||||||
|
getTranslation("admindashboard.stat.database.error"), VaadinIcon.DATABASE, "red"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messaging status
|
// Messaging status
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.websocket"), getTranslation("admindashboard.stat.websocket.active"), VaadinIcon.CONNECT, "green"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.websocket"),
|
||||||
|
getTranslation("admindashboard.stat.websocket.active"), VaadinIcon.CONNECT, "green"));
|
||||||
|
|
||||||
// System uptime (placeholder)
|
// System uptime (placeholder)
|
||||||
cards.add(createStatCard(getTranslation("admindashboard.stat.app"), getTranslation("admindashboard.stat.app.running"), VaadinIcon.HEART, "green"));
|
cards.add(createStatCard(getTranslation("admindashboard.stat.app"),
|
||||||
|
getTranslation("admindashboard.stat.app.running"), VaadinIcon.HEART, "green"));
|
||||||
|
|
||||||
// Memory usage (placeholder)
|
// Memory usage (placeholder)
|
||||||
Runtime runtime = Runtime.getRuntime();
|
Runtime runtime = Runtime.getRuntime();
|
||||||
|
|||||||
@@ -75,9 +75,11 @@ public class AdminPricetableView extends VerticalLayout implements HasDynamicTit
|
|||||||
priceTable.setRevenueParticipation(revenueParticipation.getValue());
|
priceTable.setRevenueParticipation(revenueParticipation.getValue());
|
||||||
|
|
||||||
priceTableRepository.save(priceTable);
|
priceTableRepository.save(priceTable);
|
||||||
Notification.show(getTranslation("adminpricetable.notification.saved"), 3000, Notification.Position.BOTTOM_CENTER);
|
Notification.show(getTranslation("adminpricetable.notification.saved"), 3000,
|
||||||
|
Notification.Position.BOTTOM_CENTER);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Notification.show(getTranslation("adminpricetable.notification.save.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_CENTER);
|
Notification.show(getTranslation("adminpricetable.notification.save.error", ex.getMessage()), 5000,
|
||||||
|
Notification.Position.BOTTOM_CENTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,11 +52,15 @@ public class AppUserView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
appUserGrid.setSizeFull();
|
appUserGrid.setSizeFull();
|
||||||
|
|
||||||
// Grid-Spalten konfigurieren
|
// Grid-Spalten konfigurieren
|
||||||
appUserGrid.addColumn(AppUser::getBezeichnung).setHeader(getTranslation("appuser.column.designation")).setAutoWidth(true);
|
appUserGrid.addColumn(AppUser::getBezeichnung).setHeader(getTranslation("appuser.column.designation"))
|
||||||
appUserGrid.addColumn(AppUser::getVorname).setHeader(getTranslation("appuser.column.firstname")).setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
appUserGrid.addColumn(AppUser::getNachname).setHeader(getTranslation("appuser.column.lastname")).setAutoWidth(true);
|
appUserGrid.addColumn(AppUser::getVorname).setHeader(getTranslation("appuser.column.firstname"))
|
||||||
|
.setAutoWidth(true);
|
||||||
|
appUserGrid.addColumn(AppUser::getNachname).setHeader(getTranslation("appuser.column.lastname"))
|
||||||
|
.setAutoWidth(true);
|
||||||
appUserGrid.addColumn(AppUser::getTelefon).setHeader(getTranslation("appuser.column.phone")).setAutoWidth(true);
|
appUserGrid.addColumn(AppUser::getTelefon).setHeader(getTranslation("appuser.column.phone")).setAutoWidth(true);
|
||||||
appUserGrid.addColumn(AppUser::getAppCode).setHeader(getTranslation("appuser.column.appcode")).setAutoWidth(true);
|
appUserGrid.addColumn(AppUser::getAppCode).setHeader(getTranslation("appuser.column.appcode"))
|
||||||
|
.setAutoWidth(true);
|
||||||
appUserGrid.addColumn(AppUser::getEmail).setHeader(getTranslation("appuser.column.email")).setAutoWidth(true);
|
appUserGrid.addColumn(AppUser::getEmail).setHeader(getTranslation("appuser.column.email")).setAutoWidth(true);
|
||||||
|
|
||||||
// Make grid rows clickable
|
// Make grid rows clickable
|
||||||
|
|||||||
@@ -106,14 +106,11 @@ public class AuthenticatedStartView extends VerticalLayout implements HasDynamic
|
|||||||
|
|
||||||
// Feature Cards
|
// Feature Cards
|
||||||
featuresGrid.add(
|
featuresGrid.add(
|
||||||
createFeatureCard(VaadinIcon.COG,
|
createFeatureCard(VaadinIcon.COG, getTranslation("dashboard.feature.setup.title"),
|
||||||
getTranslation("dashboard.feature.setup.title"),
|
|
||||||
getTranslation("dashboard.feature.setup.desc")),
|
getTranslation("dashboard.feature.setup.desc")),
|
||||||
createFeatureCard(VaadinIcon.USERS,
|
createFeatureCard(VaadinIcon.USERS, getTranslation("dashboard.feature.customers.title"),
|
||||||
getTranslation("dashboard.feature.customers.title"),
|
|
||||||
getTranslation("dashboard.feature.customers.desc")),
|
getTranslation("dashboard.feature.customers.desc")),
|
||||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT,
|
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, getTranslation("dashboard.feature.jobs.title"),
|
||||||
getTranslation("dashboard.feature.jobs.title"),
|
|
||||||
getTranslation("dashboard.feature.jobs.desc")));
|
getTranslation("dashboard.feature.jobs.desc")));
|
||||||
|
|
||||||
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
systemSection.add(systemTitle, systemIntro, featuresGrid);
|
||||||
|
|||||||
@@ -188,11 +188,14 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
jobInfo.setSpacing(true);
|
jobInfo.setSpacing(true);
|
||||||
jobInfo.setWidthFull();
|
jobInfo.setWidthFull();
|
||||||
|
|
||||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.jobnumber")), new Span(currentJob.getJobNumber())));
|
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.jobnumber")),
|
||||||
|
new Span(currentJob.getJobNumber())));
|
||||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.customer")),
|
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.customer")),
|
||||||
new Span(extractCompanyName(currentJob.getCustomerSelection()))));
|
new Span(extractCompanyName(currentJob.getCustomerSelection()))));
|
||||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.status")), new Span(currentJob.getStatus().toString())));
|
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.status")),
|
||||||
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.price")), new Span(currentJob.getPrice() + " €")));
|
new Span(currentJob.getStatus().toString())));
|
||||||
|
jobInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.field.price")),
|
||||||
|
new Span(currentJob.getPrice() + " €")));
|
||||||
|
|
||||||
section.add(jobInfo);
|
section.add(jobInfo);
|
||||||
return section;
|
return section;
|
||||||
@@ -307,8 +310,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
// Show only net sum, VAT sums, and total amount without individual services
|
// Show only net sum, VAT sums, and total amount without individual services
|
||||||
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.net")),
|
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.net")),
|
||||||
new Span(netAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
new Span(netAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
||||||
summaryInfo
|
summaryInfo.add(new HorizontalLayout(
|
||||||
.add(new HorizontalLayout(
|
|
||||||
new Span(getTranslation("createinvoice.summary.vat",
|
new Span(getTranslation("createinvoice.summary.vat",
|
||||||
vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).toString())),
|
vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP).toString())),
|
||||||
new Span(vatAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
new Span(vatAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
||||||
@@ -398,7 +400,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
|
|
||||||
private void createInvoice() {
|
private void createInvoice() {
|
||||||
if (getSelectedServices().isEmpty()) {
|
if (getSelectedServices().isEmpty()) {
|
||||||
Notification.show(getTranslation("createinvoice.notification.noservices"), 3000, Notification.Position.BOTTOM_END);
|
Notification.show(getTranslation("createinvoice.notification.noservices"), 3000,
|
||||||
|
Notification.Position.BOTTOM_END);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,38 +414,47 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
.flatMap(auth -> userRepository.findByEmail(auth.getUsername()));
|
.flatMap(auth -> userRepository.findByEmail(auth.getUsername()));
|
||||||
|
|
||||||
if (currentUserOpt.isEmpty()) {
|
if (currentUserOpt.isEmpty()) {
|
||||||
Notification.show(getTranslation("createinvoice.notification.nouser"), 3000, Notification.Position.BOTTOM_END);
|
Notification.show(getTranslation("createinvoice.notification.nouser"), 3000,
|
||||||
|
Notification.Position.BOTTOM_END);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentUser = currentUserOpt.get();
|
currentUser = currentUserOpt.get();
|
||||||
|
|
||||||
// Load invoice template from service
|
// Load invoice template from service
|
||||||
Optional<InvoiceTemplate> templateOpt = invoiceTemplateService.getTemplateByUserId(currentUser.getId().toString());
|
Optional<InvoiceTemplate> templateOpt = invoiceTemplateService
|
||||||
|
.getTemplateByUserId(currentUser.getId().toString());
|
||||||
if (templateOpt.isEmpty()) {
|
if (templateOpt.isEmpty()) {
|
||||||
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000, Notification.Position.BOTTOM_END);
|
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000,
|
||||||
|
Notification.Position.BOTTOM_END);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String templateData = templateOpt.get().getTemplateData();
|
String templateData = templateOpt.get().getTemplateData();
|
||||||
System.out.println("DEBUG CreateInvoiceView: Template data length: " + (templateData != null ? templateData.length() : 0));
|
System.out.println("DEBUG CreateInvoiceView: Template data length: "
|
||||||
System.out.println("DEBUG CreateInvoiceView: Template data preview: " + (templateData != null ? templateData.substring(0, Math.min(200, templateData.length())) : "null"));
|
+ (templateData != null ? templateData.length() : 0));
|
||||||
|
System.out.println("DEBUG CreateInvoiceView: Template data preview: "
|
||||||
|
+ (templateData != null ? templateData.substring(0, Math.min(200, templateData.length()))
|
||||||
|
: "null"));
|
||||||
|
|
||||||
if (templateData == null || templateData.isBlank()) {
|
if (templateData == null || templateData.isBlank()) {
|
||||||
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000, Notification.Position.BOTTOM_END);
|
Notification.show(getTranslation("createinvoice.notification.notemplate"), 3000,
|
||||||
|
Notification.Position.BOTTOM_END);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate PDF with template and actual job data
|
// Generate PDF with template and actual job data
|
||||||
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser);
|
byte[] pdfBytes = generateInvoicePdfFromTemplate(templateData, currentUser);
|
||||||
System.out.println("DEBUG CreateInvoiceView: PDF bytes generated: " + (pdfBytes != null ? pdfBytes.length : 0));
|
System.out.println(
|
||||||
|
"DEBUG CreateInvoiceView: PDF bytes generated: " + (pdfBytes != null ? pdfBytes.length : 0));
|
||||||
|
|
||||||
// Show PDF in dialog
|
// Show PDF in dialog
|
||||||
showPdfInDialog(pdfBytes, "Rechnung " + currentJob.getJobNumber());
|
showPdfInDialog(pdfBytes, "Rechnung " + currentJob.getJobNumber());
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
log.error("Fehler beim Erstellen der Rechnung", ex);
|
log.error("Fehler beim Erstellen der Rechnung", ex);
|
||||||
Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END);
|
Notification.show(getTranslation("createinvoice.notification.error", ex.getMessage()), 5000,
|
||||||
|
Notification.Position.BOTTOM_END);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,10 +518,14 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
// Invoice data
|
// Invoice data
|
||||||
variables.put("invoice.number", currentJob.getJobNumber() + "-" + System.currentTimeMillis());
|
variables.put("invoice.number", currentJob.getJobNumber() + "-" + System.currentTimeMillis());
|
||||||
variables.put("invoice.date", java.time.LocalDate.now().toString());
|
variables.put("invoice.date", java.time.LocalDate.now().toString());
|
||||||
variables.put("invoice.net_total", netAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
variables.put("invoice.net_total",
|
||||||
variables.put("invoice.vat_total", vatAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
netAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||||
variables.put("invoice.gross_total", totalAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
variables.put("invoice.vat_total",
|
||||||
variables.put("invoice.vat_rate", vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
vatAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||||
|
variables.put("invoice.gross_total",
|
||||||
|
totalAmount.setScale(2, RoundingMode.HALF_UP).toString().replace(".", ",") + " €");
|
||||||
|
variables.put("invoice.vat_rate",
|
||||||
|
vatRate.multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
||||||
|
|
||||||
// Job data
|
// Job data
|
||||||
if (currentJob.getRouteDistanceKm() != null) {
|
if (currentJob.getRouteDistanceKm() != null) {
|
||||||
@@ -534,7 +550,8 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
|
|
||||||
// VAT rate
|
// VAT rate
|
||||||
if (service.getVatRate() != null) {
|
if (service.getVatRate() != null) {
|
||||||
serviceData.put("vatRate", service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
serviceData.put("vatRate",
|
||||||
|
service.getVatRate().multiply(new BigDecimal("100")).setScale(0, RoundingMode.HALF_UP) + "%");
|
||||||
} else {
|
} else {
|
||||||
serviceData.put("vatRate", "19%");
|
serviceData.put("vatRate", "19%");
|
||||||
}
|
}
|
||||||
@@ -593,11 +610,9 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
|
|
||||||
// Download button
|
// Download button
|
||||||
Button downloadButton = new Button(getTranslation("button.download"), e -> {
|
Button downloadButton = new Button(getTranslation("button.download"), e -> {
|
||||||
getElement()
|
getElement().executeJs("const link = document.createElement('a');"
|
||||||
.executeJs("const link = document.createElement('a');" +
|
+ "link.href = 'data:application/pdf;base64," + base64Pdf + "';" + "link.download = '"
|
||||||
"link.href = 'data:application/pdf;base64," + base64Pdf + "';" +
|
+ title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf';" + "link.click();");
|
||||||
"link.download = '" + title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf';" +
|
|
||||||
"link.click();");
|
|
||||||
});
|
});
|
||||||
downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
downloadButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,8 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
binder.readBean(appUser);
|
binder.readBean(appUser);
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Notification.show(getTranslation("editappuser.notification.invalid.id"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editappuser.notification.invalid.id"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -191,7 +192,8 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
// Passwords match, set new password for hashing
|
// Passwords match, set new password for hashing
|
||||||
appUser.setPassword(newPassword);
|
appUser.setPassword(newPassword);
|
||||||
} else {
|
} else {
|
||||||
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -216,18 +218,21 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
boolean confirmPasswordFilled = confirmPassword != null && !confirmPassword.trim().isEmpty();
|
boolean confirmPasswordFilled = confirmPassword != null && !confirmPassword.trim().isEmpty();
|
||||||
|
|
||||||
if (newPasswordFilled && !confirmPasswordFilled) {
|
if (newPasswordFilled && !confirmPasswordFilled) {
|
||||||
Notification.show(getTranslation("editappuser.notification.password.confirm"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editappuser.notification.password.confirm"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newPasswordFilled && confirmPasswordFilled) {
|
if (!newPasswordFilled && confirmPasswordFilled) {
|
||||||
Notification.show(getTranslation("editappuser.notification.password.enter"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editappuser.notification.password.enter"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both are filled, they must match
|
// If both are filled, they must match
|
||||||
if (newPasswordFilled && confirmPasswordFilled && newPassword != null && !newPassword.equals(confirmPassword)) {
|
if (newPasswordFilled && confirmPasswordFilled && newPassword != null && !newPassword.equals(confirmPassword)) {
|
||||||
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editappuser.notification.password.mismatch"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +248,8 @@ public class EditAppUserView extends VerticalLayout implements HasUrlParameter<S
|
|||||||
Button confirmDeleteButton = new Button(getTranslation("editappuser.dialog.delete.confirm"), e -> {
|
Button confirmDeleteButton = new Button(getTranslation("editappuser.dialog.delete.confirm"), e -> {
|
||||||
if (appUser != null && appUser.getId() != null) {
|
if (appUser != null && appUser.getId() != null) {
|
||||||
appUserService.deleteById(appUser.getId());
|
appUserService.deleteById(appUser.getId());
|
||||||
Notification.show(getTranslation("editappuser.notification.deleted"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editappuser.notification.deleted"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
confirmDialog.close();
|
confirmDialog.close();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,7 +151,8 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
customer = customerService.findById(customerId);
|
customer = customerService.findById(customerId);
|
||||||
|
|
||||||
if (customer == null) {
|
if (customer == null) {
|
||||||
Notification.show(getTranslation("editcustomer.notification.notfound"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editcustomer.notification.notfound"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -160,7 +161,8 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
binder.readBean(customer);
|
binder.readBean(customer);
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Notification.show(getTranslation("editcustomer.notification.invalid.id"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editcustomer.notification.invalid.id"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
navigateBack();
|
navigateBack();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,7 +186,8 @@ public class EditCustomerView extends VerticalLayout implements HasUrlParameter<
|
|||||||
HorizontalLayout buttonLayout = new HorizontalLayout();
|
HorizontalLayout buttonLayout = new HorizontalLayout();
|
||||||
Button confirmDeleteButton = new Button(getTranslation("editcustomer.dialog.delete.confirm"), e -> {
|
Button confirmDeleteButton = new Button(getTranslation("editcustomer.dialog.delete.confirm"), e -> {
|
||||||
if (customer != null && customer.getId() != null) {
|
if (customer != null && customer.getId() != null) {
|
||||||
Notification.show(getTranslation("editcustomer.notification.deleted"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("editcustomer.notification.deleted"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
confirmDialog.close();
|
confirmDialog.close();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
TextField companyAddField = new TextField(getTranslation("profile.companyadd"));
|
TextField companyAddField = new TextField(getTranslation("profile.companyadd"));
|
||||||
|
|
||||||
TextField firstnameField = new TextField(getTranslation("profile.firstname"));
|
TextField firstnameField = new TextField(getTranslation("profile.firstname"));
|
||||||
firstnameField.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
firstnameField
|
||||||
|
.addBlurListener(e -> validateField(firstnameField, getTranslation("profile.validation.firstname")));
|
||||||
|
|
||||||
TextField lastnameField = new TextField(getTranslation("profile.lastname"));
|
TextField lastnameField = new TextField(getTranslation("profile.lastname"));
|
||||||
lastnameField.addBlurListener(e -> validateField(lastnameField, getTranslation("profile.validation.lastname")));
|
lastnameField.addBlurListener(e -> validateField(lastnameField, getTranslation("profile.validation.lastname")));
|
||||||
@@ -144,7 +145,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
streetField.addBlurListener(e -> validateField(streetField, getTranslation("profile.validation.street")));
|
streetField.addBlurListener(e -> validateField(streetField, getTranslation("profile.validation.street")));
|
||||||
|
|
||||||
TextField houseNumberField = new TextField(getTranslation("profile.housenr"));
|
TextField houseNumberField = new TextField(getTranslation("profile.housenr"));
|
||||||
houseNumberField.addBlurListener(e -> validateField(houseNumberField, getTranslation("profile.validation.housenr")));
|
houseNumberField
|
||||||
|
.addBlurListener(e -> validateField(houseNumberField, getTranslation("profile.validation.housenr")));
|
||||||
|
|
||||||
TextField addressAddField = new TextField(getTranslation("profile.addressadd"));
|
TextField addressAddField = new TextField(getTranslation("profile.addressadd"));
|
||||||
|
|
||||||
@@ -227,22 +229,29 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
cityField.setRequiredIndicatorVisible(true);
|
cityField.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
// Hauptadresse binden
|
// Hauptadresse binden
|
||||||
binder.forField(companyField).asRequired(getTranslation("profile.validation.company.required")).bind(User::getCompany, User::setCompany);
|
binder.forField(companyField).asRequired(getTranslation("profile.validation.company.required"))
|
||||||
|
.bind(User::getCompany, User::setCompany);
|
||||||
binder.forField(companyAddField).bind(User::getCompanyAddition, User::setCompanyAddition);
|
binder.forField(companyAddField).bind(User::getCompanyAddition, User::setCompanyAddition);
|
||||||
binder.forField(streetField).asRequired(getTranslation("profile.validation.street.required")).bind(User::getStreet, User::setStreet);
|
binder.forField(streetField).asRequired(getTranslation("profile.validation.street.required"))
|
||||||
binder.forField(houseNumberField).asRequired(getTranslation("profile.validation.housenr.required")).bind(User::getHouseNumber,
|
.bind(User::getStreet, User::setStreet);
|
||||||
User::setHouseNumber);
|
binder.forField(houseNumberField).asRequired(getTranslation("profile.validation.housenr.required"))
|
||||||
|
.bind(User::getHouseNumber, User::setHouseNumber);
|
||||||
binder.forField(addressAddField).bind(User::getAddressAddition, User::setAddressAddition);
|
binder.forField(addressAddField).bind(User::getAddressAddition, User::setAddressAddition);
|
||||||
binder.forField(zipField).asRequired(getTranslation("profile.validation.zip.required")).bind(User::getZip, User::setZip);
|
binder.forField(zipField).asRequired(getTranslation("profile.validation.zip.required")).bind(User::getZip,
|
||||||
binder.forField(cityField).asRequired(getTranslation("profile.validation.city.required")).bind(User::getCity, User::setCity);
|
User::setZip);
|
||||||
|
binder.forField(cityField).asRequired(getTranslation("profile.validation.city.required")).bind(User::getCity,
|
||||||
|
User::setCity);
|
||||||
|
|
||||||
// Personendaten binden
|
// Personendaten binden
|
||||||
binder.forField(firstnameField).asRequired(getTranslation("profile.validation.firstname.required")).bind(User::getFirstname,
|
binder.forField(firstnameField).asRequired(getTranslation("profile.validation.firstname.required"))
|
||||||
User::setFirstname);
|
.bind(User::getFirstname, User::setFirstname);
|
||||||
binder.forField(lastnameField).asRequired(getTranslation("profile.validation.lastname.required")).bind(User::getName, User::setName);
|
binder.forField(lastnameField).asRequired(getTranslation("profile.validation.lastname.required"))
|
||||||
binder.forField(phoneField).asRequired(getTranslation("profile.validation.phone.required")).bind(User::getPhone, User::setPhone);
|
.bind(User::getName, User::setName);
|
||||||
|
binder.forField(phoneField).asRequired(getTranslation("profile.validation.phone.required")).bind(User::getPhone,
|
||||||
|
User::setPhone);
|
||||||
binder.forField(emailField).asRequired(getTranslation("profile.validation.email.required"))
|
binder.forField(emailField).asRequired(getTranslation("profile.validation.email.required"))
|
||||||
.withValidator(new EmailValidator(getTranslation("profile.validation.email.invalid"))).bind(User::getEmail, User::setEmail);
|
.withValidator(new EmailValidator(getTranslation("profile.validation.email.invalid")))
|
||||||
|
.bind(User::getEmail, User::setEmail);
|
||||||
// Optionale Felder
|
// Optionale Felder
|
||||||
binder.forField(mobileField).bind(User::getPhone2, User::setPhone2);
|
binder.forField(mobileField).bind(User::getPhone2, User::setPhone2);
|
||||||
binder.forField(faxField).bind(User::getFax, User::setFax);
|
binder.forField(faxField).bind(User::getFax, User::setFax);
|
||||||
@@ -590,7 +599,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
UI.getCurrent().getPage().reload();
|
UI.getCurrent().getPage().reload();
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Notification.show(getTranslation("profile.save.error", ex.getMessage()), 4000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("profile.save.error", ex.getMessage()), 4000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -648,7 +658,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Log error or show notification
|
// Log error or show notification
|
||||||
Notification.show(getTranslation("profile.pdf.error", e.getMessage()), 3000, Notification.Position.BOTTOM_END);
|
Notification.show(getTranslation("profile.pdf.error", e.getMessage()), 3000,
|
||||||
|
Notification.Position.BOTTOM_END);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -863,7 +874,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Notification.show(getTranslation("profile.error", ex.getMessage()), 3000, Notification.Position.BOTTOM_CENTER);
|
Notification.show(getTranslation("profile.error", ex.getMessage()), 3000,
|
||||||
|
Notification.Position.BOTTOM_CENTER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -930,18 +942,22 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
String email = safe(currentUser.getEmail());
|
String email = safe(currentUser.getEmail());
|
||||||
String phone = safe(currentUser.getPhone());
|
String phone = safe(currentUser.getPhone());
|
||||||
|
|
||||||
Div senderCompany = createVariableTemplate(getTranslation("profile.company"), VaadinIcon.OFFICE, "masterdata.company_name",
|
Div senderCompany = createVariableTemplate(getTranslation("profile.company"), VaadinIcon.OFFICE,
|
||||||
|
"masterdata.company_name",
|
||||||
company.isEmpty() ? getTranslation("profile.invoice.placeholder.company") : company);
|
company.isEmpty() ? getTranslation("profile.invoice.placeholder.company") : company);
|
||||||
Div senderName = createVariableTemplate(getTranslation("profile.invoice.name"), VaadinIcon.USER, "masterdata.contact_name",
|
Div senderName = createVariableTemplate(getTranslation("profile.invoice.name"), VaadinIcon.USER,
|
||||||
|
"masterdata.contact_name",
|
||||||
fullName.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.name") : fullName.trim());
|
fullName.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.name") : fullName.trim());
|
||||||
Div senderAddress = createVariableTemplate(getTranslation("profile.street"), VaadinIcon.MAP_MARKER, "masterdata.street",
|
Div senderAddress = createVariableTemplate(getTranslation("profile.street"), VaadinIcon.MAP_MARKER,
|
||||||
|
"masterdata.street",
|
||||||
street.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.street") : street.trim());
|
street.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.street") : street.trim());
|
||||||
Div senderCity = createVariableTemplate(getTranslation("profile.invoice.city"), VaadinIcon.BUILDING, "masterdata.city",
|
Div senderCity = createVariableTemplate(getTranslation("profile.invoice.city"), VaadinIcon.BUILDING,
|
||||||
|
"masterdata.city",
|
||||||
city.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.city") : city.trim());
|
city.trim().isEmpty() ? getTranslation("profile.invoice.placeholder.city") : city.trim());
|
||||||
Div senderEmail = createVariableTemplate(getTranslation("profile.invoice.email"), VaadinIcon.ENVELOPE, "masterdata.email",
|
Div senderEmail = createVariableTemplate(getTranslation("profile.invoice.email"), VaadinIcon.ENVELOPE,
|
||||||
email.isEmpty() ? getTranslation("profile.invoice.placeholder.email") : email);
|
"masterdata.email", email.isEmpty() ? getTranslation("profile.invoice.placeholder.email") : email);
|
||||||
Div senderPhone = createVariableTemplate(getTranslation("profile.invoice.phone"), VaadinIcon.PHONE, "masterdata.phone",
|
Div senderPhone = createVariableTemplate(getTranslation("profile.invoice.phone"), VaadinIcon.PHONE,
|
||||||
phone.isEmpty() ? getTranslation("profile.invoice.placeholder.phone") : phone);
|
"masterdata.phone", phone.isEmpty() ? getTranslation("profile.invoice.placeholder.phone") : phone);
|
||||||
|
|
||||||
// Bereich 2: Leistungen
|
// Bereich 2: Leistungen
|
||||||
Span servicesHeader = new Span(getTranslation("profile.services.label"));
|
Span servicesHeader = new Span(getTranslation("profile.services.label"));
|
||||||
@@ -949,14 +965,14 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
.set("margin-top", "var(--lumo-space-m)");
|
.set("margin-top", "var(--lumo-space-m)");
|
||||||
|
|
||||||
// Leistungen als draggable Variable
|
// Leistungen als draggable Variable
|
||||||
Div servicesListBlock = createServicesVariableTemplate(getTranslation("profile.invoice.services.list"), VaadinIcon.LIST, "services.list",
|
Div servicesListBlock = createServicesVariableTemplate(getTranslation("profile.invoice.services.list"),
|
||||||
"Artikel 1: 100,00 €\nArtikel 2: 50,00 €");
|
VaadinIcon.LIST, "services.list", "Artikel 1: 100,00 €\nArtikel 2: 50,00 €");
|
||||||
Div servicesNetBlock = createServicesVariableTemplate(getTranslation("profile.invoice.net"), VaadinIcon.COIN_PILES, "services.net_total",
|
Div servicesNetBlock = createServicesVariableTemplate(getTranslation("profile.invoice.net"),
|
||||||
"150,00 €");
|
VaadinIcon.COIN_PILES, "services.net_total", "150,00 €");
|
||||||
Div servicesVatBlock = createServicesVariableTemplate(getTranslation("profile.invoice.vat"), VaadinIcon.COIN_PILES,
|
Div servicesVatBlock = createServicesVariableTemplate(getTranslation("profile.invoice.vat"),
|
||||||
"services.vat_total", "28,50 €");
|
VaadinIcon.COIN_PILES, "services.vat_total", "28,50 €");
|
||||||
Div servicesGrossBlock = createServicesVariableTemplate(getTranslation("profile.invoice.gross"), VaadinIcon.MONEY, "services.gross_total",
|
Div servicesGrossBlock = createServicesVariableTemplate(getTranslation("profile.invoice.gross"),
|
||||||
"178,50 €");
|
VaadinIcon.MONEY, "services.gross_total", "178,50 €");
|
||||||
|
|
||||||
// Bereich 3: Kundendaten
|
// Bereich 3: Kundendaten
|
||||||
Span customerHeader = new Span(getTranslation("profile.invoice.customerdata"));
|
Span customerHeader = new Span(getTranslation("profile.invoice.customerdata"));
|
||||||
@@ -964,18 +980,18 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
.set("margin-top", "var(--lumo-space-m)");
|
.set("margin-top", "var(--lumo-space-m)");
|
||||||
|
|
||||||
// Kundendaten als Variablen (grün hinterlegt)
|
// Kundendaten als Variablen (grün hinterlegt)
|
||||||
Div customerCompany = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.company"), VaadinIcon.OFFICE, "customer.company_name",
|
Div customerCompany = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.company"),
|
||||||
"Kundenfirma GmbH");
|
VaadinIcon.OFFICE, "customer.company_name", "Kundenfirma GmbH");
|
||||||
Div customerName = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.name"), VaadinIcon.USER, "customer.contact_name",
|
Div customerName = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.name"),
|
||||||
"Erika Mustermann");
|
VaadinIcon.USER, "customer.contact_name", "Erika Mustermann");
|
||||||
Div customerAddress = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.street"), VaadinIcon.MAP_MARKER, "customer.street",
|
Div customerAddress = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.street"),
|
||||||
"Kundenstraße 456");
|
VaadinIcon.MAP_MARKER, "customer.street", "Kundenstraße 456");
|
||||||
Div customerCity = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.city"), VaadinIcon.BUILDING, "customer.city",
|
Div customerCity = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.city"),
|
||||||
"54321 Kundenstadt");
|
VaadinIcon.BUILDING, "customer.city", "54321 Kundenstadt");
|
||||||
Div customerEmail = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.email"), VaadinIcon.ENVELOPE, "customer.email",
|
Div customerEmail = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.email"),
|
||||||
"kunde@beispiel.de");
|
VaadinIcon.ENVELOPE, "customer.email", "kunde@beispiel.de");
|
||||||
Div customerPhone = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.phone"), VaadinIcon.PHONE, "customer.phone",
|
Div customerPhone = createCustomerVariableTemplate(getTranslation("profile.invoice.customer.phone"),
|
||||||
"0987 654321");
|
VaadinIcon.PHONE, "customer.phone", "0987 654321");
|
||||||
|
|
||||||
// Bereich 2: Freie Elemente
|
// Bereich 2: Freie Elemente
|
||||||
Span freeHeader = new Span(getTranslation("profile.invoice.free.elements"));
|
Span freeHeader = new Span(getTranslation("profile.invoice.free.elements"));
|
||||||
@@ -983,14 +999,22 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
"var(--lumo-space-m)");
|
"var(--lumo-space-m)");
|
||||||
|
|
||||||
// Draggable Templates
|
// Draggable Templates
|
||||||
Div textBlock = createDraggableTemplate(getTranslation("profile.invoice.element.text"), VaadinIcon.TEXT_LABEL, "text");
|
Div textBlock = createDraggableTemplate(getTranslation("profile.invoice.element.text"), VaadinIcon.TEXT_LABEL,
|
||||||
Div headerBlock = createDraggableTemplate(getTranslation("profile.invoice.element.header"), VaadinIcon.HEADER, "header");
|
"text");
|
||||||
Div dateBlock = createDraggableTemplate(getTranslation("profile.invoice.element.date"), VaadinIcon.CALENDAR, "date");
|
Div headerBlock = createDraggableTemplate(getTranslation("profile.invoice.element.header"), VaadinIcon.HEADER,
|
||||||
Div customerBlock = createDraggableTemplate(getTranslation("profile.invoice.element.customer"), VaadinIcon.USER, "customer");
|
"header");
|
||||||
Div companyBlock = createDraggableTemplate(getTranslation("profile.invoice.element.company"), VaadinIcon.WORKPLACE, "company");
|
Div dateBlock = createDraggableTemplate(getTranslation("profile.invoice.element.date"), VaadinIcon.CALENDAR,
|
||||||
Div amountBlock = createDraggableTemplate(getTranslation("profile.invoice.element.amount"), VaadinIcon.COIN_PILES, "amount");
|
"date");
|
||||||
Div lineBlock = createDraggableTemplate(getTranslation("profile.invoice.element.line"), VaadinIcon.LINE_V, "line");
|
Div customerBlock = createDraggableTemplate(getTranslation("profile.invoice.element.customer"), VaadinIcon.USER,
|
||||||
Div imageBlock = createDraggableTemplate(getTranslation("profile.invoice.element.image"), VaadinIcon.PICTURE, "image");
|
"customer");
|
||||||
|
Div companyBlock = createDraggableTemplate(getTranslation("profile.invoice.element.company"),
|
||||||
|
VaadinIcon.WORKPLACE, "company");
|
||||||
|
Div amountBlock = createDraggableTemplate(getTranslation("profile.invoice.element.amount"),
|
||||||
|
VaadinIcon.COIN_PILES, "amount");
|
||||||
|
Div lineBlock = createDraggableTemplate(getTranslation("profile.invoice.element.line"), VaadinIcon.LINE_V,
|
||||||
|
"line");
|
||||||
|
Div imageBlock = createDraggableTemplate(getTranslation("profile.invoice.element.image"), VaadinIcon.PICTURE,
|
||||||
|
"image");
|
||||||
|
|
||||||
panel.add(invoiceHeader, senderCompany, senderName, senderAddress, senderCity, senderEmail, senderPhone,
|
panel.add(invoiceHeader, senderCompany, senderName, senderAddress, senderCity, senderEmail, senderPhone,
|
||||||
servicesHeader, servicesListBlock, servicesNetBlock, servicesVatBlock, servicesGrossBlock,
|
servicesHeader, servicesListBlock, servicesNetBlock, servicesVatBlock, servicesGrossBlock,
|
||||||
@@ -1208,7 +1232,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
getElement()
|
getElement()
|
||||||
.executeJs("if (window.updateProfileElementImage) { window.updateProfileElementImage('"
|
.executeJs("if (window.updateProfileElementImage) { window.updateProfileElementImage('"
|
||||||
+ elementId + "', $0); }", dataUrl);
|
+ elementId + "', $0); }", dataUrl);
|
||||||
Notification.show(getTranslation("profile.invoice.image.uploaded"), 3000, Notification.Position.BOTTOM_CENTER);
|
Notification.show(getTranslation("profile.invoice.image.uploaded"), 3000,
|
||||||
|
Notification.Position.BOTTOM_CENTER);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Notification.show(getTranslation("profile.invoice.image.upload.error", ex.getMessage()), 3000,
|
Notification.show(getTranslation("profile.invoice.image.upload.error", ex.getMessage()), 3000,
|
||||||
Notification.Position.BOTTOM_CENTER);
|
Notification.Position.BOTTOM_CENTER);
|
||||||
@@ -1333,7 +1358,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Löschen Button
|
// Löschen Button
|
||||||
Button deleteButton = new Button(getTranslation("profile.invoice.element.delete"), new Icon(VaadinIcon.TRASH));
|
Button deleteButton = new Button(getTranslation("profile.invoice.element.delete"),
|
||||||
|
new Icon(VaadinIcon.TRASH));
|
||||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||||
deleteButton.setWidthFull();
|
deleteButton.setWidthFull();
|
||||||
deleteButton.addClickListener(e -> {
|
deleteButton.addClickListener(e -> {
|
||||||
@@ -1498,8 +1524,10 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
return "";
|
return "";
|
||||||
}).setHeader(getTranslation("profile.services.vatrate")).setSortable(true);
|
}).setHeader(getTranslation("profile.services.vatrate")).setSortable(true);
|
||||||
|
|
||||||
servicesGrid.addColumn(service -> service.isMandatory() ? getTranslation("common.yes") : getTranslation("common.no")).setHeader(getTranslation("profile.services.mandatory"))
|
servicesGrid
|
||||||
.setSortable(true);
|
.addColumn(
|
||||||
|
service -> service.isMandatory() ? getTranslation("common.yes") : getTranslation("common.no"))
|
||||||
|
.setHeader(getTranslation("profile.services.mandatory")).setSortable(true);
|
||||||
|
|
||||||
// Actions column with edit and delete buttons
|
// Actions column with edit and delete buttons
|
||||||
servicesGrid.addComponentColumn(service -> {
|
servicesGrid.addComponentColumn(service -> {
|
||||||
@@ -1554,7 +1582,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
*/
|
*/
|
||||||
private void openServiceDialog(Service service) {
|
private void openServiceDialog(Service service) {
|
||||||
Dialog dialog = new Dialog();
|
Dialog dialog = new Dialog();
|
||||||
dialog.setHeaderTitle(service == null ? getTranslation("profile.services.dialog.create") : getTranslation("profile.services.dialog.edit"));
|
dialog.setHeaderTitle(service == null ? getTranslation("profile.services.dialog.create")
|
||||||
|
: getTranslation("profile.services.dialog.edit"));
|
||||||
dialog.setWidth("500px");
|
dialog.setWidth("500px");
|
||||||
|
|
||||||
// Form layout
|
// Form layout
|
||||||
@@ -1570,7 +1599,8 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
nameField.setRequiredIndicatorVisible(true);
|
nameField.setRequiredIndicatorVisible(true);
|
||||||
|
|
||||||
// Calculation basis combo box
|
// Calculation basis combo box
|
||||||
ComboBox<Service.CalculationBasis> calculationBasisCombo = new ComboBox<>(getTranslation("profile.services.basis"));
|
ComboBox<Service.CalculationBasis> calculationBasisCombo = new ComboBox<>(
|
||||||
|
getTranslation("profile.services.basis"));
|
||||||
calculationBasisCombo.setWidthFull();
|
calculationBasisCombo.setWidthFull();
|
||||||
calculationBasisCombo.setItems(Service.CalculationBasis.values());
|
calculationBasisCombo.setItems(Service.CalculationBasis.values());
|
||||||
calculationBasisCombo.setItemLabelGenerator(basis -> {
|
calculationBasisCombo.setItemLabelGenerator(basis -> {
|
||||||
@@ -1855,10 +1885,11 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the language cookie via JavaScript so the selected language
|
* Sets the language cookie via JavaScript so the selected language is available
|
||||||
* is available even before the user logs in (e.g., on the login page).
|
* even before the user logs in (e.g., on the login page).
|
||||||
*
|
*
|
||||||
* @param language the selected language
|
* @param language
|
||||||
|
* the selected language
|
||||||
*/
|
*/
|
||||||
private void setLanguageCookie(Language language) {
|
private void setLanguageCookie(Language language) {
|
||||||
String languageCode = switch (language) {
|
String languageCode = switch (language) {
|
||||||
@@ -1875,11 +1906,9 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Execute JavaScript to set the cookie
|
// Execute JavaScript to set the cookie
|
||||||
UI.getCurrent().getPage().executeJs(
|
UI.getCurrent().getPage().executeJs("const maxAge = 365 * 24 * 60 * 60;"
|
||||||
"const maxAge = 365 * 24 * 60 * 60;" +
|
+ "document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
||||||
"document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
languageCode);
|
||||||
languageCode
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -109,14 +109,22 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
|||||||
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
header.getStyle().set("font-weight", "bold").set("font-size", "var(--lumo-font-size-l)");
|
||||||
|
|
||||||
// Draggable Templates
|
// Draggable Templates
|
||||||
Div textBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.text"), VaadinIcon.TEXT_LABEL, "text");
|
Div textBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.text"), VaadinIcon.TEXT_LABEL,
|
||||||
Div headerBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.header"), VaadinIcon.HEADER, "header");
|
"text");
|
||||||
Div dateBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.date"), VaadinIcon.CALENDAR, "date");
|
Div headerBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.header"), VaadinIcon.HEADER,
|
||||||
Div customerBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.customerinfo"), VaadinIcon.USER, "customer");
|
"header");
|
||||||
Div companyBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.companyinfo"), VaadinIcon.OFFICE, "company");
|
Div dateBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.date"), VaadinIcon.CALENDAR,
|
||||||
Div amountBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.amount"), VaadinIcon.COIN_PILES, "amount");
|
"date");
|
||||||
Div lineBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.line"), VaadinIcon.LINE_V, "line");
|
Div customerBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.customerinfo"),
|
||||||
Div imageBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.image"), VaadinIcon.PICTURE, "image");
|
VaadinIcon.USER, "customer");
|
||||||
|
Div companyBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.companyinfo"),
|
||||||
|
VaadinIcon.OFFICE, "company");
|
||||||
|
Div amountBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.amount"),
|
||||||
|
VaadinIcon.COIN_PILES, "amount");
|
||||||
|
Div lineBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.line"), VaadinIcon.LINE_V,
|
||||||
|
"line");
|
||||||
|
Div imageBlock = createDraggableTemplate(getTranslation("invoicegenerator.template.image"), VaadinIcon.PICTURE,
|
||||||
|
"image");
|
||||||
|
|
||||||
panel.add(header, textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock, lineBlock,
|
panel.add(header, textBlock, headerBlock, dateBlock, customerBlock, companyBlock, amountBlock, lineBlock,
|
||||||
imageBlock);
|
imageBlock);
|
||||||
@@ -238,7 +246,8 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
|||||||
getElement().executeJs("if (window.invoiceGenerator) { window.invoiceGenerator.exportTemplate(); }");
|
getElement().executeJs("if (window.invoiceGenerator) { window.invoiceGenerator.exportTemplate(); }");
|
||||||
});
|
});
|
||||||
|
|
||||||
Button generatePdfButton = new Button(getTranslation("invoicegenerator.button.generatepdf"), new Icon(VaadinIcon.FILE_TEXT_O));
|
Button generatePdfButton = new Button(getTranslation("invoicegenerator.button.generatepdf"),
|
||||||
|
new Icon(VaadinIcon.FILE_TEXT_O));
|
||||||
generatePdfButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
generatePdfButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||||
generatePdfButton.addClickListener(e -> generatePdf());
|
generatePdfButton.addClickListener(e -> generatePdf());
|
||||||
|
|
||||||
@@ -289,7 +298,8 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
|||||||
byte[] pdfBytes = customerInvoiceService.generatePdfFromCanvasTemplate(templateData);
|
byte[] pdfBytes = customerInvoiceService.generatePdfFromCanvasTemplate(templateData);
|
||||||
showPdfInDialog(pdfBytes);
|
showPdfInDialog(pdfBytes);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
showNotification(getTranslation("invoicegenerator.notification.preview.error", ex.getMessage()));
|
showNotification(
|
||||||
|
getTranslation("invoicegenerator.notification.preview.error", ex.getMessage()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -557,7 +567,8 @@ public class InvoiceGeneratorView extends VerticalLayout implements HasDynamicTi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Löschen Button
|
// Löschen Button
|
||||||
Button deleteButton = new Button(getTranslation("invoicegenerator.button.delete"), new Icon(VaadinIcon.TRASH));
|
Button deleteButton = new Button(getTranslation("invoicegenerator.button.delete"),
|
||||||
|
new Icon(VaadinIcon.TRASH));
|
||||||
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
deleteButton.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);
|
||||||
deleteButton.setWidthFull();
|
deleteButton.setWidthFull();
|
||||||
deleteButton.addClickListener(e -> {
|
deleteButton.addClickListener(e -> {
|
||||||
|
|||||||
@@ -46,11 +46,16 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
add(title);
|
add(title);
|
||||||
|
|
||||||
invoiceGrid = new Grid<>(SystemInvoice.class, false);
|
invoiceGrid = new Grid<>(SystemInvoice.class, false);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getId).setHeader(getTranslation("invoices.column.number")).setAutoWidth(true);
|
invoiceGrid.addColumn(SystemInvoice::getId).setHeader(getTranslation("invoices.column.number"))
|
||||||
invoiceGrid.addColumn(SystemInvoice::getKunde).setHeader(getTranslation("invoices.column.customer")).setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getDatum).setHeader(getTranslation("invoices.column.date")).setAutoWidth(true);
|
invoiceGrid.addColumn(SystemInvoice::getKunde).setHeader(getTranslation("invoices.column.customer"))
|
||||||
invoiceGrid.addColumn(SystemInvoice::getBetrag).setHeader(getTranslation("invoices.column.amount")).setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
invoiceGrid.addColumn(SystemInvoice::getBeschreibung).setHeader(getTranslation("invoices.column.description")).setAutoWidth(true);
|
invoiceGrid.addColumn(SystemInvoice::getDatum).setHeader(getTranslation("invoices.column.date"))
|
||||||
|
.setAutoWidth(true);
|
||||||
|
invoiceGrid.addColumn(SystemInvoice::getBetrag).setHeader(getTranslation("invoices.column.amount"))
|
||||||
|
.setAutoWidth(true);
|
||||||
|
invoiceGrid.addColumn(SystemInvoice::getBeschreibung).setHeader(getTranslation("invoices.column.description"))
|
||||||
|
.setAutoWidth(true);
|
||||||
invoiceGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
invoiceGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
|
||||||
invoiceGrid.getStyle().set("cursor", "pointer");
|
invoiceGrid.getStyle().set("cursor", "pointer");
|
||||||
|
|
||||||
@@ -88,7 +93,8 @@ public class InvoicesView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
UI.getCurrent().getPage().open(registration.getResourceUri().toString());
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Notification.show(getTranslation("invoices.notification.pdf.error", e.getMessage()), 5000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("invoices.notification.pdf.error", e.getMessage()), 5000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,8 @@ public class JobHistoryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
Icon typeIcon = getTypeIcon(entry.getChangeType());
|
Icon typeIcon = getTypeIcon(entry.getChangeType());
|
||||||
typeIcon.getStyle().set("color", getTypeColor(entry.getChangeType()));
|
typeIcon.getStyle().set("color", getTypeColor(entry.getChangeType()));
|
||||||
|
|
||||||
Span reason = new Span(entry.getReason() != null ? entry.getReason() : getTranslation("jobhistory.entry.unknown"));
|
Span reason = new Span(
|
||||||
|
entry.getReason() != null ? entry.getReason() : getTranslation("jobhistory.entry.unknown"));
|
||||||
reason.getStyle().set("font-weight", "500");
|
reason.getStyle().set("font-weight", "500");
|
||||||
|
|
||||||
Span timestamp = new Span(formatDateTime(entry.getTimestamp()));
|
Span timestamp = new Span(formatDateTime(entry.getTimestamp()));
|
||||||
|
|||||||
@@ -149,7 +149,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
sendMessageButton.addClickListener(e -> {
|
sendMessageButton.addClickListener(e -> {
|
||||||
// Check if job has an app user assigned
|
// Check if job has an app user assigned
|
||||||
if (job.getAppUser() == null || job.getAppUser().isBlank()) {
|
if (job.getAppUser() == null || job.getAppUser().isBlank()) {
|
||||||
Notification.show(getTranslation("jobsummary.notification.noappuser"), 3000, Notification.Position.MIDDLE)
|
Notification
|
||||||
|
.show(getTranslation("jobsummary.notification.noappuser"), 3000, Notification.Position.MIDDLE)
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -189,7 +190,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
topRow.setSpacing(true);
|
topRow.setSpacing(true);
|
||||||
|
|
||||||
VerticalLayout pickupBox = borderedBox();
|
VerticalLayout pickupBox = borderedBox();
|
||||||
pickupBox.add(new H3(getTranslation("jobsummary.section.pickup") + " " + formatDateWithTime(job.getPickupDate(), job.getPickupTime())));
|
pickupBox.add(new H3(getTranslation("jobsummary.section.pickup") + " "
|
||||||
|
+ formatDateWithTime(job.getPickupDate(), job.getPickupTime())));
|
||||||
pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany())));
|
pickupBox.add(new Span(valueOrEmpty(job.getPickupCompany())));
|
||||||
pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation()) + (job.getPickupSalutation() != null ? " " : "")
|
pickupBox.add(new Span(valueOrEmpty(job.getPickupSalutation()) + (job.getPickupSalutation() != null ? " " : "")
|
||||||
+ valueOrEmpty(job.getPickupFirstName()) + (job.getPickupFirstName() != null ? " " : "")
|
+ valueOrEmpty(job.getPickupFirstName()) + (job.getPickupFirstName() != null ? " " : "")
|
||||||
@@ -198,7 +200,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity())));
|
pickupBox.add(new Span(concatZipCity(job.getPickupZip(), job.getPickupCity())));
|
||||||
|
|
||||||
VerticalLayout deliveryBox = borderedBox();
|
VerticalLayout deliveryBox = borderedBox();
|
||||||
deliveryBox.add(new H3(getTranslation("jobsummary.section.delivery") + " " + formatDateWithTime(job.getDeliveryDate(), job.getDeliveryTime())));
|
deliveryBox.add(new H3(getTranslation("jobsummary.section.delivery") + " "
|
||||||
|
+ formatDateWithTime(job.getDeliveryDate(), job.getDeliveryTime())));
|
||||||
deliveryBox.add(new Span(valueOrEmpty(job.getDeliveryCompany())));
|
deliveryBox.add(new Span(valueOrEmpty(job.getDeliveryCompany())));
|
||||||
deliveryBox.add(new Span(valueOrEmpty(job.getDeliverySalutation())
|
deliveryBox.add(new Span(valueOrEmpty(job.getDeliverySalutation())
|
||||||
+ (job.getDeliverySalutation() != null ? " " : "") + valueOrEmpty(job.getDeliveryFirstName())
|
+ (job.getDeliverySalutation() != null ? " " : "") + valueOrEmpty(job.getDeliveryFirstName())
|
||||||
@@ -279,7 +282,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
infoBox.add(new Span(getTranslation("jobsummary.info.digital")));
|
infoBox.add(new Span(getTranslation("jobsummary.info.digital")));
|
||||||
}
|
}
|
||||||
if (job.getAppUser() != null && !job.getAppUser().isBlank()) {
|
if (job.getAppUser() != null && !job.getAppUser().isBlank()) {
|
||||||
infoBox.add(new Span(getTranslation("jobsummary.info.appuser") + ": " + resolveAppUserName(job.getAppUser())));
|
infoBox.add(
|
||||||
|
new Span(getTranslation("jobsummary.info.appuser") + ": " + resolveAppUserName(job.getAppUser())));
|
||||||
}
|
}
|
||||||
|
|
||||||
cargoBox.setWidth("50%");
|
cargoBox.setWidth("50%");
|
||||||
@@ -298,7 +302,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
buttonRow.setJustifyContentMode(HorizontalLayout.JustifyContentMode.CENTER);
|
buttonRow.setJustifyContentMode(HorizontalLayout.JustifyContentMode.CENTER);
|
||||||
buttonRow.getStyle().set("margin-top", "var(--lumo-space-l)");
|
buttonRow.getStyle().set("margin-top", "var(--lumo-space-l)");
|
||||||
|
|
||||||
Button completeButton = new Button(getTranslation("jobsummary.button.complete"), new Icon(VaadinIcon.CHECK_CIRCLE));
|
Button completeButton = new Button(getTranslation("jobsummary.button.complete"),
|
||||||
|
new Icon(VaadinIcon.CHECK_CIRCLE));
|
||||||
completeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
completeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
|
||||||
completeButton.addClickListener(e -> {
|
completeButton.addClickListener(e -> {
|
||||||
ConfirmDialog dialog = new ConfirmDialog();
|
ConfirmDialog dialog = new ConfirmDialog();
|
||||||
@@ -475,14 +480,15 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
Integer savedDuration = job.getRouteDurationSeconds();
|
Integer savedDuration = job.getRouteDurationSeconds();
|
||||||
boolean hasSavedRouteData = savedDistance != null && savedDuration != null;
|
boolean hasSavedRouteData = savedDistance != null && savedDuration != null;
|
||||||
|
|
||||||
String js = buildMapJs(origin, destination, hasPosition, position, appUserId, shouldUpdate,
|
String js = buildMapJs(origin, destination, hasPosition, position, appUserId, shouldUpdate, hasSavedRouteData,
|
||||||
hasSavedRouteData, savedDistance != null ? savedDistance : 0.0, savedDuration != null ? savedDuration : 0);
|
savedDistance != null ? savedDistance : 0.0, savedDuration != null ? savedDuration : 0);
|
||||||
|
|
||||||
map.getElement().executeJs(js, map.getElement(), routeInfo.getElement());
|
map.getElement().executeJs(js, map.getElement(), routeInfo.getElement());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildMapJs(String origin, String destination, boolean hasPosition, LocationPosition position,
|
private String buildMapJs(String origin, String destination, boolean hasPosition, LocationPosition position,
|
||||||
String appUserId, boolean shouldUpdate, boolean hasSavedRouteData, double savedDistance, int savedDuration) {
|
String appUserId, boolean shouldUpdate, boolean hasSavedRouteData, double savedDistance,
|
||||||
|
int savedDuration) {
|
||||||
String apiKey = getGoogleMapsApiKey();
|
String apiKey = getGoogleMapsApiKey();
|
||||||
// Explizit mit Punkt als Dezimaltrennzeichen formatieren
|
// Explizit mit Punkt als Dezimaltrennzeichen formatieren
|
||||||
String lat = hasPosition ? String.format(java.util.Locale.US, "%.6f", position.getLatitude()) : "0";
|
String lat = hasPosition ? String.format(java.util.Locale.US, "%.6f", position.getLatitude()) : "0";
|
||||||
@@ -493,8 +499,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
// Gespeicherte Dauer formatieren
|
// Gespeicherte Dauer formatieren
|
||||||
int hours = savedDuration / 3600;
|
int hours = savedDuration / 3600;
|
||||||
int minutes = (savedDuration % 3600) / 60;
|
int minutes = (savedDuration % 3600) / 60;
|
||||||
String savedDurationText = hours > 0
|
String savedDurationText = hours > 0 ? String.format("%d Std. %d Min.", hours, minutes)
|
||||||
? String.format("%d Std. %d Min.", hours, minutes)
|
|
||||||
: String.format("%d Min.", minutes);
|
: String.format("%d Min.", minutes);
|
||||||
|
|
||||||
return """
|
return """
|
||||||
@@ -1213,7 +1218,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
|
|
||||||
for (String serviceId : serviceIds) {
|
for (String serviceId : serviceIds) {
|
||||||
Service service = serviceRepository.findById(serviceId).orElse(null);
|
Service service = serviceRepository.findById(serviceId).orElse(null);
|
||||||
if (service == null) continue;
|
if (service == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
BigDecimal price = calculateServicePrice(service, routeDistance, durationSeconds);
|
BigDecimal price = calculateServicePrice(service, routeDistance, durationSeconds);
|
||||||
BigDecimal vatRate = service.getVatRate() != null ? service.getVatRate() : new BigDecimal("0.19");
|
BigDecimal vatRate = service.getVatRate() != null ? service.getVatRate() : new BigDecimal("0.19");
|
||||||
@@ -1227,7 +1233,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Berechnet den Preis für eine einzelne Leistung basierend auf ihrer Berechnungsgrundlage.
|
* Berechnet den Preis für eine einzelne Leistung basierend auf ihrer
|
||||||
|
* Berechnungsgrundlage.
|
||||||
*/
|
*/
|
||||||
private BigDecimal calculateServicePrice(Service service, Double routeDistance, Integer durationSeconds) {
|
private BigDecimal calculateServicePrice(Service service, Double routeDistance, Integer durationSeconds) {
|
||||||
if (service.getCalculationBasis() == null) {
|
if (service.getCalculationBasis() == null) {
|
||||||
@@ -1248,7 +1255,8 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
|
if (service.getPricePer15Minutes() != null && durationSeconds != null && durationSeconds > 0) {
|
||||||
// Dauer in 15-Minuten-Einheiten umrechnen
|
// Dauer in 15-Minuten-Einheiten umrechnen
|
||||||
int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
|
int units = durationSeconds / 900; // 900 Sekunden = 15 Minuten
|
||||||
if (durationSeconds % 900 > 0) units++; // Aufrunden
|
if (durationSeconds % 900 > 0)
|
||||||
|
units++; // Aufrunden
|
||||||
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
|
return service.getPricePer15Minutes().multiply(BigDecimal.valueOf(units));
|
||||||
}
|
}
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
|
|||||||
@@ -184,8 +184,7 @@ public class LoginView extends VerticalLayout implements BeforeEnterObserver, Af
|
|||||||
|
|
||||||
private void handleVerify2fa() {
|
private void handleVerify2fa() {
|
||||||
if (pendingAuth == null) {
|
if (pendingAuth == null) {
|
||||||
Notification.show(getTranslation("login.2fa.no.credentials"), 3000,
|
Notification.show(getTranslation("login.2fa.no.credentials"), 3000, Notification.Position.BOTTOM_CENTER);
|
||||||
Notification.Position.BOTTOM_CENTER);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String username = pendingAuth.getName();
|
String username = pendingAuth.getName();
|
||||||
|
|||||||
@@ -106,8 +106,10 @@ public class MessagesView extends Main implements HasDynamicTitle {
|
|||||||
return span;
|
return span;
|
||||||
})).setHeader(getTranslation("messages.column.status")).setWidth("80px").setFlexGrow(0);
|
})).setHeader(getTranslation("messages.column.status")).setWidth("80px").setFlexGrow(0);
|
||||||
|
|
||||||
grid.addColumn(ClientMessageSummary::getClientName).setHeader(getTranslation("messages.column.client")).setAutoWidth(true);
|
grid.addColumn(ClientMessageSummary::getClientName).setHeader(getTranslation("messages.column.client"))
|
||||||
grid.addColumn(ClientMessageSummary::getClientEmail).setHeader(getTranslation("messages.column.email")).setAutoWidth(true);
|
.setAutoWidth(true);
|
||||||
|
grid.addColumn(ClientMessageSummary::getClientEmail).setHeader(getTranslation("messages.column.email"))
|
||||||
|
.setAutoWidth(true);
|
||||||
|
|
||||||
grid.addColumn(new ComponentRenderer<>(summary -> {
|
grid.addColumn(new ComponentRenderer<>(summary -> {
|
||||||
Span span = new Span(String.valueOf(summary.getTotalMessages()));
|
Span span = new Span(String.valueOf(summary.getTotalMessages()));
|
||||||
|
|||||||
@@ -137,13 +137,14 @@ public class MyInvoicesView extends Main implements HasDynamicTitle {
|
|||||||
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_COMPACT,
|
grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_COMPACT,
|
||||||
GridVariant.LUMO_WRAP_CELL_CONTENT, GridVariant.LUMO_COLUMN_BORDERS);
|
GridVariant.LUMO_WRAP_CELL_CONTENT, GridVariant.LUMO_COLUMN_BORDERS);
|
||||||
grid.setWidthFull();
|
grid.setWidthFull();
|
||||||
grid.addColumn(new ComponentRenderer<>(row -> statusBadge(row.status()))).setHeader(getTranslation("myinvoices.column.status")).setAutoWidth(true)
|
grid.addColumn(new ComponentRenderer<>(row -> statusBadge(row.status())))
|
||||||
.setFlexGrow(0);
|
.setHeader(getTranslation("myinvoices.column.status")).setAutoWidth(true).setFlexGrow(0);
|
||||||
grid.addColumn(MyInvoicesView::formatInvoiceNumber).setHeader(getTranslation("myinvoices.column.number")).setAutoWidth(true);
|
grid.addColumn(MyInvoicesView::formatInvoiceNumber).setHeader(getTranslation("myinvoices.column.number"))
|
||||||
grid.addColumn(row -> DateTimeFormatUtil.formatDate(row.date())).setHeader(getTranslation("myinvoices.column.date")).setAutoWidth(true)
|
.setAutoWidth(true);
|
||||||
.setFlexGrow(0);
|
grid.addColumn(row -> DateTimeFormatUtil.formatDate(row.date()))
|
||||||
grid.addColumn(row -> CURRENCY_FMT.format(row.amount())).setHeader(getTranslation("myinvoices.column.amount")).setAutoWidth(true)
|
.setHeader(getTranslation("myinvoices.column.date")).setAutoWidth(true).setFlexGrow(0);
|
||||||
.setTextAlign(ColumnTextAlign.END).setFlexGrow(0);
|
grid.addColumn(row -> CURRENCY_FMT.format(row.amount())).setHeader(getTranslation("myinvoices.column.amount"))
|
||||||
|
.setAutoWidth(true).setTextAlign(ColumnTextAlign.END).setFlexGrow(0);
|
||||||
grid.setAllRowsVisible(true);
|
grid.setAllRowsVisible(true);
|
||||||
grid.setItems(allRows); // zunächst leer
|
grid.setItems(allRows); // zunächst leer
|
||||||
grid.addItemClickListener(event -> {
|
grid.addItemClickListener(event -> {
|
||||||
|
|||||||
@@ -164,7 +164,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
resendButton.setVisible(false);
|
resendButton.setVisible(false);
|
||||||
|
|
||||||
// Zurück-Link
|
// Zurück-Link
|
||||||
Button backButton = new Button(getTranslation("register.button.back"), event -> getUI().ifPresent(ui -> ui.navigate("")));
|
Button backButton = new Button(getTranslation("register.button.back"),
|
||||||
|
event -> getUI().ifPresent(ui -> ui.navigate("")));
|
||||||
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
backButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
backButton.setWidthFull();
|
backButton.setWidthFull();
|
||||||
|
|
||||||
@@ -223,12 +224,14 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
|
|
||||||
// Validierung
|
// Validierung
|
||||||
if (email.isEmpty()) {
|
if (email.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.email.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.email.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
emailField.focus();
|
emailField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!email.contains("@") || !email.contains(".")) {
|
if (!email.contains("@") || !email.contains(".")) {
|
||||||
Notification.show(getTranslation("register.notification.email.invalid"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.email.invalid"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
emailField.focus();
|
emailField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -238,7 +241,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (password.isEmpty()) {
|
if (password.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.password.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.password.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
passwordField.focus();
|
passwordField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -248,7 +252,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!password.equals(confirmPassword)) {
|
if (!password.equals(confirmPassword)) {
|
||||||
Notification.show(getTranslation("register.notification.password.mismatch"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.password.mismatch"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
confirmPasswordField.focus();
|
confirmPasswordField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -256,37 +261,43 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
// Weitere Pflichtfelder prüfen (aus Edit-Profile)
|
// Weitere Pflichtfelder prüfen (aus Edit-Profile)
|
||||||
var firstName = firstNameField.getValue() != null ? firstNameField.getValue().trim() : "";
|
var firstName = firstNameField.getValue() != null ? firstNameField.getValue().trim() : "";
|
||||||
if (firstName.isEmpty()) {
|
if (firstName.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.firstname.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.firstname.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
firstNameField.focus();
|
firstNameField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var lastName = lastNameField.getValue() != null ? lastNameField.getValue().trim() : "";
|
var lastName = lastNameField.getValue() != null ? lastNameField.getValue().trim() : "";
|
||||||
if (lastName.isEmpty()) {
|
if (lastName.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.lastname.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.lastname.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
lastNameField.focus();
|
lastNameField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var phone = phoneField.getValue() != null ? phoneField.getValue().trim() : "";
|
var phone = phoneField.getValue() != null ? phoneField.getValue().trim() : "";
|
||||||
if (phone.isEmpty()) {
|
if (phone.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.phone.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.phone.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
phoneField.focus();
|
phoneField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var company = companyField.getValue() != null ? companyField.getValue().trim() : "";
|
var company = companyField.getValue() != null ? companyField.getValue().trim() : "";
|
||||||
if (company.isEmpty()) {
|
if (company.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.company.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.company.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
companyField.focus();
|
companyField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var street = streetField.getValue() != null ? streetField.getValue().trim() : "";
|
var street = streetField.getValue() != null ? streetField.getValue().trim() : "";
|
||||||
if (street.isEmpty()) {
|
if (street.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.street.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.street.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
streetField.focus();
|
streetField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var houseNo = houseNumberField.getValue() != null ? houseNumberField.getValue().trim() : "";
|
var houseNo = houseNumberField.getValue() != null ? houseNumberField.getValue().trim() : "";
|
||||||
if (houseNo.isEmpty()) {
|
if (houseNo.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.housenr.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.housenr.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
houseNumberField.focus();
|
houseNumberField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -298,7 +309,8 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
}
|
}
|
||||||
var city = cityField.getValue() != null ? cityField.getValue().trim() : "";
|
var city = cityField.getValue() != null ? cityField.getValue().trim() : "";
|
||||||
if (city.isEmpty()) {
|
if (city.isEmpty()) {
|
||||||
Notification.show(getTranslation("register.notification.city.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.city.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
cityField.focus();
|
cityField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -351,23 +363,25 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
Notification.Position.MIDDLE);
|
Notification.Position.MIDDLE);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
awaitingVerification = false;
|
awaitingVerification = false;
|
||||||
Notification.show(getTranslation("register.notification.code.emailerror", e.getMessage()), 5000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.code.emailerror", e.getMessage()), 5000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onVerifyCode() {
|
private void onVerifyCode() {
|
||||||
if (!awaitingVerification) {
|
if (!awaitingVerification) {
|
||||||
Notification.show(getTranslation("register.notification.code.startfirst"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.code.startfirst"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String entered = codeField.getValue() != null ? codeField.getValue().trim() : "";
|
String entered = codeField.getValue() != null ? codeField.getValue().trim() : "";
|
||||||
if (!entered.matches("\\d{6}")) {
|
if (!entered.matches("\\d{6}")) {
|
||||||
Notification.show(getTranslation("register.notification.code.required"), 3000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.code.required"), 3000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (codeExpiresAt == null || LocalDateTime.now().isAfter(codeExpiresAt)) {
|
if (codeExpiresAt == null || LocalDateTime.now().isAfter(codeExpiresAt)) {
|
||||||
Notification.show(getTranslation("register.notification.code.expired"), 4000,
|
Notification.show(getTranslation("register.notification.code.expired"), 4000, Notification.Position.MIDDLE);
|
||||||
Notification.Position.MIDDLE);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!entered.equals(pendingCode)) {
|
if (!entered.equals(pendingCode)) {
|
||||||
@@ -397,11 +411,11 @@ public class RegisterView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
user.setZip(zip);
|
user.setZip(zip);
|
||||||
user.setCity(city);
|
user.setCity(city);
|
||||||
userService.save(user);
|
userService.save(user);
|
||||||
VaadinSession.getCurrent().setAttribute("flashMessage",
|
VaadinSession.getCurrent().setAttribute("flashMessage", getTranslation("register.notification.success"));
|
||||||
getTranslation("register.notification.success"));
|
|
||||||
getUI().ifPresent(ui -> ui.navigate("login"));
|
getUI().ifPresent(ui -> ui.navigate("login"));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Notification.show(getTranslation("register.notification.failed", e.getMessage()), 5000, Notification.Position.MIDDLE);
|
Notification.show(getTranslation("register.notification.failed", e.getMessage()), 5000,
|
||||||
|
Notification.Position.MIDDLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,25 +42,28 @@ public class ShowCustomersView extends VerticalLayout implements HasDynamicTitle
|
|||||||
add(header);
|
add(header);
|
||||||
|
|
||||||
// Add hint text
|
// Add hint text
|
||||||
var hintText = new com.vaadin.flow.component.html.Paragraph(
|
var hintText = new com.vaadin.flow.component.html.Paragraph(getTranslation("customers.hint.click"));
|
||||||
getTranslation("customers.hint.click"));
|
|
||||||
hintText.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
hintText.getStyle().set("color", "var(--lumo-secondary-text-color)");
|
||||||
hintText.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
hintText.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||||
add(hintText);
|
add(hintText);
|
||||||
|
|
||||||
// Configure grid columns
|
// Configure grid columns
|
||||||
grid.addColumn(Customer::getCompanyName).setHeader(getTranslation("customers.column.company")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
grid.addColumn(Customer::getCompanyName).setHeader(getTranslation("customers.column.company"))
|
||||||
grid.addColumn(customer -> (customer.getFirstname() != null ? customer.getFirstname() : "") + " "
|
|
||||||
+ (customer.getLastName() != null ? customer.getLastName() : "")).setHeader(getTranslation("customers.column.name")).setAutoWidth(true)
|
|
||||||
.setFlexGrow(1).setSortable(true);
|
|
||||||
grid.addColumn(Customer::getMail).setHeader(getTranslation("customers.column.email")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
|
||||||
grid.addColumn(Customer::getTelephone).setHeader(getTranslation("customers.column.phone")).setAutoWidth(true).setSortable(true);
|
|
||||||
grid.addColumn(customer -> (customer.getStreet() != null ? customer.getStreet() : "") + " "
|
|
||||||
+ (customer.getHouseNumber() != null ? customer.getHouseNumber() : "")).setHeader(getTranslation("customers.column.street"))
|
|
||||||
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
grid.addColumn(customer -> (customer.getZip() != null ? customer.getZip() : "") + " "
|
grid.addColumn(customer -> (customer.getFirstname() != null ? customer.getFirstname() : "") + " "
|
||||||
+ (customer.getCity() != null ? customer.getCity() : "")).setHeader(getTranslation("customers.column.city")).setAutoWidth(true)
|
+ (customer.getLastName() != null ? customer.getLastName() : ""))
|
||||||
|
.setHeader(getTranslation("customers.column.name")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
|
grid.addColumn(Customer::getMail).setHeader(getTranslation("customers.column.email")).setAutoWidth(true)
|
||||||
.setFlexGrow(1).setSortable(true);
|
.setFlexGrow(1).setSortable(true);
|
||||||
|
grid.addColumn(Customer::getTelephone).setHeader(getTranslation("customers.column.phone")).setAutoWidth(true)
|
||||||
|
.setSortable(true);
|
||||||
|
grid.addColumn(customer -> (customer.getStreet() != null ? customer.getStreet() : "") + " "
|
||||||
|
+ (customer.getHouseNumber() != null ? customer.getHouseNumber() : ""))
|
||||||
|
.setHeader(getTranslation("customers.column.street")).setAutoWidth(true).setFlexGrow(1)
|
||||||
|
.setSortable(true);
|
||||||
|
grid.addColumn(customer -> (customer.getZip() != null ? customer.getZip() : "") + " "
|
||||||
|
+ (customer.getCity() != null ? customer.getCity() : ""))
|
||||||
|
.setHeader(getTranslation("customers.column.city")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
|
|
||||||
grid.setMultiSort(true);
|
grid.setMultiSort(true);
|
||||||
grid.setSizeFull();
|
grid.setSizeFull();
|
||||||
|
|||||||
@@ -71,7 +71,8 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
add(title);
|
add(title);
|
||||||
|
|
||||||
// Configure status filter
|
// Configure status filter
|
||||||
statusFilter.setItems(getTranslation("jobs.status.all"), getTranslation("jobs.status.open"), getTranslation("jobs.status.done"));
|
statusFilter.setItems(getTranslation("jobs.status.all"), getTranslation("jobs.status.open"),
|
||||||
|
getTranslation("jobs.status.done"));
|
||||||
statusFilter.setValue(getTranslation("jobs.status.open"));
|
statusFilter.setValue(getTranslation("jobs.status.open"));
|
||||||
statusFilter.setWidth("150px");
|
statusFilter.setWidth("150px");
|
||||||
|
|
||||||
@@ -108,12 +109,14 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
endDate.addValueChangeListener(e -> loadData());
|
endDate.addValueChangeListener(e -> loadData());
|
||||||
|
|
||||||
// Configure grid columns: Auftraggeber, Auftragsnummer, Auftragsdatum, Zielort
|
// Configure grid columns: Auftraggeber, Auftragsnummer, Auftragsdatum, Zielort
|
||||||
grid.addColumn(job -> extractCompanyName(job.getCustomerSelection())).setHeader(getTranslation("jobs.column.customer"))
|
grid.addColumn(job -> extractCompanyName(job.getCustomerSelection()))
|
||||||
.setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
.setHeader(getTranslation("jobs.column.customer")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
||||||
grid.addColumn(Job::getJobNumber).setHeader(getTranslation("jobs.column.jobnumber")).setAutoWidth(true).setSortable(true);
|
grid.addColumn(Job::getJobNumber).setHeader(getTranslation("jobs.column.jobnumber")).setAutoWidth(true)
|
||||||
grid.addColumn(job -> DateTimeFormatUtil.formatDateTime(job.getCreatedAt())).setHeader(getTranslation("jobs.column.jobdate"))
|
.setSortable(true);
|
||||||
.setAutoWidth(true).setSortable(true);
|
grid.addColumn(job -> DateTimeFormatUtil.formatDateTime(job.getCreatedAt()))
|
||||||
grid.addColumn(Job::getDeliveryCity).setHeader(getTranslation("jobs.column.destination")).setAutoWidth(true).setFlexGrow(1).setSortable(true);
|
.setHeader(getTranslation("jobs.column.jobdate")).setAutoWidth(true).setSortable(true);
|
||||||
|
grid.addColumn(Job::getDeliveryCity).setHeader(getTranslation("jobs.column.destination")).setAutoWidth(true)
|
||||||
|
.setFlexGrow(1).setSortable(true);
|
||||||
|
|
||||||
// Action column: manual completion for jobs without digital processing
|
// Action column: manual completion for jobs without digital processing
|
||||||
grid.addComponentColumn(job -> {
|
grid.addComponentColumn(job -> {
|
||||||
@@ -201,8 +204,8 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||||
loadData();
|
loadData();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Notification.show(getTranslation("jobs.notification.complete.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END)
|
Notification.show(getTranslation("jobs.notification.complete.error", ex.getMessage()), 5000,
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialog.open();
|
dialog.open();
|
||||||
@@ -226,8 +229,8 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_SUCCESS);
|
||||||
loadData();
|
loadData();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Notification.show(getTranslation("jobs.notification.delete.error", ex.getMessage()), 5000, Notification.Position.BOTTOM_END)
|
Notification.show(getTranslation("jobs.notification.delete.error", ex.getMessage()), 5000,
|
||||||
.addThemeVariants(NotificationVariant.LUMO_ERROR);
|
Notification.Position.BOTTOM_END).addThemeVariants(NotificationVariant.LUMO_ERROR);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialog.open();
|
dialog.open();
|
||||||
@@ -314,9 +317,8 @@ public class ShowJobsView extends VerticalLayout implements HasDynamicTitle {
|
|||||||
private String generateCsv(java.util.List<Job> jobs) {
|
private String generateCsv(java.util.List<Job> jobs) {
|
||||||
StringBuilder csv = new StringBuilder();
|
StringBuilder csv = new StringBuilder();
|
||||||
// CSV Header
|
// CSV Header
|
||||||
csv.append(getTranslation("csv.header.customer")).append(",")
|
csv.append(getTranslation("csv.header.customer")).append(",").append(getTranslation("csv.header.jobnumber"))
|
||||||
.append(getTranslation("csv.header.jobnumber")).append(",")
|
.append(",").append(getTranslation("csv.header.jobdate")).append(",")
|
||||||
.append(getTranslation("csv.header.jobdate")).append(",")
|
|
||||||
.append(getTranslation("csv.header.destination")).append("\n");
|
.append(getTranslation("csv.header.destination")).append("\n");
|
||||||
|
|
||||||
// CSV Data
|
// CSV Data
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Button createLanguageSelector() {
|
private Button createLanguageSelector() {
|
||||||
// Aktuelle Sprache aus der UI-Locale ermitteln (wird vom LocaleVaadinInitListener gesetzt)
|
// Aktuelle Sprache aus der UI-Locale ermitteln (wird vom
|
||||||
|
// LocaleVaadinInitListener gesetzt)
|
||||||
String currentLang = getCurrentLanguageFromLocale();
|
String currentLang = getCurrentLanguageFromLocale();
|
||||||
String flag = getFlagForLanguage(currentLang);
|
String flag = getFlagForLanguage(currentLang);
|
||||||
|
|
||||||
@@ -134,7 +135,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getCurrentLanguageFromLocale() {
|
private String getCurrentLanguageFromLocale() {
|
||||||
// Aktuelle UI-Locale verwenden (wird vom LocaleVaadinInitListener aus Cookie/Browser gesetzt)
|
// Aktuelle UI-Locale verwenden (wird vom LocaleVaadinInitListener aus
|
||||||
|
// Cookie/Browser gesetzt)
|
||||||
Locale locale = UI.getCurrent().getLocale();
|
Locale locale = UI.getCurrent().getLocale();
|
||||||
if (locale == null) {
|
if (locale == null) {
|
||||||
return "DE";
|
return "DE";
|
||||||
@@ -175,11 +177,9 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setLanguageCookie(String languageCode) {
|
private void setLanguageCookie(String languageCode) {
|
||||||
UI.getCurrent().getPage().executeJs(
|
UI.getCurrent().getPage().executeJs("const maxAge = 365 * 24 * 60 * 60;"
|
||||||
"const maxAge = 365 * 24 * 60 * 60;" +
|
+ "document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
||||||
"document.cookie = 'votianlt.language=' + $0 + ';path=/;max-age=' + maxAge + ';SameSite=Lax';",
|
languageCode);
|
||||||
languageCode
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Component createAuthenticatedNavigation() {
|
private Component createAuthenticatedNavigation() {
|
||||||
@@ -307,7 +307,8 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
|||||||
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
featuresGrid.setDefaultVerticalComponentAlignment(FlexComponent.Alignment.STRETCH);
|
||||||
|
|
||||||
// Feature Cards
|
// Feature Cards
|
||||||
featuresGrid.add(createFeatureCard(VaadinIcon.COG, getTranslation("start.feature.setup.title"),
|
featuresGrid.add(
|
||||||
|
createFeatureCard(VaadinIcon.COG, getTranslation("start.feature.setup.title"),
|
||||||
getTranslation("start.feature.setup.desc")),
|
getTranslation("start.feature.setup.desc")),
|
||||||
createFeatureCard(VaadinIcon.USERS, getTranslation("start.feature.customers.title"),
|
createFeatureCard(VaadinIcon.USERS, getTranslation("start.feature.customers.title"),
|
||||||
getTranslation("start.feature.customers.desc")),
|
getTranslation("start.feature.customers.desc")),
|
||||||
@@ -389,8 +390,10 @@ public class StartView extends VerticalLayout implements BeforeEnterObserver, Ha
|
|||||||
companyInfo.setPadding(false);
|
companyInfo.setPadding(false);
|
||||||
companyInfo.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
companyInfo.setDefaultHorizontalComponentAlignment(FlexComponent.Alignment.CENTER);
|
||||||
|
|
||||||
companyInfo.add(new Paragraph(getTranslation("start.imprint.company")), new Paragraph(getTranslation("start.imprint.address")),
|
companyInfo.add(new Paragraph(getTranslation("start.imprint.company")),
|
||||||
new Paragraph(getTranslation("start.imprint.phone")), new Paragraph(getTranslation("start.imprint.email")));
|
new Paragraph(getTranslation("start.imprint.address")),
|
||||||
|
new Paragraph(getTranslation("start.imprint.phone")),
|
||||||
|
new Paragraph(getTranslation("start.imprint.email")));
|
||||||
|
|
||||||
// Call to Action
|
// Call to Action
|
||||||
Paragraph ctaText = new Paragraph(getTranslation("start.cta.text"));
|
Paragraph ctaText = new Paragraph(getTranslation("start.cta.text"));
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ public class UserMessagesView extends Main implements HasUrlParameter<String>, H
|
|||||||
LocalDateTime lastMessageTime = latest != null ? latest.getCreatedAt() : null;
|
LocalDateTime lastMessageTime = latest != null ? latest.getCreatedAt() : null;
|
||||||
String preview = resolvePreview(latest);
|
String preview = resolvePreview(latest);
|
||||||
|
|
||||||
section.add(createMessageCard(getTranslation("usermessages.general.conversation"), preview, lastMessageTime, messageCount, unreadCount,
|
section.add(createMessageCard(getTranslation("usermessages.general.conversation"), preview, lastMessageTime,
|
||||||
"general"));
|
messageCount, unreadCount, "general"));
|
||||||
|
|
||||||
return section;
|
return section;
|
||||||
}
|
}
|
||||||
@@ -233,8 +233,7 @@ public class UserMessagesView extends Main implements HasUrlParameter<String>, H
|
|||||||
titleRow.expand(titleSpan);
|
titleRow.expand(titleSpan);
|
||||||
|
|
||||||
// Preview text
|
// Preview text
|
||||||
Span preview = new Span(
|
Span preview = new Span(Optional.ofNullable(lastMessagePreview).filter(s -> !s.isBlank())
|
||||||
Optional.ofNullable(lastMessagePreview).filter(s -> !s.isBlank())
|
|
||||||
.orElse(getTranslation("usermessages.preview.empty")));
|
.orElse(getTranslation("usermessages.preview.empty")));
|
||||||
preview.getStyle().set("color", "#666666");
|
preview.getStyle().set("color", "#666666");
|
||||||
preview.getStyle().set("font-size", "14px");
|
preview.getStyle().set("font-size", "14px");
|
||||||
|
|||||||
@@ -459,11 +459,8 @@ public class CustomerInvoiceService {
|
|||||||
StringBuilder html = new StringBuilder();
|
StringBuilder html = new StringBuilder();
|
||||||
|
|
||||||
// Sample data for preview (will be replaced with actual job data later)
|
// Sample data for preview (will be replaced with actual job data later)
|
||||||
String[][] sampleData = {
|
String[][] sampleData = { { "Umzugsleistung inkl. Verpackung", "19%", "450,00 €" },
|
||||||
{"Umzugsleistung inkl. Verpackung", "19%", "450,00 €"},
|
{ "Entsorgung Möbel", "19%", "85,00 €" }, { "Montage/De-Montage", "19%", "120,00 €" } };
|
||||||
{"Entsorgung Möbel", "19%", "85,00 €"},
|
|
||||||
{"Montage/De-Montage", "19%", "120,00 €"}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate totals
|
// Calculate totals
|
||||||
double netTotal = 655.00;
|
double netTotal = 655.00;
|
||||||
@@ -478,18 +475,25 @@ public class CustomerInvoiceService {
|
|||||||
|
|
||||||
// Header row
|
// Header row
|
||||||
html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
|
html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
|
||||||
html.append("<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
html.append(
|
||||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
"<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
||||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
html.append(
|
||||||
|
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
||||||
|
html.append(
|
||||||
|
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
// Data rows
|
// Data rows
|
||||||
for (int i = 0; i < sampleData.length; i++) {
|
for (int i = 0; i < sampleData.length; i++) {
|
||||||
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
|
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
|
||||||
html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
|
html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
|
||||||
html.append("<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>").append(sampleData[i][0]).append("</td>");
|
html.append(
|
||||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][1]).append("</td>");
|
"<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>")
|
||||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][2]).append("</td>");
|
.append(sampleData[i][0]).append("</td>");
|
||||||
|
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][1])
|
||||||
|
.append("</td>");
|
||||||
|
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(sampleData[i][2])
|
||||||
|
.append("</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,21 +509,26 @@ public class CustomerInvoiceService {
|
|||||||
html.append("<tr>");
|
html.append("<tr>");
|
||||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
|
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
|
||||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", netTotal)).append("</td>");
|
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||||
|
.append(String.format(java.util.Locale.GERMANY, "%,.2f €", netTotal)).append("</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
// Umsatzsteuer - label in col 2, value in col 3
|
// Umsatzsteuer - label in col 2, value in col 3
|
||||||
html.append("<tr>");
|
html.append("<tr>");
|
||||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. 19% USt:</td>");
|
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. 19% USt:</td>");
|
||||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", vatTotal)).append("</td>");
|
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||||
|
.append(String.format(java.util.Locale.GERMANY, "%,.2f €", vatTotal)).append("</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
// Gesamtsumme - label in col 2, value in col 3
|
// Gesamtsumme - label in col 2, value in col 3
|
||||||
html.append("<tr>");
|
html.append("<tr>");
|
||||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||||
html.append("<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
html.append(
|
||||||
html.append("<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>").append(String.format(java.util.Locale.GERMANY, "%,.2f €", grossTotal)).append("</td>");
|
"<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
||||||
|
html.append(
|
||||||
|
"<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>")
|
||||||
|
.append(String.format(java.util.Locale.GERMANY, "%,.2f €", grossTotal)).append("</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
html.append("</table>");
|
html.append("</table>");
|
||||||
@@ -530,8 +539,9 @@ public class CustomerInvoiceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a PDF preview from canvas template data with pre-built variables map.
|
* Generate a PDF preview from canvas template data with pre-built variables
|
||||||
* This version accepts a pre-built variables map instead of building it from the User object.
|
* map. This version accepts a pre-built variables map instead of building it
|
||||||
|
* from the User object.
|
||||||
*/
|
*/
|
||||||
public byte[] generatePdfFromCanvasTemplateWithData(String jsonTemplateData,
|
public byte[] generatePdfFromCanvasTemplateWithData(String jsonTemplateData,
|
||||||
java.util.Map<String, String> variables, de.assecutor.votianlt.model.User user) throws Exception {
|
java.util.Map<String, String> variables, de.assecutor.votianlt.model.User user) throws Exception {
|
||||||
@@ -542,7 +552,8 @@ public class CustomerInvoiceService {
|
|||||||
// Parse the JSON template data - JSON is stored as a string in the database
|
// Parse the JSON template data - JSON is stored as a string in the database
|
||||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||||
|
|
||||||
// The template data is stored as a JSON string, so we need to parse it as a string first
|
// The template data is stored as a JSON string, so we need to parse it as a
|
||||||
|
// string first
|
||||||
String jsonContent = mapper.readValue(jsonTemplateData, String.class);
|
String jsonContent = mapper.readValue(jsonTemplateData, String.class);
|
||||||
com.fasterxml.jackson.databind.JsonNode rootNode = mapper.readTree(jsonContent);
|
com.fasterxml.jackson.databind.JsonNode rootNode = mapper.readTree(jsonContent);
|
||||||
|
|
||||||
@@ -597,9 +608,11 @@ public class CustomerInvoiceService {
|
|||||||
String variable = element.has("variable") ? element.get("variable").asText(null) : null;
|
String variable = element.has("variable") ? element.get("variable").asText(null) : null;
|
||||||
String text = "";
|
String text = "";
|
||||||
|
|
||||||
System.out.println("DEBUG: Processing element " + elementCount + ", type=" + type + ", variable=" + variable);
|
System.out.println(
|
||||||
|
"DEBUG: Processing element " + elementCount + ", type=" + type + ", variable=" + variable);
|
||||||
|
|
||||||
// Use percentage values if available, otherwise fall back to legacy pixel values
|
// Use percentage values if available, otherwise fall back to legacy pixel
|
||||||
|
// values
|
||||||
double xPercent, yPercent, widthPercent, heightPercent;
|
double xPercent, yPercent, widthPercent, heightPercent;
|
||||||
if (element.has("xPercent")) {
|
if (element.has("xPercent")) {
|
||||||
xPercent = element.get("xPercent").asDouble(0);
|
xPercent = element.get("xPercent").asDouble(0);
|
||||||
@@ -671,7 +684,8 @@ public class CustomerInvoiceService {
|
|||||||
System.out.println("DEBUG: Replaced variable " + variable + " with: " + text);
|
System.out.println("DEBUG: Replaced variable " + variable + " with: " + text);
|
||||||
} else if (variable != null) {
|
} else if (variable != null) {
|
||||||
// Variable exists but no value provided - use placeholder
|
// Variable exists but no value provided - use placeholder
|
||||||
text = "[" + variable.replace("masterdata.", "").replace("customer.", "").replace("invoice.", "").replace("job.", "") + "]";
|
text = "[" + variable.replace("masterdata.", "").replace("customer.", "").replace("invoice.", "")
|
||||||
|
.replace("job.", "") + "]";
|
||||||
System.out.println("DEBUG: No value for variable " + variable + ", using placeholder");
|
System.out.println("DEBUG: No value for variable " + variable + ", using placeholder");
|
||||||
} else {
|
} else {
|
||||||
System.out.println("DEBUG: Using static text: " + text);
|
System.out.println("DEBUG: Using static text: " + text);
|
||||||
@@ -744,8 +758,8 @@ public class CustomerInvoiceService {
|
|||||||
if (servicesJson != null && !servicesJson.isEmpty() && !servicesJson.equals("[]")) {
|
if (servicesJson != null && !servicesJson.isEmpty() && !servicesJson.equals("[]")) {
|
||||||
try {
|
try {
|
||||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||||
com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, String>>> typeRef =
|
com.fasterxml.jackson.core.type.TypeReference<java.util.List<java.util.Map<String, String>>> typeRef = new com.fasterxml.jackson.core.type.TypeReference<>() {
|
||||||
new com.fasterxml.jackson.core.type.TypeReference<>() {};
|
};
|
||||||
servicesData = mapper.readValue(servicesJson, typeRef);
|
servicesData = mapper.readValue(servicesJson, typeRef);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.err.println("DEBUG: Failed to parse services JSON: " + e.getMessage());
|
System.err.println("DEBUG: Failed to parse services JSON: " + e.getMessage());
|
||||||
@@ -760,16 +774,20 @@ public class CustomerInvoiceService {
|
|||||||
|
|
||||||
// Header row
|
// Header row
|
||||||
html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
|
html.append("<tr style='background-color:#f5f5f5;border-bottom:1px solid #cccccc;'>");
|
||||||
html.append("<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
html.append(
|
||||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
"<th style='text-align:left;padding:4px 8px;font-weight:bold;width:55%;white-space:nowrap;'>Name</th>");
|
||||||
html.append("<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
html.append(
|
||||||
|
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:20%;white-space:nowrap;'>Steuersatz</th>");
|
||||||
|
html.append(
|
||||||
|
"<th style='text-align:right;padding:4px 8px;font-weight:bold;width:25%;white-space:nowrap;'>Nettobetrag</th>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
// Data rows - use actual service data from the job
|
// Data rows - use actual service data from the job
|
||||||
if (servicesData.isEmpty()) {
|
if (servicesData.isEmpty()) {
|
||||||
// Fallback: show a single row with no data
|
// Fallback: show a single row with no data
|
||||||
html.append("<tr style='border-bottom:1px solid #eeeeee;'>");
|
html.append("<tr style='border-bottom:1px solid #eeeeee;'>");
|
||||||
html.append("<td colspan='3' style='text-align:center;padding:4px 8px;white-space:nowrap;'>Keine Leistungen vorhanden</td>");
|
html.append(
|
||||||
|
"<td colspan='3' style='text-align:center;padding:4px 8px;white-space:nowrap;'>Keine Leistungen vorhanden</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < servicesData.size(); i++) {
|
for (int i = 0; i < servicesData.size(); i++) {
|
||||||
@@ -780,9 +798,13 @@ public class CustomerInvoiceService {
|
|||||||
|
|
||||||
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
|
String bgColor = (i % 2 == 1) ? "background-color:rgba(0,0,0,0.02);" : "";
|
||||||
html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
|
html.append("<tr style='").append(bgColor).append("border-bottom:1px solid #eeeeee;'>");
|
||||||
html.append("<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>").append(escapeHtml(name)).append("</td>");
|
html.append(
|
||||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(serviceVatRate).append("</td>");
|
"<td style='text-align:left;padding:4px 8px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;'>")
|
||||||
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(netAmount).append(" €</td>");
|
.append(escapeHtml(name)).append("</td>");
|
||||||
|
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(serviceVatRate)
|
||||||
|
.append("</td>");
|
||||||
|
html.append("<td style='text-align:right;padding:4px 8px;white-space:nowrap;'>").append(netAmount)
|
||||||
|
.append(" €</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -800,21 +822,27 @@ public class CustomerInvoiceService {
|
|||||||
html.append("<tr>");
|
html.append("<tr>");
|
||||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
|
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>Nettosumme:</td>");
|
||||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(netTotal).append("</td>");
|
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||||
|
.append(netTotal).append("</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
// Umsatzsteuer
|
// Umsatzsteuer
|
||||||
html.append("<tr>");
|
html.append("<tr>");
|
||||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||||
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. ").append(vatPercent).append("% USt:</td>");
|
html.append("<td style='width:20%;text-align:left;padding:2px 8px;white-space:nowrap;'>zzgl. ")
|
||||||
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>").append(vatTotal).append("</td>");
|
.append(vatPercent).append("% USt:</td>");
|
||||||
|
html.append("<td style='width:25%;text-align:right;padding:2px 8px;white-space:nowrap;font-weight:bold;'>")
|
||||||
|
.append(vatTotal).append("</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
// Gesamtsumme
|
// Gesamtsumme
|
||||||
html.append("<tr>");
|
html.append("<tr>");
|
||||||
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
html.append("<td style='width:55%;padding:2px 0;'></td>");
|
||||||
html.append("<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
html.append(
|
||||||
html.append("<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>").append(grossTotal).append("</td>");
|
"<td style='width:20%;text-align:left;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>Gesamtsumme:</td>");
|
||||||
|
html.append(
|
||||||
|
"<td style='width:25%;text-align:right;padding:4px 8px;white-space:nowrap;font-weight:bold;font-size:1.05em;'>")
|
||||||
|
.append(grossTotal).append("</td>");
|
||||||
html.append("</tr>");
|
html.append("</tr>");
|
||||||
|
|
||||||
html.append("</table>");
|
html.append("</table>");
|
||||||
@@ -831,10 +859,7 @@ public class CustomerInvoiceService {
|
|||||||
if (input == null) {
|
if (input == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return input.replace("&", "&")
|
return input.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """)
|
||||||
.replace("<", "<")
|
|
||||||
.replace(">", ">")
|
|
||||||
.replace("\"", """)
|
|
||||||
.replace("'", "'");
|
.replace("'", "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user