/******************************************************************************* * Copyright (c) 2007-2015, D. Lutz and Elexis. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * D. Lutz - initial API and implementation * Gerry Weirich - adapted for 2.1 * Niklaus Giger - small improvements, split into 20 classes * * Sponsors: * Dr. Peter Schönbucher, Luzern ******************************************************************************/ package org.iatrix.views; import static ch.elexis.core.data.events.ElexisEvent.EVENT_DESELECTED; import static ch.elexis.core.data.events.ElexisEvent.EVENT_RELOAD; import static ch.elexis.core.data.events.ElexisEvent.EVENT_SELECTED; import static ch.elexis.core.data.events.ElexisEvent.EVENT_UPDATE; import java.util.ArrayList; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.ISaveablePart2; import org.eclipse.ui.contexts.IContextService; import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.part.ViewPart; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.iatrix.Iatrix; import org.iatrix.data.KonsTextLock; import org.iatrix.util.Constants; import org.iatrix.util.Heartbeat; import org.iatrix.util.Helpers; import org.iatrix.widgets.IJournalArea; import org.iatrix.widgets.IJournalArea.KonsActions; import org.iatrix.widgets.JournalHeader; import org.iatrix.widgets.KonsDiagnosen; import org.iatrix.widgets.KonsHeader; import org.iatrix.widgets.KonsListDisplay; import org.iatrix.widgets.KonsProblems; import org.iatrix.widgets.KonsText; import org.iatrix.widgets.KonsVerrechnung; import org.iatrix.widgets.ProblemArea; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.elexis.admin.AccessControlDefaults; import ch.elexis.core.data.activator.CoreHub; import ch.elexis.core.data.events.ElexisEvent; import ch.elexis.core.data.events.ElexisEventDispatcher; import ch.elexis.core.ui.UiDesk; import ch.elexis.core.ui.actions.GlobalActions; import ch.elexis.core.ui.actions.GlobalEventDispatcher; import ch.elexis.core.ui.actions.IActivationListener; import ch.elexis.core.ui.events.ElexisUiEventListenerImpl; import ch.elexis.core.ui.icons.Images; import ch.elexis.core.ui.util.SWTHelper; import ch.elexis.core.ui.util.ViewMenus; import ch.elexis.data.Anwender; import ch.elexis.data.Fall; import ch.elexis.data.Konsultation; import ch.elexis.data.Patient; import ch.elexis.extdoc.util.Email; import ch.elexis.icpc.Episode; import ch.rgw.tools.TimeTool; import de.kupzog.ktable.KTable; /** * KG-Ansicht nach Iatrix-Vorstellungen * * Oben wird die Problemliste dargestellt, unten die aktuelle Konsultation und die bisherigen * Konsultationen. Hinweis: Es wird sichergestellt, dass die Problemliste und die Konsultation(en) * zum gleichen Patienten gehoeren. * * @author Daniel Lutz <danlutz@watz.ch> Original implementation (Up to Elexis 2.0) * Niklaus Giger <niklaus.giger@member.fsf.org> Reworked for Elexis 3.x */ public class JournalView extends ViewPart implements IActivationListener, ISaveablePart2 { /** * ID of the Journal View */ public static final String ID = Constants.ID; private static Logger log = LoggerFactory.getLogger(JournalView.class); private static Patient actPatient = null; private static Konsultation actKons = null; private static boolean removedStaleKonsLocks = false; private FormToolkit tk; private Form form; // container for hKonsultationDatum, hlMandant, cbFall // Parts (from top to bottom that make up our display private JournalHeader formHeader = null; // Patient name, sex, birthday, remarks, sticker, account, balance, account overview private KTable problemsKTable = null; // On top private ProblemArea problemsArea = null; // KTable with Date, nr, diagnosis, therapy, code, activ/inactiv private KonsProblems konsProblems = null; // left: List of Checkbox of all problems for this consultation private KonsText konsTextComposite; // Konsultationtext (with lock over all stations), revision info private KonsVerrechnung konsVerrechnung = null; // right: Items to be billed for select consultation private KonsDiagnosen konsDiagnosen = null; // diagnosis line private KonsListDisplay konsListDisplay; // bottom list of all consultations date, decreasing with date, mandant, case, text, billed items private ViewMenus menus; /* Actions */ private IAction exportToClipboardAction; private IAction sendEmailAction; private IAction addKonsultationAction; private Action showAllChargesAction; private Action showAllConsultationsAction; private static List<IJournalArea> allAreas; private KonsHeader konsHeader; private Heartbeat heartbeat; @Override public void createPartControl(Composite parent){ parent.setLayout(new FillLayout()); heartbeat = Heartbeat.getInstance(); tk = UiDesk.getToolkit(); form = tk.createForm(parent); Composite formBody = form.getBody(); formBody.setLayout(new GridLayout(1, true)); formHeader = new JournalHeader(formBody); SashForm mainSash = new SashForm(form.getBody(), SWT.VERTICAL); mainSash.setLayoutData(SWTHelper.getFillGridData(1, true, 1, true)); Composite topArea = tk.createComposite(mainSash, SWT.NONE); topArea.setLayout(new FillLayout(SWT.VERTICAL)); topArea.setBackground(topArea.getDisplay().getSystemColor(SWT.COLOR_WHITE)); problemsArea = new ProblemArea(topArea, JournalView.this.getPartName(), getViewSite()); problemsKTable = problemsArea.getProblemKTable(); Composite middleArea = tk.createComposite(mainSash, SWT.NONE); middleArea.setLayout(new FillLayout()); Composite konsultationComposite = tk.createComposite(middleArea); konsultationComposite.setLayout(new GridLayout(1, true)); konsHeader = new KonsHeader(konsultationComposite); SashForm konsultationSash = new SashForm(konsultationComposite, SWT.HORIZONTAL); konsultationSash.setLayoutData(SWTHelper.getFillGridData(1, true, 1, true)); Composite assignmentComposite = tk.createComposite(konsultationSash); assignmentComposite.setLayout(new GridLayout(1, true)); konsProblems = new KonsProblems(assignmentComposite); // on the left side Composite konsultationTextComposite = tk.createComposite(konsultationSash); konsultationTextComposite.setLayout(new GridLayout(1, true)); konsTextComposite = new KonsText(konsultationTextComposite); konsDiagnosen = new KonsDiagnosen(konsultationComposite); Composite verrechnungComposite = tk.createComposite(konsultationSash); konsVerrechnung = new KonsVerrechnung(verrechnungComposite, form, JournalView.this.getPartName(), assignmentComposite); if (konsultationSash.getChildren().length == 3) { konsultationSash.setWeights(new int[] { 15, 65, 20 }); } else { // System.out.println("konsSash should have 3, but has " + konsultationSash.getChildren().length + " children"); } Composite bottomArea = tk.createComposite(mainSash, SWT.NONE); bottomArea.setLayout(new FillLayout()); bottomArea.setBackground(bottomArea.getDisplay().getSystemColor(SWT.COLOR_WHITE)); konsListDisplay = new KonsListDisplay(bottomArea); mainSash.setWeights(new int[] { 20, 40, 30 }); allAreas = new ArrayList<>(); allAreas.add(konsTextComposite); // Let the konsText be available for input as soon as possible allAreas.add(formHeader); allAreas.add(problemsArea); allAreas.add(konsHeader); allAreas.add(konsProblems); allAreas.add(konsDiagnosen); allAreas.add(konsVerrechnung); allAreas.add(konsListDisplay); makeActions(); menus = new ViewMenus(getViewSite()); if (CoreHub.acl.request(AccessControlDefaults.AC_PURGE)) { menus.createMenu(addKonsultationAction, GlobalActions.redateAction, problemsArea.addProblemAction, GlobalActions.delKonsAction, problemsArea.delProblemAction, exportToClipboardAction, sendEmailAction, konsTextComposite.getVersionForwardAction(), konsTextComposite.getVersionBackAction(), konsTextComposite.getChooseVersionAction(), konsTextComposite.getPurgeAction(), konsTextComposite.getSaveAction(), showAllConsultationsAction, showAllChargesAction, problemsArea.addFixmedikationAction); } else { menus.createMenu(addKonsultationAction, GlobalActions.redateAction, problemsArea.addProblemAction, GlobalActions.delKonsAction, problemsArea.delProblemAction, exportToClipboardAction, sendEmailAction, konsTextComposite.getVersionForwardAction(), konsTextComposite.getVersionBackAction(), konsTextComposite.getChooseVersionAction(), konsTextComposite.getSaveAction(), showAllConsultationsAction, showAllChargesAction, problemsArea.addFixmedikationAction); } menus.createToolbar(sendEmailAction, exportToClipboardAction, addKonsultationAction, problemsArea.getAddProblemAction(), konsTextComposite.getSaveAction()); menus.createViewerContextMenu(konsProblems.getProblemAssignmentViewer(), konsProblems.unassignProblemAction); menus.createViewerContextMenu(konsVerrechnung.getVerrechnungViewer(), konsVerrechnung.changeVerrechnetPreisAction, konsVerrechnung.changeVerrechnetZahlAction, konsVerrechnung.delVerrechnetAction); GlobalEventDispatcher.addActivationListener(this, this); activateContext(); } /** * Save actual Kons */ public static void saveActKonst(){ if (actKons == null) { return; } logEvent(actKons, "saveActKonst"); for (int i = 0; i < allAreas.size(); i++) { IJournalArea a = allAreas.get(i); if (a != null) { a.setKons(actKons, KonsActions.SAVE_KONS); } } } /** * First ste the global variable actKons * Then updates all dependent widgets, like header, konsText konsList * @param newKons * @param op */ public static void updateAllKonsAreas(Konsultation newKons, IJournalArea.KonsActions op){ /* * Not yet sure whether comparing only the id or the whole cons is better */ actKons = newKons; if (newKons == null) { return; } // It is a bad idea to skip updating the kons, when the Id matches // Some changes, e.g. when date of actual kons are possible even when the compare matches. // Therefore we return only when we have nothing to update savedKonst == newKons?" + newId + " konsId match? " + savedKonsId.equals(newId)); logEvent(newKons, "updateAllKonsAreas: newKons"); for (int i = 0; i < allAreas.size(); i++) { IJournalArea a = allAreas.get(i); if (a != null) { a.setKons(newKons, op); } } } private void activateAllKonsAreas(boolean mode){ logEvent(null, "activateAllKonsAreas: " + mode); for (int i = 0; i < allAreas.size(); i++) { IJournalArea a = allAreas.get(i); if (a != null) { a.activation(mode); } } } private void visibleAllKonsAreas(boolean mode){ logEvent(null, "visibleAllKonsAreas: " + mode); for (int i = 0; i < allAreas.size(); i++) { IJournalArea a = allAreas.get(i); if (a != null) { a.visible(mode); } } } private final ElexisUiEventListenerImpl eeli_problem = new ElexisUiEventListenerImpl(Episode.class, EVENT_UPDATE | EVENT_DESELECTED) { @Override public void runInUi(ElexisEvent ev){ switch (ev.getType()) { case EVENT_UPDATE: // problem change may affect current problems list and consultation // TODO check if problem is part of current consultation // work-around: just update the current patient and consultation logEvent(null, "eeli_problem EVENT_UPDATE"); problemsArea.reloadAndRefresh(); break; case EVENT_DESELECTED: logEvent(null, "eeli_problem EVENT_DESELECTED"); problemsKTable.clearSelection(); break; } } }; private final ElexisUiEventListenerImpl eeli_kons = new ElexisUiEventListenerImpl(Konsultation.class, EVENT_SELECTED | EVENT_UPDATE | EVENT_RELOAD) { @Override public void runInUi(ElexisEvent ev){ Konsultation newKons = (Konsultation) ev.getObject(); String msg = "unknown"; switch (ev.getType()) { case EVENT_SELECTED: msg = "EVENT_SELECTED"; break; case EVENT_UPDATE: msg = "EVENT_UPDATE"; break; case EVENT_RELOAD: msg = "EVENT_RELOAD"; break; } if (!removedStaleKonsLocks) { removedStaleKonsLocks = true; KonsTextLock.deleteObsoleteLocks(newKons); } // when we get an update or select event the parameter is always not null if ((actKons == null) || !Helpers.haveSameContent(newKons, actKons)) { logEvent(newKons, "eeli_kons " + msg + " SAVE_KONS"); // updateAllKonsAreas(actKons, KonsActions.SAVE_KONS); Patient newPatient = newKons.getFall().getPatient(); if (newPatient != actPatient) { displaySelectedPatient(newPatient, "eeli_kons newPatient"); } logEvent(newKons, "eeli_kons " + msg + " ACTIVATE_KONS"); updateAllKonsAreas(newKons, KonsActions.ACTIVATE_KONS); } actKons = newKons; } }; /** * Helper to update every thing whether we got notified by opening the view * or the selected patient changed * * @param selectedPatient patient to be displayed * @param why Where do we come from (Only used for the logging) */ private void displaySelectedPatient(Patient selectedPatient, String why){ if (selectedPatient == null) { logEvent(null, why + " displaySelectedPatient " + "no patient"); actPatient = null; updateAllKonsAreas(null, KonsActions.ACTIVATE_KONS); return; } else { logEvent(null, why + " displaySelectedPatient " + selectedPatient.getId() + selectedPatient.getPersonalia()); } showAllChargesAction.setChecked(false); showAllConsultationsAction.setChecked(false); // Find the most recent open konsultation for the given fall // If nothing found or not of today, create a new konsultation Konsultation konsultation = null; konsultation = selectedPatient.getLetzteKons(false); if (konsultation == null) { Fall[] faelle = selectedPatient.getFaelle(); if (faelle.length == 0) { konsultation = selectedPatient.createFallUndKons(); } else { for (Fall fall : faelle) { if (fall.isOpen()) { konsultation = fall.getLetzteBehandlung(); if (konsultation == null) { konsultation = fall.neueKonsultation(); } else { TimeTool konsDate = new TimeTool(konsultation.getDatum()); if (!konsDate.isSameDay(new TimeTool())) { konsultation = konsultation.getFall().neueKonsultation(); } } log.debug("displaySelectedPatient neue Kons fall.isOpen " + konsultation.getId() + " " + konsultation.getLabel()); break; } } if (konsultation == null) { konsultation = selectedPatient.createFallUndKons(); log.debug("displaySelectedPatient neue Kons createFallUndKons " + konsultation.getId() + " " + konsultation.getLabel()); } } } TimeTool konsDate = new TimeTool(konsultation.getDatum()); if (!konsDate.isSameDay(new TimeTool())) { konsultation = konsultation.getFall().neueKonsultation(); } // actKons = konsultation; // We do not call updateAllKonsAreas(actKons, KonsActions.ACTIVATE_KONS); // as this would overwrite the konstext when we change the patient and continue typing // See Ticket #5696 } private final ElexisUiEventListenerImpl eeli_pat = // Soll hier auch noch auf RELOAD und UPDATE reagiert werden new ElexisUiEventListenerImpl(Patient.class, ElexisEvent.EVENT_SELECTED | ElexisEvent.EVENT_RELOAD | ElexisEvent.EVENT_UPDATE) { @Override public void runInUi(ElexisEvent ev){ displaySelectedPatient((Patient) ev.getObject(), "eeli_pat " + ev.getType()); // setPatient((Patient) ev.getObject()); } }; private final ElexisUiEventListenerImpl eeli_user = new ElexisUiEventListenerImpl(Anwender.class, ElexisEvent.EVENT_USER_CHANGED) { @Override public void runInUi(ElexisEvent ev){ logEvent(null, "runInUi eeli_user adaptMenus"); adaptMenus(); } }; /** * Activate a context that this view uses. It will be tied to this view activation events and * will be removed when the view is disposed. Copied from * org.eclipse.ui.examples.contributions.InfoView.java */ private void activateContext(){ IContextService contextService = (IContextService) getSite().getService(IContextService.class); contextService.activateContext(Constants.VIEW_CONTEXT_ID); } @Override public void dispose(){ GlobalEventDispatcher.removeActivationListener(this, this); ElexisEventDispatcher.getInstance().removeListeners(eeli_kons, eeli_problem, eeli_pat, eeli_user); super.dispose(); } @Override public void setFocus(){} /** * Adapt the menus (create/delete kons) according to the ACL settings */ public void adaptMenus(){ konsVerrechnung.getVerrechnungViewer().getTable().getMenu() .setEnabled(CoreHub.acl.request(AccessControlDefaults.LSTG_VERRECHNEN)); // TODO this belongs to GlobalActions itself (action creator) GlobalActions.delKonsAction .setEnabled(CoreHub.acl.request(AccessControlDefaults.KONS_DELETE)); GlobalActions.neueKonsAction .setEnabled(CoreHub.acl.request(AccessControlDefaults.KONS_CREATE)); } private void makeActions(){ // Konsultation // Replacement for GlobalActions.neueKonsAction (other image) addKonsultationAction = new Action(GlobalActions.neueKonsAction.getText()) { { setImageDescriptor(AbstractUIPlugin.imageDescriptorFromPlugin("org.iatrix", "icons/new_konsultation.ico")); setToolTipText(GlobalActions.neueKonsAction.getToolTipText()); } @Override public void run(){ GlobalActions.neueKonsAction.run(); } }; addKonsultationAction.setActionDefinitionId(Constants.NEWCONS_COMMAND); GlobalActions.registerActionHandler(this, addKonsultationAction); // Probleme if (problemsArea != null) { GlobalActions.registerActionHandler(this, problemsArea.addProblemAction); problemsArea.addProblemAction.setActionDefinitionId(Constants.NEWPROBLEM_COMMAND); } exportToClipboardAction = new Action("Export (Zwischenablage)") { { setImageDescriptor(Images.IMG_EXPORT.getImageDescriptor()); setToolTipText("Zusammenfassung in Zwischenablage kopieren"); } @Override public void run(){ Helpers.exportToClipboard(actPatient, null); // TODO: selected problem } }; exportToClipboardAction.setActionDefinitionId(Constants.EXPORT_CLIPBOARD_COMMAND); GlobalActions.registerActionHandler(this, exportToClipboardAction); sendEmailAction = new Action("E-Mail verschicken") { { setImageDescriptor(Images.IMG_MAIL.getImageDescriptor()); setToolTipText("E-Mail Programm öffnent (mit Medikation und allen Konsultationen)"); } @Override public void run(){ Email.openMailApplication("", // No default to address null, Helpers.exportToClipboard(actPatient, null), // TODO: selected problem null); } }; sendEmailAction.setActionDefinitionId(Constants.EXPORT_SEND_EMAIL_COMMAND); GlobalActions.registerActionHandler(this, sendEmailAction); // history display showAllChargesAction = new Action("Alle Leistungen anzeigen", Action.AS_CHECK_BOX) { { setChecked(false); setToolTipText( "Leistungen aller Konsultationen anzeigen, nicht nur der ersten paar."); } @Override public void run(){ konsListDisplay.setKonsultation(actKons, showAllChargesAction.isChecked(), showAllConsultationsAction.isChecked()); } }; showAllChargesAction.setActionDefinitionId(Iatrix.SHOW_ALL_CHARGES_COMMAND); GlobalActions.registerActionHandler(this, showAllChargesAction); showAllConsultationsAction = new Action("Alle Konsultationen anzeigen", Action.AS_CHECK_BOX) { { setChecked(false); setToolTipText("Alle Konsultationen anzeigen"); } @Override public void run(){ konsListDisplay.setKonsultation(actKons, showAllChargesAction.isChecked(), showAllConsultationsAction.isChecked()); } }; showAllConsultationsAction.setActionDefinitionId(Iatrix.SHOW_ALL_CONSULTATIONS_COMMAND); GlobalActions.registerActionHandler(this, showAllConsultationsAction); } @Override public void activation(boolean mode){ Konsultation selected_kons = (Konsultation) ElexisEventDispatcher.getSelected(Konsultation.class); if (selected_kons != null && actKons != null && !selected_kons.getId().equals(actKons.getId())) { // this should never happen logEvent(null, "activation " + mode + " sel: " + selected_kons.getLabel() + " act: " + actKons.getId()); return; } activateAllKonsAreas(mode); if (mode == false) { // text is neither dirty nor changed. // If it es empty and nothing has been billed, we just delete this kons. if (actKons == null) { return; } boolean noLeistungen = actKons.getLeistungen() == null || actKons.getLeistungen().isEmpty(); log.debug("Delete the kons? " + konsTextComposite.getPlainText().length() + " noLeistungen " + noLeistungen); if (konsTextComposite.getPlainText().length() == 0 && (noLeistungen)) { Fall f = actKons.getFall(); Konsultation[] ret = f.getBehandlungen(false); actKons.delete(true); if (ret.length == 1) { /* Trying to remove the associated case got me into problems. * Peter Schoenbucher argued on September, 2, 2015, that we should never * delete a case, because the case holds the information which Krankenkasse is * attached to this client. Therefore often the assistant opens a case before * the consultation starts */ } } } } @Override public void visible(boolean mode){ if (mode == true) { ElexisEventDispatcher.getInstance().addListeners(eeli_kons, eeli_problem, eeli_pat, eeli_user); Konsultation newKons = (Konsultation) ElexisEventDispatcher.getSelected(Konsultation.class); if (newKons != null) { String msg = newKons.getId()+ " " + newKons.getLabel() + " " + newKons.getFall().getPatient().getPersonalia(); logEvent(newKons, "visible true " + msg); updateAllKonsAreas(newKons, KonsActions.ACTIVATE_KONS); } else { logEvent(newKons, "visible true newKons is null"); displaySelectedPatient(ElexisEventDispatcher.getSelectedPatient(), "view visible"); } heartbeat.enableListener(true); } else { heartbeat.enableListener(false); ElexisEventDispatcher.getInstance().removeListeners(eeli_kons, eeli_problem, eeli_pat, eeli_user); // TODO: Braucht es diesen Aufruf wirklich? updateAllKonsAreas(null, KonsActions.ACTIVATE_KONS); } visibleAllKonsAreas(mode); }; private static void logEvent(Konsultation kons, String msg){ StringBuilder sb = new StringBuilder(msg); if (kons != null) { Fall f = kons.getFall(); if (f != null) { Patient pat = f.getPatient(); sb.append(" kons: "+ kons.getId()); sb.append(" vom " + kons.getDatum()); sb.append(" " + pat.getId() + ": " + pat.getPersonalia()); } } log.debug(sb.toString()); } /*********************************************************************************************** * Die folgenden 6 Methoden implementieren das Interface ISaveablePart2 Wir benötigen das * Interface nur, um das Schliessen einer View zu verhindern, wenn die Perspektive fixiert ist. * Gibt es da keine einfachere Methode? */ @Override public int promptToSaveOnClose(){ return GlobalActions.fixLayoutAction.isChecked() ? ISaveablePart2.CANCEL : ISaveablePart2.NO; } @Override public void doSave(IProgressMonitor monitor){ /* leer */} @Override public void doSaveAs(){ /* leer */} @Override public boolean isDirty(){ return true; } @Override public boolean isSaveAsAllowed(){ return false; } @Override public boolean isSaveOnCloseNeeded(){ return true; } }