Runnable port with keycloak authentication

master
Markus Kreth 4 years ago
parent 013df232ec
commit d595ab7534
  1. 455
      pom.xml
  2. 19
      src/main/java/de/kreth/invoice/Application.java
  3. 324
      src/main/java/de/kreth/invoice/Localization_Properties.java
  4. 57
      src/main/java/de/kreth/invoice/business/AbstractBusiness.java
  5. 34
      src/main/java/de/kreth/invoice/business/ArticleBusiness.java
  6. 13
      src/main/java/de/kreth/invoice/business/Business.java
  7. 84
      src/main/java/de/kreth/invoice/business/InvoiceBusiness.java
  8. 33
      src/main/java/de/kreth/invoice/business/InvoiceItemBusiness.java
  9. 177
      src/main/java/de/kreth/invoice/components/InvoiceItemGrid.java
  10. 53
      src/main/java/de/kreth/invoice/components/InvoiceItemOverviewComponent.java
  11. 18
      src/main/java/de/kreth/invoice/components/InvoiceOverviewComponent.java
  12. 112
      src/main/java/de/kreth/invoice/data/Adress.java
  13. 63
      src/main/java/de/kreth/invoice/data/Article.java
  14. 83
      src/main/java/de/kreth/invoice/data/BankingConnection.java
  15. 29
      src/main/java/de/kreth/invoice/data/BaseEntity.java
  16. 23
      src/main/java/de/kreth/invoice/data/Invoice.java
  17. 13
      src/main/java/de/kreth/invoice/data/InvoiceItem.java
  18. 29
      src/main/java/de/kreth/invoice/data/User.java
  19. 124
      src/main/java/de/kreth/invoice/data/UserAdress.java
  20. 113
      src/main/java/de/kreth/invoice/data/UserBank.java
  21. 4
      src/main/java/de/kreth/invoice/persistence/ArticleRepository.java
  22. 6
      src/main/java/de/kreth/invoice/persistence/InvoiceItemRepository.java
  23. 12
      src/main/java/de/kreth/invoice/persistence/InvoiceRepository.java
  24. 2
      src/main/java/de/kreth/invoice/persistence/UserAdressRepository.java
  25. 2
      src/main/java/de/kreth/invoice/persistence/UserBankRepository.java
  26. 19
      src/main/java/de/kreth/invoice/report/InvoiceReportSource.java
  27. 18
      src/main/java/de/kreth/invoice/security/UserManager.java
  28. 7
      src/main/java/de/kreth/invoice/views/DataFormat.java
  29. 34
      src/main/java/de/kreth/invoice/views/ErrorNotification.java
  30. 78
      src/main/java/de/kreth/invoice/views/FooterComponent.java
  31. 69
      src/main/java/de/kreth/invoice/views/PriceConverter.java
  32. 167
      src/main/java/de/kreth/invoice/views/View.java
  33. 269
      src/main/java/de/kreth/invoice/views/article/ArticleDialog.java
  34. 30
      src/main/java/de/kreth/invoice/views/article/ArticleGrid.java
  35. 255
      src/main/java/de/kreth/invoice/views/invoice/InvoiceDialog.java
  36. 16
      src/main/java/de/kreth/invoice/views/invoice/InvoiceGrid.java
  37. 2
      src/main/java/de/kreth/invoice/views/invoiceitem/InvoiceItemDialog.java
  38. 179
      src/main/java/de/kreth/invoice/views/invoiceitem/InvoiceItemGrid.java
  39. 162
      src/main/java/de/kreth/invoice/views/invoiceitem/InvoiceItemOverviewComponent.java
  40. 120
      src/main/java/de/kreth/invoice/views/invoiceitem/InvoiceOverviewComponent.java
  41. 73
      src/main/java/de/kreth/invoice/views/user/UserDetailsDialog.java
  42. 79
      src/main/resources/localization.properties
  43. 18
      src/main/resources/schema.sql
  44. 83
      src/test/java/de/kreth/invoice/business/InvoiceBusinessTest.java

@ -1,77 +1,77 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Project from https://start.vaadin.com/project/21111318-49ac-485b-92f4-892e176b446f -->
<groupId>de.kreth.invoice</groupId>
<artifactId>trainerinvoice</artifactId>
<name>Übungsleiterabrechnungen</name>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<modelVersion>4.0.0</modelVersion>
<!-- Project from https://start.vaadin.com/project/21111318-49ac-485b-92f4-892e176b446f -->
<groupId>de.kreth.invoice</groupId>
<artifactId>trainerinvoice</artifactId>
<name>Übungsleiterabrechnungen</name>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<java.version>11</java.version>
<vaadin.version>23.0.8</vaadin.version>
<properties>
<java.version>11</java.version>
<vaadin.version>23.0.8</vaadin.version>
<org.keycloak.version>11.0.1</org.keycloak.version>
</properties>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<repositories>
<!-- The order of definitions matters. Explicitly defining central here to make sure it has the highest priority. -->
<repositories>
<!-- The order of definitions matters. Explicitly defining central here to make sure it has the highest priority. -->
<!-- Main Maven repository -->
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>vaadin-prereleases</id>
<url>
<!-- Main Maven repository -->
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>vaadin-prereleases</id>
<url>
https://maven.vaadin.com/vaadin-prereleases/
</url>
</repository>
<!-- Repository used by many Vaadin add-ons -->
<repository>
<id>Vaadin Directory</id>
<url>https://maven.vaadin.com/vaadin-addons</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</repository>
<!-- Repository used by many Vaadin add-ons -->
<repository>
<id>Vaadin Directory</id>
<url>https://maven.vaadin.com/vaadin-addons</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<!-- The order of definitions matters. Explicitly defining central here to make sure it has the highest priority. -->
<pluginRepository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>vaadin-prereleases</id>
<url>
<pluginRepositories>
<!-- The order of definitions matters. Explicitly defining central here to make sure it has the highest priority. -->
<pluginRepository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>vaadin-prereleases</id>
<url>
https://maven.vaadin.com/vaadin-prereleases/
</url>
</pluginRepository>
</pluginRepositories>
</pluginRepository>
</pluginRepositories>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.keycloak.bom</groupId>
@ -80,40 +80,35 @@
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<!-- Replace artifactId with vaadin-core to use only free components -->
<artifactId>vaadin</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<!-- Replace artifactId with vaadin-core to use only free components -->
<artifactId>vaadin</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>exampledata</artifactId>
<version>4.1.4</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -128,163 +123,163 @@
<artifactId>keycloak-spring-security-adapter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-testbench</artifactId>
<scope>test</scope>
</dependency>
<!-- Include JUnit 4 support for TestBench and others -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.19.1</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-testbench</artifactId>
<scope>test</scope>
</dependency>
<!-- Include JUnit 4 support for TestBench and others -->
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.github.bonigarcia</groupId>
<artifactId>webdrivermanager</artifactId>
<version>5.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.jasperreports</groupId>
<artifactId>jasperreports</artifactId>
<version>6.19.1</version>
</dependency>
<!-- <dependency>
<!-- <dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
-->
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>2.1.7</version>
</dependency>
</dependencies>
</dependencies>
<build>
<defaultGoal>spring-boot:run</defaultGoal>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- Clean build and startup time for Vaadin apps sometimes may exceed
<build>
<defaultGoal>spring-boot:run</defaultGoal>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- Clean build and startup time for Vaadin apps sometimes may exceed
the default Spring Boot's 30sec timeout. -->
<configuration>
<wait>500</wait>
<maxAttempts>240</maxAttempts>
</configuration>
</plugin>
<configuration>
<wait>500</wait>
<maxAttempts>240</maxAttempts>
</configuration>
</plugin>
<!--
<!--
Take care of synchronizing java dependencies and imports in
package.json and main.js files.
It also creates webpack.config.js if not exists yet.
-->
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-frontend</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<goals>
<goal>prepare-frontend</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<!-- Production mode is activated using -Pproduction -->
<id>production</id>
<build>
<plugins>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<goals>
<goal>build-frontend</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<productionMode>true</productionMode>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profiles>
<profile>
<!-- Production mode is activated using -Pproduction -->
<id>production</id>
<build>
<plugins>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<goals>
<goal>build-frontend</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<productionMode>true</productionMode>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>it</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>start-spring-boot</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-spring-boot</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<profile>
<id>it</id>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>start-spring-boot</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-spring-boot</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Runs the integration tests (*IT) after the server is started -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<trimStackTrace>false</trimStackTrace>
<enableAssertions>true</enableAssertions>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<!-- Runs the integration tests (*IT) after the server is started -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<trimStackTrace>false</trimStackTrace>
<enableAssertions>true</enableAssertions>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</profiles>
</project>

@ -1,8 +1,12 @@
package de.kreth.invoice;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.page.AppShellConfigurator;
@ -23,9 +27,24 @@ import com.vaadin.flow.theme.Theme;
public class Application extends SpringBootServletInitializer implements AppShellConfigurator {
private static final long serialVersionUID = 8632833774084603989L;
private static ResourceBundle bundle = null;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
public static String getString(Localization_Properties property) {
if (bundle == null) {
bundle = resourceBundle();
}
return property.getString(bundle::getString);
}
@Bean
static ResourceBundle resourceBundle() {
ResourceBundle bundle = PropertyResourceBundle.getBundle("localization");
return bundle;
}
}

