parent
013df232ec
commit
d595ab7534
@ -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,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; |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
@ -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); |
||||||
|
} |
||||||
@ -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("© 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()); |
||||||
|
} |
||||||
|
} |
||||||
@ -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,4 +1,4 @@ |
|||||||
package de.kreth.invoice.components; |
package de.kreth.invoice.views.invoiceitem; |
||||||
|
|
||||||
import java.time.LocalDate; |
import java.time.LocalDate; |
||||||
import java.time.LocalDateTime; |
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(); |
||||||
|
} |
||||||
|
} |
||||||
@ -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! |
||||||
@ -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…
Reference in new issue