/*
* Copyright (C) 2014 GG-Net GmbH - Oliver Günther
*
* This program 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.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.ggnet.dwoss.redtape;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.text.NumberFormat;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.SoftBevelBorder;
import net.sf.jasperreports.engine.JasperPrint;
import eu.ggnet.dwoss.customer.api.CustomerCos;
import eu.ggnet.dwoss.customer.api.CustomerService;
import eu.ggnet.dwoss.mandator.MandatorSupporter;
import eu.ggnet.dwoss.mandator.api.DocumentViewType;
import eu.ggnet.dwoss.mandator.api.service.ShippingCostService;
import eu.ggnet.dwoss.redtape.action.*;
import eu.ggnet.dwoss.redtape.dossiertable.DossierTableController;
import eu.ggnet.dwoss.redtape.entity.*;
import eu.ggnet.dwoss.redtape.entity.Document.Condition;
import eu.ggnet.dwoss.redtape.entity.Document.Directive;
import eu.ggnet.dwoss.redtape.format.DocumentFormater;
import eu.ggnet.dwoss.redtape.state.*;
import eu.ggnet.dwoss.redtape.state.RedTapeStateTransition.Hint;
import eu.ggnet.dwoss.rights.api.AtomicRight;
import eu.ggnet.dwoss.rules.*;
import eu.ggnet.dwoss.util.*;
import eu.ggnet.saft.core.*;
import eu.ggnet.saft.core.authorisation.AccessableAction;
import eu.ggnet.saft.core.authorisation.Guardian;
import eu.ggnet.statemachine.StateTransition;
import lombok.Getter;
import static eu.ggnet.dwoss.rights.api.AtomicRight.CREATE_ANNULATION_INVOICE;
import static eu.ggnet.saft.core.Client.lookup;
/**
* The RedTape main component controller handling all in/output as well as update actions provided by the {@link RedTapeView}.
* <p/>
* @author pascal.perau
*/
public class RedTapeController implements IDossierSelectionHandler {
@Override
public void selected(Dossier dossier) {
model.setSelectedDossier(dossier);
}
private RedTapeModel model;
private RedTapeView view;
@Getter
private final DossierTableController dossierTableController;
private Set<Action> accessDependentActions;
private final NavigableSet<Long> viewOnlyCustomerIds = Client.lookup(MandatorSupporter.class).loadSalesdata().getViewOnlyCustomerIds();
private SwingWorker<Void, Dossier> closedLoader;
private final boolean isShippingCostUiHelpEnabled = Client.hasFound(ShippingCostService.class);
private final PropertyChangeListener redTapeViewListener = new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
switch (evt.getPropertyName()) {
case RedTapeModel.PROP_SELECTED_DOSSIER:
//break if null selection
if ( model.getSelectedDossier() == null ) {
model.setDocuments(new ArrayList<>());
view.dossierCommentArea.setText("");
break;
}
Dossier selectedDossier = model.getSelectedDossier();
// Set null to ensure a fire for property change.
model.setSelectedDocument(null);
model.setDocuments(new ArrayList<>());
model.setDocuments(selectedDossier.getActiveDocuments());
model.setSelectedDocument(refreshDocumentSelection(selectedDossier));
view.documentList.setSelectedValue(model.getSelectedDocument(), true);
view.dossierCommentArea.setText(model.getSelectedDossier().getComment());
fillToolBar();
break;
case RedTapeModel.PROP_SELECTED_DOCUMENT:
model.setPositions(new TreeSet<>());
if ( model.getSelectedDocument() == null ) {
view.priceSumLabel.setText(NumberFormat.getCurrencyInstance().format(0.));
view.afterTaxSumLabel.setText(NumberFormat.getCurrencyInstance().format(0.));
view.positionAmountLabel.setText("" + 0);
} else {
if ( model.getPurchaseCustomer() == null ) JOptionPane.showMessageDialog(null, "Kein Kunde gewählt");
else {
CustomerDocument cdoc = new CustomerDocument(
model.getPurchaseCustomer().getFlags(),
model.getSelectedDocument(),
model.getPurchaseCustomer().getShippingCondition(),
model.getPurchaseCustomer().getPaymentMethod());
List<StateTransition<CustomerDocument>> transitions = lookup(RedTapeWorker.class).getPossibleTransitions(cdoc);
// Remove old Actions from the receiving of right changes.
for (Action accessDependent : accessDependentActions) {
if ( accessDependent instanceof AccessableAction )
lookup(Guardian.class).remove((AccessableAction)accessDependent);
else lookup(Guardian.class).remove(accessDependent);
}
accessDependentActions.clear();
List<Action> stateActions = new ArrayList<>();
//check if selected document is order and invoice exist to restrict transitions
if ( model.getSelectedDocument().getDossier().getId() == 0 ) {
// Now this implies a legacy wrapped dossier, so no actions are possible.
} else if ( (model.getSelectedDocument().getType() == DocumentType.ORDER
&& !model.getSelectedDossier().getActiveDocuments(DocumentType.INVOICE).isEmpty())
|| viewOnlyCustomerIds.contains(model.getPurchaseCustomer().getId()) ) {
} else {
for (StateTransition<CustomerDocument> originalStateTransition : transitions) {
RedTapeStateTransition stateTransition = (RedTapeStateTransition)originalStateTransition;
if ( RedTapeStateTransitions.ADD_SHIPPING_COSTS.contains(stateTransition) && isShippingCostUiHelpEnabled ) {
Action a = new ModifyShippingCostStateAction(parent(), RedTapeController.this, cdoc, stateTransition);
stateActions.add(a);
} else if ( RedTapeStateTransitions.REMOVE_SHIPPING_COSTS.contains(stateTransition) && isShippingCostUiHelpEnabled ) {
stateActions.add(new RemoveShippingCostStateAction(parent(), RedTapeController.this, cdoc, stateTransition));
} else if ( stateTransition.getHints().contains(Hint.CREATES_CREDIT_MEMO) ) {
CreditMemoAction creditMemoAction = new CreditMemoAction(parent(), RedTapeController.this, model.getSelectedDocument(), stateTransition);
lookup(Guardian.class).add(creditMemoAction);
accessDependentActions.add(creditMemoAction);
stateActions.add(creditMemoAction);
} else if ( stateTransition.getHints().contains(Hint.CREATES_COMPLAINT) ) {
ComplaintAction action = new ComplaintAction(parent(), RedTapeController.this, model.getSelectedDocument(), stateTransition);
stateActions.add(action);
} else if ( stateTransition.getHints().contains(Hint.CREATES_ANNULATION_INVOICE) ) {
AnnulationInvoiceAction action = new AnnulationInvoiceAction(parent(), RedTapeController.this, model.getSelectedDocument(), stateTransition);
lookup(Guardian.class).add(action);
accessDependentActions.add(action);
stateActions.add(action);
} else if ( stateTransition.getEnablingRight() != null && stateTransition.getEnablingRight().equals(AtomicRight.CREATE_ANNULATION_INVOICE) ) {
StateTransitionAction action = new StateTransitionAction(parent(), RedTapeController.this, cdoc, stateTransition);
lookup(Guardian.class).add(action, AtomicRight.CREATE_ANNULATION_INVOICE);
accessDependentActions.add(action);
stateActions.add(action);
} else {
stateActions.add(new StateTransitionAction(parent(), RedTapeController.this, cdoc, stateTransition));
}
}
if ( model.getSelectedDocument().getType() == DocumentType.BLOCK ) {
DossierDeleteAction action = new DossierDeleteAction(parent(), RedTapeController.this, model.getSelectedDossier());
lookup(Guardian.class).add(action);
accessDependentActions.add(action);
stateActions.add(action);
}
}
view.setStateActions(stateActions);
model.setPositions(new TreeSet<>(model.getSelectedDocument().getPositions().values()));
}
view.priceSumLabel.setText(NumberFormat.getCurrencyInstance().format(model.getSelectedDocument().getPrice()));
view.afterTaxSumLabel.setText(NumberFormat.getCurrencyInstance().format(model.getSelectedDocument().getAfterTaxPrice()));
int i = model.getSelectedDocument().getPositions().size();
view.positionAmountLabel.setText("" + i);
view.positionAmountLabel.setForeground(i >= 20 ? Color.red : Color.black);
Font f = new Font(view.positionAmountLabel.getFont().getName(), i >= 20 ? Font.BOLD : Font.PLAIN, view.positionAmountLabel.getFont().getSize());
view.positionAmountLabel.setFont(f);
fillToolBar();
}
break;
case RedTapeModel.PROP_SELECTED_SEARCH_RESULT:
reloadSelectionOnCustomerChange();
break;
case RedTapeModel.PROP_SEARCH:
model.setSearchResult(lookup(UniversalSearcher.class).searchCustomers(model.getSearch()));
break;
}
}
};
public RedTapeController() {
this.accessDependentActions = new HashSet<>();
this.dossierTableController = new DossierTableController();
dossierTableController.setSelectionHandler(RedTapeController.this);
}
/**
* Set the model to the controller
* <p/>
* @param model
*/
public void setModel(RedTapeModel model) {
if ( this.model != null ) this.model.removePropertyChangeListener(redTapeViewListener);
this.model = model;
dossierTableController.setModel(model.getDossierTableModel());
this.model.addPropertyChangeListener(redTapeViewListener);
}
/**
* Get the model from the controller.
* <p/>
* @return The recent model of the view
*/
public RedTapeModel getModel() {
return model;
}
/**
* Get the value of view
*
* @return the value of view
*/
public RedTapeView getView() {
return view;
}
/**
* Set the value of view
*
* @param view new value of view
*/
public void setView(RedTapeView view) {
if ( this.view != null ) this.view.removePropertyChangeListener(redTapeViewListener);
this.view = view;
dossierTableController.setView(view.dossierTableView);
this.view.addPropertyChangeListener(redTapeViewListener);
}
/**
* Reload customer and all Dossiers.
*/
private void reloadSelectionOnCustomerChange() {
model.setSelectedDocument(null);
model.setSelectedDossier(null);
//ensure that even without selection a customer is found
if ( model.getSelectedSearchResult() == 0 ) model.setPurchaseCustomer(model.getPurchaseCustomer().getId());
else model.setPurchaseCustomer(model.getSelectedSearchResult());
view.dossierButtonPanel.removeAll();
view.dossierButtonPanel.repaint();
view.dossierCommentArea.setText("");
if ( closedLoader != null && !closedLoader.isDone() ) {
if ( !closedLoader.cancel(false) )
JOptionPane.showMessageDialog(view, "Canceling of running loader not possible, call Olli!");
}
view.dossierTableView.resetTableData((int)model.getPurchaseCustomer().getId());
}
/**
* Updates the selection in case of data change.
*/
public void reloadSelectionOnStateChange(Dossier dos) {
Dossier oldDos = model.getSelectedDossier();
dossierTableController.getModel().update(oldDos, dos);
model.setSelectedDossier(null);
model.setSelectedDossier(dos);
}
/**
* Updates the selection in case of dossier deletion.
* <p/>
* @param dos
*/
public void reloadOnDelete(Dossier dos) {
dossierTableController.getModel().delete(dos);
model.setSelectedDocument(null);
model.setSelectedDossier(null);
view.dossierButtonPanel.removeAll();
view.dossierButtonPanel.repaint();
}
/**
* Opens a dialog to create a Customer.
*/
public void openCreateCustomer() {
long customerId = lookup(CustomerCos.class).createCustomer();
if ( customerId == 0 ) {
System.out.println("Da zero customer of doom !!!!!!!!!!!!!!!!!!!!!!!!");
return;
}
model.setPurchaseCustomer(customerId);
//reset search to avoid wrong customer selections
model.setSearch(String.valueOf(customerId));
view.searchResultList.setSelectedIndex(0);
}
/**
* Opens a dialog to edit a Customer.
* <p/>
* @param recentCustomerId The customer that shall be edited
*/
public void openUpdateCustomer(long recentCustomerId) {
if ( !lookup(CustomerCos.class).updateCustomer(recentCustomerId) ) return;
//reset search to avoid wrong customer selections
model.setSearch(String.valueOf(recentCustomerId));
view.searchResultList.setSelectedIndex(0);
reloadSelectionOnCustomerChange();
}
/**
* Opens a dialog to edit a dossiers comment.
* <p/>
* If Dossier.id equals 0, a wrapped Dossier from an old Sopo Auftrag is assumed and no change can be made.
* <p/>
* @param dossier the dossier from wich the comment will be changed
*/
public void openEditComment(Dossier dossier) {
if ( model.getSelectedDossier() == null ) {
JOptionPane.showMessageDialog(view, "Kein Auftrag ausgewählt");
return;
}
StringAreaView sav = new StringAreaView(dossier.getComment());
OkCancelDialog<StringAreaView> dialog = new OkCancelDialog<>(parent(), Dialog.ModalityType.DOCUMENT_MODAL, "Bemerkungen editieren", sav);
dialog.setVisible(true);
if ( dialog.getCloseType() == CloseType.OK ) {
try {
Dossier dos = lookup(RedTapeWorker.class).updateComment(model.getSelectedDossier(), sav.getText());
reloadSelectionOnStateChange(dos);
} catch (UserInfoException ex) {
UiCore.handle(ex);
}
}
}
/**
* This method is called if a chosen Document will be printed and/or sent per E-Mail.
* This Method become a Document and will open a JasperViewer that contains also a Send Button for sending E-Mail
* <p/>
* @param document
*/
public void openDocument(Document document, boolean printAsReservation) {
JasperPrint print = lookup(DocumentSupporter.class).render(document, (printAsReservation ? DocumentViewType.RESERVATION : DocumentViewType.DEFAULT));
JDialog d = new JDialog(parent(), "Dokument drucken/versenden");
d.setSize(800, 1000);
d.setModalityType(Dialog.ModalityType.DOCUMENT_MODAL);
d.setLocationRelativeTo(view);
d.getContentPane().setLayout(new BorderLayout());
boolean canEmaild = model.getPurchaseCustomer().getEmail() != null && model.getPurchaseCustomer().getEmail().trim().isEmpty();
JRViewerCask jrViewerCask = new JRViewerCask(print, document, (printAsReservation ? DocumentViewType.RESERVATION : DocumentViewType.DEFAULT), canEmaild);
d.getContentPane().add(jrViewerCask, BorderLayout.CENTER);
d.setVisible(true);
if ( jrViewerCask.isCorrectlyBriefed() ) {
reloadSelectionOnStateChange(lookup(DocumentSupporter.class).briefed(document, lookup(Guardian.class).getUsername()));
}
}
/**
* Opens a dialog with detailed information of a {@link Dossier}.
* <p/>
* @param dos the {@link Dossier} entity.
*/
public void openDossierDetailViewer(Dossier dos) {
new HtmlDialog(parent(), Dialog.ModalityType.MODELESS).setText(RedTapeUiUtil.toHtmlDetailed(dos)).setVisible(true);
}
/**
* Opens a dialog with detailed information of a {@link Document}.
* <p/>
* @param doc the {@link Document} entity.
*/
public void openDocumentViewer(Document doc) {
HtmlDialog dialog = new HtmlDialog(parent(), Dialog.ModalityType.MODELESS);
dialog.setText("<html>" + DocumentFormater.toHtmlDetailedWithPositions(doc) + "<br />"
+ lookup(CustomerService.class).asHtmlHighDetailed(model.getPurchaseCustomer().getId()) + "</html>");
dialog.setVisible(true);
}
/**
* Fills the toolbar as well as organizing the popupmenus.
* <p/>
*/
public void fillToolBar() {
view.actionBar.removeAll();
view.actionBar.setLayout(new FlowLayout(FlowLayout.LEADING, 3, 3));
view.actionBar.add(new JButton(new AbstractAction("Kunden bearbeiten") {
@Override
public void actionPerformed(ActionEvent e) {
openUpdateCustomer(model.getPurchaseCustomer().getId());
view.customerDetailArea.setText(lookup(CustomerService.class).asHtmlHighDetailed(model.getPurchaseCustomer().getId()));
}
}));
//build customer dependant actions.
if ( viewOnlyCustomerIds.contains(model.getPurchaseCustomer().getId()) ) {
// Don't allow anything here.
} else if ( model.getPurchaseCustomer().getFlags().contains(CustomerFlag.SYSTEM_CUSTOMER) ) {
view.actionBar.add(new JButton(new DossierCreateAction(parent(), false, RedTapeController.this, model.getPurchaseCustomer().getId())));
} else {
view.actionBar.add(new JButton(new DossierCreateAction(parent(), false, RedTapeController.this, model.getPurchaseCustomer().getId())));
view.actionBar.add(new JButton(new DossierCreateAction(parent(), true, RedTapeController.this, model.getPurchaseCustomer().getId())));
}
JToolBar.Separator sep = new JToolBar.Separator();
sep.setAlignmentX(JComponent.CENTER_ALIGNMENT);
view.actionBar.add(sep);
if ( model.getSelectedDocument() != null && !viewOnlyCustomerIds.contains(model.getPurchaseCustomer().getId()) ) {
Document selDocument = model.getSelectedDocument();
UpdateDocumentAction action = new UpdateDocumentAction(parent(), this, model.getPurchaseCustomer().getId(), model.getSelectedDocument());
view.actionBar.add(new JButton(action));
//Deactivate Button if a Update isn't possible or allowed.
if ( !isSelectedDocumentEditable() ) {
action.setEnabled(false);
}
if ( selDocument.getType().equals(DocumentType.CREDIT_MEMO) ) {
lookup(Guardian.class).add(action, CREATE_ANNULATION_INVOICE);
accessDependentActions.add(action);
}
view.setDocumentPopupActions(action, new AbstractAction("Details") {
@Override
public void actionPerformed(ActionEvent e) {
openDocumentViewer(model.getSelectedDocument());
}
});
view.actionBar.add(new JButton(new DocumentPrintAction(selDocument, DocumentViewType.DEFAULT, this, model.getPurchaseCustomer().getId())));
if ( selDocument.getType() == DocumentType.ORDER )
view.actionBar.add(new JButton(new DocumentPrintAction(selDocument, DocumentViewType.RESERVATION, this, model.getPurchaseCustomer().getId())));
if ( !EnumSet.of(DocumentType.ANNULATION_INVOICE, DocumentType.COMPLAINT, DocumentType.CREDIT_MEMO).contains(selDocument.getType()) ) {
view.actionBar.add(new DocumentPrintAction(selDocument, DocumentViewType.SHIPPING, this, model.getPurchaseCustomer().getId()));
}
}
for (Component component : view.actionBar.getComponents()) {
if ( component instanceof JButton ) {
((JButton)component).setBorder(new BevelBorder(SoftBevelBorder.LOWERED, Color.lightGray, Color.DARK_GRAY, Color.DARK_GRAY, Color.lightGray));
}
}
view.actionBar.revalidate();
view.actionBar.repaint();
}
private Document refreshDocumentSelection(Dossier dos) {
for (Document document : dos.getActiveDocuments()) {
if ( document.getDirective() != Directive.NONE ) return dos.getActiveDocuments(document.getType()).get(0);
}
if ( !dos.getActiveDocuments(DocumentType.INVOICE).isEmpty() ) {
return dos.getActiveDocuments(DocumentType.INVOICE).get(0);
} else {
return dos.getActiveDocuments().get(dos.getActiveDocuments().size() - 1);
}
}
/**
* Return weither the selected Document is editable or not.
* <p/>
* @return true if its possible to fire the {@link UpdateDocumentAction}.
*/
private boolean isSelectedDocumentEditable() {
//Document is canceled.
if ( model.getSelectedDocument().getConditions().contains(Condition.CANCELED) ) return false;
//Dossier is a wrapped SopoAuftrag
if ( model.getSelectedDossier().getId() == 0 ) return false;
//INVOICE is the hightst document in the hierarchy.
if ( !model.getSelectedDossier().getActiveDocuments(DocumentType.INVOICE).isEmpty() && model.getSelectedDocument().getType() == DocumentType.ORDER )
return false;
//COMPLAINT is the hightst document in the hierarchy.
if ( !model.getSelectedDossier().getActiveDocuments(DocumentType.COMPLAINT).isEmpty() && model.getSelectedDocument().getType() == DocumentType.INVOICE )
return false;
//CREDIT_MEMO is the hightst document in the hierarchy.
if ( !model.getSelectedDossier().getActiveDocuments(DocumentType.CREDIT_MEMO).isEmpty()
&& (model.getSelectedDocument().getType() == DocumentType.COMPLAINT
|| model.getSelectedDocument().getType() == DocumentType.INVOICE) ) return false;
//ANNULATION_INVOICE is the hightst document in the hierarchy.
if ( !model.getSelectedDossier().getActiveDocuments(DocumentType.ANNULATION_INVOICE).isEmpty()
&& (model.getSelectedDocument().getType() == DocumentType.COMPLAINT
|| model.getSelectedDocument().getType() == DocumentType.INVOICE) ) return false;
return true;
}
// /**
// * Generates a html formated String with detailed information for a {@link Position}.
// * <p/>
// * @param p the {@link Position} entity
// * @return a html formated String with detailed information for a {@link Position}.
// */
// @Deprecated // remove
// public String getDetailedPositionToHtml(Position p) {
// StringBuilder sb = new StringBuilder();
// sb.append(PositionFormater.toHtmlDetailed(p)).append("<br />");
// if ( p.getType() == PositionType.UNIT ) {
// StockUnit su = lookup(StockAgent.class).findStockUnitByUniqueUnitIdEager(p.getUniqueUnitId());
// UniqueUnit uu = lookup(UniqueUnitAgent.class).findByIdEager(UniqueUnit.class, p.getUniqueUnitId());
// if ( su != null ) sb.append(StockUnitFormater.detailedTransactionToHtml(su));
// if ( uu != null ) sb.append(UniqueUnitFormater.toHtmlPriceInformation(uu)).append(UniqueUnitFormater.toHtmlUniqueUnitHistory(uu));
// }
// return sb.toString();
// }
private Window parent() {
return SwingCore.windowAncestor(view).orElse(SwingCore.mainFrame());
}
}