@ -0,0 +1,324 @@
package de.kreth.invoice;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.function.UnaryOperator;
import javax.annotation.processing.Generated;
/**
* Property keys from localization.properties
*/
@Generated(date = "22.05.2022, 19:41:47", value = "de.kreth.property2java.Generator")
public enum Localization_Properties {
/**
* caption.invoiceitem.date = "Datum"
*/
CAPTION_INVOICEITEM_DATE ("caption.invoiceitem.date"),
/**
* caption.user.login = "Anmelden"
*/
CAPTION_USER_LOGIN ("caption.user.login"),
/**
* error.invoice.title.noitems = "Leere Abrechnung nicht erlaubt."
*/
ERROR_INVOICE_TITLE_NOITEMS ("error.invoice.title.noitems"),
/**
* caption.invoice.sum = "Summe"
*/
CAPTION_INVOICE_SUM ("caption.invoice.sum"),
/**
* caption.invoiceitem.participants = "Teilnehmer"
*/
CAPTION_INVOICEITEM_PARTICIPANTS ("caption.invoiceitem.participants"),
/**
* caption.invoiceitem.add = "Neuer Posten"
*/
CAPTION_INVOICEITEM_ADD ("caption.invoiceitem.add"),
/**
* caption.invoice.pattern = "Rechnung-{0}"
*/
CAPTION_INVOICE_PATTERN ("caption.invoice.pattern"),
/**
* caption.invoiceitem.start = "Beginn"
*/
CAPTION_INVOICEITEM_START ("caption.invoiceitem.start"),
/**
* caption.article.report = "Mit Trainer-Lizenz"
*/
CAPTION_ARTICLE_REPORT ("caption.article.report"),
/**
* caption.invoice.invoiceno = "Rechnungsnummer"
*/
CAPTION_INVOICE_INVOICENO ("caption.invoice.invoiceno"),
/**
* caption.invoiceitems = "Rechnungspositionen"
*/
CAPTION_INVOICEITEMS ("caption.invoiceitems"),
/**
* message.user.passwordmissmatch = "Passworter stimmen nicht überein!"
*/
MESSAGE_USER_PASSWORDMISSMATCH ("message.user.passwordmissmatch"),
/**
* label.delete = "Löschen"
*/
LABEL_DELETE ("label.delete"),
/**
* message.user.loginfailure = "Anmeldefehler! Falscher Name oder Passwort?"
*/
MESSAGE_USER_LOGINFAILURE ("message.user.loginfailure"),
/**
* caption.article.type.trainer = "Trainer"
*/
CAPTION_ARTICLE_TYPE_TRAINER ("caption.article.type.trainer"),
/**
* caption.invoiceitem.sumprice = "Betrag"
*/
CAPTION_INVOICEITEM_SUMPRICE ("caption.invoiceitem.sumprice"),
/**
* message.delete.text = "Soll {0} wirklich gelöscht werden?"
*/
MESSAGE_DELETE_TEXT ("message.delete.text"),
/**
* label.user.register = "Registrieren"
*/
LABEL_USER_REGISTER ("label.user.register"),
/**
* error.userdetails.bankname_empty = "Bankname darf nicht leer sein."
*/
ERROR_USERDETAILS_BANKNAME_EMPTY ("error.userdetails.bankname_empty"),
/**
* label.ok = "OK"
*/
LABEL_OK ("label.ok"),
/**
* label.open = "Öffnen"
*/
LABEL_OPEN ("label.open"),
/**
* label.discart = "Verwerfen"
*/
LABEL_DISCART ("label.discart"),
/**
* caption.article = "Artikel"
*/
CAPTION_ARTICLE ("caption.article"),
/**
* message.article.priceerror = "Bitte legen Sie den Preis fest."
*/
MESSAGE_ARTICLE_PRICEERROR ("message.article.priceerror"),
/**
* error.userdetails.iban_empty = "Iban darf nicht leer sein."
*/
ERROR_USERDETAILS_IBAN_EMPTY ("error.userdetails.iban_empty"),
/**
* message.user.create.success = "{0} erstellt!"
*/
MESSAGE_USER_CREATE_SUCCESS ("message.user.create.success"),
/**
* error.userdetails.prename_empty = "Vorname darf nicht leer sein."
*/
ERROR_USERDETAILS_PRENAME_EMPTY ("error.userdetails.prename_empty"),
/**
* error.invoice.text.noitems = "Bitte Posten für Rechnung auswählen."
*/
ERROR_INVOICE_TEXT_NOITEMS ("error.invoice.text.noitems"),
/**
* caption.invoiceitem.end = "Ende"
*/
CAPTION_INVOICEITEM_END ("caption.invoiceitem.end"),
/**
* caption.invoiceitem = ""
*/
CAPTION_INVOICEITEM ("caption.invoiceitem"),
/**
* caption.adress.zipcode = "Postleitzahl"
*/
CAPTION_ADRESS_ZIPCODE ("caption.adress.zipcode"),
/**
* caption.user.password = "Ihr Password:"
*/
CAPTION_USER_PASSWORD ("caption.user.password"),
/**
* caption.invoiceitem.name = "Rechnungsposition"
*/
CAPTION_INVOICEITEM_NAME ("caption.invoiceitem.name"),
/**
* message.delete.title = "Wirklich löschen?"
*/
MESSAGE_DELETE_TITLE ("message.delete.title"),
/**
* error.userdetails.zip_empty = "Postleitzahl darf nicht leer sein."
*/
ERROR_USERDETAILS_ZIP_EMPTY ("error.userdetails.zip_empty"),
/**
* message.invoiceitem.allfieldsmustbeset = "Start, Ende und Artikel müssen gesetzt sein!"
*/
MESSAGE_INVOICEITEM_ALLFIELDSMUSTBESET ("message.invoiceitem.allfieldsmustbeset"),
/**
* caption.invoices = "Rechnungen"
*/
CAPTION_INVOICES ("caption.invoices"),
/**
* caption.user.passwordconfirmation = "Password bestätigen:"
*/
CAPTION_USER_PASSWORDCONFIRMATION ("caption.user.passwordconfirmation"),
/**
* caption.adress.city = "Ort"
*/
CAPTION_ADRESS_CITY ("caption.adress.city"),
/**
* caption.invoice.printsignature = "Unterschrift drucken"
*/
CAPTION_INVOICE_PRINTSIGNATURE ("caption.invoice.printsignature"),
/**
* caption.article.title = "Titel"
*/
CAPTION_ARTICLE_TITLE ("caption.article.title"),
/**
* caption.article.price = "Stundenpreis"
*/
CAPTION_ARTICLE_PRICE ("caption.article.price"),
/**
* error.userdetails.adress_empty = "Adresse darf nicht leer sein."
*/
ERROR_USERDETAILS_ADRESS_EMPTY ("error.userdetails.adress_empty"),
/**
* caption.bank.iban = "IBAN"
*/
CAPTION_BANK_IBAN ("caption.bank.iban"),
/**
* caption.invoice.create = "Rechnung erstellen"
*/
CAPTION_INVOICE_CREATE ("caption.invoice.create"),
/**
* error.article.undefined = "Bitte Artikel anlegen."
*/
ERROR_ARTICLE_UNDEFINED ("error.article.undefined"),
/**
* caption.bank.bic = "BIC"
*/
CAPTION_BANK_BIC ("caption.bank.bic"),
/**
* caption.article.type.assistant = "Übungsleiter"
*/
CAPTION_ARTICLE_TYPE_ASSISTANT ("caption.article.type.assistant"),
/**
* error.userdetails.city_empty = "Ort darf nicht leer sein."
*/
ERROR_USERDETAILS_CITY_EMPTY ("error.userdetails.city_empty"),
/**
* label.cancel = "Abbrechen"
*/
LABEL_CANCEL ("label.cancel"),
/**
* message.user.create.failure = "Fehler beim Erstellen von Benutzer {0}! Ändern Sie den Benutzernamen oder fragen Sie nach dem Passwort. Detail: {1}"
*/
MESSAGE_USER_CREATE_FAILURE ("message.user.create.failure"),
/**
* caption.article.description = "Beschreibung"
*/
CAPTION_ARTICLE_DESCRIPTION ("caption.article.description"),
/**
* caption.user.loginname = "Anmeldename:"
*/
CAPTION_USER_LOGINNAME ("caption.user.loginname"),
/**
* message.article.error.invoiceexists = "Kann nicht geändert werden, da bereits Rechnungen bestehen. Bitte neuen Artikel anlegen."
*/
MESSAGE_ARTICLE_ERROR_INVOICEEXISTS ("message.article.error.invoiceexists"),
/**
* error.userdetails.surname_empty = "Nachname darf nicht leer sein."
*/
ERROR_USERDETAILS_SURNAME_EMPTY ("error.userdetails.surname_empty"),
/**
* label.close = "Schließen"
*/
LABEL_CLOSE ("label.close"),
/**
* caption.adress.street2 = "Adresse"
*/
CAPTION_ADRESS_STREET2 ("caption.adress.street2"),
/**
* caption.adress.street1 = "Adresse"
*/
CAPTION_ADRESS_STREET1 ("caption.adress.street1"),
/**
* caption.user.surname = "Nachname:"
*/
CAPTION_USER_SURNAME ("caption.user.surname"),
/**
* label.loggedin = "Angemeldet:"
*/
LABEL_LOGGEDIN ("label.loggedin"),
/**
* label.logout = "Abmelden"
*/
LABEL_LOGOUT ("label.logout"),
/**
* caption.user.prename = "Vorname:"
*/
CAPTION_USER_PRENAME ("caption.user.prename"),
/**
* caption.bank.name = "Bankname"
*/
CAPTION_BANK_NAME ("caption.bank.name"),
/**
* label.addarticle = "Neuer Artikel"
*/
LABEL_ADDARTICLE ("label.addarticle"),
/**
* label.preview = "Vorschau"
*/
LABEL_PREVIEW ("label.preview"),
/**
* message.invoiceitem.startbeforeend = "Ende darf nicht vor Start liegen."
*/
MESSAGE_INVOICEITEM_STARTBEFOREEND ("message.invoiceitem.startbeforeend"),
/**
* caption.articles = "Artikel"
*/
CAPTION_ARTICLES ("caption.articles"),
/**
* caption.invoice.invoicedate = "Rechnungsdatum"
*/
CAPTION_INVOICE_INVOICEDATE ("caption.invoice.invoicedate"),
/**
* caption.user.details = "Benutzer Details"
*/
CAPTION_USER_DETAILS ("caption.user.details"),
/**
* error.userdetails.username_empty = "Anmeldename darf nicht leer sein."
*/
ERROR_USERDETAILS_USERNAME_EMPTY ("error.userdetails.username_empty"),
/**
* label.store = "Speichern"
*/
LABEL_STORE ("label.store");
private final String value;
private Localization_Properties (String value) {
this.value = value;
}
/**
* Represented Key in property File.
* @return key
*/
public String getValue() {
return value;
}
/**
* Resolves the value for this key from the parameter function.
* <p>
* e.g. <code>Localization_Properties.getString(resBundle::getString)</code>
* @param resourceFunction {@link Properties#getProperty(String)} or {@link ResourceBundle#getString(String)}
* @return
*/
public String getString(UnaryOperator<String> resourceFunction) {
return resourceFunction.apply(value);
}
}

@ -0,0 +1,57 @@
package de.kreth.invoice.business;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.BaseEntity;
public abstract class AbstractBusiness<T extends BaseEntity> implements Business<T> {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Class<T> itemClass;
private CrudRepository<T, Long> repository;
public AbstractBusiness(CrudRepository<T, Long> repository, Class<T> itemClass) {
super();
this.repository = repository;
this.itemClass = itemClass;
}
@Override
public boolean save(T obj) {
repository.save(obj);
logger.debug("Stored {}", obj);
return true;
}
@Override
public boolean delete(T obj) {
repository.delete(obj);
logger.info("Deleted {}", obj);
return true;
}
@Override
public List<T> loadAll() {
List<T> list = new ArrayList<T>();
repository.findAll().forEach(list::add);
logger.trace("Loaded {} of {}", list.size(), itemClass.getSimpleName());
return list;
}
public List<T> loadAll(Predicate<T> predicate) {
List<T> loadAll = loadAll();
List<T> result = loadAll.stream().filter(predicate).collect(Collectors.toList());
logger.trace("Filtered {} of {} total {}", result.size(), loadAll.size(), itemClass.getSimpleName());
return result;
}
}

@ -0,0 +1,34 @@
package de.kreth.invoice.business;
import java.util.List;
import org.springframework.stereotype.Component;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.persistence.ArticleRepository;
import de.kreth.invoice.persistence.InvoiceItemRepository;
@Component
public class ArticleBusiness extends AbstractBusiness<Article> {
private final ArticleRepository articleRepository;
private final InvoiceItemRepository invoiceItemRepository;
public ArticleBusiness(ArticleRepository articleRepository, InvoiceItemRepository invoiceItemRepository) {
super(articleRepository, Article.class);
this.articleRepository = articleRepository;
this.invoiceItemRepository = invoiceItemRepository;
}
public boolean hasInvoiceItem(Article current) {
List<InvoiceItem> items = invoiceItemRepository.findByArticle(current);
int size = items.size();
return size > 0;
}
public List<Article> findByUserId(Long id) {
return articleRepository.findByUserId(id);
}
}

@ -0,0 +1,13 @@
package de.kreth.invoice.business;
import java.util.List;
public interface Business<T> {
boolean save(T obj);
boolean delete(T obj);
List<T> loadAll();
}

@ -0,0 +1,84 @@
package de.kreth.invoice.business;
import java.text.MessageFormat;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import de.kreth.invoice.data.Invoice;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.persistence.InvoiceItemRepository;
import de.kreth.invoice.persistence.InvoiceRepository;
@Component
public class InvoiceBusiness extends AbstractBusiness<Invoice> {
private final InvoiceRepository invoiceRepository;
private final InvoiceItemRepository itemRepository;
@Autowired
public InvoiceBusiness(InvoiceRepository invoiceRepository, InvoiceItemRepository itemRepository) {
super(invoiceRepository, Invoice.class);
this.invoiceRepository = invoiceRepository;
this.itemRepository = itemRepository;
}
@Override
public boolean save(Invoice obj) {
for (InvoiceItem i : obj.getItems()) {
i.setInvoice(obj);
}
boolean save = super.save(obj);
for (InvoiceItem i : obj.getItems()) {
itemRepository.save(i);
}
return save;
}
public String createNextInvoiceId(List<Invoice> invoices, String pattern) {
Optional<Invoice> latest = invoices.stream()
.filter(i -> filter(i.getInvoiceId(), pattern))
.max((o1, o2) -> {
return o1.getInvoiceId().compareTo(o2.getInvoiceId());
});
int lastInvoiceId = 0;
if (latest.isPresent()) {
String old = latest.get().getInvoiceId();
int start = pattern.indexOf("{0}");
String substring = old.substring(start);
if (substring.matches("[0-9]+")) {
lastInvoiceId = Integer.parseInt(substring);
} else {
lastInvoiceId++;
}
}
lastInvoiceId++;
String invoiceNo = MessageFormat.format(pattern, lastInvoiceId);
return invoiceNo;
}
boolean filter(String invoiceId, String pattern) {
int start = Math.min(pattern.indexOf("{0}"), invoiceId.length() - 1);
int end = start + 1;
while (end < invoiceId.length() && Character.isDigit(invoiceId.charAt(end))) {
end++;
}
String strippedPattern = pattern.substring(0, start) + pattern.substring(start + 3);
String strippedIId = invoiceId.substring(0, start) + invoiceId.substring(end);
return strippedIId.contentEquals(strippedPattern);
}
public List<Invoice> findByUserId(long userId) {
return invoiceRepository.findByUserId(userId);
}
}

@ -0,0 +1,33 @@
package de.kreth.invoice.business;
import java.util.Iterator;
import java.util.List;
import org.springframework.stereotype.Component;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.data.User;
import de.kreth.invoice.persistence.InvoiceItemRepository;
@Component
public class InvoiceItemBusiness extends AbstractBusiness<InvoiceItem> {
private final InvoiceItemRepository repository;
public InvoiceItemBusiness(InvoiceItemRepository invoiceItemRepository) {
super(invoiceItemRepository, InvoiceItem.class);
this.repository = invoiceItemRepository;
}
public List<InvoiceItem> findByInvoiceIsNull(User user) {
List<InvoiceItem> findByInvoiceIsNull = repository.findByInvoiceIsNull();
for (Iterator<InvoiceItem> iterator = findByInvoiceIsNull.iterator(); iterator.hasNext();) {
InvoiceItem invoiceItem = iterator.next();
if (invoiceItem.getArticle().getUserId() != user.getId()) {
iterator.remove();
}
}
return findByInvoiceIsNull;
}
}

