/*
* Copyright (C) 2011 4th Line GmbH, Switzerland
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fourthline.konto.client.report;
import com.google.gwt.activity.shared.AbstractActivity;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.AcceptsOneWidget;
import com.google.web.bindery.event.shared.EventBus;
import org.fourthline.konto.client.ledger.event.AccountSelectionChange;
import org.fourthline.konto.client.ledger.event.AccountSelectionModeChange;
import org.fourthline.konto.client.report.view.ReportResultView;
import org.fourthline.konto.client.report.view.ReportSelectView;
import org.fourthline.konto.client.report.view.ReportView;
import org.fourthline.konto.client.service.CurrencyServiceAsync;
import org.fourthline.konto.client.service.LedgerServiceAsync;
import org.fourthline.konto.client.service.ReportServiceAsync;
import org.fourthline.konto.client.settings.GlobalSettings;
import org.fourthline.konto.client.settings.event.GlobalSettingsRefreshedEvent;
import org.fourthline.konto.shared.AccountType;
import org.fourthline.konto.shared.Constants;
import org.fourthline.konto.shared.entity.MonetaryUnit;
import org.fourthline.konto.shared.entity.settings.GlobalOption;
import org.fourthline.konto.shared.query.LineReportCriteria;
import org.fourthline.konto.shared.query.LineReportOption;
import org.fourthline.konto.shared.result.ReportLines;
import org.seamless.gwt.component.client.Print;
import org.seamless.gwt.notify.client.Message;
import org.seamless.gwt.notify.client.NotifyEvent;
import org.seamless.gwt.notify.client.ServerFailureNotifyEvent;
import org.seamless.util.time.DateRange;
import javax.inject.Inject;
import java.util.*;
import java.util.logging.Level;
/**
* TODO: The view/criteria state management is buggy and a mess.
* Cleanup when graph feature has been written.
*
* @author Christian Bauer
*/
public class ReportActivity extends AbstractActivity
implements
ReportView.Presenter,
ReportSelectView.Presenter,
ReportResultView.Presenter,
AccountSelectionChange.Handler,
GlobalSettingsRefreshedEvent.Handler {
class InitCurrencyCodesCallback implements AsyncCallback<List<MonetaryUnit>> {
@Override
public void onFailure(Throwable caught) {
bus.fireEvent(new ServerFailureNotifyEvent(caught));
}
@Override
public void onSuccess(List<MonetaryUnit> result) {
List<String> currencyCodes = new ArrayList<String>();
for (MonetaryUnit monetaryUnit : result) {
currencyCodes.add(monetaryUnit.getCurrencyCode());
}
view.getReportSelectView().setCurrencyCodes(
currencyCodes.toArray(new String[currencyCodes.size()]),
criteria != null ? criteria.getCurrencyCode() : view.getReportSelectView().getCurrencyCode()
);
}
}
final ReportView view;
final PlaceController placeController;
final EventBus bus;
final ReportServiceAsync reportService;
final LedgerServiceAsync ledgerService;
final CurrencyServiceAsync currencyService;
GlobalSettings globalSettings;
LineReportCriteria criteria;
@Inject
public ReportActivity(ReportView view,
PlaceController placeController,
EventBus bus,
ReportServiceAsync reportService,
LedgerServiceAsync ledgerService,
CurrencyServiceAsync currencyService,
GlobalSettings globalSettings) {
this.view = view;
this.placeController = placeController;
this.bus = bus;
this.reportService = reportService;
this.ledgerService = ledgerService;
this.currencyService = currencyService;
onSettingsRefreshed(globalSettings);
}
public ReportActivity init(ReportPlace place) {
criteria = place.getCriteria();
return this;
}
@Override
public void start(AcceptsOneWidget containerWidget, com.google.gwt.event.shared.EventBus activityBus) {
if (criteria == null) {
resetCriteria(view.getReportSelectView().getReportType(), true);
// Now update the URL bar in the browser
new Timer() {
@Override
public void run() {
goTo(new ReportPlace(criteria));
}
}.schedule(1); // TODO: Asynchronous, otherwise URL bar doesn't refresh
return;
}
view.setPresenter(this);
view.getReportSelectView().setPresenter(this);
view.getReportSelectView().setCriteria(criteria);
view.getReportResultWidget().setPresenter(this);
containerWidget.setWidget(view.asWidget());
activityBus.addHandler(AccountSelectionChange.TYPE, this);
activityBus.addHandler(GlobalSettingsRefreshedEvent.TYPE, this);
// We store the account selection in our view because we have no direct
// connection with the account select tree and can't query its state
view.setAccountSelection(criteria.getAccountSelection());
// Init the currencies
currencyService.getMonetaryUnits(new InitCurrencyCodesCallback());
// Switch the account tree select widget to whatever is in the criteria
bus.fireEvent(new AccountSelectionModeChange(
criteria.getType().getAccountSelectOptions(),
criteria.getAccountSelection()
));
// Execute the query on start, the only way to "refresh" is to go to a new ReportPlace
bus.fireEvent(new NotifyEvent(
new Message(
true, // Modal
Level.INFO,
"Generating Report",
"Please wait..."
)
));
reportService.getReportLines(
criteria,
new AsyncCallback<ReportLines[]>() {
@Override
public void onFailure(Throwable caught) {
bus.fireEvent(new ServerFailureNotifyEvent(caught));
}
@Override
public void onSuccess(ReportLines[] result) {
Map<AccountType, ReportLines> linesByType = new LinkedHashMap();
for (ReportLines r : result) {
linesByType.put(r.getCriteria().getType(), r);
}
if (linesByType.size() == 0 ||
linesByType.entrySet().iterator().next().getKey() == null) {
bus.fireEvent(new NotifyEvent(new Message(
Level.INFO,
"Empty report generated...",
"Please select accounts or change settings!")
));
return;
} else {
bus.fireEvent(new NotifyEvent());
}
view.getReportResultWidget().setReportLines(
criteria,
linesByType
);
}
}
);
}
@Override
public void onSettingsRefreshed(GlobalSettings gs) {
this.globalSettings = gs;
view.getReportSelectView().setDateFormat(gs.getValue(GlobalOption.OPT_DATE_FORMAT));
view.getReportResultWidget().setDateFormat(gs.getValue(GlobalOption.OPT_DATE_FORMAT));
view.getReportResultWidget().setRoundFractions(gs.getValue(GlobalOption.OPT_ROUND_FRACTIONS_IN_REPORTS));
}
@Override
public void onReportTypeSelected(LineReportType type) {
resetCriteria(type, false);
goTo(new ReportPlace(criteria));
}
@Override
public void onReportOptionsUpdated(LineReportOption reportOptions) {
criteria = new LineReportCriteria(
criteria.getAccountSelection(),
criteria.getCurrencyCode(),
criteria.getDayOfExchangeRate(),
criteria.getType(),
criteria.getRange(),
reportOptions
);
goTo(new ReportPlace(criteria));
}
@Override
public void onAccountSelectionChange(AccountSelectionChange event) {
view.setAccountSelection(event.getSelection()); // Store it in the shared view
criteria = new LineReportCriteria(
event.getSelection(),
criteria.getCurrencyCode(),
criteria.getDayOfExchangeRate(),
criteria.getType(),
criteria.getRange(),
criteria.getOptions()
);
goTo(new ReportPlace(criteria));
}
@Override
public void onDateRangeUpdated(DateRange dateRange) {
criteria = new LineReportCriteria(
criteria.getAccountSelection(),
criteria.getCurrencyCode(),
criteria.getDayOfExchangeRate(),
criteria.getType(),
dateRange,
criteria.getOptions()
);
goTo(new ReportPlace(criteria));
}
@Override
public void onCurrencySelected(String currencyCode) {
criteria = new LineReportCriteria(
criteria.getAccountSelection(),
currencyCode,
criteria.getDayOfExchangeRate(),
criteria.getType(),
criteria.getRange(),
criteria.getOptions()
);
goTo(new ReportPlace(criteria));
}
@Override
public void onBookmark() {
StringBuilder title = new StringBuilder();
title.append(criteria.getType().getLabel());
title.append(" | ");
title.append(criteria.getType().getLabelSub(
globalSettings.getValue(GlobalOption.OPT_DATE_FORMAT),
criteria
));
view.promptBookmark(title.toString());
}
@Override
public void onPrint() {
ReportResultView.Style style = view.getReportResultWidget().getPrintStyle();
StringBuilder sb = new StringBuilder();
sb.append("<style type=\"text/css\" media=\"print\">");
sb.append(style.getText());
sb.append("body { font-family: sans-serif;}");
// TODO: Font size smaller!
sb.append("</style>");
Print.it(sb.toString(), view.getReportResultWidget().getPrintObject());
}
@Override
public void goTo(Place place) {
placeController.goTo(place);
}
protected void resetCriteria(LineReportType type, boolean useViewState) {
// TODO: Ugh...
// If the type didn't change we can preserve some existing criteria/view settings
criteria = new LineReportCriteria(
// Reset account selection - unless we have a stored one in the view
view.getAccountSelection() != null && useViewState
? view.getAccountSelection()
: type.getDefaultAccountSelection(),
// Reset currency (always take the selected currency code from view)
view.getReportSelectView().getCurrencyCode(),
// Reset day of exchange rate
new Date(),
// Reset type
type,
// Preserve date range of current view
view.getReportSelectView().getDateRange(type),
// Preserve other options of current view, if possible
useViewState
?
new LineReportOption(
ReportSelectView.Option.ENABLE_ENTRY_DETAILS.in(type.getReportSelectOptions())
&& view.getReportSelectView().getReportOptions().isEntryDetails(),
ReportSelectView.Option.ENABLE_ENTRY_DETAILS.in(type.getReportSelectOptions())
&& view.getReportSelectView().getReportOptions().isEntryAccounts(),
view.getReportSelectView().getReportOptions().isExchangeRates(),
view.getReportSelectView().getReportOptions().isZeroBalances()
)
:
type.getDefaultOptions()
);
}
}