Erweiterungen
This commit is contained in:
@@ -9,8 +9,8 @@ const COOKIE_MAX_AGE_DAYS = 365; // Cookie gültig für 1 Jahr
|
|||||||
* @param languageCode - The language code (e.g., 'de', 'en', 'fr', 'es')
|
* @param languageCode - The language code (e.g., 'de', 'en', 'fr', 'es')
|
||||||
*/
|
*/
|
||||||
export function setLanguageCookie(languageCode: string): void {
|
export function setLanguageCookie(languageCode: string): void {
|
||||||
const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60; // Convert days to seconds
|
const maxAge = COOKIE_MAX_AGE_DAYS * 24 * 60 * 60; // Convert days to seconds
|
||||||
document.cookie = `${LANGUAGE_COOKIE_NAME}=${languageCode};path=/;max-age=${maxAge};SameSite=Lax`;
|
document.cookie = `${LANGUAGE_COOKIE_NAME}=${languageCode};path=/;max-age=${maxAge};SameSite=Lax`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,39 +18,39 @@ export function setLanguageCookie(languageCode: string): void {
|
|||||||
* @returns The language code or null if not found
|
* @returns The language code or null if not found
|
||||||
*/
|
*/
|
||||||
export function getLanguageCookie(): string | null {
|
export function getLanguageCookie(): string | null {
|
||||||
const cookies = document.cookie.split(';');
|
const cookies = document.cookie.split(';');
|
||||||
for (const cookie of cookies) {
|
for (const cookie of cookies) {
|
||||||
const [name, value] = cookie.trim().split('=');
|
const [name, value] = cookie.trim().split('=');
|
||||||
if (name === LANGUAGE_COOKIE_NAME) {
|
if (name === LANGUAGE_COOKIE_NAME) {
|
||||||
return decodeURIComponent(value);
|
return decodeURIComponent(value);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears the language cookie
|
* Clears the language cookie
|
||||||
*/
|
*/
|
||||||
export function clearLanguageCookie(): void {
|
export function clearLanguageCookie(): void {
|
||||||
document.cookie = `${LANGUAGE_COOKIE_NAME}=;path=/;max-age=0;SameSite=Lax`;
|
document.cookie = `${LANGUAGE_COOKIE_NAME}=;path=/;max-age=0;SameSite=Lax`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@@ -40,7 +41,7 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
|||||||
private void applyLocale(UI ui) {
|
private void applyLocale(UI ui) {
|
||||||
try {
|
try {
|
||||||
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
|
||||||
if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken)) {
|
if (auth != null && auth.isAuthenticated() && !(auth instanceof AnonymousAuthenticationToken)) {
|
||||||
// Authenticated user: use profile language
|
// Authenticated user: use profile language
|
||||||
applyLocaleFromAuthenticatedUser(ui, auth);
|
applyLocaleFromAuthenticatedUser(ui, auth);
|
||||||
@@ -70,14 +71,14 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
|||||||
|
|
||||||
private void applyLocaleFromCookieOrBrowser(UI ui) {
|
private void applyLocaleFromCookieOrBrowser(UI ui) {
|
||||||
Locale targetLocale = null;
|
Locale targetLocale = null;
|
||||||
|
|
||||||
// Try to get locale from cookie first
|
// Try to get locale from cookie first
|
||||||
String cookieLanguage = getLanguageFromCookie(ui);
|
String cookieLanguage = getLanguageFromCookie(ui);
|
||||||
if (cookieLanguage != null) {
|
if (cookieLanguage != null) {
|
||||||
targetLocale = getLocaleFromLanguageCode(cookieLanguage);
|
targetLocale = getLocaleFromLanguageCode(cookieLanguage);
|
||||||
log.debug("Using locale {} from cookie for anonymous user", targetLocale);
|
log.debug("Using locale {} from cookie for anonymous user", targetLocale);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no cookie, use browser's preferred locale if supported
|
// If no cookie, use browser's preferred locale if supported
|
||||||
if (targetLocale == null) {
|
if (targetLocale == null) {
|
||||||
targetLocale = getSupportedLocaleFromBrowser(ui);
|
targetLocale = getSupportedLocaleFromBrowser(ui);
|
||||||
@@ -85,7 +86,7 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
|||||||
log.debug("Using browser locale {} for anonymous user", targetLocale);
|
log.debug("Using browser locale {} for anonymous user", targetLocale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the locale if different from current
|
// Apply the locale if different from current
|
||||||
if (targetLocale != null && !targetLocale.equals(ui.getLocale())) {
|
if (targetLocale != null && !targetLocale.equals(ui.getLocale())) {
|
||||||
ui.setLocale(targetLocale);
|
ui.setLocale(targetLocale);
|
||||||
@@ -99,12 +100,12 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
|||||||
if (request == null) {
|
if (request == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Cookie[] cookies = request.getCookies();
|
Cookie[] cookies = request.getCookies();
|
||||||
if (cookies == null) {
|
if (cookies == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Cookie cookie : cookies) {
|
for (Cookie cookie : cookies) {
|
||||||
if (LANGUAGE_COOKIE_NAME.equals(cookie.getName())) {
|
if (LANGUAGE_COOKIE_NAME.equals(cookie.getName())) {
|
||||||
return cookie.getValue();
|
return cookie.getValue();
|
||||||
@@ -122,30 +123,30 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
|||||||
if (locale == null) {
|
if (locale == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the browser locale is supported
|
// Check if the browser locale is supported
|
||||||
String language = locale.getLanguage().toLowerCase();
|
String language = locale.getLanguage().toLowerCase();
|
||||||
return switch (language) {
|
return switch (language) {
|
||||||
case "de" -> Locale.GERMAN;
|
case "de" -> Locale.GERMAN;
|
||||||
case "en" -> Locale.ENGLISH;
|
case "en" -> Locale.ENGLISH;
|
||||||
case "fr" -> Locale.FRENCH;
|
case "fr" -> Locale.FRENCH;
|
||||||
case "es" -> Locale.of("es", "ES");
|
case "es" -> Locale.of("es", "ES");
|
||||||
default -> null; // Return null to use Vaadin's default
|
default -> null; // Return null to use Vaadin's default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Locale getLocaleFromLanguage(Language language) {
|
private Locale getLocaleFromLanguage(Language language) {
|
||||||
return switch (language) {
|
return switch (language) {
|
||||||
case DE -> Locale.GERMAN;
|
case DE -> Locale.GERMAN;
|
||||||
case EN -> Locale.ENGLISH;
|
case EN -> Locale.ENGLISH;
|
||||||
case FR -> Locale.FRENCH;
|
case FR -> Locale.FRENCH;
|
||||||
case ES -> Locale.of("es", "ES");
|
case ES -> Locale.of("es", "ES");
|
||||||
case TR -> Locale.of("tr", "TR");
|
case TR -> Locale.of("tr", "TR");
|
||||||
case PL -> Locale.of("pl", "PL");
|
case PL -> Locale.of("pl", "PL");
|
||||||
case RU -> Locale.of("ru", "RU");
|
case RU -> Locale.of("ru", "RU");
|
||||||
case EE -> Locale.of("et", "EE");
|
case EE -> Locale.of("et", "EE");
|
||||||
case LV -> Locale.of("lv", "LV");
|
case LV -> Locale.of("lv", "LV");
|
||||||
case LT -> Locale.of("lt", "LT");
|
case LT -> Locale.of("lt", "LT");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,19 +154,19 @@ public class LocaleVaadinInitListener implements VaadinServiceInitListener {
|
|||||||
if (languageCode == null) {
|
if (languageCode == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return switch (languageCode.toUpperCase()) {
|
return switch (languageCode.toUpperCase()) {
|
||||||
case "DE" -> Locale.GERMAN;
|
case "DE" -> Locale.GERMAN;
|
||||||
case "EN" -> Locale.ENGLISH;
|
case "EN" -> Locale.ENGLISH;
|
||||||
case "FR" -> Locale.FRENCH;
|
case "FR" -> Locale.FRENCH;
|
||||||
case "ES" -> Locale.of("es", "ES");
|
case "ES" -> Locale.of("es", "ES");
|
||||||
case "TR" -> Locale.of("tr", "TR");
|
case "TR" -> Locale.of("tr", "TR");
|
||||||
case "PL" -> Locale.of("pl", "PL");
|
case "PL" -> Locale.of("pl", "PL");
|
||||||
case "RU" -> Locale.of("ru", "RU");
|
case "RU" -> Locale.of("ru", "RU");
|
||||||
case "EE" -> Locale.of("et", "EE");
|
case "EE" -> Locale.of("et", "EE");
|
||||||
case "LV" -> Locale.of("lv", "LV");
|
case "LV" -> Locale.of("lv", "LV");
|
||||||
case "LT" -> Locale.of("lt", "LT");
|
case "LT" -> Locale.of("lt", "LT");
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -15,48 +16,38 @@ import java.util.ResourceBundle.Control;
|
|||||||
public class TranslationProvider implements I18NProvider {
|
public class TranslationProvider implements I18NProvider {
|
||||||
|
|
||||||
public static final String BUNDLE_PREFIX = "messages";
|
public static final String BUNDLE_PREFIX = "messages";
|
||||||
|
|
||||||
// Custom Control to map language codes to file names
|
// Custom Control to map language codes to file names
|
||||||
private static final Control BUNDLE_CONTROL = new Control() {
|
private static final Control BUNDLE_CONTROL = new Control() {
|
||||||
@Override
|
@Override
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
return super.getCandidateLocales(baseName, mappedLocale);
|
return super.getCandidateLocales(baseName, mappedLocale);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@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;
|
||||||
@@ -81,16 +72,16 @@ public class TranslationProvider implements I18NProvider {
|
|||||||
|
|
||||||
public String getTranslation(String key, Language language) {
|
public String getTranslation(String key, Language language) {
|
||||||
Locale locale = switch (language) {
|
Locale locale = switch (language) {
|
||||||
case DE -> Locale.GERMAN;
|
case DE -> Locale.GERMAN;
|
||||||
case EN -> Locale.ENGLISH;
|
case EN -> Locale.ENGLISH;
|
||||||
case FR -> Locale.FRENCH;
|
case FR -> Locale.FRENCH;
|
||||||
case ES -> Locale.of("es", "ES");
|
case ES -> Locale.of("es", "ES");
|
||||||
case TR -> Locale.of("tr", "TR");
|
case TR -> Locale.of("tr", "TR");
|
||||||
case PL -> Locale.of("pl", "PL");
|
case PL -> Locale.of("pl", "PL");
|
||||||
case RU -> Locale.of("ru", "RU");
|
case RU -> Locale.of("ru", "RU");
|
||||||
case EE -> Locale.of("et", "EE");
|
case EE -> Locale.of("et", "EE");
|
||||||
case LV -> Locale.of("lv", "LV");
|
case LV -> Locale.of("lv", "LV");
|
||||||
case LT -> Locale.of("lt", "LT");
|
case LT -> Locale.of("lt", "LT");
|
||||||
};
|
};
|
||||||
return getTranslation(key, locale);
|
return getTranslation(key, locale);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,15 +25,15 @@ public enum TaskType {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Fallback to German if translation fails
|
// Fallback to German if translation fails
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to German translations
|
// Fallback to German translations
|
||||||
return switch (this) {
|
return switch (this) {
|
||||||
case CONFIRMATION -> "Bestätigung";
|
case CONFIRMATION -> "Bestätigung";
|
||||||
case SIGNATURE -> "Unterschrift";
|
case SIGNATURE -> "Unterschrift";
|
||||||
case TODOLIST -> "To-Do Liste";
|
case TODOLIST -> "To-Do Liste";
|
||||||
case PHOTO -> "Foto";
|
case PHOTO -> "Foto";
|
||||||
case BARCODE -> "Barcode";
|
case BARCODE -> "Barcode";
|
||||||
case COMMENT -> "Kommentar";
|
case COMMENT -> "Kommentar";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,42 +109,51 @@ public final class MainLayout extends AppLayout {
|
|||||||
private Component createSideNav() {
|
private Component createSideNav() {
|
||||||
// Create tree data with hierarchical menu structure
|
// Create tree data with hierarchical menu structure
|
||||||
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);
|
||||||
|
|
||||||
// Store reference to messages item for badge updates
|
// Store reference to messages item for badge updates
|
||||||
messagesTreeItem = nachrichtenItem;
|
messagesTreeItem = nachrichtenItem;
|
||||||
|
|
||||||
// Add root items
|
// Add root items
|
||||||
treeData.addItem(null, auftragserstellungItem);
|
treeData.addItem(null, auftragserstellungItem);
|
||||||
treeData.addItem(null, nachrichtenItem);
|
treeData.addItem(null, nachrichtenItem);
|
||||||
treeData.addItem(null, verwaltungItem);
|
treeData.addItem(null, verwaltungItem);
|
||||||
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<>();
|
||||||
tree.setDataProvider(new TreeDataProvider<>(treeData));
|
tree.setDataProvider(new TreeDataProvider<>(treeData));
|
||||||
tree.addClassNames(Margin.Horizontal.MEDIUM);
|
tree.addClassNames(Margin.Horizontal.MEDIUM);
|
||||||
|
|
||||||
// Custom item renderer to show icon and label with badge
|
// Custom item renderer to show icon and label with badge
|
||||||
tree.addComponentHierarchyColumn(item -> {
|
tree.addComponentHierarchyColumn(item -> {
|
||||||
HorizontalLayout row = new HorizontalLayout();
|
HorizontalLayout row = new HorizontalLayout();
|
||||||
@@ -153,7 +162,7 @@ public final class MainLayout extends AppLayout {
|
|||||||
row.setPadding(false);
|
row.setPadding(false);
|
||||||
row.setMargin(false);
|
row.setMargin(false);
|
||||||
row.setWidthFull();
|
row.setWidthFull();
|
||||||
|
|
||||||
// Icon
|
// Icon
|
||||||
if (item.icon() != null) {
|
if (item.icon() != null) {
|
||||||
Icon icon = item.icon().create();
|
Icon icon = item.icon().create();
|
||||||
@@ -161,13 +170,13 @@ public final class MainLayout extends AppLayout {
|
|||||||
icon.getStyle().set("min-width", "var(--lumo-icon-size-s)");
|
icon.getStyle().set("min-width", "var(--lumo-icon-size-s)");
|
||||||
row.add(icon);
|
row.add(icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label
|
// Label
|
||||||
Span label = new Span(item.label());
|
Span label = new Span(item.label());
|
||||||
label.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
label.getStyle().set("font-size", "var(--lumo-font-size-s)");
|
||||||
row.add(label);
|
row.add(label);
|
||||||
row.setFlexGrow(1, label);
|
row.setFlexGrow(1, label);
|
||||||
|
|
||||||
// Badge for messages
|
// Badge for messages
|
||||||
if (item == messagesTreeItem && item.badgeCount() > 0) {
|
if (item == messagesTreeItem && item.badgeCount() > 0) {
|
||||||
Span badge = new Span(String.valueOf(item.badgeCount()));
|
Span badge = new Span(String.valueOf(item.badgeCount()));
|
||||||
@@ -183,10 +192,10 @@ public final class MainLayout extends AppLayout {
|
|||||||
badge.getStyle().set("margin-left", "auto");
|
badge.getStyle().set("margin-left", "auto");
|
||||||
row.add(badge);
|
row.add(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle selection/navigation
|
// Handle selection/navigation
|
||||||
tree.addSelectionListener(event -> {
|
tree.addSelectionListener(event -> {
|
||||||
event.getFirstSelectedItem().ifPresent(item -> {
|
event.getFirstSelectedItem().ifPresent(item -> {
|
||||||
@@ -195,16 +204,16 @@ public final class MainLayout extends AppLayout {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Expand management and user nodes by default
|
// Expand management and user nodes by default
|
||||||
tree.expand(verwaltungItem, benutzerItem);
|
tree.expand(verwaltungItem, benutzerItem);
|
||||||
|
|
||||||
// Create container
|
// Create container
|
||||||
VerticalLayout navContainer = new VerticalLayout();
|
VerticalLayout navContainer = new VerticalLayout();
|
||||||
navContainer.setPadding(false);
|
navContainer.setPadding(false);
|
||||||
navContainer.setSpacing(false);
|
navContainer.setSpacing(false);
|
||||||
navContainer.add(tree);
|
navContainer.add(tree);
|
||||||
|
|
||||||
return navContainer;
|
return navContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,28 +226,22 @@ public final class MainLayout extends AppLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
long unreadCount = resolveUnreadMessageCount();
|
long unreadCount = resolveUnreadMessageCount();
|
||||||
|
|
||||||
// Get current data provider and update the messages item
|
// Get current data provider and update the messages item
|
||||||
TreeDataProvider<MenuTreeItem> dataProvider = (TreeDataProvider<MenuTreeItem>) tree.getDataProvider();
|
TreeDataProvider<MenuTreeItem> dataProvider = (TreeDataProvider<MenuTreeItem>) tree.getDataProvider();
|
||||||
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()))
|
.ifPresent(oldItem -> {
|
||||||
.findFirst()
|
MenuTreeItem newItem = new MenuTreeItem(getTranslation("nav.messages"), "messages",
|
||||||
.ifPresent(oldItem -> {
|
VaadinIcon.ENVELOPE, unreadCount);
|
||||||
MenuTreeItem newItem = new MenuTreeItem(
|
messagesTreeItem = newItem;
|
||||||
getTranslation("nav.messages"),
|
// Refresh to show updated badge
|
||||||
"messages",
|
dataProvider.refreshAll();
|
||||||
VaadinIcon.ENVELOPE,
|
});
|
||||||
unreadCount
|
|
||||||
);
|
|
||||||
messagesTreeItem = newItem;
|
|
||||||
// Refresh to show updated badge
|
|
||||||
dataProvider.refreshAll();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record representing a menu item in the tree
|
* Record representing a menu item in the tree
|
||||||
*/
|
*/
|
||||||
@@ -246,15 +249,17 @@ public final class MainLayout extends AppLayout {
|
|||||||
MenuTreeItem(String label, String path, VaadinIcon icon) {
|
MenuTreeItem(String label, String path, VaadinIcon icon) {
|
||||||
this(label, path, icon, 0);
|
this(label, path, icon, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(label, path);
|
return Objects.hash(label, 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 {
|
||||||
@@ -386,11 +392,11 @@ public final class MainLayout extends AppLayout {
|
|||||||
|
|
||||||
private Locale getLocaleFromLanguage(Language language) {
|
private Locale getLocaleFromLanguage(Language language) {
|
||||||
return switch (language) {
|
return switch (language) {
|
||||||
case DE -> Locale.GERMAN;
|
case DE -> Locale.GERMAN;
|
||||||
case EN -> Locale.ENGLISH;
|
case EN -> Locale.ENGLISH;
|
||||||
case FR -> Locale.FRENCH;
|
case FR -> Locale.FRENCH;
|
||||||
case ES -> Locale.of("es", "ES");
|
case ES -> Locale.of("es", "ES");
|
||||||
default -> Locale.GERMAN;
|
default -> Locale.GERMAN;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
private VerticalLayout routeInfoBox;
|
private VerticalLayout routeInfoBox;
|
||||||
private Span routeDistanceLabel;
|
private Span routeDistanceLabel;
|
||||||
private Span routeDurationLabel;
|
private Span routeDurationLabel;
|
||||||
|
|
||||||
// Manuelle Eingabe für Route (wenn keine Berechnung erfolgt)
|
// Manuelle Eingabe für Route (wenn keine Berechnung erfolgt)
|
||||||
private VerticalLayout manualRouteInputBox;
|
private VerticalLayout manualRouteInputBox;
|
||||||
private com.vaadin.flow.component.textfield.NumberField manualDistanceInput;
|
private com.vaadin.flow.component.textfield.NumberField manualDistanceInput;
|
||||||
@@ -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
|
||||||
@@ -1515,7 +1520,7 @@ public class AddJobView extends Main implements HasDynamicTitle {
|
|||||||
.map(s -> calculateServicePrice(s, routeDistance, durationSeconds))
|
.map(s -> calculateServicePrice(s, routeDistance, durationSeconds))
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
job.setPrice(netTotal);
|
job.setPrice(netTotal);
|
||||||
|
|
||||||
// Store route distance and duration in job for invoice creation
|
// Store route distance and duration in job for invoice creation
|
||||||
if (routeCalculationResult != null && routeCalculationResult.isValid()) {
|
if (routeCalculationResult != null && routeCalculationResult.isValid()) {
|
||||||
// Berechnete Route verwenden
|
// Berechnete Route verwenden
|
||||||
@@ -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,11 +310,10 @@ 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) + " €")));
|
|
||||||
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.total")),
|
summaryInfo.add(new HorizontalLayout(new Span(getTranslation("createinvoice.summary.total")),
|
||||||
new Span(totalAmount.setScale(2, RoundingMode.HALF_UP) + " €")));
|
new Span(totalAmount.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,43 +412,52 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
// Get current user
|
// Get current user
|
||||||
Optional<User> currentUserOpt = securityService.getAuthenticatedUser()
|
Optional<User> currentUserOpt = securityService.getAuthenticatedUser()
|
||||||
.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an invoice PDF from the template with actual job data.
|
* Generates an invoice PDF from the template with actual job data.
|
||||||
*/
|
*/
|
||||||
@@ -455,10 +467,10 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
BigDecimal vatRate = calculateAverageVatRate();
|
BigDecimal vatRate = calculateAverageVatRate();
|
||||||
BigDecimal vatAmount = netAmount.multiply(vatRate);
|
BigDecimal vatAmount = netAmount.multiply(vatRate);
|
||||||
BigDecimal totalAmount = netAmount.add(vatAmount);
|
BigDecimal totalAmount = netAmount.add(vatAmount);
|
||||||
|
|
||||||
// Build variable substitution map
|
// Build variable substitution map
|
||||||
Map<String, String> variables = new HashMap<>();
|
Map<String, String> variables = new HashMap<>();
|
||||||
|
|
||||||
// Master data (from user profile)
|
// Master data (from user profile)
|
||||||
variables.put("masterdata.company_name", safe(currentUser.getCompany()));
|
variables.put("masterdata.company_name", safe(currentUser.getCompany()));
|
||||||
variables.put("masterdata.contact_name", safe(currentUser.getFirstname()) + " " + safe(currentUser.getName()));
|
variables.put("masterdata.contact_name", safe(currentUser.getFirstname()) + " " + safe(currentUser.getName()));
|
||||||
@@ -466,7 +478,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
variables.put("masterdata.city", safe(currentUser.getZip()) + " " + safe(currentUser.getCity()));
|
variables.put("masterdata.city", safe(currentUser.getZip()) + " " + safe(currentUser.getCity()));
|
||||||
variables.put("masterdata.email", safe(currentUser.getEmail()));
|
variables.put("masterdata.email", safe(currentUser.getEmail()));
|
||||||
variables.put("masterdata.phone", safe(currentUser.getPhone()));
|
variables.put("masterdata.phone", safe(currentUser.getPhone()));
|
||||||
|
|
||||||
// Customer data (from job)
|
// Customer data (from job)
|
||||||
String customerSelection = currentJob.getCustomerSelection();
|
String customerSelection = currentJob.getCustomerSelection();
|
||||||
if (customerSelection != null && !customerSelection.isBlank()) {
|
if (customerSelection != null && !customerSelection.isBlank()) {
|
||||||
@@ -474,7 +486,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
String[] parts = customerSelection.split("\\|");
|
String[] parts = customerSelection.split("\\|");
|
||||||
String companyName = parts.length > 0 ? parts[0].trim() : "";
|
String companyName = parts.length > 0 ? parts[0].trim() : "";
|
||||||
variables.put("customer.company_name", companyName);
|
variables.put("customer.company_name", companyName);
|
||||||
|
|
||||||
// Extract other customer info if available
|
// Extract other customer info if available
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
String remaining = parts[1].trim();
|
String remaining = parts[1].trim();
|
||||||
@@ -502,28 +514,32 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
variables.put("customer.street", "");
|
variables.put("customer.street", "");
|
||||||
variables.put("customer.city", "");
|
variables.put("customer.city", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
variables.put("job.distance_km", String.format("%.1f", currentJob.getRouteDistanceKm()));
|
variables.put("job.distance_km", String.format("%.1f", currentJob.getRouteDistanceKm()));
|
||||||
} else {
|
} else {
|
||||||
variables.put("job.distance_km", "-");
|
variables.put("job.distance_km", "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Services data - add as JSON array for the template
|
// Services data - add as JSON array for the template
|
||||||
List<Map<String, String>> servicesData = new ArrayList<>();
|
List<Map<String, String>> servicesData = new ArrayList<>();
|
||||||
for (Service service : getSelectedServices()) {
|
for (Service service : getSelectedServices()) {
|
||||||
Map<String, String> serviceData = new HashMap<>();
|
Map<String, String> serviceData = new HashMap<>();
|
||||||
serviceData.put("name", service.getName());
|
serviceData.put("name", service.getName());
|
||||||
|
|
||||||
// Calculate price based on calculation basis
|
// Calculate price based on calculation basis
|
||||||
BigDecimal price = calculateServicePrice(service);
|
BigDecimal price = calculateServicePrice(service);
|
||||||
if (price != null) {
|
if (price != null) {
|
||||||
@@ -531,17 +547,18 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
} else {
|
} else {
|
||||||
serviceData.put("netAmount", "0,00");
|
serviceData.put("netAmount", "0,00");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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%");
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesData.add(serviceData);
|
servicesData.add(serviceData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize services data to JSON and store in variables
|
// Serialize services data to JSON and store in variables
|
||||||
if (!servicesData.isEmpty()) {
|
if (!servicesData.isEmpty()) {
|
||||||
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
com.fasterxml.jackson.databind.ObjectMapper mapper = new com.fasterxml.jackson.databind.ObjectMapper();
|
||||||
@@ -551,25 +568,25 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
variables.put("services.json", "[]");
|
variables.put("services.json", "[]");
|
||||||
variables.put("services.count", "0");
|
variables.put("services.count", "0");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate PDF using CustomerInvoiceService
|
// Generate PDF using CustomerInvoiceService
|
||||||
return customerInvoiceService.generatePdfFromCanvasTemplateWithData(templateData, variables, currentUser);
|
return customerInvoiceService.generatePdfFromCanvasTemplateWithData(templateData, variables, currentUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String safe(String value) {
|
private String safe(String value) {
|
||||||
return value != null ? value : "";
|
return value != null ? value : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPdfInDialog(byte[] pdfBytes, String title) {
|
private void showPdfInDialog(byte[] pdfBytes, String title) {
|
||||||
// Create a stream resource for the PDF
|
// Create a stream resource for the PDF
|
||||||
StreamResource resource = new StreamResource(title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf",
|
StreamResource resource = new StreamResource(title.replaceAll("[^a-zA-Z0-9\\-]", "_") + ".pdf",
|
||||||
() -> new java.io.ByteArrayInputStream(pdfBytes));
|
() -> new java.io.ByteArrayInputStream(pdfBytes));
|
||||||
resource.setContentType("application/pdf");
|
resource.setContentType("application/pdf");
|
||||||
resource.setCacheTime(0);
|
resource.setCacheTime(0);
|
||||||
|
|
||||||
// Store resource in session for access
|
// Store resource in session for access
|
||||||
VaadinSession.getCurrent().setAttribute("currentInvoicePdf", resource);
|
VaadinSession.getCurrent().setAttribute("currentInvoicePdf", resource);
|
||||||
|
|
||||||
// Create dialog
|
// Create dialog
|
||||||
Dialog pdfDialog = new Dialog();
|
Dialog pdfDialog = new Dialog();
|
||||||
pdfDialog.setHeaderTitle(title);
|
pdfDialog.setHeaderTitle(title);
|
||||||
@@ -580,7 +597,7 @@ public class CreateInvoiceView extends VerticalLayout implements HasUrlParameter
|
|||||||
IFrame pdfFrame = new IFrame();
|
IFrame pdfFrame = new IFrame();
|
||||||
pdfFrame.setWidth("100%");
|
pdfFrame.setWidth("100%");
|
||||||
pdfFrame.setHeight("100%");
|
pdfFrame.setHeight("100%");
|
||||||
|
|
||||||
// Use data URL for PDF display
|
// Use data URL for PDF display
|
||||||
String base64Pdf = java.util.Base64.getEncoder().encodeToString(pdfBytes);
|
String base64Pdf = java.util.Base64.getEncoder().encodeToString(pdfBytes);
|
||||||
String dataUrl = "data:application/pdf;base64," + base64Pdf;
|
String dataUrl = "data:application/pdf;base64," + base64Pdf;
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
this.invoiceTemplateService = invoiceTemplateService;
|
this.invoiceTemplateService = invoiceTemplateService;
|
||||||
this.currentUser = securityService.getCurrentDatabaseUser();
|
this.currentUser = securityService.getCurrentDatabaseUser();
|
||||||
this.serviceRepository = serviceRepository;
|
this.serviceRepository = serviceRepository;
|
||||||
|
|
||||||
// Store the original language before any changes
|
// Store the original language before any changes
|
||||||
this.originalLanguage = this.currentUser != null ? this.currentUser.getLanguage() : Language.DE;
|
this.originalLanguage = this.currentUser != null ? this.currentUser.getLanguage() : Language.DE;
|
||||||
setSizeFull();
|
setSizeFull();
|
||||||
@@ -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);
|
||||||
@@ -494,16 +503,16 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
languageCombo.setItemLabelGenerator(language -> {
|
languageCombo.setItemLabelGenerator(language -> {
|
||||||
// Flaggen-Emoji für jede Sprache
|
// Flaggen-Emoji für jede Sprache
|
||||||
String flag = switch (language) {
|
String flag = switch (language) {
|
||||||
case DE -> "🇩🇪 ";
|
case DE -> "🇩🇪 ";
|
||||||
case EN -> "🇬🇧 ";
|
case EN -> "🇬🇧 ";
|
||||||
case FR -> "🇫🇷 ";
|
case FR -> "🇫🇷 ";
|
||||||
case ES -> "🇪🇸 ";
|
case ES -> "🇪🇸 ";
|
||||||
case TR -> "🇹🇷 ";
|
case TR -> "🇹🇷 ";
|
||||||
case PL -> "🇵🇱 ";
|
case PL -> "🇵🇱 ";
|
||||||
case RU -> "🇷🇺 ";
|
case RU -> "🇷🇺 ";
|
||||||
case EE -> "🇪🇪 ";
|
case EE -> "🇪🇪 ";
|
||||||
case LV -> "🇱🇻 ";
|
case LV -> "🇱🇻 ";
|
||||||
case LT -> "🇱🇹 ";
|
case LT -> "🇱🇹 ";
|
||||||
};
|
};
|
||||||
return flag + language.getDisplayName();
|
return flag + language.getDisplayName();
|
||||||
});
|
});
|
||||||
@@ -513,7 +522,7 @@ public class EditProfileView extends HorizontalLayout implements HasDynamicTitle
|
|||||||
languageCombo.addValueChangeListener(e -> {
|
languageCombo.addValueChangeListener(e -> {
|
||||||
// Language will be saved when the user clicks save button
|
// Language will be saved when the user clicks save button
|
||||||
currentUser.setLanguage(e.getValue());
|
currentUser.setLanguage(e.getValue());
|
||||||
|
|
||||||
// Also set the language cookie so it's available before login
|
// Also set the language cookie so it's available before login
|
||||||
setLanguageCookie(e.getValue());
|
setLanguageCookie(e.getValue());
|
||||||
});
|
});
|
||||||
@@ -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,31 +1885,30 @@ 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) {
|
||||||
case DE -> "DE";
|
case DE -> "DE";
|
||||||
case EN -> "EN";
|
case EN -> "EN";
|
||||||
case FR -> "FR";
|
case FR -> "FR";
|
||||||
case ES -> "ES";
|
case ES -> "ES";
|
||||||
case TR -> "TR";
|
case TR -> "TR";
|
||||||
case PL -> "PL";
|
case PL -> "PL";
|
||||||
case RU -> "RU";
|
case RU -> "RU";
|
||||||
case EE -> "EE";
|
case EE -> "EE";
|
||||||
case LV -> "LV";
|
case LV -> "LV";
|
||||||
case LT -> "LT";
|
case LT -> "LT";
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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())
|
||||||
@@ -265,13 +268,13 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
|
|
||||||
VerticalLayout infoBox = borderedBox();
|
VerticalLayout infoBox = borderedBox();
|
||||||
infoBox.add(new H3(getTranslation("jobsummary.section.info")));
|
infoBox.add(new H3(getTranslation("jobsummary.section.info")));
|
||||||
|
|
||||||
// Preis basierend auf den hinterlegten Leistungen berechnen
|
// Preis basierend auf den hinterlegten Leistungen berechnen
|
||||||
PriceCalculationResult priceResult = calculatePriceFromServices(job);
|
PriceCalculationResult priceResult = calculatePriceFromServices(job);
|
||||||
infoBox.add(new Span(getTranslation("jobsummary.info.netto") + ": " + formatPrice(priceResult.netAmount())));
|
infoBox.add(new Span(getTranslation("jobsummary.info.netto") + ": " + formatPrice(priceResult.netAmount())));
|
||||||
infoBox.add(new Span(getTranslation("jobsummary.info.ust") + ": " + formatPrice(priceResult.vatAmount())));
|
infoBox.add(new Span(getTranslation("jobsummary.info.ust") + ": " + formatPrice(priceResult.vatAmount())));
|
||||||
infoBox.add(new Span(getTranslation("jobsummary.info.gesamt") + ": " + formatPrice(priceResult.totalAmount())));
|
infoBox.add(new Span(getTranslation("jobsummary.info.gesamt") + ": " + formatPrice(priceResult.totalAmount())));
|
||||||
|
|
||||||
if (job.getRemark() != null && !job.getRemark().isBlank()) {
|
if (job.getRemark() != null && !job.getRemark().isBlank()) {
|
||||||
infoBox.add(new Span(getTranslation("jobsummary.info.bemerkung") + ": " + job.getRemark()));
|
infoBox.add(new Span(getTranslation("jobsummary.info.bemerkung") + ": " + job.getRemark()));
|
||||||
}
|
}
|
||||||
@@ -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,9 +499,8 @@ 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 """
|
||||||
(function(){
|
(function(){
|
||||||
@@ -540,7 +545,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
}, function(res, status){
|
}, function(res, status){
|
||||||
if(status === 'OK'){
|
if(status === 'OK'){
|
||||||
infoEl.innerHTML = '';
|
infoEl.innerHTML = '';
|
||||||
|
|
||||||
// Gespeicherte Route-Daten anzeigen (wenn vorhanden)
|
// Gespeicherte Route-Daten anzeigen (wenn vorhanden)
|
||||||
if(hasSavedRouteData){
|
if(hasSavedRouteData){
|
||||||
var savedRouteDiv = document.createElement('div');
|
var savedRouteDiv = document.createElement('div');
|
||||||
@@ -552,7 +557,7 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
savedRouteDiv.textContent = '📍 Geplante Route: ' + savedDistance.toFixed(1) + ' km • Fahrtzeit: ' + savedDurationText;
|
savedRouteDiv.textContent = '📍 Geplante Route: ' + savedDistance.toFixed(1) + ' km • Fahrtzeit: ' + savedDurationText;
|
||||||
infoEl.appendChild(savedRouteDiv);
|
infoEl.appendChild(savedRouteDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bounds = new google.maps.LatLngBounds();
|
var bounds = new google.maps.LatLngBounds();
|
||||||
|
|
||||||
// Nur die erste (beste) Route anzeigen
|
// Nur die erste (beste) Route anzeigen
|
||||||
@@ -1199,65 +1204,68 @@ public class JobSummaryView extends Main implements HasUrlParameter<String>, Has
|
|||||||
private PriceCalculationResult calculatePriceFromServices(Job job) {
|
private PriceCalculationResult calculatePriceFromServices(Job job) {
|
||||||
BigDecimal netTotal = BigDecimal.ZERO;
|
BigDecimal netTotal = BigDecimal.ZERO;
|
||||||
BigDecimal vatTotal = BigDecimal.ZERO;
|
BigDecimal vatTotal = BigDecimal.ZERO;
|
||||||
|
|
||||||
List<String> serviceIds = job.getServiceIds();
|
List<String> serviceIds = job.getServiceIds();
|
||||||
if (serviceIds == null || serviceIds.isEmpty()) {
|
if (serviceIds == null || serviceIds.isEmpty()) {
|
||||||
// Fallback auf gespeicherten Preis
|
// Fallback auf gespeicherten Preis
|
||||||
BigDecimal price = job.getPrice() != null ? job.getPrice() : BigDecimal.ZERO;
|
BigDecimal price = job.getPrice() != null ? job.getPrice() : BigDecimal.ZERO;
|
||||||
return new PriceCalculationResult(price, BigDecimal.ZERO, price);
|
return new PriceCalculationResult(price, BigDecimal.ZERO, price);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Routendaten für die Berechnung
|
// Routendaten für die Berechnung
|
||||||
Double routeDistance = job.getRouteDistanceKm();
|
Double routeDistance = job.getRouteDistanceKm();
|
||||||
Integer durationSeconds = job.getRouteDurationSeconds();
|
Integer durationSeconds = job.getRouteDurationSeconds();
|
||||||
|
|
||||||
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");
|
||||||
|
|
||||||
netTotal = netTotal.add(price);
|
netTotal = netTotal.add(price);
|
||||||
vatTotal = vatTotal.add(price.multiply(vatRate));
|
vatTotal = vatTotal.add(price.multiply(vatRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal totalAmount = netTotal.add(vatTotal);
|
BigDecimal totalAmount = netTotal.add(vatTotal);
|
||||||
return new PriceCalculationResult(netTotal, vatTotal, totalAmount);
|
return new PriceCalculationResult(netTotal, vatTotal, totalAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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) {
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (service.getCalculationBasis()) {
|
switch (service.getCalculationBasis()) {
|
||||||
case FLAT_RATE:
|
case FLAT_RATE:
|
||||||
return service.getPrice() != null ? service.getPrice() : BigDecimal.ZERO;
|
return service.getPrice() != null ? service.getPrice() : BigDecimal.ZERO;
|
||||||
|
|
||||||
case DISTANCE:
|
case DISTANCE:
|
||||||
if (service.getPricePerKilometer() != null && routeDistance != null && routeDistance > 0) {
|
if (service.getPricePerKilometer() != null && routeDistance != null && routeDistance > 0) {
|
||||||
return service.getPricePerKilometer().multiply(BigDecimal.valueOf(routeDistance));
|
return service.getPricePerKilometer().multiply(BigDecimal.valueOf(routeDistance));
|
||||||
}
|
}
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
|
|
||||||
case TIME:
|
case TIME:
|
||||||
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;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return BigDecimal.ZERO;
|
return BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Record für die Preisberechnungsergebnisse.
|
* Record für die Preisberechnungsergebnisse.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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,10 +317,9 @@ 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
|
||||||
for (Job job : jobs) {
|
for (Job job : jobs) {
|
||||||
|
|||||||
@@ -107,79 +107,79 @@ 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);
|
||||||
|
|
||||||
Button languageBtn = new Button(flag + " " + currentLang);
|
Button languageBtn = new Button(flag + " " + currentLang);
|
||||||
languageBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
languageBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);
|
||||||
|
|
||||||
ContextMenu languageMenu = new ContextMenu();
|
ContextMenu languageMenu = new ContextMenu();
|
||||||
languageMenu.setOpenOnClick(true);
|
languageMenu.setOpenOnClick(true);
|
||||||
languageMenu.setTarget(languageBtn);
|
languageMenu.setTarget(languageBtn);
|
||||||
|
|
||||||
// Alle Sprachen hinzufügen
|
// Alle Sprachen hinzufügen
|
||||||
for (Language lang : Language.values()) {
|
for (Language lang : Language.values()) {
|
||||||
String langFlag = getFlagForLanguage(lang.name());
|
String langFlag = getFlagForLanguage(lang.name());
|
||||||
String langText = langFlag + " " + lang.getDisplayName();
|
String langText = langFlag + " " + lang.getDisplayName();
|
||||||
|
|
||||||
languageMenu.addItem(langText, event -> {
|
languageMenu.addItem(langText, event -> {
|
||||||
setLanguageCookie(lang.name());
|
setLanguageCookie(lang.name());
|
||||||
// Seite neu laden um die neue Sprache anzuwenden
|
// Seite neu laden um die neue Sprache anzuwenden
|
||||||
UI.getCurrent().getPage().reload();
|
UI.getCurrent().getPage().reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return languageBtn;
|
return languageBtn;
|
||||||
}
|
}
|
||||||
|
|
||||||
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
String language = locale.getLanguage();
|
String language = locale.getLanguage();
|
||||||
return switch (language) {
|
return switch (language) {
|
||||||
case "en" -> "EN";
|
case "en" -> "EN";
|
||||||
case "fr" -> "FR";
|
case "fr" -> "FR";
|
||||||
case "es" -> "ES";
|
case "es" -> "ES";
|
||||||
case "tr" -> "TR";
|
case "tr" -> "TR";
|
||||||
case "pl" -> "PL";
|
case "pl" -> "PL";
|
||||||
case "ru" -> "RU";
|
case "ru" -> "RU";
|
||||||
case "et" -> "EE"; // Estnisch
|
case "et" -> "EE"; // Estnisch
|
||||||
case "lv" -> "LV"; // Lettisch
|
case "lv" -> "LV"; // Lettisch
|
||||||
case "lt" -> "LT"; // Litauisch
|
case "lt" -> "LT"; // Litauisch
|
||||||
default -> "DE"; // German as default
|
default -> "DE"; // German as default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getFlagForLanguage(String languageCode) {
|
private String getFlagForLanguage(String languageCode) {
|
||||||
if (languageCode == null) {
|
if (languageCode == null) {
|
||||||
return "🇩🇪";
|
return "🇩🇪";
|
||||||
}
|
}
|
||||||
return switch (languageCode.toUpperCase()) {
|
return switch (languageCode.toUpperCase()) {
|
||||||
case "DE" -> "🇩🇪";
|
case "DE" -> "🇩🇪";
|
||||||
case "EN" -> "🇬🇧";
|
case "EN" -> "🇬🇧";
|
||||||
case "FR" -> "🇫🇷";
|
case "FR" -> "🇫🇷";
|
||||||
case "ES" -> "🇪🇸";
|
case "ES" -> "🇪🇸";
|
||||||
case "TR" -> "🇹🇷";
|
case "TR" -> "🇹🇷";
|
||||||
case "PL" -> "🇵🇱";
|
case "PL" -> "🇵🇱";
|
||||||
case "RU" -> "🇷🇺";
|
case "RU" -> "🇷🇺";
|
||||||
case "EE" -> "🇪🇪";
|
case "EE" -> "🇪🇪";
|
||||||
case "LV" -> "🇱🇻";
|
case "LV" -> "🇱🇻";
|
||||||
case "LT" -> "🇱🇹";
|
case "LT" -> "🇱🇹";
|
||||||
default -> "🇩🇪";
|
default -> "🇩🇪";
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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,8 +307,9 @@ 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(
|
||||||
getTranslation("start.feature.setup.desc")),
|
createFeatureCard(VaadinIcon.COG, getTranslation("start.feature.setup.title"),
|
||||||
|
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")),
|
||||||
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, getTranslation("start.feature.jobs.title"),
|
createFeatureCard(VaadinIcon.CLIPBOARD_TEXT, getTranslation("start.feature.jobs.title"),
|
||||||
@@ -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,9 +233,8 @@ 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");
|
||||||
|
|
||||||
|
|||||||
@@ -457,99 +457,110 @@ public class CustomerInvoiceService {
|
|||||||
*/
|
*/
|
||||||
private String generateServicesTableHtml(double widthMm) {
|
private String generateServicesTableHtml(double widthMm) {
|
||||||
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;
|
||||||
double vatTotal = 124.45;
|
double vatTotal = 124.45;
|
||||||
double grossTotal = 779.45;
|
double grossTotal = 779.45;
|
||||||
|
|
||||||
// Wrapper div
|
// Wrapper div
|
||||||
html.append("<div style='width:100%;box-sizing:border-box;'>");
|
html.append("<div style='width:100%;box-sizing:border-box;'>");
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
||||||
|
|
||||||
// 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>");
|
||||||
}
|
}
|
||||||
|
|
||||||
html.append("</table>");
|
html.append("</table>");
|
||||||
|
|
||||||
// Summary section
|
// Summary section
|
||||||
html.append("<div style='margin-top:8px;width:100%;'>");
|
html.append("<div style='margin-top:8px;width:100%;'>");
|
||||||
|
|
||||||
// Summary table for alignment with proper column separation
|
// Summary table for alignment with proper column separation
|
||||||
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
||||||
|
|
||||||
// Nettosumme - label in col 2, value in col 3
|
// Nettosumme - 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;'>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>");
|
||||||
html.append("</div>");
|
html.append("</div>");
|
||||||
html.append("</div>");
|
html.append("</div>");
|
||||||
|
|
||||||
return html.toString();
|
return html.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
||||||
|
|
||||||
System.out.println("DEBUG: generatePdfFromCanvasTemplateWithData called");
|
System.out.println("DEBUG: generatePdfFromCanvasTemplateWithData called");
|
||||||
System.out.println("DEBUG: templateData length: " + (jsonTemplateData != null ? jsonTemplateData.length() : 0));
|
System.out.println("DEBUG: templateData length: " + (jsonTemplateData != null ? jsonTemplateData.length() : 0));
|
||||||
|
|
||||||
// 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);
|
||||||
|
|
||||||
System.out.println("DEBUG: rootNode has 'elements': " + rootNode.has("elements"));
|
System.out.println("DEBUG: rootNode has 'elements': " + rootNode.has("elements"));
|
||||||
|
|
||||||
com.fasterxml.jackson.databind.JsonNode elements = rootNode.get("elements");
|
com.fasterxml.jackson.databind.JsonNode elements = rootNode.get("elements");
|
||||||
|
|
||||||
System.out.println("DEBUG: elements isArray: " + (elements != null ? elements.isArray() : false));
|
System.out.println("DEBUG: elements isArray: " + (elements != null ? elements.isArray() : false));
|
||||||
if (elements != null && !elements.isMissingNode()) {
|
if (elements != null && !elements.isMissingNode()) {
|
||||||
System.out.println("DEBUG: elements nodeType: " + elements.getNodeType());
|
System.out.println("DEBUG: elements nodeType: " + elements.getNodeType());
|
||||||
@@ -576,10 +587,10 @@ public class CustomerInvoiceService {
|
|||||||
// Handle elements - could be an array or an object with element IDs as keys
|
// Handle elements - could be an array or an object with element IDs as keys
|
||||||
if (elements != null && !elements.isMissingNode() && !elements.isNull()) {
|
if (elements != null && !elements.isMissingNode() && !elements.isNull()) {
|
||||||
int elementCount = 0;
|
int elementCount = 0;
|
||||||
|
|
||||||
// Convert elements to a list we can iterate over
|
// Convert elements to a list we can iterate over
|
||||||
java.util.List<com.fasterxml.jackson.databind.JsonNode> elementList = new java.util.ArrayList<>();
|
java.util.List<com.fasterxml.jackson.databind.JsonNode> elementList = new java.util.ArrayList<>();
|
||||||
|
|
||||||
if (elements.isArray()) {
|
if (elements.isArray()) {
|
||||||
// Already an array - iterate directly
|
// Already an array - iterate directly
|
||||||
elements.forEach(elementList::add);
|
elements.forEach(elementList::add);
|
||||||
@@ -590,16 +601,18 @@ public class CustomerInvoiceService {
|
|||||||
} else {
|
} else {
|
||||||
System.out.println("DEBUG: Unexpected elements type: " + elements.getNodeType());
|
System.out.println("DEBUG: Unexpected elements type: " + elements.getNodeType());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (com.fasterxml.jackson.databind.JsonNode element : elementList) {
|
for (com.fasterxml.jackson.databind.JsonNode element : elementList) {
|
||||||
elementCount++;
|
elementCount++;
|
||||||
String type = element.has("type") ? element.get("type").asText("text") : "text";
|
String type = element.has("type") ? element.get("type").asText("text") : "text";
|
||||||
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);
|
|
||||||
|
|
||||||
// Use percentage values if available, otherwise fall back to legacy pixel values
|
System.out.println(
|
||||||
|
"DEBUG: Processing element " + elementCount + ", type=" + type + ", variable=" + variable);
|
||||||
|
|
||||||
|
// 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);
|
||||||
@@ -664,14 +677,15 @@ public class CustomerInvoiceService {
|
|||||||
if (element.has("text") && !element.get("text").asText("").isEmpty()) {
|
if (element.has("text") && !element.get("text").asText("").isEmpty()) {
|
||||||
text = element.get("text").asText("");
|
text = element.get("text").asText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace variables with actual values
|
// Replace variables with actual values
|
||||||
if (variable != null && variables.containsKey(variable)) {
|
if (variable != null && variables.containsKey(variable)) {
|
||||||
text = variables.get(variable);
|
text = variables.get(variable);
|
||||||
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);
|
||||||
@@ -718,7 +732,7 @@ public class CustomerInvoiceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
htmlBuilder.append("</body></html>");
|
htmlBuilder.append("</body></html>");
|
||||||
|
|
||||||
String htmlOutput = htmlBuilder.toString();
|
String htmlOutput = htmlBuilder.toString();
|
||||||
System.out.println("DEBUG: Generated HTML length: " + htmlOutput.length());
|
System.out.println("DEBUG: Generated HTML length: " + htmlOutput.length());
|
||||||
System.out.println("DEBUG: HTML preview: " + htmlOutput.substring(0, Math.min(500, htmlOutput.length())));
|
System.out.println("DEBUG: HTML preview: " + htmlOutput.substring(0, Math.min(500, htmlOutput.length())));
|
||||||
@@ -731,45 +745,49 @@ public class CustomerInvoiceService {
|
|||||||
*/
|
*/
|
||||||
private String generateServicesTableHtmlWithData(double widthMm, java.util.Map<String, String> variables) {
|
private String generateServicesTableHtmlWithData(double widthMm, java.util.Map<String, String> variables) {
|
||||||
StringBuilder html = new StringBuilder();
|
StringBuilder html = new StringBuilder();
|
||||||
|
|
||||||
// Get invoice data from variables
|
// Get invoice data from variables
|
||||||
String netTotal = variables.getOrDefault("invoice.net_total", "0,00 €");
|
String netTotal = variables.getOrDefault("invoice.net_total", "0,00 €");
|
||||||
String vatTotal = variables.getOrDefault("invoice.vat_total", "0,00 €");
|
String vatTotal = variables.getOrDefault("invoice.vat_total", "0,00 €");
|
||||||
String grossTotal = variables.getOrDefault("invoice.gross_total", "0,00 €");
|
String grossTotal = variables.getOrDefault("invoice.gross_total", "0,00 €");
|
||||||
String vatRate = variables.getOrDefault("invoice.vat_rate", "19%");
|
String vatRate = variables.getOrDefault("invoice.vat_rate", "19%");
|
||||||
|
|
||||||
// Parse services JSON from variables
|
// Parse services JSON from variables
|
||||||
java.util.List<java.util.Map<String, String>> servicesData = new java.util.ArrayList<>();
|
java.util.List<java.util.Map<String, String>> servicesData = new java.util.ArrayList<>();
|
||||||
String servicesJson = variables.get("services.json");
|
String servicesJson = variables.get("services.json");
|
||||||
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper div
|
// Wrapper div
|
||||||
html.append("<div style='width:100%;box-sizing:border-box;'>");
|
html.append("<div style='width:100%;box-sizing:border-box;'>");
|
||||||
|
|
||||||
// Table
|
// Table
|
||||||
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
||||||
|
|
||||||
// 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++) {
|
||||||
@@ -777,53 +795,63 @@ public class CustomerInvoiceService {
|
|||||||
String name = service.getOrDefault("name", "Unbekannte Leistung");
|
String name = service.getOrDefault("name", "Unbekannte Leistung");
|
||||||
String serviceVatRate = service.getOrDefault("vatRate", vatRate);
|
String serviceVatRate = service.getOrDefault("vatRate", vatRate);
|
||||||
String netAmount = service.getOrDefault("netAmount", "0,00");
|
String netAmount = service.getOrDefault("netAmount", "0,00");
|
||||||
|
|
||||||
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>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.append("</table>");
|
html.append("</table>");
|
||||||
|
|
||||||
// Summary section with actual totals from variables
|
// Summary section with actual totals from variables
|
||||||
html.append("<div style='margin-top:8px;width:100%;'>");
|
html.append("<div style='margin-top:8px;width:100%;'>");
|
||||||
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
html.append("<table style='width:100%;border-collapse:collapse;font-size:inherit;table-layout:fixed;'>");
|
||||||
|
|
||||||
// Extract numeric value from VAT rate for display
|
// Extract numeric value from VAT rate for display
|
||||||
String vatPercent = vatRate.replace(" %", "").replace("%", "");
|
String vatPercent = vatRate.replace(" %", "").replace("%", "");
|
||||||
|
|
||||||
// Nettosumme
|
// Nettosumme
|
||||||
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>");
|
||||||
html.append("</div>");
|
html.append("</div>");
|
||||||
html.append("</div>");
|
html.append("</div>");
|
||||||
|
|
||||||
return html.toString();
|
return html.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escape HTML special characters to prevent XSS.
|
* Escape HTML special characters to prevent XSS.
|
||||||
*/
|
*/
|
||||||
@@ -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("'", "'");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,20 +33,20 @@ public class LanguageService {
|
|||||||
try {
|
try {
|
||||||
Locale locale;
|
Locale locale;
|
||||||
switch (language) {
|
switch (language) {
|
||||||
case DE:
|
case DE:
|
||||||
locale = Locale.GERMAN;
|
locale = Locale.GERMAN;
|
||||||
break;
|
break;
|
||||||
case EN:
|
case EN:
|
||||||
locale = Locale.ENGLISH;
|
locale = Locale.ENGLISH;
|
||||||
break;
|
break;
|
||||||
case FR:
|
case FR:
|
||||||
locale = Locale.FRENCH;
|
locale = Locale.FRENCH;
|
||||||
break;
|
break;
|
||||||
case ES:
|
case ES:
|
||||||
locale = Locale.of("es", "ES");
|
locale = Locale.of("es", "ES");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
locale = Locale.GERMAN;
|
locale = Locale.GERMAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
|
ResourceBundle bundle = ResourceBundle.getBundle("messages", locale);
|
||||||
@@ -74,11 +74,11 @@ public class LanguageService {
|
|||||||
|
|
||||||
private Locale getLocaleFromLanguage(Language language) {
|
private Locale getLocaleFromLanguage(Language language) {
|
||||||
return switch (language) {
|
return switch (language) {
|
||||||
case DE -> Locale.GERMAN;
|
case DE -> Locale.GERMAN;
|
||||||
case EN -> Locale.ENGLISH;
|
case EN -> Locale.ENGLISH;
|
||||||
case FR -> Locale.FRENCH;
|
case FR -> Locale.FRENCH;
|
||||||
case ES -> Locale.of("es", "ES");
|
case ES -> Locale.of("es", "ES");
|
||||||
default -> Locale.GERMAN;
|
default -> Locale.GERMAN;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user