/**
* This file is part of Pau's Asset Manager Project.
*
* Pau's Asset Manager Project is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Pau's Asset Manager Project is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Pau's Asset Manager Project. If not, see <http://www.gnu.org/licenses/>.
*/
package org.pau.assetmanager.viewmodel;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang.time.DateUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.pau.assetmanager.business.AnnotationsBusiness;
import org.pau.assetmanager.business.ConceptsBusiness;
import org.pau.assetmanager.business.DaoFunction;
import org.pau.assetmanager.entities.Annotation;
import org.pau.assetmanager.entities.Annotation.AnnotationType;
import org.pau.assetmanager.entities.Book;
import org.pau.assetmanager.entities.Client;
import org.pau.assetmanager.entities.ExpensesAnnotation;
import org.pau.assetmanager.entities.GeneralExpensesAnnotation;
import org.pau.assetmanager.entities.GeneralIncomeAnnotation;
import org.pau.assetmanager.entities.IncomeAnnotation;
import org.pau.assetmanager.entities.MovementExpensesAnnotation;
import org.pau.assetmanager.entities.PropertyExpensesAnnotation;
import org.pau.assetmanager.entities.PropertyIncomeAnnotation;
import org.pau.assetmanager.entities.StockExpensesAnnotation;
import org.pau.assetmanager.entities.StockIncomeAnnotation;
import org.pau.assetmanager.viewmodel.stocks.ConceptValidation;
import org.pau.assetmanager.viewmodel.stocks.HistoricalStocksValuesDownloader;
import org.pau.assetmanager.viewmodel.stocks.StocksHistory;
import org.pau.assetmanager.viewmodel.stocks.StocksIncomeError;
import org.pau.assetmanager.viewmodel.stocks.StocksUtils;
import org.pau.assetmanager.viewmodel.type.BookSelectionType;
import org.pau.assetmanager.viewmodel.utils.AnnotationsFilter;
import org.pau.assetmanager.viewmodel.utils.BookSelection;
import org.pau.assetmanager.viewmodel.utils.SortingCriteria;
import org.zkoss.bind.BindUtils;
import org.zkoss.bind.annotation.BindingParam;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.bind.annotation.DependsOn;
import org.zkoss.bind.annotation.GlobalCommand;
import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.util.Clients;
import com.google.common.base.Optional;
/**
* This class is the ViewModel class that deals with all the UI interactions
* related to the editable grid of annotations. This grid has sorting and
* filtering enabled for all the fields and also controls the enabling and the
* visibility of the editable cells depending on its applicability.
*
* The UI ZUL files involved directly or indirectly (using subclasses of the
* this class) are the following: - general_expenses_annotations.zul -
* general_income_annotations.zul - movement_annotations.zul -
* property_expenses_annotations.zul - property_income_annotations.zul -
* stocks_expenses_annotations.zul - stocks_income_annotations.zul
*
*
* @author Pau Carré Cardona
*
*/
public class AnnotationViewModel {
// book selection in the main view
protected BookSelection bookSelection = null;
// current year selected
protected Integer currentYear = null;
// filtering applied
protected AnnotationsFilter annotationsFilter = new AnnotationsFilter();
// reference to the annotation that is just created
protected Annotation justCreatedAnnotation = null;
// reference to the last created annotation
// TODO: this is not clear at all and makes confusion with the field
// 'justCreatedAnnotation' --> refactor!!
protected Annotation lastestCreatedAnnotation = null;
// the type of annotation selected (income or expenses)
protected AnnotationType annotationType;
protected static Logger logger = LogManager
.getLogger(MonthlyReportViewModel.class);
@Init
public void init(@BindingParam("annotationType") String annotationType) {
Calendar calendar = GregorianCalendar.getInstance();
calendar.setTime(new Date());
this.currentYear = calendar.get(Calendar.YEAR);
this.annotationType = AnnotationType.valueOf(annotationType);
}
public BookSelectionType getBookSelectionType() {
return bookSelection.getBookSelectionType();
}
public Client getSelectedClient() {
return bookSelection.getSelectedClient();
}
public Integer getCurrentYear() {
return currentYear;
}
// Annotations are notified by updateCurrentYear
public void setCurrentYear(Integer currentYear) {
this.currentYear = currentYear;
}
@Command
@NotifyChange({ "annotations", "currentYear" })
public void updateCurrentYear(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
String value = (String) event.getData();
Integer newValue = new Integer(value);
if (!newValue.equals(currentYear)) {
this.currentYear = newValue;
}
}
@Command
public void saveAnnotation(@BindingParam("annotation") Annotation annotation) {
if (annotation.getId() != null) {
if(annotation instanceof MovementExpensesAnnotation){
MovementExpensesAnnotation movementExpensesAnnotation = (MovementExpensesAnnotation) annotation;
DaoFunction.mergeFunction().apply(movementExpensesAnnotation.getMovementIncomeAnnotation());
}
DaoFunction.mergeFunction().apply(annotation);
HistoricalStocksValuesDownloader.updateStocksHistoricalValuesAsync();
BindUtils.postGlobalCommand(null, null, "updateReportAnnotations",
null);
}
}
@NotifyChange({ "annotations", "getConceptsForFiltering", "getConcepts" })
@Command
public void saveAnnotationWithConcept(
@BindingParam("annotation") Annotation annotation,
@ContextParam(ContextType.TRIGGER_EVENT) InputEvent event) {
if (annotation.getId() != null) {
String concept = event.getValue();
annotation.setConcept(concept);
if(annotation instanceof MovementExpensesAnnotation){
MovementExpensesAnnotation movementExpensesAnnotation = (MovementExpensesAnnotation) annotation;
DaoFunction.mergeFunction().apply(movementExpensesAnnotation.getMovementIncomeAnnotation());
}
DaoFunction.mergeFunction().apply(annotation);
HistoricalStocksValuesDownloader.updateStocksHistoricalValuesAsync();
BindUtils.postGlobalCommand(null, null, "updateReportAnnotations",
null);
}
}
@DependsOn({ "selectedBook", "bookSelectionType", "selectedClient",
"currentYear", "annotationsFilter" })
public List<Annotation> getAnnotations() {
List<Annotation> annotations = new LinkedList<Annotation>();
if (bookSelection.getSelectedBook() != null
&& bookSelection.getBookSelectionType().equals(
BookSelectionType.SINGLE_BOOK)) {
annotations = AnnotationsBusiness.getNonMovementAnnotations(
Optional.<AnnotationType> of(annotationType),
bookSelection, Optional.<Integer> of(currentYear),
SortingCriteria.DESCENDING, annotationsFilter);
if (justCreatedAnnotation != null
&& annotations.contains(justCreatedAnnotation)) {
annotations.remove(justCreatedAnnotation);
annotations.add(0, justCreatedAnnotation);
justCreatedAnnotation = null;
}
}
Clients.clearBusy();
return annotations;
}
@NotifyChange({ "selectedBook", "bookSelectionType", "selectedClient" })
@GlobalCommand
public void updateBookSelection(
@BindingParam("bookSelection") BookSelection bookSelection) {
this.bookSelection = bookSelection;
}
@NotifyChange({ "annotations" })
@Command
public void addPropertyAnnotation() {
Annotation annotation = null;
if (annotationType.equals(AnnotationType.INCOME)) {
PropertyIncomeAnnotation incomeAnnotation = new PropertyIncomeAnnotation();
incomeAnnotation.setUpDefaults();
annotation = incomeAnnotation;
addDefaultValuesToAnnotation(annotation,
bookSelection.getSelectedBook(), currentYear,
annotationsFilter);
} else {
PropertyExpensesAnnotation expensesAnnotation = new PropertyExpensesAnnotation();
expensesAnnotation.setUpDefaults();
annotation = expensesAnnotation;
addDefaultValuesToAnnotation(annotation,
bookSelection.getSelectedBook(), currentYear,
annotationsFilter);
}
addAnnotation(annotation);
Clients.clearBusy();
}
@NotifyChange("wrongStockConcepts")
@GlobalCommand
public void updateReportAnnotations() {
}
public Annotation getJustCreatedAnnotation() {
return justCreatedAnnotation;
}
public AnnotationsFilter getAnnotationsFilter() {
return annotationsFilter;
}
public void setAnnotationsFilter(AnnotationsFilter annotationsFilter) {
this.annotationsFilter = annotationsFilter;
}
public Annotation getLastestCreatedAnnotation() {
return lastestCreatedAnnotation;
}
// TODO: check it!!
public List<String> getConceptsForFiltering(
@BindingParam("type") String type) {
List<String> conceptsForFiltering = new LinkedList<String>();
conceptsForFiltering.addAll(getConcepts(type));
// sort concept in a case-unsensitive way
Collections.sort(conceptsForFiltering, new Comparator<String>() {
@Override
public int compare(String stringA, String stringB) {
return stringA.compareToIgnoreCase(stringB);
}
});
conceptsForFiltering.add(0, AnnotationsFilter.ALL_CONCEPTS);
return conceptsForFiltering;
}
public Book getSelectedBook() {
return bookSelection.getSelectedBook();
}
@NotifyChange({ "annotations" })
@Command
public void addGeneralAnnotation() {
Annotation annotation = null;
if (annotationType.equals(AnnotationType.EXPENSES)) {
ExpensesAnnotation stockExpensesAnnotation = new GeneralExpensesAnnotation();
annotation = stockExpensesAnnotation;
addDefaultValuesToAnnotation(annotation,
bookSelection.getSelectedBook(), currentYear,
annotationsFilter);
} else if (annotationType.equals(AnnotationType.INCOME)) {
IncomeAnnotation stockIncomeAnnotation = new GeneralIncomeAnnotation();
annotation = stockIncomeAnnotation;
addDefaultValuesToAnnotation(annotation,
bookSelection.getSelectedBook(), currentYear,
annotationsFilter);
}
addAnnotation(annotation);
Clients.clearBusy();
}
@NotifyChange({ "annotations" })
@Command
public void addStocksAnnotation() {
Annotation annotation = null;
if (annotationType.equals(AnnotationType.EXPENSES)) {
StockExpensesAnnotation stockExpensesAnnotation = new StockExpensesAnnotation();
annotation = stockExpensesAnnotation;
} else if (annotationType.equals(AnnotationType.INCOME)) {
StockIncomeAnnotation stockIncomeAnnotation = new StockIncomeAnnotation();
annotation = stockIncomeAnnotation;
}
addDefaultValuesToAnnotation(annotation,
bookSelection.getSelectedBook(), currentYear, annotationsFilter);
addAnnotation(annotation);
Clients.clearBusy();
}
protected Annotation addAnnotation(Annotation annotation) {
annotation = AnnotationsBusiness.createAnnotation(annotation);
justCreatedAnnotation = annotation;
lastestCreatedAnnotation = justCreatedAnnotation;
annotationsFilter.clearDateAndCostFilter();
BindUtils
.postGlobalCommand(null, null, "updateReportAnnotations", null);
return annotation;
}
protected void addDefaultValuesToAnnotation(Annotation annotation,
Book selectedBook, Integer currentYear,
AnnotationsFilter annotationsFilter) {
annotation.setBook(selectedBook);
annotation.setAmount(0.0);
Calendar calendar = GregorianCalendar.getInstance();
Integer systemDateYear = calendar.get(Calendar.YEAR);
if (!systemDateYear.equals(currentYear)) {
String dateAsString = "01/01/" + currentYear;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(
"dd/MM/yyyy");
try {
Date dateForNewAnnotation = simpleDateFormat
.parse(dateAsString);
annotation.setDate(dateForNewAnnotation);
} catch (ParseException e) {
logger.error("Error parsing date", e);
}
} else {
Calendar currentCalendar = GregorianCalendar.getInstance();
currentCalendar = DateUtils
.truncate(currentCalendar, Calendar.DATE);
annotation.setDate(currentCalendar.getTime());
}
if (annotationsFilter.getConcept() != null
&& !annotationsFilter.getConcept().equals(
AnnotationsFilter.ALL_CONCEPTS)) {
annotation.setConcept(annotationsFilter.getConcept());
} else {
annotation.setConcept(getDefaultConcept(annotation));
}
}
private String getDefaultConcept(Annotation annotation) {
String concept = ConceptsBusiness.getDefaultConcept(annotation
.getClass().getSimpleName());
return concept;
}
@NotifyChange({ "annotations" })
@Command
public void deleteAnnotation(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
Annotation annotation = (Annotation) event.getData();
AnnotationsBusiness.deleteAnnotation(annotation);
BindUtils
.postGlobalCommand(null, null, "updateReportAnnotations", null);
Clients.clearBusy();
}
@NotifyChange({ "annotations" })
@Command
public void duplicateAnnotation(
@BindingParam("annotation") Annotation annotation,
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotation.setId(null);
annotation.setDate(new Date());
addAnnotation(annotation);
}
// TODO: check this for type
public List<String> getConcepts(@BindingParam("type") String type) {
List<String> concepts = new LinkedList<String>();
concepts.add(ConceptsBusiness.getDefaultConcept(type));
concepts.addAll(ConceptsBusiness.getConceptsUsedInBook(bookSelection));
Collections.sort(concepts);
return concepts;
}
/**
* @return the list of wrong/inconsistent stock concepts. The method returns
* all the sold stock that were done at a time in which the number
* of bought stocks is below the amount of bought stocks (i.e. it is
* not possible to sell stocks one does not previously have)
*/
@DependsOn("annotations")
public List<ConceptValidation> getWrongStockConcepts() {
List<ConceptValidation> conceptValidations = new LinkedList<ConceptValidation>();
List<Annotation> annotations = StocksYearlyReportViewModel
.getStocksAnnotationsUntilYear(bookSelection, currentYear);
StocksHistory stocksHistory = StocksUtils.getStocksHistory(annotations);
for (String currentConcept : stocksHistory.getStocksErrorsHistoryMap()
.keySet()) {
StocksIncomeError currentError = stocksHistory
.getStocksErrorsHistoryMap().get(currentConcept);
ConceptValidation conceptValidation = new ConceptValidation(
currentConcept, currentError.toString());
conceptValidations.add(conceptValidation);
}
return conceptValidations;
}
@Command
@NotifyChange({ "annotations" })
public void refreshFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableDateFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setDateFrom(null);
annotationsFilter.setDateTo(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableForCompanyFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setForCompany(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableUseQuarterly(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setUseQuarterly(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableCostFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setCostFrom(null);
annotationsFilter.setCostTo(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableRetentionFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setRetentionFrom(null);
annotationsFilter.setRetentionTo(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableVatFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setVatFrom(null);
annotationsFilter.setVatTo(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableUseYearlyFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setUseYearly(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableDoneFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setDone(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableDeductiblePercentageFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setDeductiblePercentageFrom(null);
annotationsFilter.setDeductiblePercentageTo(null);
}
@Command
@NotifyChange({ "annotations", "annotationsFilter" })
public void disableCommunityFilter(
@ContextParam(ContextType.TRIGGER_EVENT) Event event) {
annotationsFilter.setCommunity(null);
}
}