@ -1,177 +0,0 @@
package de.kreth.invoice.components;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.vaadin.flow.component.grid.FooterRow;
import com.vaadin.flow.component.grid.FooterRow.FooterCell;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridSelectionModel;
import com.vaadin.flow.data.provider.DataChangeEvent;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.renderer.LocalDateTimeRenderer;
import com.vaadin.flow.data.renderer.NumberRenderer;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.persistence.InvoiceItemRepository;
class InvoiceItemGrid extends Grid<InvoiceItem> {
private static final long serialVersionUID = -8653320112619816426L;
private final DateTimeFormatter ofLocalizedDateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
private FooterCell priceSumCell;
private FooterCell countCell;
private FooterCell dateSpan;
private final List<InvoiceItem> items = new ArrayList<>();
private InvoiceItemRepository repository;
public InvoiceItemGrid(InvoiceItemRepository invoiceItemRepository) {
this.repository = invoiceItemRepository;
addClassName("bordered");
Column<InvoiceItem> titleCol = addColumn(InvoiceItem::getTitle);
titleCol.setId("Article");
titleCol.setHeader("Artikel");
Column<InvoiceItem> dateColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getStart,
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM)));
dateColumn.setId("Date");
dateColumn.setHeader("Datum");
Column<InvoiceItem> startColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getStart,
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)));
startColumn.setId("Start");
startColumn.setHeader("Beginn");
Column<InvoiceItem> endColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getEnd,
DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)));
endColumn.setId("Ende");
endColumn.setHeader("Ende");
Column<InvoiceItem> participantColumn = addColumn(InvoiceItem::getParticipants);
participantColumn.setHeader("Teilnehmer");
Column<InvoiceItem> sumPriceColumn = addColumn(
new NumberRenderer<>(InvoiceItem::getSumPrice, NumberFormat.getCurrencyInstance()));
sumPriceColumn.setId("price");
sumPriceColumn.setHeader("Betrag");
// setSortOrder(GridSortOrder.asc(dateColumn).thenAsc(startColumn));
FooterRow footer = appendFooterRow();
priceSumCell = footer.getCell(sumPriceColumn);
// dateSpan = footer.join(dateColumn, startColumn, endColumn);
dateSpan = footer.getCell(dateColumn);
countCell = footer.getCell(titleCol);
// addSelectionListener(this::selectionChanged);
items.addAll(repository.findByInvoiceIsNull());
ListDataProvider<InvoiceItem> dataProvider = new ListDataProvider<InvoiceItem>(items);
setDataProvider(dataProvider);
dataProvider.addDataProviderListener(new InnerDataProviderListener());
}
public void refreshData() {
items.clear();
items.addAll(repository.findByInvoiceIsNull());
}
@Override
public GridSelectionModel<InvoiceItem> setSelectionMode(SelectionMode selectionMode) {
GridSelectionModel<InvoiceItem> setSelectionMode = super.setSelectionMode(selectionMode);
// setSelectionMode.addSelectionListener(this::selectionChanged);
return setSelectionMode;
}
// @SuppressWarnings("unchecked")
// private void selectionChanged(SelectionEvent<T> event) {
// if (event.getAllSelectedItems().isEmpty()) {
// updateFooterWith(((ListDataProvider<T>) getDataProvider()).getItems());
// } else {
// updateFooterWith(event.getAllSelectedItems());
// }
// }
protected void internalSetDataProvider(DataProvider<InvoiceItem, ?> dataProvider) {
if (!(dataProvider instanceof ListDataProvider)) {
throw new IllegalArgumentException("dataProvider must be an instance of ListDataProvider");
}
// super.internalSetDataProvider(dataProvider);
dataProvider.addDataProviderListener(new InnerDataProviderListener());
updateFooterWith(((ListDataProvider<InvoiceItem>) getDataProvider()).getItems());
}
private void updateFooterWith(Collection<InvoiceItem> selected) {
BigDecimal priceSum = BigDecimal.ZERO;
LocalDate min = null;
LocalDate max = null;
for (InvoiceItem t : selected) {
priceSum = priceSum.add(t.getSumPrice());
min = getMin(min, t.getStart().toLocalDate());
max = getMax(max, t.getEnd().toLocalDate());
}
priceSumCell.setText(NumberFormat.getCurrencyInstance().format(priceSum));
if (min != null && max != null) {
dateSpan.setText(min.format(ofLocalizedDateFormatter) + " - " + max.format(ofLocalizedDateFormatter));
} else {
dateSpan.setText("");
}
countCell.setText("Anzahl: " + selected.size());
}
private LocalDate getMax(LocalDate max, LocalDate localDate) {
if (max == null) {
max = localDate;
} else {
if (max.isBefore(localDate)) {
max = localDate;
}
}
return max;
}
private LocalDate getMin(LocalDate min, LocalDate localDate) {
if (min == null) {
min = localDate;
} else {
if (min.isAfter(localDate)) {
min = localDate;
}
}
return min;
}
private class InnerDataProviderListener implements DataProviderListener<InvoiceItem> {
private static final long serialVersionUID = -6094992880488082586L;
@Override
public void onDataChange(DataChangeEvent<InvoiceItem> event) {
if (event.getSource() == getDataProvider()) {
@SuppressWarnings("unchecked")
ListDataProvider<InvoiceItem> provider = (ListDataProvider<InvoiceItem>) getDataProvider();
updateFooterWith(provider.getItems());
}
}
}
}

@ -1,53 +0,0 @@
package de.kreth.invoice.components;
import java.util.List;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.data.User;
import de.kreth.invoice.persistence.ArticleRepository;
import de.kreth.invoice.persistence.InvoiceItemRepository;
public class InvoiceItemOverviewComponent extends VerticalLayout {
private static final long serialVersionUID = -4486121981960039L;
private final InvoiceItemGrid grid;
private final InvoiceItemRepository invoiceItemRepository;
private final ArticleRepository articleRepository;
private final User user;
public InvoiceItemOverviewComponent(InvoiceItemRepository invoiceItemRepository,
ArticleRepository articleRepository, User user) {
this.invoiceItemRepository = invoiceItemRepository;
this.articleRepository = articleRepository;
this.user = user;
Button addButton = new Button("Hinzufügen", this::createNewitem);
add(new HorizontalLayout(new H3("Rechnungspositionen"), addButton));
grid = new InvoiceItemGrid(invoiceItemRepository);
add(grid);
}
public void refreshData() {
grid.refreshData();
}
private void createNewitem(ClickEvent<Button> ev) {
InvoiceItem item = new InvoiceItem();
List<Article> articles = articleRepository.findByUserId(user.getId());
InvoiceItemDialog dialog = new InvoiceItemDialog(articles, dlg -> {
if (dlg.isClosedWithOk()) {
dlg.writeTo(item);
invoiceItemRepository.save(item);
}
});
dialog.readFrom(item);
dialog.setVisible();
}
}

@ -1,18 +0,0 @@
package de.kreth.invoice.components;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
public class InvoiceOverviewComponent extends VerticalLayout {
private static final long serialVersionUID = 1067257075519373200L;
private final InvoiceGrid grid;
public InvoiceOverviewComponent() {
addClassName("bordered");
this.grid = new InvoiceGrid();
add(new H2("Rechnungen"), grid);
}
}

