/******************************************************************************* * Copyright (c) 2005-2010, G. Weirich 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: * G. Weirich - initial implementation * M. Descher - added loadByPatientID * *******************************************************************************/ package ch.elexis.data; import static ch.elexis.core.model.PatientConstants.FLD_EXTINFO_LEGAL_GUARDIAN; import static ch.elexis.core.model.PatientConstants.FLD_EXTINFO_STAMMARZT; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import ch.elexis.admin.AccessControlDefaults; import ch.elexis.core.constants.Preferences; import ch.elexis.core.constants.StringConstants; import ch.elexis.core.data.activator.CoreHub; import ch.elexis.core.data.events.ElexisEventDispatcher; import ch.elexis.core.data.interfaces.events.MessageEvent; import ch.elexis.core.jdt.Nullable; import ch.elexis.core.model.prescription.EntryType; import ch.rgw.tools.ExHandler; import ch.rgw.tools.JdbcLink; import ch.rgw.tools.JdbcLink.Stm; import ch.rgw.tools.Money; import ch.rgw.tools.StringTool; import ch.rgw.tools.TimeTool; import ch.rgw.tools.TimeTool.TimeFormatException; /** * Ein Patient ist eine Person (und damit auch ein Kontakt), mit folgenden zusätzlichen * Eigenschaften * <ul> * <li>Anamnesen : PA, SA, FA</li> * <li>Fixe Diagnosen</li> * <li>Fixe Medikation</li> * <li>Risiken</li> * <li>Einer Liste der Fälle, die zu diesem Patienten existieren</li> * <li>Einer Liste der Garanten, die diesem Patienten zugeordnet wurden</li> * <li>Einer Liste aller Forderungen und Zahlungen im Verkehr mit diesem Patienten</li> * </ul> * * @author gerry * */ public class Patient extends Person { public static final String FLD_ALLERGIES = "Allergien"; public static final String FLD_RISKS = "Risiken"; public static final String FLD_GROUP = "Gruppe"; public static final String FLD_DIAGNOSES = "Diagnosen"; public static final String FLD_PATID = "PatientNr"; public final static String FLD_NAME = "Name"; public static final String FLD_FIRSTNAME = "Vorname"; public static final String FLD_SEX = "Geschlecht"; public static final String FLD_DOB = "Geburtsdatum"; public static final String FLD_STREET = "Strasse"; public static final String FLD_ZIP = "Plz"; public static final String FLD_PLACE = "Ort"; public static final String FLD_PHONE1 = "Telefon1"; public static final String FLD_FAX = "Fax"; public static final String FLD_BALANCE = "Konto"; public static final String FLD_PERS_ANAMNESE = "PersAnamnese"; public static final String FLD_SYS_ANAMNESE = "SysAnamnese"; public static final String FLD_FAM_ANAMNESE = "FamilienAnamnese"; public static final String[] DEFAULT_SORT = { FLD_NAME, FLD_FIRSTNAME, FLD_DOB }; static { addMapping(Kontakt.TABLENAME, FLD_DIAGNOSES + " =S:C:Diagnosen", FLD_PERS_ANAMNESE + " =S:C:PersAnamnese", "SystemAnamnese =S:C:SysAnamnese", "FamilienAnamnese =S:C:FamAnamnese", FLD_RISKS, FLD_ALLERGIES, "Faelle =LIST:PatientID:FAELLE:DatumVon", "Garanten =JOINT:GarantID:PatientID:PATIENT_GARANT_JOINT:" + Kontakt.class.getCanonicalName(), "Dauermedikation =JOINT:ArtikelID:PatientID:PATIENT_ARTIKEL_JOINT:" + Artikel.class.getCanonicalName(), FLD_BALANCE + " =LIST:PatientID:KONTO", FLD_GROUP, FLD_PATID, Kontakt.FLD_IS_PATIENT); } public String getDiagnosen(){ return get(FLD_DIAGNOSES); } public String getPersAnamnese(){ return get("PersAnamnese"); } public String getSystemAnamnese(){ return get("Systemanamnese"); } protected Patient(){/* leer */ } @Override public boolean isValid(){ if (!super.isValid()) { return false; } String geb = (get(Person.BIRTHDATE)); if (geb.equals("WERT?")) { return false; } String g = get(Person.SEX); if (g.equals(Person.MALE) || g.equals(Person.FEMALE)) { return true; } return false; } /** * Dieser oder der folgende Konstruktor sollte normalerweise verwendet werden, um einen neuen, * bisher noch nicht in der Datenbank vorhandenen Patienten anzulegen. * * @param Name * @param Vorname * @param Geburtsdatum * Als String in Notation dd.mm.jj * @param s * Geschlecht m oder w */ public Patient(final String Name, final String Vorname, final String Geburtsdatum, final String s){ super(Name, Vorname, Geburtsdatum, s); getPatCode(); } /** * This constructor is more critical than the previous one * * @param name * will be checked for non-alphabetic characters * @param vorname * will be checked for non alphabetiic characters * @param gebDat * will be checked for unplausible values * @param s * will be checked for undefined values * @throws TimeFormatException */ public Patient(final String name, final String vorname, final TimeTool gebDat, final String s) throws PersonDataException{ super(name, vorname, gebDat, s); getPatCode(); } /** * Eine Liste aller zu diesem Patient gehörenden Fälle liefern * * @return Array mit allen Fällen (das die Länge null haben kann) */ public Fall[] getFaelle(){ List<String> cas = getList("Faelle", true); Fall[] ret = new Fall[cas.size()]; int i = 0; for (String id : cas) { Fall fall = Fall.load(id); fall.setDBConnection(getDBConnection()); ret[i++] = fall; } return ret; } /** * Get the patients active medication filtered by {@link EntryType}. * * @param filterType * or null * @return */ public List<Prescription> getMedication(@Nullable EntryType filterType){ // prefetch the values needed for filter operations Query<Prescription> qbe = new Query<Prescription>(Prescription.class, null, null, Prescription.TABLENAME, new String[] { Prescription.FLD_DATE_UNTIL, Prescription.FLD_REZEPT_ID, Prescription.FLD_PRESC_TYPE, Prescription.FLD_ARTICLE }); qbe.add(Prescription.FLD_PATIENT_ID, Query.EQUALS, getId()); List<Prescription> prescriptions = qbe.execute(); // make sure just now closed are not included TimeTool now = new TimeTool(); now.add(TimeTool.SECOND, 5); if (filterType != null) { return prescriptions.parallelStream().filter(p -> !p.isStopped(now) && p.getEntryType() == filterType) .collect(Collectors.toList()); } else { return prescriptions.parallelStream().filter(p -> !p.isStopped(now)) .collect(Collectors.toList()); } } /** * Get the patients medication filtered by {@link EntryType} as text. * * @param filterType * or null * @return */ public String getMedicationText(@Nullable EntryType filterType){ List<Prescription> prescriptions = getMedication(filterType); StringBuilder sb = new StringBuilder(); prescriptions.stream().forEach(p -> { if(sb.length() > 0) { sb.append(StringTool.lf); } sb.append(p.getLabel()); }); return sb.toString(); } /** * Fixmedikation dieses Patienten einlesen * * @return ein Array aus {@link Prescription} * @deprecated does not filter by EntryType, use {@link Patient#getMedication(EntryType)} * instead. */ public Prescription[] getFixmedikation(){ Query<Prescription> qbe = new Query<Prescription>(Prescription.class); qbe.add(Prescription.FLD_PATIENT_ID, Query.EQUALS, getId()); qbe.add(Prescription.FLD_REZEPT_ID, StringTool.leer, null); String today = new TimeTool().toString(TimeTool.DATE_COMPACT); qbe.startGroup(); qbe.add(Prescription.FLD_DATE_UNTIL, Query.GREATER_OR_EQUAL, today); qbe.or(); qbe.add(Prescription.FLD_DATE_UNTIL, StringTool.leer, null); qbe.endGroup(); List<Prescription> l = qbe.execute(); return l.toArray(new Prescription[0]); } /** * Fixmedikation als Text * * @return * @deprecated does not filter by EntryType, use {@link Patient#getMedication(EntryType)} * instead. */ public String getMedikation(){ Prescription[] pre = getFixmedikation(); StringBuilder sb = new StringBuilder(); for (Prescription p : pre) { sb.append(p.getLabel()).append(StringTool.lf); } return sb.toString(); } /** * Die neueste Konsultation dieses Patienten holen, soweit eruierbar * * @param create * : eine Kons erstellen, falls keine existiert * @return die letzte Konsultation oder null */ public Konsultation getLetzteKons(final boolean create){ if (ElexisEventDispatcher.getSelectedMandator() == null) { MessageEvent.fireError("Kein Mandant angemeldet", "Es ist kein Mandant angemeldet."); return null; } Query<Konsultation> qbe = new Query<Konsultation>(Konsultation.class); // if not configured otherwise load only consultations of active mandant if (!CoreHub.userCfg.get(Preferences.USR_DEFLOADCONSALL, false)) { Mandant mandator = ElexisEventDispatcher.getSelectedMandator(); if (mandator != null) { qbe.add(Konsultation.FLD_MANDATOR_ID, Query.EQUALS, mandator.getId()); } } // qbe.add("Datum", "=", new // TimeTool().toString(TimeTool.DATE_COMPACT)); Fall[] faelle = getFaelle(); if ((faelle == null) || (faelle.length == 0)) { return create ? createFallUndKons() : null; } qbe.startGroup(); boolean termInserted = false; for (Fall fall : faelle) { if (fall.isOpen()) { qbe.add(Konsultation.FLD_CASE_ID, Query.EQUALS, fall.getId()); qbe.or(); termInserted = true; } } if (!termInserted) { return create ? createFallUndKons() : null; } qbe.endGroup(); qbe.orderBy(true, Konsultation.DATE); List<Konsultation> list = qbe.execute(); if ((list == null) || list.isEmpty()) { return null; } else { return list.get(0); } } public Konsultation createFallUndKons(){ Fall fall = neuerFall(Fall.getDefaultCaseLabel(), Fall.getDefaultCaseReason(), Fall.getDefaultCaseLaw()); Konsultation k = fall.neueKonsultation(); k.setMandant(ElexisEventDispatcher.getSelectedMandator()); return k; } /** * Einen neuen Fall erstellen und an den Patienten binden * * @return der eben erstellte Fall oder null bei Fehler */ public Fall neuerFall(final String Bezeichnung, final String grund, final String Abrechnungsmethode){ Fall fall = new Fall(getId(), Bezeichnung, grund, Abrechnungsmethode); ElexisEventDispatcher.reload(Fall.class); return fall; } /** * Einen Kurzcode, der diesen Patienten identifiziert, zurückliefern. Der Kurzcode kann je nach * Voreinstellung eine eindeutige, jeweils nur einmal vergebene Nummer sein, oder ein aus den * Personalien gebildetes Kürzel. Dieser Code kann beispielsweise als Index für die Archivierung * der KG's in Papierform verwendet werden. * * @return einen String, (der eine Zahl sein kann), und der innerhalb dieser Installation * eindeutig ist. */ public String getPatCode(){ String rc = get(FLD_PATID); if (!StringTool.isNothing(rc)) { return rc; } while (true) { String lockid = PersistentObject.lock("PatNummer", true); String pid = getDBConnection() .queryString("SELECT WERT FROM CONFIG WHERE PARAM='PatientNummer'"); if (StringTool.isNothing(pid)) { pid = "0"; getDBConnection() .exec("INSERT INTO CONFIG (PARAM,WERT) VALUES ('PatientNummer','0')"); } int lastNum = Integer.parseInt(pid) + 1; rc = Integer.toString(lastNum); getDBConnection().exec("UPDATE CONFIG set wert='" + rc + "', lastupdate=" + Long.toString(System.currentTimeMillis()) + " where param='PatientNummer'"); PersistentObject.unlock("PatNummer", lockid); String exists = getDBConnection() .queryString("SELECT ID FROM KONTAKT WHERE PatientNr=" + JdbcLink.wrap(rc)); if (exists == null) { break; } } set(FLD_PATID, rc); return rc; } public Money getKontostand(){ StringBuilder sql = new StringBuilder(); sql.append("SELECT betrag FROM KONTO WHERE PatientID=").append(getWrappedId()); Stm stm = getDBConnection().getStatement(); Money konto = new Money(); try { ResultSet res = stm.query(sql.toString()); while (res.next()) { int buchung = res.getInt(1); konto.addCent(buchung); } return konto; } catch (Exception ex) { ExHandler.handle(ex); return null; } finally { getDBConnection().releaseStatement(stm); } } /** * to be used by the platzhalter system, allows for presentation of the current balance of this * patient by using [Patient.Konto] * * @return the current balance of the patient */ public String getBalance(){ return getKontostand().getAmountAsString(); } /** * Calculates a possibly available account excess. (This value may be added to a bill as * prepayment.) * <p> * Considers all overpaid bills and account transactions not bound to a bill. The garant of the * bill must be the patient itself. (Bills not yet paid or partly paid are not considered.) * <p> * This value is not the same as the current account balance, since we ignore outstanding debts * of not yet paid bills. * * @return the account excess (may be zero or positive) */ public Money getAccountExcess(){ Money prepayment = new Money(); // overpaid bills of this patient // TODO do an optimized query over KONTAKT/FALL/RECHNUNG Query<Rechnung> rQuery = new Query<Rechnung>(Rechnung.class); // normally do not display other mandator's balance if (CoreHub.acl.request(AccessControlDefaults.ACCOUNTING_GLOBAL) == false) { Mandant mandator = ElexisEventDispatcher.getSelectedMandator(); if (mandator != null) { rQuery.add(Rechnung.MANDATOR_ID, Query.EQUALS, mandator.getId()); } } // let the database engine do the filtering Fall[] faelle = getFaelle(); if ((faelle != null) && (faelle.length > 0)) { rQuery.startGroup(); for (Fall fall : faelle) { rQuery.add(Rechnung.CASE_ID, Query.EQUALS, fall.getId()); rQuery.or(); } rQuery.endGroup(); } List<Rechnung> rechnungen = rQuery.execute(); if (rechnungen != null) { for (Rechnung rechnung : rechnungen) { Fall fall = rechnung.getFall(); if (fall != null) { // of course this should never happen Query<AccountTransaction> atQuery = new Query<AccountTransaction>(AccountTransaction.class); atQuery.add(AccountTransaction.FLD_PATIENT_ID, Query.EQUALS, getId()); atQuery.add(AccountTransaction.FLD_BILL_ID, Query.EQUALS, rechnung.getId()); List<AccountTransaction> transactions = atQuery.execute(); if (transactions != null) { Money sum = new Money(); for (AccountTransaction transaction : transactions) { sum.addMoney(transaction.getAmount()); } if (sum.getCents() > 0) { prepayment.addMoney(sum); } } } } } // account (sum over all account transactions not assigned to a bill) Query<AccountTransaction> atQuery = new Query<AccountTransaction>(AccountTransaction.class); atQuery.add(AccountTransaction.FLD_PATIENT_ID, Query.EQUALS, getId()); List<AccountTransaction> transactions = atQuery.execute(); if (transactions != null) { Money sum = new Money(); for (AccountTransaction transaction : transactions) { Rechnung rechnung = transaction.getRechnung(); if ((rechnung == null) || !rechnung.exists()) { sum.addMoney(transaction.getAmount()); } } prepayment.addMoney(sum); } return prepayment; } /** Einen Patienten mit gegebener ID aus der Datenbank einlesen */ public static Patient load(final String id){ Patient ret = new Patient(id); return ret; } /** * Einen Patienten aufgrund seiner PatientenNr laden * * @param patientNr * @return Patient falls gefunden, <code>null</code> wenn nicht gefunden */ public static Patient loadByPatientID(String patientNr){ String patID = new Query<Patient>(Patient.class).findSingle(Patient.FLD_PATID, Query.EQUALS, patientNr); return Patient.load(patID); } private Patient(final String id){ super(id); } @Override protected String getConstraint(){ return new StringBuilder(Kontakt.FLD_IS_PATIENT).append(Query.EQUALS) .append(JdbcLink.wrap(StringConstants.ONE)).toString(); } @Override protected void setConstraint(){ set(new String[] { Kontakt.FLD_IS_PATIENT, Kontakt.FLD_IS_PERSON }, StringConstants.ONE, StringConstants.ONE); } @Override /* * * Return a short or long label for this Patient * * This implementation returns "<Vorname> <Name>" for the sort label, and calls getPersonalia() * for the long label. * * @return a label describing this Patient */ public String getLabel(final boolean shortLabel){ if (shortLabel) { return super.getLabel(true); } else { return getPersonalia(); } } /** * We do not allow direct deletion -> use remove instead */ @Override public boolean delete(){ return delete(false); } /** * Einen Patienten aus der Datenbank entfernen. Dabei werden auch alle verknüpften Daten * gelöscht (Labor, Rezepte, AUF, Rechnungen etc.) Plugins, welche patientenspezifische Daten * speichern, sollten diese ebenfalls löschen (sie erhalten einen ObjectEvent) * * @param force * bei true wird der Patient auf jeden Faöll gelöscht, bei false nur, wenn keine * Fälle von ihm existieren. * @return false wenn der Patient nicht gelöscht werden konnte. */ public boolean delete(final boolean force){ Fall[] fl = getFaelle(); if ((fl.length == 0) || ((force == true) && (CoreHub.acl.request(AccessControlDefaults.DELETE_FORCED) == true))) { for (Fall f : fl) { f.delete(true); } delete_dependent(); return super.delete(); } return false; } private boolean delete_dependent(){ for (LabResult lr : new Query<LabResult>(LabResult.class, LabResult.PATIENT_ID, getId()) .execute()) { lr.delete(); } for (Rezept rp : new Query<Rezept>(Rezept.class, Rezept.PATIENT_ID, getId()).execute()) { rp.delete(); } for (Brief br : new Query<Brief>(Brief.class, Brief.FLD_PATIENT_ID, getId()).execute()) { br.delete(); } for (AccountTransaction at : new Query<AccountTransaction>(AccountTransaction.class, AccountTransaction.FLD_PATIENT_ID, getId()).execute()) { at.delete(); } return true; } @Override public boolean isDragOK(){ return true; } /** * Eine Auftragsnummer erstellen. Diese enthält die Patientennummer ergänzt mit der * Modulo10-Prüfsumme über diese Nummer, plus die aktuelle Uhrzeit als -hhmm * * @return eine verifizierbare Auftragsnummer. */ public String getAuftragsnummer(){ String pid = StringTool.addModulo10(getPatCode()) + "-" //$NON-NLS-1$ + new TimeTool().toString(TimeTool.TIME_COMPACT); return pid; } /** * Das Alter des Patienten in Jahren errechnen * * @return Das Alter in ganzen Jahren als String */ public String getAlter(){ TimeTool now = new TimeTool(); TimeTool bd = new TimeTool(getGeburtsdatum()); int jahre = now.get(TimeTool.YEAR) - bd.get(TimeTool.YEAR); bd.set(TimeTool.YEAR, now.get(TimeTool.YEAR)); if (bd.isAfter(now)) { jahre -= 1; } return Integer.toString(jahre); } /** * Return all bills of this patient * * @return a list of bills of this patient */ public List<Rechnung> getRechnungen(){ List<Rechnung> rechnungen = new ArrayList<Rechnung>(); Fall[] faelle = getFaelle(); if ((faelle != null) && (faelle.length > 0)) { Query<Rechnung> query = new Query<Rechnung>(Rechnung.class); query.insertTrue(); query.startGroup(); for (Fall fall : faelle) { query.add(Rechnung.CASE_ID, Query.EQUALS, fall.getId()); query.or(); } query.endGroup(); List<Rechnung> rnList = query.execute(); if (rnList != null) { rechnungen.addAll(rnList); } } return rechnungen; } // PatientDetailView backport from 2.2 - databinding bean compatibility public String getAllergies(){ return get(FLD_ALLERGIES); } public void setAllergies(String allergien){ set(FLD_ALLERGIES, allergien); } public String getPersonalAnamnese(){ return get(FLD_PERS_ANAMNESE); } public void setPersonalAnamnese(String anamnese){ set(FLD_PERS_ANAMNESE, anamnese); } public String getComment(){ return get(FLD_REMARK); } public void setComment(String bemerkungen){ set(FLD_REMARK, bemerkungen); } public String getFamilyAnamnese(){ return get(FLD_FAM_ANAMNESE); } public void setFamilyAnamnese(String anamnese){ set(FLD_FAM_ANAMNESE, anamnese); } public void setDiagnosen(String diagnosen){ set(FLD_DIAGNOSES, diagnosen); } public String getRisk(){ return get(FLD_RISKS); } public void setRisk(String risk){ set(FLD_RISKS, risk); } public void removeStammarzt(){ removeFromExtInfo(FLD_EXTINFO_STAMMARZT); } public void setStammarzt(Kontakt stammarzt){ if (stammarzt == null) return; // we override the name to force PersistentObject#get(String) to revert // to the method getStammarzt to fetch the entry setExtInfoStoredObjectByKey(FLD_EXTINFO_STAMMARZT, stammarzt.getId()); } /** * @return Stammarzt for the patient if defined, else <code>null</code> */ public Kontakt getStammarzt(){ // we override the name to force PersistentObject#get(String) to revert // to the method getStammarzt to fetch the entry // unfortunately lots of PersistentObject: field is not mapped Stammarzt // will be thrown .. return (getExtInfoStoredObjectByKey(FLD_EXTINFO_STAMMARZT) != null) ? Kontakt.load((String) getExtInfoStoredObjectByKey(FLD_EXTINFO_STAMMARZT)) : null; } public void setLegalGuardian(Kontakt legalGuardian){ if (legalGuardian == null) { removeFromExtInfo(FLD_EXTINFO_LEGAL_GUARDIAN); return; } setExtInfoStoredObjectByKey(FLD_EXTINFO_LEGAL_GUARDIAN, legalGuardian.getId()); } public Kontakt getLegalGuardian(){ Object guardianId = getExtInfoStoredObjectByKey(FLD_EXTINFO_LEGAL_GUARDIAN); if (guardianId != null && !((String) guardianId).isEmpty()) { return Kontakt.load((String) guardianId); } return null; } @SuppressWarnings("unchecked") private void removeFromExtInfo(String key){ Map h = getMap(FLD_EXTINFO); h.remove(key); setMap(FLD_EXTINFO, h); } }