@ -1,112 +0,0 @@
package de.kreth.invoice.data;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
@Entity
@Table(name = "ADRESS")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "adress_type", discriminatorType = DiscriminatorType.STRING)
public class Adress extends BaseEntity {
private static final long serialVersionUID = 8331249424121577387L;
@Column(nullable = false, length = 255)
private String adress1;
@Column(nullable = true, length = 255)
private String adress2;
@Column(nullable = true, length = 45)
private String zip;
@Column(nullable = true, length = 155)
private String city;
public String getAdress1() {
return adress1;
}
public void setAdress1(String adress1) {
this.adress1 = adress1;
}
public String getAdress2() {
return adress2;
}
public void setAdress2(String adress2) {
this.adress2 = adress2;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString() {
return "Adress [adress1=" + adress1 + ", adress2=" + adress2 + ", zip="
+ zip + ", city=" + city + "]";
}
public boolean isValid() {
return adress1 != null && !adress1.isBlank();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((adress1 == null) ? 0 : adress1.hashCode());
result = prime * result + ((adress2 == null) ? 0 : adress2.hashCode());
result = prime * result + ((city == null) ? 0 : city.hashCode());
result = prime * result + ((zip == null) ? 0 : zip.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Adress other = (Adress) obj;
if (adress1 == null) {
if (other.adress1 != null)
return false;
} else if (!adress1.equals(other.adress1))
return false;
if (adress2 == null) {
if (other.adress2 != null)
return false;
} else if (!adress2.equals(other.adress2))
return false;
if (city == null) {
if (other.city != null)
return false;
} else if (!city.equals(other.city))
return false;
if (zip == null) {
if (other.zip != null)
return false;
} else if (!zip.equals(other.zip))
return false;
return true;
}
}

@ -1,6 +1,7 @@
package de.kreth.invoice.data;
import java.math.BigDecimal;
import java.text.NumberFormat;
import javax.persistence.Column;
import javax.persistence.Entity;
@ -19,7 +20,8 @@ public class Article extends BaseEntity {
private String description;
@Column(name = "user_id")
private int userId;
private long userId;
private String reportRessource;
public BigDecimal getPricePerHour() {
return pricePerHour;
@ -45,18 +47,20 @@ public class Article extends BaseEntity {
this.description = description;
}
public int getUserId() {
public long getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
public void setUserId(long long1) {
this.userId = long1;
}
@Override
public String toString() {
return "Article [pricePerHour=" + pricePerHour + ", title=" + title
+ ", description=" + description + "]";
public void setReport(String reportRessource) {
this.reportRessource = reportRessource;
}
public String getReport() {
return reportRessource;
}
@Override
@ -68,37 +72,58 @@ public class Article extends BaseEntity {
result = prime * result
+ ((pricePerHour == null) ? 0 : pricePerHour.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
result = prime * result + userId;
result = (int) (prime * result + userId);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
if (this == obj) {
return true;
if (obj == null)
}
if (obj == null) {
return false;
if (getClass() != obj.getClass())
}
if (getClass() != obj.getClass()) {
return false;
}
Article other = (Article) obj;
if (description == null) {
if (other.description != null)
if (other.description != null) {
return false;
} else if (!description.equals(other.description))
}
} else if (!description.equals(other.description)) {
return false;
}
if (pricePerHour == null) {
if (other.pricePerHour != null)
if (other.pricePerHour != null) {
return false;
} else if (!pricePerHour.equals(other.pricePerHour))
}
} else if (!pricePerHour.equals(other.pricePerHour)) {
return false;
}
if (title == null) {
if (other.title != null)
if (other.title != null) {
return false;
} else if (!title.equals(other.title))
}
} else if (!title.equals(other.title)) {
return false;
if (userId != other.userId)
}
if (userId != other.userId) {
return false;
}
return true;
}
@Override
protected String getMediumRepresentation() {
return title + " (" + NumberFormat.getCurrencyInstance().format(pricePerHour) + ")";
}
@Override
public String toString() {
return "Article [pricePerHour=" + pricePerHour + ", title=" + title
+ ", description=" + description + "]";
}
}

@ -1,83 +0,0 @@
package de.kreth.invoice.data;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;
@Entity
@Table(name = "BANKING_CONNECTION")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "owner_type", discriminatorType = DiscriminatorType.STRING)
public class BankingConnection extends BaseEntity {
private static final long serialVersionUID = -6168631092559375156L;
@Column(nullable = false, length = 150)
private String bankName;
@Column(nullable = false, length = 150)
private String iban;
@Column(nullable = true, length = 150)
private String bic;
public String getBankName() {
return bankName;
}
public void setBankName(String bankName) {
this.bankName = bankName;
}
public String getIban() {
return iban;
}
public void setIban(String iban) {
this.iban = iban;
}
public String getBic() {
return bic;
}
public void setBic(String bic) {
this.bic = bic;
}
@Override
public String toString() {
return iban;
}
public boolean isValid() {
return iban != null;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((iban == null) ? 0 : iban.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BankingConnection other = (BankingConnection) obj;
if (iban == null) {
if (other.iban != null)
return false;
} else if (!iban.equals(other.iban))
return false;
return true;
}
}

@ -12,14 +12,16 @@ import javax.persistence.PrePersist;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import de.kreth.invoice.views.DataFormat;
@MappedSuperclass
public class BaseEntity implements Serializable {
public abstract class BaseEntity implements Serializable {
private static final long serialVersionUID = 6953593432069408729L;
@Id
@GeneratedValue
private int id;
private Long id;
@Column(name = "created")
@CreationTimestamp
private LocalDateTime createdDate;
@ -27,11 +29,11 @@ public class BaseEntity implements Serializable {
@UpdateTimestamp
private LocalDateTime changeDate;
public int getId() {
public Long getId() {
return id;
}
public void setId(int id) {
public void setId(long id) {
this.id = id;
}
@ -68,10 +70,27 @@ public class BaseEntity implements Serializable {
int result = 1;
result = prime * result
+ ((createdDate == null) ? 0 : createdDate.hashCode());
result = prime * result + id;
result = (int) (prime * result + id);
return result;
}
public String toString(DataFormat format) {
switch (format) {
case SMALL:
return getSmallRepresentation();
case MEDIUM:
return getMediumRepresentation();
default:
return toString();
}
}
protected String getSmallRepresentation() {
return String.valueOf(id);
}
protected abstract String getMediumRepresentation();
@Override
public boolean equals(Object obj) {
if (this == obj) {

@ -2,6 +2,8 @@ package de.kreth.invoice.data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -9,6 +11,7 @@ import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
@ -25,8 +28,9 @@ public class Invoice extends BaseEntity {
private LocalDateTime invoiceDate;
private String signImagePath;
private String reportRessource;
@OneToMany(mappedBy = "invoice")
@OneToMany(mappedBy = "invoice", fetch = FetchType.EAGER, cascade = CascadeType.REFRESH)
private List<InvoiceItem> items;
@ManyToOne(cascade = CascadeType.REFRESH)
@ -77,6 +81,23 @@ public class Invoice extends BaseEntity {
return signImagePath;
}
public void setSignImagePath(String signImagePath) {
this.signImagePath = signImagePath;
}
public String getReportRessource() {
return reportRessource;
}
public void setReportRessource(String reportRessource) {
this.reportRessource = reportRessource;
}
@Override
protected String getMediumRepresentation() {
return invoiceId + " (" + DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(invoiceDate) + ")";
}
@Override
public String toString() {
return "Invoice [invoiceId=" + invoiceId + ", itemscount="

@ -3,6 +3,8 @@ package de.kreth.invoice.data;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.time.temporal.ChronoUnit;
import javax.persistence.Column;
@ -73,6 +75,9 @@ public class InvoiceItem extends BaseEntity {
public void setArticle(Article article) {
this.article = article;
if (article != null) {
this.pricePerHour = article.getPricePerHour();
}
getSumPrice();
}
@ -110,9 +115,17 @@ public class InvoiceItem extends BaseEntity {
}
public BigDecimal getPricePerHour() {
if (pricePerHour == null && article != null) {
this.pricePerHour = article.getPricePerHour();
}
return pricePerHour;
}
@Override
protected String getMediumRepresentation() {
return DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM, FormatStyle.SHORT).format(start);
}
@Override
public String toString() {
return "InvoiceItem [id=" + getId() + ", start=" + start + ", end="

@ -5,10 +5,10 @@ import java.util.Objects;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import org.keycloak.representations.AccessToken;
@ -24,10 +24,12 @@ public class User extends BaseEntity {
private String familyName;
private String email;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private UserBank bank;
@OneToOne(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL)
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
private UserAdress adress;
@Column(name = "PRINCIPAL_ID", nullable = false, length = 40, updatable = false, insertable = true, unique = true)
@ -71,4 +73,25 @@ public class User extends BaseEntity {
public UserAdress getAdress() {
return adress;
}
public void setBank(UserBank bank) {
if (bank.getUser() != null && !bank.getUser().equals(this)) {
throw new IllegalStateException("User of bank is set and not equal to this.");
}
bank.setUser(this);
this.bank = bank;
}
public void setAdress(UserAdress adress) {
if (adress.getUser() != null && !adress.getUser().equals(this)) {
throw new IllegalStateException("User of adress is set and not equal to this.");
}
adress.setUser(this);
this.adress = adress;
}
@Override
protected String getMediumRepresentation() {
return givenName + " " + familyName;
}
}

@ -1,16 +1,49 @@
package de.kreth.invoice.data;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
@Entity
public class UserAdress extends Adress {
@Table(name = "ADRESS")
public class UserAdress implements Cloneable, Serializable {
private static final long serialVersionUID = -8104370538500175340L;
@OneToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
@Id
@Column(name = "USER_ID")
private Long id;
@Column(nullable = false, length = 255)
private String adress1;
@Column(nullable = true, length = 255)
private String adress2;
@Column(nullable = true, length = 45)
private String zip;
@Column(nullable = true, length = 155)
private String city;
@Column(name = "created")
@CreationTimestamp
private LocalDateTime createdDate;
@Column(name = "updated")
@UpdateTimestamp
private LocalDateTime changeDate;
@OneToOne
@MapsId
@JoinColumn(name = "USER_ID")
private User user;
public User getUser() {
@ -21,4 +54,85 @@ public class UserAdress extends Adress {
this.user = user;
}
public String getAdress1() {
return adress1;
}
public void setAdress1(String adress1) {
this.adress1 = adress1;
}
public String getAdress2() {
return adress2;
}
public void setAdress2(String adress2) {
this.adress2 = adress2;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public boolean isValid() {
return adress1 != null && !adress1.isBlank();
}
@Override
public String toString() {
return "Adress [adress1=" + adress1 + ", adress2=" + adress2 + ", zip="
+ zip + ", city=" + city + "]";
}
public LocalDateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(LocalDateTime createdDate) {
this.createdDate = createdDate;
}
public LocalDateTime getChangeDate() {
return changeDate;
}
public void setChangeDate(LocalDateTime changeDate) {
this.changeDate = changeDate;
}
@PrePersist
void prePersist() {
if (this.createdDate == null) {
LocalDateTime now = LocalDateTime.now();
this.createdDate = now;
this.changeDate = now;
} else {
this.changeDate = LocalDateTime.now();
}
}
@Override
public UserAdress clone() {
UserAdress adress = new UserAdress();
adress.setAdress1(getAdress1());
adress.setAdress2(getAdress2());
adress.setCity(getCity());
adress.setZip(getZip());
adress.setUser(getUser());
adress.setChangeDate(getChangeDate());
adress.setCreatedDate(getCreatedDate());
return adress;
}
}

@ -1,18 +1,82 @@
package de.kreth.invoice.data;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.MapsId;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
@Entity
public class UserBank extends BankingConnection {
@Table(name = "BANKING_CONNECTION")
public class UserBank implements Cloneable, Serializable {
private static final long serialVersionUID = -7356424394007978241L;
@OneToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
@Id
@Column(name = "USER_ID")
private Long id;
@OneToOne
@MapsId
@JoinColumn(name = "USER_ID")
private User user;
@Column(nullable = false, length = 150)
private String bankName;
@Column(nullable = false, length = 150)
private String iban;
@Column(nullable = true, length = 150)
private String bic;
@Column(name = "created")
@CreationTimestamp
private LocalDateTime createdDate;
@Column(name = "updated")
@UpdateTimestamp
private LocalDateTime changeDate;
public String getBankName() {
return bankName;
}
public void setBankName(String bankName) {
this.bankName = bankName;
}
public String getIban() {
return iban;
}
public void setIban(String iban) {
this.iban = iban;
}
public String getBic() {
return bic;
}
public void setBic(String bic) {
this.bic = bic;
}
@Override
public String toString() {
return iban;
}
public boolean isValid() {
return iban != null;
}
public User getUser() {
return user;
}
@ -29,4 +93,43 @@ public class UserBank extends BankingConnection {
// }
}
public LocalDateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(LocalDateTime createdDate) {
this.createdDate = createdDate;
}
public LocalDateTime getChangeDate() {
return changeDate;
}
public void setChangeDate(LocalDateTime changeDate) {
this.changeDate = changeDate;
}
@PrePersist
void prePersist() {
if (this.createdDate == null) {
LocalDateTime now = LocalDateTime.now();
this.createdDate = now;
this.changeDate = now;
} else {
this.changeDate = LocalDateTime.now();
}
}
@Override
public UserBank clone() {
UserBank clone = new UserBank();
clone.setUser(user);
clone.setBankName(getBankName());
clone.setBic(getBic());
clone.setIban(getIban());
clone.setChangeDate(getChangeDate());
clone.setCreatedDate(getCreatedDate());
return clone;
}
}

@ -6,7 +6,7 @@ import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.Article;
public interface ArticleRepository extends CrudRepository<Article, Integer> {
public interface ArticleRepository extends CrudRepository<Article, Long> {
List<Article> findByUserId(int userId);
List<Article> findByUserId(long userId);
}

@ -3,11 +3,15 @@ package de.kreth.invoice.persistence;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.InvoiceItem;
public interface InvoiceItemRepository extends CrudRepository<InvoiceItem, Integer> {
@Repository
public interface InvoiceItemRepository extends CrudRepository<InvoiceItem, Long> {
List<InvoiceItem> findByInvoiceIsNull();
List<InvoiceItem> findByArticle(Article article);
}

@ -0,0 +1,12 @@
package de.kreth.invoice.persistence;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.Invoice;
public interface InvoiceRepository extends CrudRepository<Invoice, Long> {
List<Invoice> findByUserId(long userId);
}

@ -5,7 +5,7 @@ import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserAdress;
public interface UserAdressRepository extends CrudRepository<UserAdress, Integer> {
public interface UserAdressRepository extends CrudRepository<UserAdress, Long> {
UserAdress findByUser(User user);
}

@ -5,7 +5,7 @@ import org.springframework.data.repository.CrudRepository;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserBank;
public interface UserBankRepository extends CrudRepository<UserBank, Integer> {
public interface UserBankRepository extends CrudRepository<UserBank, Long> {
UserBank findByUser(User user);
}

@ -6,6 +6,9 @@ import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.List;
import org.slf4j.LoggerFactory;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.Invoice;
import de.kreth.invoice.data.InvoiceItem;
import net.sf.jasperreports.engine.JRDataSource;
@ -64,6 +67,7 @@ public class InvoiceReportSource implements JRDataSource, JRDataSourceProvider {
private Iterator<InvoiceItem> itemIterator;
private InvoiceItem currentItem;
private Article article;
public InvoiceReportSource() {
}
@ -72,6 +76,7 @@ public class InvoiceReportSource implements JRDataSource, JRDataSourceProvider {
this.invoice = invoice;
List<InvoiceItem> items = invoice.getItems();
items.sort(this::compare);
article = items.get(0).getArticle();
itemIterator = items.iterator();
}
@ -147,8 +152,22 @@ public class InvoiceReportSource implements JRDataSource, JRDataSourceProvider {
default:
break;
}
} else {
switch (jrField.getName()) {
case FIELD_ARTICLE_TITLE:
return article.getTitle();
case FIELD_ARTICLE_DESCRIPTION:
return article.getDescription();
case FIELD_ARTICLE_PRICE_PER_HOUR:
return article.getPricePerHour();
default:
break;
}
}
LoggerFactory.getLogger(getClass()).error("Error filling Report field name=" + jrField.getName()
+ ", description=" + jrField.getDescription() + ", className=" + jrField.getValueClassName());
return null;
}

@ -9,6 +9,8 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserAdress;
import de.kreth.invoice.data.UserBank;
import de.kreth.invoice.persistence.UserRepository;
@Component
@ -31,7 +33,9 @@ public class UserManager {
public User getLoggedInUser() {
AccessToken accessToken = getAccessToken();
if (accessToken != null) {
return userRepository.findByPrincipalId(accessToken.getSubject());
User user = userRepository.findByPrincipalId(accessToken.getSubject());
return user;
}
return null;
}
@ -46,10 +50,18 @@ public class UserManager {
}
public User create() {
User user = new User();
AccessToken accessToken = getAccessToken();
User user = new User();
user.setPrincipal(accessToken);
return save(user);
UserBank bank = new UserBank();
bank.setUser(user);
user.setBank(bank);
UserAdress adress = new UserAdress();
adress.setUser(user);
user.setAdress(adress);
return user;
}
}

@ -0,0 +1,7 @@
package de.kreth.invoice.views;
public enum DataFormat {
DEBUG,
MEDIUM,
SMALL
}

@ -0,0 +1,34 @@
package de.kreth.invoice.views;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.notification.NotificationVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
public final class ErrorNotification {
public static void showError(String message) {
Notification notification = new Notification();
notification.addThemeVariants(NotificationVariant.LUMO_ERROR);
Div text = new Div(new Text(message));
Button closeButton = new Button(new Icon("lumo", "cross"));
closeButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY_INLINE);
closeButton.getElement().setAttribute("aria-label", "Close");
closeButton.addClickListener(event -> {
notification.close();
});
HorizontalLayout layout = new HorizontalLayout(text, closeButton);
layout.setAlignItems(Alignment.CENTER);
notification.add(layout);
notification.open();
}
}

@ -0,0 +1,78 @@
package de.kreth.invoice.views;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
public class FooterComponent extends HorizontalLayout {
private static final long serialVersionUID = 4845822203421115202L;
private static final Logger LOGGER = LoggerFactory
.getLogger(FooterComponent.class);
private static final Properties VERSION = new Properties();
static {
String path = "/../version.properties";
try {
recursivelyLoadPropFromPath(FooterComponent.class, path, 0);
} catch (Exception e) {
LOGGER.error("Error loading version properties file = " + path
+ ", cause: " + e.getMessage());
}
}
private static void recursivelyLoadPropFromPath(
Class<FooterComponent> thisClass, String path, int level)
throws IOException {
URL resource = thisClass.getResource(path);
if (resource != null) {
VERSION.load(resource.openStream());
LOGGER.info("Successfully loaded version info from " + resource);
} else if (level < 4) {
recursivelyLoadPropFromPath(thisClass, "/.." + path, level + 1);
} else {
throw new IOException("File not Found in any subdir of " + path);
}
}
public FooterComponent() {
Text copyright = new Text("&copy; Markus Kreth");
add(copyright);
// if (propertiesLoaded()) {
//
// String dateTimeProperty = Version_Properties.BUILD_DATETIME.getString(VERSION::getProperty);
// SimpleDateFormat sourceFormat = new SimpleDateFormat(
// "yyyy-MM-dd HH:mm:ss");
// sourceFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
// try {
// Date date = sourceFormat.parse(dateTimeProperty);
// dateTimeProperty = DateFormat.getDateTimeInstance(
// DateFormat.MEDIUM, DateFormat.SHORT).format(date);
// } catch (ParseException e) {
// LOGGER.warn(
// "Unable to parse dateTimeProperty=" + dateTimeProperty,
// e);
// }
// Label vers = new Label(
// "Version: " + Version_Properties.PROJECT_VERSION.getString(VERSION::getProperty));
// Label buildTime = new Label("Build: " + dateTimeProperty);
// adds(vers, buildTime);
// }
}
// private boolean propertiesLoaded() {
// return Version_Properties.BUILD_DATETIME.getString(VERSION::getProperty) != null
// && Version_Properties.BUILD_DATETIME.getString(VERSION::getProperty).trim().isEmpty() == false;
// }
}

@ -0,0 +1,69 @@
package de.kreth.invoice.views;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
import com.vaadin.flow.data.binder.ErrorMessageProvider;
import com.vaadin.flow.data.binder.Result;
import com.vaadin.flow.data.binder.ValueContext;
import com.vaadin.flow.data.converter.AbstractStringToNumberConverter;
public class PriceConverter extends AbstractStringToNumberConverter<BigDecimal> {
private static final long serialVersionUID = -2627857228509459543L;
private final NumberFormat formatter;
private final NumberFormat backupFormat;
public PriceConverter(BigDecimal emptyValue, ErrorMessageProvider errorMessageProvider) {
this(emptyValue, errorMessageProvider, Locale.getDefault());
}
public PriceConverter(BigDecimal emptyValue, String errorMessage) {
this(emptyValue, errorMessage, Locale.getDefault());
}
public PriceConverter(BigDecimal emptyValue, ErrorMessageProvider errorMessageProvider, Locale inLocale) {
super(emptyValue, errorMessageProvider);
formatter = NumberFormat.getCurrencyInstance(inLocale);
backupFormat = NumberFormat.getInstance(inLocale);
}
public PriceConverter(BigDecimal emptyValue, String errorMessage, Locale inLocale) {
super(emptyValue, errorMessage);
formatter = NumberFormat.getCurrencyInstance(inLocale);
backupFormat = NumberFormat.getInstance(inLocale);
}
@Override
public Result<BigDecimal> convertToModel(String value, ValueContext context) {
if (value == null) {
return Result.ok(BigDecimal.ZERO);
}
try {
Number numValue = formatter.parse(value);
return Result.ok(BigDecimal.valueOf(numValue.doubleValue()));
} catch (ParseException e) {
try {
Number numValue = backupFormat.parse(value.trim());
return Result.ok(BigDecimal.valueOf(numValue.doubleValue()));
} catch (ParseException e2) {
return Result.error(e.getMessage());
}
}
}
@Override
protected NumberFormat getFormat(Locale locale) {
return formatter;
}
@Override
public String convertToPresentation(BigDecimal value, ValueContext context) {
if (value == null) {
return null;
}
return formatter.format(value.doubleValue());
}
}

@ -1,7 +1,10 @@
package de.kreth.invoice.views;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import static de.kreth.invoice.Application.getString;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
@ -12,6 +15,7 @@ import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.contextmenu.ContextMenu;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.H1;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.icon.VaadinIcon;
@ -22,16 +26,19 @@ import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import de.kreth.invoice.components.InvoiceItemOverviewComponent;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.Localization_Properties;
import de.kreth.invoice.business.ArticleBusiness;
import de.kreth.invoice.business.InvoiceBusiness;
import de.kreth.invoice.business.InvoiceItemBusiness;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserAdress;
import de.kreth.invoice.data.UserBank;
import de.kreth.invoice.persistence.ArticleRepository;
import de.kreth.invoice.persistence.InvoiceItemRepository;
import de.kreth.invoice.persistence.UserAdressRepository;
import de.kreth.invoice.persistence.UserBankRepository;
import de.kreth.invoice.security.UserManager;
import de.kreth.invoice.views.article.ArticleDialog;
import de.kreth.invoice.views.invoiceitem.InvoiceItemOverviewComponent;
import de.kreth.invoice.views.invoiceitem.InvoiceOverviewComponent;
import de.kreth.invoice.views.user.UserDetailsDialog;
@PageTitle("")
@Route(value = "")
@ -39,63 +46,87 @@ import de.kreth.invoice.security.UserManager;
public class View extends VerticalLayout implements BeforeEnterObserver {
private static final long serialVersionUID = 1L;
private final UserManager userRepository;
private final UserBankRepository bankRepository;
private final UserAdressRepository adressRepository;
private final InvoiceItemRepository invoiceItemRepository;
private final ArticleRepository articleRepository;
private final UserManager userManager;
private final InvoiceItemBusiness invoiceItemBusiness;
private final ArticleBusiness articleBusiness;
private InvoiceItemOverviewComponent invoiceItems;
private User user;
private InvoiceBusiness invoiceRepository;
private InvoiceOverviewComponent invoiceCompoent;
public View(@Autowired UserManager userRepository,
@Autowired InvoiceItemBusiness invoiceItemRepository,
@Autowired InvoiceBusiness invoiceRepository,
@Autowired ArticleBusiness articleRepository) {
this.userManager = userRepository;
this.invoiceItemBusiness = invoiceItemRepository;
this.invoiceRepository = invoiceRepository;
this.articleBusiness = articleRepository;
public View(@Autowired UserManager userRepository, @Autowired UserBankRepository userBankRepository,
@Autowired UserAdressRepository adressRepository,
@Autowired InvoiceItemRepository invoiceItemRepository,
@Autowired ArticleRepository articleRepository) {
this.userRepository = userRepository;
this.bankRepository = userBankRepository;
this.adressRepository = adressRepository;
this.invoiceItemRepository = invoiceItemRepository;
this.articleRepository = articleRepository;
}
@Override
public void beforeEnter(BeforeEnterEvent event) {
user = userRepository.getLoggedInUser();
user = userManager.getLoggedInUser();
if (user == null) {
user = userRepository.create();
user = userManager.create();
}
// UserBank bank = bankRepository.findByUser(user);
// UserAdress adress = adressRepository.findByUser(user);
if (isBankAndAdressInvalid()) {
openDetailDialog();
} else if (articleBusiness.findByUserId(user.getId()).isEmpty()) {
openArticleDialog();
} else {
createUi();
}
// if (isBankOrAdressInvalid(bank, adress)) {
// event.getUI().navigate(LoginDataView.class);
// } else {
// checkArticle();
// createUi();
// }
}
checkArticle();
createUi();
private void openDetailDialog() {
UserDetailsDialog dlg = new UserDetailsDialog();
dlg.setUser(user);
dlg.open();
dlg.addOpenedChangeListener(ev -> {
boolean bankAndAdressInvalid = isBankAndAdressInvalid();
doCloseDialog(dlg);
if (dlg.isValidAndClosedWithOk() && bankAndAdressInvalid) {
openArticleDialog();
}
});
}
private void checkArticle() {
if (articleRepository.count() <= 0) {
Article article = new Article();
LocalDateTime now = LocalDateTime.now();
article.setChangeDate(now);
article.setCreatedDate(now);
article.setDescription("Dummy Übungsleiter");
article.setTitle("Dummy");
article.setPricePerHour(BigDecimal.valueOf(7.5));
article.setUserId(user.getId());
articleRepository.save(article);
private void doCloseDialog(UserDetailsDialog dlg) {
if (dlg.isValidAndClosedWithOk()) {
this.user = dlg.getUser();
userManager.save(this.user);
getUI().ifPresent(ui -> ui.navigate(View.class));
} else if (isBankAndAdressInvalid()) {
openDetailDialog();
}
}
private boolean isBankOrAdressInvalid(UserBank bank, UserAdress adress) {
private void openArticleDialog() {
ArticleDialog dlg = new ArticleDialog(articleBusiness, user);
dlg.open();
dlg.addOpenedChangeListener(ev -> {
if (articleBusiness.findByUserId(user.getId()).isEmpty()) {
openArticleDialog();
} else {
doCloseDialog(dlg);
}
});
}
private void doCloseDialog(ArticleDialog dlg) {
getUI().ifPresent(ui -> ui.navigate(View.class));
}
private boolean isBankAndAdressInvalid() {
UserBank bank = user.getBank();
UserAdress adress = user.getAdress();
return bank == null || adress == null || !bank.isValid() || !adress.isValid();
}
@ -110,11 +141,37 @@ public class View extends VerticalLayout implements BeforeEnterObserver {
Label name = new Label(user.getGivenName() + " " + user.getFamilyName());
Label email = new Label(user.getEmail());
add(name, email);
invoiceItems = new InvoiceItemOverviewComponent(invoiceItemRepository, articleRepository, user);
add(invoiceItems);
Button openDetailDialog = new Button(getString(Localization_Properties.CAPTION_USER_DETAILS));
openDetailDialog.addClickListener(ev -> openDetailDialog());
Button openArticleDialog = new Button(getString(Localization_Properties.CAPTION_ARTICLES));
openArticleDialog.addClickListener(ev -> openArticleDialog());
FormLayout layout = new FormLayout(name, email, openDetailDialog, openArticleDialog);
add(layout);
invoiceItems = new InvoiceItemOverviewComponent(invoiceItemBusiness, articleBusiness, user);
final List<InvoiceItem> itemsForInvoice = new ArrayList<>();
invoiceItems.addSeelctionListener(ev -> {
itemsForInvoice.clear();
itemsForInvoice.addAll(ev.getValues());
if (itemsForInvoice.isEmpty() == false) {
Long articleId = itemsForInvoice.get(0).getArticle().getId();
for (Iterator<InvoiceItem> iterator = itemsForInvoice.iterator(); iterator.hasNext();) {
InvoiceItem invoiceItem = iterator.next();
if (invoiceItem.getArticle().getId() != articleId) {
iterator.remove();
}
}
}
});
invoiceCompoent = new InvoiceOverviewComponent(invoiceRepository, user, itemsForInvoice);
invoiceCompoent.addInvoiceCreationListener(() -> invoiceItems.refreshData());
layout.add(invoiceItems);
layout.add(invoiceCompoent);
invoiceItems.refreshData();
invoiceCompoent.refreshData();
}
public void onMenuButtonClick(ClickEvent<Button> event) {
@ -128,16 +185,16 @@ public class View extends VerticalLayout implements BeforeEnterObserver {
public void onSettingsButtonClick(ClickEvent<MenuItem> event) {
Dialog dlg = new Dialog();
dlg.add(new H1("Einstellungen"));
dlg.add(new Text("Einstellugen für diese App. Noch nicht implementiert."));
dlg.add(new Text("Einstellugen für diese App. noch nicht implementiert."));
dlg.open();
}
public void onAboutButtonClick(ClickEvent<MenuItem> event) {
Dialog dlg = new Dialog();
dlg.add(new H1("Personeneditor"));
dlg.add(new H1("Abrechnungen"));
dlg.add(new Text(
"Personeneditor ist eine App zur Erfassung und Änderung von Personen im Trampolin des MTV Groß-Buchholz."));
"Abrechnungen ist eine App zur Erfassung von Übungsleiterstunden und Abrechnung im MTV Groß-Buchholz."));
dlg.open();
}

@ -0,0 +1,269 @@
package de.kreth.invoice.views.article;
import static de.kreth.invoice.Application.getString;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import de.kreth.invoice.Localization_Properties;
import de.kreth.invoice.business.ArticleBusiness;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.ReportLicense;
import de.kreth.invoice.data.User;
import de.kreth.invoice.views.PriceConverter;
public class ArticleDialog extends Dialog {
private static final long serialVersionUID = 2516636187686436452L;
private final ArticleBusiness business;
private TextField title;
private TextField pricePerHour;
private TextField description;
private Grid<Article> articleGrid;
private User user;
private Article current = null;
private Binder<Article> binder;
private Button discartButton;
private Button storeButton;
private Checkbox isTrainer;
private Button deleteButton;
public ArticleDialog(ArticleBusiness articleBusiness, User user) {
this.business = articleBusiness;
title = new TextField();
title.setLabel("Artikel");
pricePerHour = new TextField();
pricePerHour.setLabel("Preis Einzel");
description = new TextField();
description.setLabel("Beschreibung");
isTrainer = new Checkbox("als Trainer", false);
FormLayout contentValues = new FormLayout();
contentValues.add(title, pricePerHour, description, isTrainer);
Button addArticle = new Button("Artikel hinzufügen");
addArticle.addClickListener(ev -> {
current = createNewArticle();
binder.setBean(current);
toggleEditFields();
});
discartButton = new Button("Abbrechen", e -> {
binder.readBean(null);
current = null;
toggleEditFields();
});
discartButton.setVisible(false);
storeButton = new Button("Speichern", e -> {
if (binder.validate().isOk()) {
business.save(current);
reloadItems();
toggleEditFields();
}
});
Button closeButton = new Button(getString(Localization_Properties.LABEL_CLOSE),
ev -> close());
deleteButton = new Button(getString(Localization_Properties.LABEL_DELETE), ev -> {
business.delete(current);
current = null;
binder.setBean(null);
reloadItems();
toggleEditFields();
});
FormLayout contentButtons = new FormLayout();
contentButtons.add(storeButton, discartButton, addArticle, deleteButton, closeButton);
setupArticleGrid();
setupBinder();
FormLayout content = new FormLayout();
content.add(contentValues, articleGrid, contentButtons);
add(content);
setWidth(80.0f, Unit.PERCENTAGE);
setModal(true);
setUser(user);
}
private void setUser(User user) {
this.user = user;
if (user != null) {
List<Article> loadAll = reloadItems();
if (loadAll.isEmpty()) {
current = createNewArticle();
} else {
current = loadAll.get(0);
articleGrid.select(current);
}
binder.setBean(current);
toggleEditFields();
} else {
close();
}
}
private void toggleEditFields() {
if (current == null || binder.getBean() == null) {
title.setEnabled(false);
pricePerHour.setEnabled(false);
description.setEnabled(false);
isTrainer.setEnabled(false);
discartButton.setVisible(false);
storeButton.setVisible(false);
deleteButton.setVisible(false);
} else {
discartButton.setVisible(true);
storeButton.setVisible(true);
deleteButton.setVisible(true);
storeButton.setEnabled(binder.validate().isOk());
title.setEnabled(true);
pricePerHour.setEnabled(true);
description.setEnabled(true);
isTrainer.setEnabled(true);
}
}
private Article createNewArticle() {
Article article = new Article();
article.setTitle("");
article.setDescription("");
article.setPricePerHour(BigDecimal.ZERO);
article.setReport(ReportLicense.ASSISTANT.getRessource());
if (user != null) {
article.setUserId(user.getId());
} else {
article.setUserId(-1);
}
return article;
}
private List<Article> reloadItems() {
List<Article> loadAll = business.loadAll(a -> a.getUserId() == user.getId());
articleGrid.setItems(loadAll);
return loadAll;
}
private void setupBinder() {
binder = new Binder<>(Article.class);
binder.forField(title).asRequired().withNullRepresentation("").bind(Article::getTitle, Article::setTitle);
PriceConverter converter = new PriceConverter(BigDecimal.ZERO, "Ungültiger Preis");
binder.forField(pricePerHour).asRequired()
.withNullRepresentation("")
.withConverter(converter)
.withValidator(t -> t != null && t.doubleValue() > 0, "Der Preis muss größer 0 sein!")
.bind(Article::getPricePerHour, Article::setPricePerHour);
binder.forField(description).withNullRepresentation("").bind(Article::getDescription, Article::setDescription);
binder.forField(isTrainer).bind(this::reportToCheckbox, this::checkboxToReportLicense);
binder.addValueChangeListener(changeEv -> {
toggleEditFields();
});
}
private boolean reportToCheckbox(Article source) {
if (source == null || source.getReport() == null) {
return false;
}
return source.getReport().contentEquals(ReportLicense.TRAINER.getRessource());
}
private void checkboxToReportLicense(Article bean, Boolean fieldvalue) {
if (bean != null && fieldvalue != null) {
if (fieldvalue) {
bean.setReport(ReportLicense.TRAINER.getRessource());
} else {
bean.setReport(ReportLicense.ASSISTANT.getRessource());
}
}
}
private void setupArticleGrid() {
articleGrid = new Grid<>();
articleGrid.addColumn(Article::getTitle).setHeader("Titel");
// ValueProvider<BigDecimal, String> currencyProvider = new ValueProvider<BigDecimal, String>() {
// private static final long serialVersionUID = -6305095230785149948L;
// private final NumberFormat formatter = NumberFormat.getCurrencyInstance();
//
// @Override
// public String apply(BigDecimal source) {
// return formatter.format(source.doubleValue());
// }
// };
// articleGrid.addColumn(Article::getPricePerHour, currencyProvider);
articleGrid.addColumn(Article::getDescription)
.setHeader("Beschreibung");
articleGrid.addColumn(this::reportToCheckbox)
.setHeader("Report");
articleGrid.addSelectionListener(sel -> {
if (!binder.hasChanges()) {
Optional<Article> selected = sel.getFirstSelectedItem();
if (selected.isPresent()) {
current = selected.get();
binder.setBean(current);
boolean hasItems = business.hasInvoiceItem(current);
deleteButton.setEnabled(!hasItems);
if (hasItems) {
// deleteButton.setT
} else {
// deleteButton.setDescription(
// "Es existieren Einträge zu diesem Artikel. Er kann nicht gelöscht werden.");
}
// if (hasInvoice) {
// String errorMessage = getString(MESSAGE_ARTICLE_ERROR_INVOICEEXISTS);
// title.setDescription(errorMessage);
// pricePerHour.setDescription(errorMessage);
// description.setDescription(errorMessage);
//
// } else {
// title.setDescription("");
// pricePerHour.setDescription("");
// description.setDescription("");
// }
}
}
toggleEditFields();
});
}
}

@ -0,0 +1,30 @@
package de.kreth.invoice.views.article;
import java.text.NumberFormat;
import java.util.ResourceBundle;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.data.renderer.NumberRenderer;
import de.kreth.invoice.data.Article;
public class ArticleGrid extends Grid<Article> {
private static final long serialVersionUID = 4063720728031721955L;
public ArticleGrid(ResourceBundle resBundle) {
addClassName("bordered");
addColumn(Article::getTitle).setHeader("Titel");
NumberRenderer<Article> re = new NumberRenderer<Article>(a -> a.getPricePerHour().doubleValue(),
NumberFormat.getCurrencyInstance());
addColumn(re)
.setHeader("Preis");
addColumn(Article::getDescription).setHeader("Beschreibung");
}
}

@ -0,0 +1,255 @@
package de.kreth.invoice.views.invoice;
import static de.kreth.invoice.Application.getString;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICE_PRINTSIGNATURE;
import static de.kreth.invoice.Localization_Properties.LABEL_CANCEL;
import static de.kreth.invoice.Localization_Properties.LABEL_OPEN;
import static de.kreth.invoice.Localization_Properties.LABEL_PREVIEW;
import static de.kreth.invoice.Localization_Properties.LABEL_STORE;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.HasValue.ValueChangeEvent;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.dialog.Dialog;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.orderedlayout.FlexComponent.Alignment;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.server.StreamResourceWriter;
import com.vaadin.flow.server.VaadinSession;
import com.vaadin.flow.shared.Registration;
import de.kreth.invoice.data.Invoice;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.report.InvoiceReportSource;
import de.kreth.invoice.report.Signature;
import de.kreth.invoice.views.invoiceitem.InvoiceItemGrid;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
public class InvoiceDialog extends Dialog {
private static final long serialVersionUID = -8997281625128779760L;
private static final Logger LOGGER = LoggerFactory.getLogger(InvoiceDialog.class);
public enum InvoiceMode {
CREATE,
VIEW_ONLY
}
private TextField invoiceNo;
private DatePicker invoiceDate;
private InvoiceItemGrid<InvoiceItem> itemGrid;
private Button okButton;
private Invoice invoice;
private Signature signature;
private Checkbox printSignature;
/**
* Initializes the Dialog with an empty {@link Invoice}.
* <p>
* Be sure to set an {@link Invoice} with at least 1 Item with
* {@link #setInvoice(Invoice)}.
* <p>
*
* @param resBundle
* @param pdfOpenLabel
*/
public InvoiceDialog(InvoiceMode pdfOpenLabel) {
setWidth(200, Unit.EM);
invoiceNo = new TextField();
invoiceNo.setLabel("Rechn.Nr.");
if (InvoiceMode.VIEW_ONLY == pdfOpenLabel) {
invoiceNo.setReadOnly(true);
} else {
invoiceNo.addValueChangeListener(this::updateInvoiceNo);
}
invoiceDate = new DatePicker();
invoiceDate.setLabel("Rechnung Datum");
invoiceDate.setEnabled(false);
itemGrid = new InvoiceItemGrid<>();
printSignature = new Checkbox(getString(CAPTION_INVOICE_PRINTSIGNATURE));
if (InvoiceMode.VIEW_ONLY == pdfOpenLabel) {
printSignature.setEnabled(false);
}
printSignature.addValueChangeListener(ev -> {
if (printSignature.getValue() == Boolean.TRUE) {
invoice.setSignImagePath(signature.getSignatureUrl().getAbsolutePath());
} else {
invoice.setSignImagePath(null);
}
});
okButton = new Button(getString(LABEL_STORE), ev -> close());
Button cancel = new Button(getString(LABEL_CANCEL), ev -> close());
String caption;
if (pdfOpenLabel == InvoiceMode.VIEW_ONLY) {
caption = getString(LABEL_OPEN);
} else {
caption = getString(LABEL_PREVIEW);
}
Button previewButton = new Button(caption, this::showPdf);
HorizontalLayout btnLayout = new HorizontalLayout();
btnLayout.add(okButton, cancel, previewButton);
VerticalLayout vLayout = new VerticalLayout();
vLayout.add(invoiceNo, invoiceDate, itemGrid, printSignature);
vLayout.add(btnLayout);
vLayout.setHorizontalComponentAlignment(Alignment.BASELINE, btnLayout);
add(vLayout);
}
private void updateInvoiceNo(ValueChangeEvent<String> ev) {
if (invoice != null) {
invoice.setInvoiceId(ev.getValue());
}
}
private void showPdf(ClickEvent<Button> ev) {
try {
JasperPrint print = createJasperPrint();
LOGGER.debug("Created JasperPrint");
showInWebWindow(print, ev);
} catch (JRException | IOException e) {
throw new RuntimeException(e);
}
}
private void showInWebWindow(JasperPrint print, ClickEvent<Button> ev) throws IOException, JRException {
Dialog window = new Dialog();
// window.setCaption("View PDF");
Component e = createEmbedded(print);
window.add(e);
window.setModal(true);
// window.setSizeFull();
window.setWidth("95%");
window.setHeight("99%");
window.open();
}
private Component createEmbedded(JasperPrint print) throws IOException, JRException {
PipedInputStream inFrame = new PipedInputStream();
final StreamResource resourceFrame = new StreamResource("invoice.pdf", () -> inFrame);
resourceFrame.setHeader("Content-Type", "application/pdf");
Element image = new Element("object");
image.setAttribute("type", "application/pdf");
image.getStyle().set("display", "block");
image.setAttribute("data", resourceFrame);
image.setAttribute("width", "100%");
image.setAttribute("height", "100%");
ExecutorService exec = Executors.newSingleThreadExecutor();
File outFile = Files.createTempFile("invoice", ".pdf").toFile();
exec.execute(() -> {
try (PipedOutputStream out1 = new PipedOutputStream(inFrame)) {
JasperExportManager.exportReportToPdfStream(print, out1);
} catch (JRException | IOException e) {
LOGGER.error("Error exporting Report to Browser Window", e);
}
});
exec.shutdown();
JasperExportManager.exportReportToPdfFile(print, outFile.getAbsolutePath());
LOGGER.info("PDF File written: {}", outFile.getAbsolutePath());
StreamResourceWriter inStream = new StreamResourceWriter() {
private static final long serialVersionUID = -3847642428860682957L;
@Override
public void accept(OutputStream stream, VaadinSession session) throws IOException {
try (FileInputStream in = new FileInputStream(outFile)) {
in.transferTo(stream);
}
}
};
new StreamResource("invoice.pdf", inStream);
Anchor link = new Anchor(resourceFrame, "Download PDF");
// r("Download PDF", new FileResource(outFile));
link.addFocusListener(ev -> LOGGER.debug("Download link clicked."));
link.addAttachListener(ev -> LOGGER.debug("Download link attached."));
VerticalLayout layout = new VerticalLayout();
layout.add(link);
layout.getElement().appendChild(image);
layout.setSizeFull();
return layout;
}
private JasperPrint createJasperPrint() throws JRException {
InvoiceReportSource source = new InvoiceReportSource();
source.setInvoice(invoice);
JasperReport report = JasperCompileManager
.compileReport(getClass().getResourceAsStream(invoice.getReportRessource()));
return JasperFillManager.fillReport(report, new HashMap<>(), source);
}
public Registration addOkClickListener(ComponentEventListener<ClickEvent<Button>> listener) {
return okButton.addClickListener(listener);
}
public void setInvoice(Invoice invoice) {
this.invoice = invoice;
signature = new Signature(invoice.getUser());
invoiceNo.setValue(invoice.getInvoiceId());
invoiceDate.setValue(invoice.getInvoiceDate().toLocalDate());
itemGrid.setItems(invoice.getItems());
printSignature.setVisible(signature.isSignatureImageExists());
if (printSignature.isEnabled()) {
printSignature.setValue(signature.isSignatureImageExists());
} else {
printSignature.setValue(invoice.getSignImagePath() != null);
}
}
public void setOkVisible(boolean visible) {
okButton.setVisible(visible);
}
}

@ -1,10 +1,13 @@
package de.kreth.invoice.components;
package de.kreth.invoice.views.invoice;
import java.text.NumberFormat;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.List;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.renderer.LocalDateTimeRenderer;
import com.vaadin.flow.data.renderer.NumberRenderer;
@ -14,8 +17,9 @@ public class InvoiceGrid extends Grid<Invoice> {
private static final long serialVersionUID = 662980245990122807L;
private final List<Invoice> items;
public InvoiceGrid() {
addClassName("bordered");
Column<Invoice> invoiceIdCol = addColumn(Invoice::getInvoiceId);
invoiceIdCol.setHeader("Rechnungsnummer");
@ -28,5 +32,13 @@ public class InvoiceGrid extends Grid<Invoice> {
NumberFormat.getCurrencyInstance()));
invoiceSum.setHeader("Summe");
items = new ArrayList<Invoice>();
setItems(DataProvider.ofCollection(items));
}
public void setItems(List<Invoice> items) {
this.items.clear();
this.items.addAll(items);
getDataProvider().refreshAll();
}
}

@ -1,4 +1,4 @@
package de.kreth.invoice.components;
package de.kreth.invoice.views.invoiceitem;
import java.time.LocalDate;
import java.time.LocalDateTime;

@ -0,0 +1,179 @@
package de.kreth.invoice.views.invoiceitem;
import static de.kreth.invoice.Application.getString;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEM_DATE;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEM_END;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEM_NAME;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEM_PARTICIPANTS;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEM_START;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEM_SUMPRICE;
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.vaadin.flow.component.grid.FooterRow;
import com.vaadin.flow.component.grid.FooterRow.FooterCell;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.GridSelectionModel;
import com.vaadin.flow.component.grid.GridSortOrder;
import com.vaadin.flow.data.provider.DataChangeEvent;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.data.renderer.LocalDateTimeRenderer;
import com.vaadin.flow.data.renderer.NumberRenderer;
import com.vaadin.flow.data.selection.SelectionEvent;
import de.kreth.invoice.data.InvoiceItem;
public class InvoiceItemGrid<T extends InvoiceItem> extends Grid<T> {
private static final long serialVersionUID = -8653320112619816426L;
private final DateTimeFormatter ofLocalizedDateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
private final List<T> items = new ArrayList<>();
private FooterCell priceSumCell;
private FooterCell countCell;
private FooterCell dateSpan;
public InvoiceItemGrid() {
Column<T> articleColumn = addColumn(InvoiceItem::getTitle)
.setHeader(getString(CAPTION_INVOICEITEM_NAME));
LocalDateTimeRenderer<T> renderer = new LocalDateTimeRenderer<>(InvoiceItem::getStart,
DateTimeFormatter.ofPattern("EEE, dd.MM.yyyy"));
Column<T> dateColumn = addColumn(renderer).setHeader(getString(CAPTION_INVOICEITEM_DATE));
dateColumn.setId("Date");
Column<T> startColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getStart,
DateTimeFormatter.ofPattern("HH:mm")))
.setHeader(getString(CAPTION_INVOICEITEM_START));
Column<T> endColumn = addColumn(new LocalDateTimeRenderer<>(InvoiceItem::getEnd,
DateTimeFormatter.ofPattern("HH:mm")))
.setHeader(getString(CAPTION_INVOICEITEM_END));
addColumn(InvoiceItem::getParticipants)
.setHeader(getString(CAPTION_INVOICEITEM_PARTICIPANTS));
Column<T> priceColumn = addColumn(
new NumberRenderer<>(InvoiceItem::getSumPrice, NumberFormat.getCurrencyInstance()))
.setHeader(getString(CAPTION_INVOICEITEM_SUMPRICE));
FooterRow footer = appendFooterRow();
footer = appendFooterRow();
priceSumCell = footer.getCell(priceColumn);
dateSpan = footer.join(dateColumn, startColumn, endColumn);
countCell = footer.getCell(articleColumn);
addSelectionListener(this::selectionChanged);
setItems(DataProvider.ofCollection(items));
this.sort(GridSortOrder.asc(dateColumn).thenAsc(startColumn).build());
getDataProvider().addDataProviderListener(new InnerDataProviderListener());
}
@Override
public ListDataProvider<T> getDataProvider() {
@SuppressWarnings("unchecked")
ListDataProvider<T> dataProvider = (ListDataProvider<T>) super.getDataProvider();
return dataProvider;
}
public void setItems(List<T> collection) {
items.clear();
items.addAll(collection);
getDataProvider().refreshAll();
}
@Override
public GridSelectionModel<T> setSelectionMode(SelectionMode selectionMode) {
GridSelectionModel<T> setSelectionMode = super.setSelectionMode(selectionMode);
setSelectionMode.addSelectionListener(this::selectionChanged);
return setSelectionMode;
}
private void selectionChanged(SelectionEvent<Grid<T>, T> event) {
if (event.getAllSelectedItems().isEmpty()) {
updateFooterWith(getDataProvider().getItems());
} else {
updateFooterWith(event.getAllSelectedItems());
}
}
//
// @SuppressWarnings("unchecked")
// @Override
// protected void internalSetDataProvider(DataProvider<T, ?> dataProvider) {
//
// if (!(dataProvider instanceof ListDataProvider)) {
// throw new IllegalArgumentException("dataProvider must be an instance of ListDataProvider");
// }
// super.internalSetDataProvider(dataProvider);
// dataProvider.addDataProviderListener(new InnerDataProviderListener());
// updateFooterWith(((ListDataProvider<T>) getDataProvider()).getItems());
// }
private void updateFooterWith(Collection<T> selected) {
BigDecimal priceSum = BigDecimal.ZERO;
LocalDate min = null;
LocalDate max = null;
for (T t : selected) {
priceSum = priceSum.add(t.getSumPrice());
min = getMin(min, t.getStart().toLocalDate());
max = getMax(max, t.getEnd().toLocalDate());
}
priceSumCell.setText(NumberFormat.getCurrencyInstance().format(priceSum));
if (min != null && max != null) {
dateSpan.setText(min.format(ofLocalizedDateFormatter) + " - " + max.format(ofLocalizedDateFormatter));
} else {
dateSpan.setText("");
}
countCell.setText("Anzahl: " + selected.size());
}
private LocalDate getMax(LocalDate max, LocalDate localDate) {
if (max == null) {
max = localDate;
} else if (max.isBefore(localDate)) {
max = localDate;
}
return max;
}
private LocalDate getMin(LocalDate min, LocalDate localDate) {
if (min == null) {
min = localDate;
} else if (min.isAfter(localDate)) {
min = localDate;
}
return min;
}
private class InnerDataProviderListener implements DataProviderListener<T> {
private static final long serialVersionUID = -6094992880488082586L;
@Override
public void onDataChange(DataChangeEvent<T> event) {
if (event.getSource() == getDataProvider()) {
ListDataProvider<T> provider = getDataProvider();
updateFooterWith(provider.getItems());
}
}
}
}

@ -0,0 +1,162 @@
package de.kreth.invoice.views.invoiceitem;
import static de.kreth.invoice.Application.getString;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEMS;
import static de.kreth.invoice.Localization_Properties.CAPTION_INVOICEITEM_ADD;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.grid.Grid.SelectionMode;
import com.vaadin.flow.component.grid.ItemClickEvent;
import com.vaadin.flow.component.grid.contextmenu.GridContextMenu;
import com.vaadin.flow.component.grid.contextmenu.GridContextMenu.GridContextMenuItemClickEvent;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.selection.SelectionEvent;
import com.vaadin.flow.data.selection.SelectionListener;
import de.kreth.invoice.Localization_Properties;
import de.kreth.invoice.business.ArticleBusiness;
import de.kreth.invoice.business.InvoiceItemBusiness;
import de.kreth.invoice.data.Article;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.data.User;
public class InvoiceItemOverviewComponent extends VerticalLayout {
private static final long serialVersionUID = -4486121981960039L;
private final InvoiceItemGrid<InvoiceItem> grid;
private final InvoiceItemBusiness invoiceItemRepository;
private final ArticleBusiness articleRepository;
private final User user;
private final List<ItemSelectionChangeListener> selectListener;
public InvoiceItemOverviewComponent(InvoiceItemBusiness invoiceItemRepository,
ArticleBusiness articleRepository, User user) {
this.invoiceItemRepository = invoiceItemRepository;
this.articleRepository = articleRepository;
this.user = user;
this.selectListener = new ArrayList<>();
Button addButton = new Button(getString(CAPTION_INVOICEITEM_ADD), this::createNewitem);
H3 title = new H3(getString(CAPTION_INVOICEITEMS));
HorizontalLayout horizontalLayout = new HorizontalLayout(title, addButton);
add(horizontalLayout);
grid = new InvoiceItemGrid<>();
grid.addItemClickListener(this::itemClicked);
grid.setSelectionMode(SelectionMode.MULTI);
GridContextMenu<InvoiceItem> contextMenu = grid.addContextMenu();
contextMenu.addItem(getString(Localization_Properties.LABEL_DELETE), this::deleteEvent);
add(grid);
}
private void deleteEvent(GridContextMenuItemClickEvent<InvoiceItem> event) {
if (event.getItem().isPresent()) {
ConfirmDialog dlg = new ConfirmDialog();
dlg.setHeader(getString(Localization_Properties.MESSAGE_DELETE_TITLE));
dlg.setText(MessageFormat.format(getString(Localization_Properties.MESSAGE_DELETE_TEXT),
event.getItem().get()));
dlg.setCancelable(true);
dlg.setCancelText("Nicht " + getString(Localization_Properties.LABEL_DELETE));
dlg.setConfirmText(getString(Localization_Properties.LABEL_DELETE));
dlg.addConfirmListener(ev -> {
invoiceItemRepository.delete(event.getItem().get());
refreshData();
});
dlg.open();
}
}
public void addSeelctionListener(ItemSelectionChangeListener listener) {
selectListener.add(listener);
grid.addSelectionListener(new SelectionListener<Grid<InvoiceItem>, InvoiceItem>() {
private static final long serialVersionUID = 597901593658493167L;
@Override
public void selectionChange(SelectionEvent<Grid<InvoiceItem>, InvoiceItem> event) {
ItemSelectionChangeEvent ev;
Set<InvoiceItem> selected = event.getAllSelectedItems();
if (selected.isEmpty()) {
ev = new ItemSelectionChangeEvent(getAllItems());
} else {
ev = new ItemSelectionChangeEvent(selected);
}
listener.itemSelectionChanged(ev);
}
});
}
public void refreshData() {
grid.setItems(invoiceItemRepository.findByInvoiceIsNull(user));
grid.deselectAll();
grid.getDataProvider().refreshAll();
ItemSelectionChangeEvent evt = new ItemSelectionChangeEvent(getAllItems());
for (ItemSelectionChangeListener itemSelectionChangeListener : selectListener) {
itemSelectionChangeListener.itemSelectionChanged(evt);
}
}
private void itemClicked(ItemClickEvent<InvoiceItem> event) {
InvoiceItem item = event.getItem();
if (event.getButton() == 0) {
editItem(item);
}
}
private void createNewitem(ClickEvent<Button> ev) {
InvoiceItem item = new InvoiceItem();
editItem(item);
}
private void editItem(InvoiceItem item) {
List<Article> articles = articleRepository.findByUserId(user.getId());
InvoiceItemDialog dialog = new InvoiceItemDialog(articles, dlg -> {
if (dlg.isClosedWithOk()) {
dlg.writeTo(item);
invoiceItemRepository.save(item);
refreshData();
}
});
dialog.readFrom(item);
dialog.setVisible();
}
public List<InvoiceItem> getAllItems() {
return new ArrayList<>(grid.getDataProvider().getItems());
}
public static interface ItemSelectionChangeListener {
void itemSelectionChanged(ItemSelectionChangeEvent event);
}
public static class ItemSelectionChangeEvent {
private List<InvoiceItem> values;
public ItemSelectionChangeEvent(Collection<InvoiceItem> values) {
this.values = Collections.unmodifiableList(new ArrayList<>(values));
}
public List<InvoiceItem> getValues() {
return values;
}
}
}

@ -0,0 +1,120 @@
package de.kreth.invoice.views.invoiceitem;
import static de.kreth.invoice.Application.getString;
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.TextStyle;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.grid.contextmenu.GridContextMenu;
import com.vaadin.flow.component.grid.contextmenu.GridContextMenu.GridContextMenuItemClickEvent;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import de.kreth.invoice.Localization_Properties;
import de.kreth.invoice.business.InvoiceBusiness;
import de.kreth.invoice.data.Invoice;
import de.kreth.invoice.data.InvoiceItem;
import de.kreth.invoice.data.User;
import de.kreth.invoice.views.invoice.InvoiceDialog;
import de.kreth.invoice.views.invoice.InvoiceDialog.InvoiceMode;
import de.kreth.invoice.views.invoice.InvoiceGrid;
public class InvoiceOverviewComponent extends VerticalLayout {
private static final long serialVersionUID = 7784319346191720329L;
private final InvoiceGrid grid;
private final InvoiceBusiness business;
private final User user;
private final List<InvoiceItem> itemsForInvoice;
private final List<InvoiceCreationListener> creationListener;
public InvoiceOverviewComponent(InvoiceBusiness business, User user, List<InvoiceItem> itemsForInvoice) {
super();
this.business = business;
this.user = user;
this.itemsForInvoice = itemsForInvoice;
this.grid = new InvoiceGrid();
this.creationListener = new ArrayList<>();
Button addButton = new Button(getString(Localization_Properties.CAPTION_INVOICE_CREATE),
this::createNewRechnung);
FormLayout titleComponent = new FormLayout(new H3(getString(Localization_Properties.CAPTION_INVOICES)),
addButton);
add(new VerticalLayout(titleComponent, grid));
grid.addItemClickListener(ev -> openDialog(ev.getItem(), InvoiceMode.VIEW_ONLY));
GridContextMenu<Invoice> menu = grid.addContextMenu();
menu.addItem("Löschen", this::delete);
}
private void delete(GridContextMenuItemClickEvent<Invoice> event) {
if (event.getItem().isPresent()) {
ConfirmDialog dlg = new ConfirmDialog();
dlg.setHeader(getString(Localization_Properties.MESSAGE_DELETE_TITLE));
dlg.setText(MessageFormat.format(getString(Localization_Properties.MESSAGE_DELETE_TEXT),
event.getItem().get()));
dlg.setCancelable(true);
dlg.setCancelText("Nicht " + getString(Localization_Properties.LABEL_DELETE));
dlg.setConfirmText(getString(Localization_Properties.LABEL_DELETE));
dlg.addConfirmListener(ev -> {
business.delete(event.getItem().get());
refreshData();
});
dlg.open();
}
}
public void refreshData() {
List<Invoice> loadAll = business.loadAll(invoice -> user.equals(invoice.getUser()));
grid.setItems(loadAll);
grid.getDataProvider().refreshAll();
}
private void createNewRechnung(ClickEvent<Button> ev) {
if (itemsForInvoice.isEmpty()) {
Notification.show("Es kann keine Abrechnung ohne Positionen erzeugt werden.");
return;
}
Invoice invoice = new Invoice();
invoice.setUser(user);
invoice.setItems(itemsForInvoice);
invoice.setInvoiceId(itemsForInvoice.get(0).getStart().getYear() + "-" +
itemsForInvoice.get(0).getStart().getMonth().getDisplayName(TextStyle.FULL, Locale.GERMANY));
invoice.setInvoiceDate(LocalDateTime.now());
// invoice.setInvoiceId(business.createNextInvoiceId(business.findByUserId(user.getId()), "Rechnung_{0}"));
invoice.setReportRessource(itemsForInvoice.get(0).getArticle().getReport());
openDialog(invoice, InvoiceMode.CREATE);
}
private void openDialog(Invoice invoice, InvoiceMode mode) {
InvoiceDialog dlg = new InvoiceDialog(mode);
dlg.setInvoice(invoice);
dlg.addOkClickListener(evt -> {
business.save(invoice);
refreshData();
for (InvoiceCreationListener invoiceCreationListener : creationListener) {
invoiceCreationListener.invoiceCreated();
}
});
dlg.open();
dlg.addOpenedChangeListener(evt -> refreshData());
}
public void addInvoiceCreationListener(InvoiceCreationListener listener) {
this.creationListener.add(listener);
}
public static interface InvoiceCreationListener {
void invoiceCreated();
}
}

@ -1,4 +1,4 @@
package de.kreth.invoice.views;
package de.kreth.invoice.views.user;
import java.io.File;
import java.io.FileInputStream;
@ -22,16 +22,12 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.upload.Upload;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.data.binder.BinderValidationStatus;
import com.vaadin.flow.server.AbstractStreamResource;
import com.vaadin.flow.server.InputStreamFactory;
import com.vaadin.flow.server.StreamResource;
import de.kreth.invoice.data.Adress;
import de.kreth.invoice.data.User;
import de.kreth.invoice.data.UserAdress;
import de.kreth.invoice.data.UserBank;
import de.kreth.invoice.persistence.UserAdressRepository;
import de.kreth.invoice.persistence.UserBankRepository;
import de.kreth.invoice.report.Signature;
public class UserDetailsDialog extends Dialog {
@ -43,7 +39,7 @@ public class UserDetailsDialog extends Dialog {
private final Binder<UserBank> bankBinder = new Binder<>();
private final Binder<UserAdress> adressBinder = new Binder<>();
private final TextField loginName;
// private final TextField loginName;
private final TextField prename;
@ -66,23 +62,22 @@ public class UserDetailsDialog extends Dialog {
private final Button okButton;
private final Image signatureImage;
private final UserBankRepository bankRepository;
private final UserAdressRepository adressRepository;
//
// private final UserBankRepository bankRepository;
// private final UserAdressRepository adressRepository;
private User user;
private UserBank bank;
private UserAdress adress;
private boolean isValidAndClosedWithOk = false;
public UserDetailsDialog(UserBankRepository bankRepository, UserAdressRepository adressRepository) {
this.bankRepository = bankRepository;
this.adressRepository = adressRepository;
public UserDetailsDialog(// UserBankRepository bankRepository, UserAdressRepository adressRepository
) {
// this.bankRepository = bankRepository;
// this.adressRepository = adressRepository;
loginName = new TextField();
loginName.setLabel("Login Name");
loginName.setEnabled(false);
// loginName = new TextField();
// loginName.setLabel("Login Name");
// loginName.setEnabled(false);
prename = new TextField();
prename.setLabel("Vorname");
@ -90,6 +85,7 @@ public class UserDetailsDialog extends Dialog {
surname = new TextField();
surname.setLabel("Nachname");
surname.setEnabled(false);
bankName = new TextField();
bankName.setLabel("Name der Bank");
@ -111,24 +107,24 @@ public class UserDetailsDialog extends Dialog {
adress1.setLabel("Straße");
adressBinder.forField(adress1)
.asRequired("Die Straße muss angegeben sein.")
.bind(Adress::getAdress1, Adress::setAdress1);
.bind(UserAdress::getAdress1, UserAdress::setAdress1);
adress2 = new TextField();
adress2.setLabel("Adresszusatz");
adressBinder.forField(adress2)
.bind(Adress::getAdress2, Adress::setAdress2);
.bind(UserAdress::getAdress2, UserAdress::setAdress2);
zipCode = new TextField();
zipCode.setLabel("Postleitzahl");
adressBinder.forField(zipCode)
.asRequired("Die Postleitzahl muss angegeben sein.")
.bind(Adress::getZip, Adress::setZip);
.bind(UserAdress::getZip, UserAdress::setZip);
city = new TextField();
city.setLabel("Ort");
adressBinder.forField(city)
.asRequired("Der Ort muss angegeben sein.")
.bind(Adress::getCity, Adress::setCity);
.bind(UserAdress::getCity, UserAdress::setCity);
signatureImage = new Image();
signatureImage.setWidth("192px");
@ -139,7 +135,7 @@ public class UserDetailsDialog extends Dialog {
upload.addFinishedListener(ev -> updateSignatureImage());
VerticalLayout layout = new VerticalLayout();
layout.add(loginName, prename, surname);
layout.add(prename, surname);
layout.add(new Hr());
layout.add(bankName, iban, bic);
layout.add(new Hr());
@ -150,9 +146,12 @@ public class UserDetailsDialog extends Dialog {
okButton = new Button("OK", ev -> {
BinderValidationStatus<UserBank> bankValidation = bankBinder.validate();
BinderValidationStatus<UserAdress> adressValidation = adressBinder.validate();
if (bankValidation.isOk() && adressValidation.isOk()) {
user.setBank(bankBinder.getBean());
user.setAdress(adressBinder.getBean());
isValidAndClosedWithOk = true;
close();
}
});
@ -175,24 +174,22 @@ public class UserDetailsDialog extends Dialog {
throw new UncheckedIOException(e);
}
}
//
// public Registration addOkClickListener(ClickListener listener) {
// return okButton.addClickListener(listener);
// }
public void setUser(User user) {
this.user = Objects.requireNonNull(user);
this.bank = bankRepository.findByUser(user);
this.adress = adressRepository.findByUser(user);
if (this.bank == null) {
this.bank = new UserBank();
}
if (this.adress == null) {
this.adress = new UserAdress();
}
this.prename.setValue(user.getGivenName());
this.surname.setValue(user.getFamilyName());
bankBinder.setBean(user.getBank().clone());
adressBinder.setBean(user.getAdress().clone());
updateSignatureImage();
}
public boolean isValidAndClosedWithOk() {
return isValidAndClosedWithOk;
}
private void updateSignatureImage() {
if (user != null) {
Signature signature = new Signature(user);
@ -200,7 +197,7 @@ public class UserDetailsDialog extends Dialog {
File signatureUrl = signature.getSignatureUrl();
logger.info("Showing signature: {}", signatureUrl);
StreamResource resource = new StreamResource(getAriaLabelString(), new InputStreamFactory() {
StreamResource resource = new StreamResource("Unterschrift", new InputStreamFactory() {
private static final long serialVersionUID = 1L;
@ -214,8 +211,6 @@ public class UserDetailsDialog extends Dialog {
}
});
signatureImage.setSrc(resource);
} else {
signatureImage.setSrc((AbstractStreamResource) null);
}
}
}

@ -0,0 +1,79 @@
#Generated by Eclipse Messages Editor (Eclipse Babel)
caption.adress.city = Ort
caption.adress.street1 = Adresse
caption.adress.street2 = Adresse
caption.adress.zipcode = Postleitzahl
caption.article = Artikel
caption.article.description = Beschreibung
caption.article.price = Stundenpreis
caption.article.title = Titel
caption.article.report = Mit Trainer-Lizenz
caption.article.type.trainer = Trainer
caption.article.type.assistant = Übungsleiter
caption.articles = Artikel
caption.bank.bic = BIC
caption.bank.iban = IBAN
caption.bank.name = Bankname
caption.invoice.create = Rechnung erstellen
caption.invoice.invoicedate = Rechnungsdatum
caption.invoice.invoiceno = Rechnungsnummer
caption.invoice.pattern = Rechnung-{0}
caption.invoice.sum = Summe
caption.invoice.printsignature = Unterschrift drucken
caption.invoiceitem =
caption.invoiceitem.add = Neuer Posten
caption.invoiceitem.date = Datum
caption.invoiceitem.end = Ende
caption.invoiceitem.name = Rechnungsposition
caption.invoiceitem.participants = Teilnehmer
caption.invoiceitem.start = Beginn
caption.invoiceitem.sumprice = Betrag
caption.invoiceitems = Rechnungspositionen
caption.invoices = Rechnungen
caption.user.details = Benutzer Details
caption.user.login = Anmelden
caption.user.loginname = Anmeldename:
caption.user.password = Ihr Password:
caption.user.passwordconfirmation = Password best\u00E4tigen:
caption.user.prename = Vorname:
caption.user.surname = Nachname:
error.invoice.text.noitems = Bitte Posten f\u00FCr Rechnung \
ausw\u00E4hlen.
error.invoice.title.noitems = Leere Abrechnung nicht erlaubt.
error.article.undefined = Bitte Artikel anlegen.
error.userdetails.adress_empty = Adresse darf nicht leer sein.
error.userdetails.bankname_empty = Bankname darf nicht leer sein.
error.userdetails.city_empty = Ort darf nicht leer sein.
error.userdetails.iban_empty = Iban darf nicht leer sein.
error.userdetails.prename_empty = Vorname darf nicht leer sein.
error.userdetails.surname_empty = Nachname darf nicht leer sein.
error.userdetails.username_empty = Anmeldename darf nicht leer sein.
error.userdetails.zip_empty = Postleitzahl darf nicht leer sein.
label.addarticle = Neuer Artikel
label.cancel = Abbrechen
label.close = Schlie\u00DFen
label.delete = L\u00F6schen
label.discart = Verwerfen
label.loggedin = Angemeldet:
label.logout = Abmelden
label.ok = OK
label.store = Speichern
label.preview = Vorschau
label.open = \u00D6ffnen
label.user.register = Registrieren
message.article.priceerror = Bitte legen Sie den Preis fest.
message.article.error.invoiceexists = Kann nicht geändert werden, da bereits Rechnungen bestehen. Bitte neuen Artikel anlegen.
message.delete.text = Soll {0} wirklich gel\u00F6scht werden?
message.delete.title = Wirklich l\u00F6schen?
message.invoiceitem.allfieldsmustbeset = Start, Ende und Artikel m\u00FCssen \
gesetzt sein!
message.invoiceitem.startbeforeend = Ende darf nicht vor Start liegen.
message.user.create.success = {0} erstellt!
message.user.create.failure = Fehler beim Erstellen von Benutzer {0}! Ändern Sie den Benutzernamen oder fragen Sie nach dem Passwort. Detail: {1}
message.user.loginfailure = Anmeldefehler! Falscher Name oder \
Passwort?
message.user.passwordmissmatch = Passworter stimmen nicht \u00FCberein!

@ -11,13 +11,15 @@ CREATE TABLE USER (
CREATE TABLE ADRESS (
ID INT(11) NOT NULL,
USER_ID INT(11) NOT NULL,
ADRESS_TYPE VARCHAR(31) NOT NULL,
ADRESS1 VARCHAR(255) DEFAULT NULL,
ADRESS2 VARCHAR(255) DEFAULT NULL,
ZIP VARCHAR(45) DEFAULT NULL,
CITY VARCHAR(155) DEFAULT NULL,
UPDATED TIMESTAMP NOT NULL,
CREATED TIMESTAMP NOT NULL
CREATED TIMESTAMP NOT NULL,
FOREIGN KEY (USER_ID) references USER(ID)
);
CREATE TABLE ARTICLE (
@ -28,7 +30,8 @@ CREATE TABLE ARTICLE (
USER_ID INT(11) NOT NULL,
REPORT VARCHAR(45) NOT NULL DEFAULT '/REPORTS/MTV_GROSS_BUCHHOLZ.JRXML',
UPDATED TIMESTAMP NOT NULL,
CREATED TIMESTAMP NOT NULL
CREATED TIMESTAMP NOT NULL,
FOREIGN KEY (USER_ID) references USER(ID)
);
CREATE TABLE BANKING_CONNECTION (
@ -37,8 +40,10 @@ CREATE TABLE BANKING_CONNECTION (
BANKNAME VARCHAR(255) DEFAULT NULL,
BIC VARCHAR(255) DEFAULT NULL,
IBAN VARCHAR(255) DEFAULT NULL,
USER_ID INT(11) NOT NULL,
UPDATED TIMESTAMP NOT NULL,
CREATED TIMESTAMP NOT NULL
CREATED TIMESTAMP NOT NULL,
FOREIGN KEY (USER_ID) references USER(ID)
);
CREATE TABLE INVOICE (
@ -48,7 +53,8 @@ CREATE TABLE INVOICE (
USER_ID INT(11) NOT NULL,
SIGN_IMAGE_PATH VARCHAR(255) DEFAULT NULL,
UPDATED TIMESTAMP NOT NULL,
CREATED TIMESTAMP NOT NULL
CREATED TIMESTAMP NOT NULL,
FOREIGN KEY (USER_ID) references USER(ID)
);
CREATE TABLE INVOICE_ITEM (
@ -66,6 +72,6 @@ CREATE TABLE INVOICE_ITEM (
DESCRIPTION VARCHAR(255) DEFAULT NULL,
USER_ID INT(11) NOT NULL,
PRICEPERHOUR DECIMAL(7,2) NOT NULL,
REPORT VARCHAR(255) DEFAULT NULL
REPORT VARCHAR(255) DEFAULT NULL,
FOREIGN KEY (INVOICE_ID) references INVOICE(ID)
);

@ -0,0 +1,83 @@
package de.kreth.invoice.business;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import de.kreth.invoice.data.Invoice;
public class InvoiceBusinessTest {
private InvoiceBusiness business;
@BeforeEach
void initBusiness() {
business = new InvoiceBusiness(null, null);
}
@Test
void testFirstRechnungNoCreation() {
String invoiceId = business.createNextInvoiceId(Collections.emptyList(), "Rechnung_{0}");
assertEquals("Rechnung_1", invoiceId);
}
@Test
void testSimpleIncrementRechnungNoCreation() {
Invoice invoice1 = new Invoice();
invoice1.setInvoiceId("Rechnung_1");
Invoice invoice2 = new Invoice();
invoice2.setInvoiceId("Rechnung_2");
String invoiceId = business.createNextInvoiceId(Arrays.asList(invoice1, invoice2), "Rechnung_{0}");
assertEquals("Rechnung_3", invoiceId);
}
@Test
void testIgnoreNotMatching() {
Invoice invoice1 = new Invoice();
invoice1.setInvoiceId("Rechnung_1");
Invoice invoice2 = new Invoice();
invoice2.setInvoiceId("Rechnung_2");
Invoice invoice3 = new Invoice();
invoice3.setInvoiceId("Rechnung_2_zusatz");
String invoiceId = business.createNextInvoiceId(Arrays.asList(invoice1, invoice2, invoice3), "Rechnung_{0}");
assertEquals("Rechnung_3", invoiceId);
}
@Test
void testFilterMatching() {
String invoiceNo = "Rechnung_324";
assertTrue(business.filter(invoiceNo, "Rechnung_{0}"));
}
@Test
void testFilterMatchingSuffix() {
String invoiceNo = "Rechnung_324_Test";
assertTrue(business.filter(invoiceNo, "Rechnung_{0}_Test"));
}
@Test
void testFilterSuffixNotMatching() {
String invoiceNo = "Rechnung_3_sonder";
assertFalse(business.filter(invoiceNo, "Rechnung_{0}"));
}
@Test
void testFilterWrongSuffixNotMatching() {
String invoiceNo = "Rechnung_3_sonder";
assertFalse(business.filter(invoiceNo, "Rechnung_{0}_Test"));
}
}
Loading…
Cancel
Save