/******************************************************************************* * Copyright (c) 2006-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 * *******************************************************************************/ package ch.elexis.data; import java.util.ArrayList; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import ch.elexis.arzttarife_schweiz.Messages; import ch.elexis.core.constants.Preferences; import ch.elexis.core.data.activator.CoreHub; import ch.elexis.core.data.interfaces.IOptifier; import ch.elexis.core.data.interfaces.IVerrechenbar; import ch.elexis.tarmedprefs.PreferenceConstants; import ch.elexis.tarmedprefs.RechnungsPrefs; import ch.rgw.tools.Result; import ch.rgw.tools.StringTool; import ch.rgw.tools.TimeTool; /** * Dies ist eine Beispielimplementation des IOptifier Interfaces, welches einige einfache Checks von * Tarmed-Verrechnungen durchführt * * @author gerry * */ public class TarmedOptifier implements IOptifier { private static final String TL = "TL"; //$NON-NLS-1$ private static final String AL = "AL"; //$NON-NLS-1$ public static final int OK = 0; public static final int PREISAENDERUNG = 1; public static final int KUMULATION = 2; public static final int KOMBINATION = 3; public static final int EXKLUSION = 4; public static final int INKLUSION = 5; public static final int LEISTUNGSTYP = 6; public static final int NOTYETVALID = 7; public static final int NOMOREVALID = 8; private static final String CHAPTER_XRAY = "39.02"; private static final String CHAPTER_ULTRA = "39.03"; private static final String DEFAULT_TAX_XRAY_ROOM = "39.2000"; boolean bOptify = true; private Verrechnet newVerrechnet; private String newVerrechnetSide; /** * Hier kann eine Konsultation als Ganzes nochmal überprüft werden */ public Result<Object> optify(Konsultation kons){ LinkedList<TarmedLeistung> postponed = new LinkedList<TarmedLeistung>(); for (Verrechnet vv : kons.getLeistungen()) { IVerrechenbar iv = vv.getVerrechenbar(); if (iv instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) iv; String tcid = tl.getCode(); if ((tcid.equals("35.0020")) || (tcid.equals("04.1930")) //$NON-NLS-1$ //$NON-NLS-2$ || tcid.startsWith("00.25")) { //$NON-NLS-1$ postponed.add(tl); } } } return null; } /** * Eine Verrechnungsposition zufügen. Der Optifier muss prüfen, ob die Verrechnungsposition im * Kontext der übergebenen Konsultation verwendet werden kann und kann sie ggf. zurückweisen * oder modifizieren. */ public Result<IVerrechenbar> add(IVerrechenbar code, Konsultation kons) { if (!(code instanceof TarmedLeistung)) { return new Result<IVerrechenbar>(Result.SEVERITY.ERROR, LEISTUNGSTYP, Messages.TarmedOptifier_BadType, null, true); } bOptify = CoreHub.userCfg.get(Preferences.LEISTUNGSCODES_OPTIFY, true); TarmedLeistung tc = (TarmedLeistung) code; List<Verrechnet> lst = kons.getLeistungen(); boolean checkBezug = false; boolean bezugOK = true; /* * TODO Hier checken, ob dieser code mit der Dignität und * Fachspezialisierung des aktuellen Mandanten usw. vereinbar ist */ Hashtable ext = tc.loadExtension(); // Bezug prüfen String bezug = (String) ext.get("Bezug"); //$NON-NLS-1$ if (!StringTool.isNothing(bezug)) { checkBezug = true; bezugOK = false; } // Gültigkeit gemäss Datum prüfen if (bOptify) { TimeTool date = new TimeTool(kons.getDatum()); String dVon = ((TarmedLeistung) code).get("GueltigVon"); //$NON-NLS-1$ if (!StringTool.isNothing(dVon)) { TimeTool tVon = new TimeTool(dVon); if (date.isBefore(tVon)) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, NOTYETVALID, code.getCode() + Messages.TarmedOptifier_NotYetValid, null, false); } } String dBis = ((TarmedLeistung) code).get("GueltigBis"); //$NON-NLS-1$ if (!StringTool.isNothing(dBis)) { TimeTool tBis = new TimeTool(dBis); if (date.isAfter(tBis)) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, NOMOREVALID, code.getCode() + Messages.TarmedOptifier_NoMoreValid, null, false); } } } newVerrechnet = null; newVerrechnetSide = null; // Korrekter Fall Typ prüfen, und ggf. den code ändern if (tc.getCode().matches("39.002[01]") || tc.getCode().matches("39.001[0156]")) { String gesetz = kons.getFall().getRequiredString("Gesetz"); if (gesetz == null || gesetz.isEmpty()) { gesetz = kons.getFall().getAbrechnungsSystem(); } if (gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0011")) { return this.add(TarmedLeistung.getFromCode("39.0010"), kons); } else if (!gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0010")) { return this.add(TarmedLeistung.getFromCode("39.0011"), kons); } if (gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0016")) { return this.add(TarmedLeistung.getFromCode("39.0015"), kons); } else if (!gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0015")) { return this.add(TarmedLeistung.getFromCode("39.0016"), kons); } if (gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0021")) { return this.add(TarmedLeistung.getFromCode("39.0020"), kons); } else if (!gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0020")) { return this.add(TarmedLeistung.getFromCode("39.0021"), kons); } } if (tc.getCode().matches("35.0020")) { List<Verrechnet> opCodes = getOPList(lst); List<Verrechnet> opReduction = getOPReduction(lst); // updated reductions to codes, and get not yet reduced codes List<Verrechnet> availableCodes = updateOPReductions(opCodes, opReduction); if (availableCodes.isEmpty()) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, KOMBINATION, code.getCode(), null, false); } for (Verrechnet verrechnet : availableCodes) { newVerrechnet = new Verrechnet(tc, kons, 1); mapOpReduction(verrechnet, newVerrechnet); } return new Result<IVerrechenbar>(null); } // Ist der Hinzuzufügende Code vielleicht schon in der Liste? Dann // nur Zahl erhöhen. for (Verrechnet v : lst) { if (v.isInstance(code)) { if (!tc.requiresSide()) { newVerrechnet = v; newVerrechnet.setZahl(newVerrechnet.getZahl() + 1); } if (bezugOK) { break; } } // "Nur zusammen mit" - Bedingung erfüllt ? if (checkBezug && bOptify) { if (v.getCode().equals(bezug)) { bezugOK = true; if (newVerrechnet != null) { break; } } } } if (tc.requiresSide()) { newVerrechnetSide = getNewVerrechnetSideOrIncrement(code, lst); } // Ausschliessende Kriterien prüfen ("Nicht zusammen mit") if (newVerrechnet == null) { newVerrechnet = new Verrechnet(code, kons, 1); // make sure side is initialized if (tc.requiresSide()) { newVerrechnet.setDetail(TarmedLeistung.SIDE, newVerrechnetSide); } // Exclusionen if (bOptify) { TarmedLeistung newTarmed = (TarmedLeistung) code; for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tarmed = (TarmedLeistung) v.getVerrechenbar(); if (tarmed != null && tarmed.exists()) { // check if new has an exclusion for this verrechnet // tarmed Result<IVerrechenbar> resCompatible = isCompatible(tarmed, newTarmed); if (resCompatible.isOK()) { // check if existing tarmed has exclusion for // new one resCompatible = isCompatible(newTarmed, tarmed); } if (!resCompatible.isOK()) { newVerrechnet.delete(); return resCompatible; } } } } if (newVerrechnet.getCode().equals("00.0750") || newVerrechnet.getCode().equals("00.0010")) { String excludeCode = null; if (newVerrechnet.getCode().equals("00.0010")) { excludeCode = "00.0750"; } else { excludeCode = "00.0010"; } for (Verrechnet v : lst) { if (v.getCode().equals(excludeCode)) { newVerrechnet.delete(); return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, EXKLUSION, "00.0750 ist nicht im Rahmen einer ärztlichen Beratung 00.0010 verrechnenbar.", //$NON-NLS-1$ null, false); } } } } newVerrechnet.setDetail(AL, Integer.toString(tc.getAL())); newVerrechnet.setDetail(TL, Integer.toString(tc.getTL())); lst.add(newVerrechnet); } /* * Dies führt zu Fehlern bei Codes mit mehreren Master-Möglichkeiten -> * vorerst raus // "Zusammen mit" - Bedingung nicht erfüllt -> * Hauptziffer einfügen. if(checkBezug){ if(bezugOK==false){ * TarmedLeistung tl=TarmedLeistung.load(bezug); Result<IVerrechenbar> * r1=add(tl,kons); if(!r1.isOK()){ * r1.add(Log.WARNINGS,KOMBINATION,code.getCode()+" nur zusammen mit * "+bezug,null,false); //$NON-NLS-1$ return r1; } } } */ // Prüfen, ob zu oft verrechnet - diese Version prüft nur "pro // Sitzung" und "pro Tag". if (bOptify) { String lim = (String) ext.get("limits"); //$NON-NLS-1$ if (lim != null) { String[] lin = lim.split("#"); //$NON-NLS-1$ for (String line : lin) { String[] f = line.split(","); //$NON-NLS-1$ if (f.length == 5) { Integer limitCode = Integer.parseInt(f[4].trim()); switch (limitCode) { case 10: // Pro Seite case 7: // Pro Sitzung if (newVerrechnet.getCode().equals("00.0020")) { if (CoreHub.mandantCfg != null && CoreHub.mandantCfg.get(PreferenceConstants.BILL_ELECTRONICALLY, false)) { break; } } // todo check if electronic billing if (f[2].equals("1") && f[0].equals("<=")) { int menge = Math.round(Float.parseFloat(f[1])); if (newVerrechnet.getZahl() > menge) { newVerrechnet.setZahl(menge); if (limitCode == 7) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, KUMULATION, Messages.TarmedOptifier_codemax + menge + Messages.TarmedOptifier_perSession, null, false); } else if (limitCode == 10) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, KUMULATION, Messages.TarmedOptifier_codemax + menge + Messages.TarmedOptifier_perSide, null, false); } } } break; case 21: // Pro Tag if (f[2].equals("1") && f[0].equals("<=")) { // 1 // Tag int menge = Math.round(Float.parseFloat(f[1])); if (newVerrechnet.getZahl() > menge) { newVerrechnet.setZahl(menge); return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, KUMULATION, Messages.TarmedOptifier_codemax + menge + "Mal pro Tag", null, false); //$NON-NLS-1$ //$NON-NLS-2$ } } break; default: break; } } } } } String tcid = code.getCode(); // check if it's an X-RAY service and add default tax if so // default xray tax will only be added once (see above) if (!tc.getCode().equals(DEFAULT_TAX_XRAY_ROOM) && !tc.getCode().matches("39.002[01]") && tc.getParent().startsWith(CHAPTER_XRAY)) { add(TarmedLeistung.getFromCode(DEFAULT_TAX_XRAY_ROOM), kons); // add 39.0020, will be changed according to case (see above) add(TarmedLeistung.getFromCode("39.0020"), kons); } // Interventionelle Schmerztherapie: Zuschlag cervical und thoracal else if (tcid.equals("29.2090")) { double sumAL = 0.0; double sumTL = 0.0; for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); String tlc = tl.getCode(); double z = v.getZahl(); if (tlc.matches("29.20[12345678]0") || (tlc.equals("29.2200"))) { sumAL += (z * tl.getAL()) / 2; sumTL += (z * tl.getTL()) / 4; } } } newVerrechnet.setTP(sumAL + sumTL); newVerrechnet.setDetail(AL, Double.toString(sumAL)); newVerrechnet.setDetail(TL, Double.toString(sumTL)); } // Zuschlag Kinder else if (tcid.equals("00.0010") || tcid.equals("00.0060")) { if (CoreHub.mandantCfg != null && CoreHub.mandantCfg.get(RechnungsPrefs.PREF_ADDCHILDREN, false)) { Fall f = kons.getFall(); if (f != null) { Patient p = f.getPatient(); if (p != null) { String alter = p.getAlter(); if (Integer.parseInt(alter) < 6) { TarmedLeistung tl = (TarmedLeistung) TarmedLeistung.getFromCode("00.0040", new TimeTool(kons.getDatum())); add(tl, kons); } } } } } // Zuschläge für Insellappen 50% auf AL und TL bei 1910,20,40,50 else if (tcid.equals("04.1930")) { //$NON-NLS-1$ double sumAL = 0.0; double sumTL = 0.0; for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); String tlc = tl.getCode(); int z = v.getZahl(); if (tlc.equals("04.1910") || tlc.equals("04.1920") || tlc.equals("04.1940") //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ || tlc.equals("04.1950")) { //$NON-NLS-1$ sumAL += tl.getAL() * z; sumTL += tl.getTL() * z; // double al = (tl.getAL() * 15) / 10.0; // double tel = (tl.getTL() * 15) / 10.0; // sum += al * z; // sum += tel * z; } } } // sum = sum * factor / 100.0; // check.setPreis(new Money(sum)); newVerrechnet.setTP(sumAL + sumTL); newVerrechnet.setDetail(AL, Double.toString(sumAL)); newVerrechnet.setDetail(TL, Double.toString(sumTL)); newVerrechnet.setPrimaryScaleFactor(0.5); } // Zuschlag fuer Ultraschall if (tc.getParent().startsWith(CHAPTER_ULTRA)) { TarmedLeistung tl = (TarmedLeistung) TarmedLeistung.getFromCode("39.3800"); add(tl, kons); } // Notfall-Zuschläge if (tcid.startsWith("00.25")) { //$NON-NLS-1$ double sum = 0.0; int subcode = Integer.parseInt(tcid.substring(5)); switch (subcode) { case 10: // Mo-Fr 7-19, Sa 7-12: 60 TP break; case 20: // Mo-Fr 19-22, Sa 12-22, So 7-22: 120 TP break; case 30: // 25% zu allen AL von 20 case 70: // 25% zu allen AL von 60 (tel.) for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); if (tl.getCode().startsWith("00.25")) { //$NON-NLS-1$ continue; } sum += (tl.getAL() * v.getZahl()); // int summand = tl.getAL() >> 2; // TODO ev. float? // -> Rundung? // ((sum.addCent(summand * v.getZahl()); } } // check.setPreis(sum.multiply(factor)); newVerrechnet.setTP(sum); newVerrechnet.setDetail(AL, Double.toString(sum)); newVerrechnet.setPrimaryScaleFactor(0.25); break; case 40: // 22-7: 180 TP break; case 50: // 50% zu allen AL von 40 case 90: // 50% zu allen AL von 70 (tel.) for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); if (tl.getCode().startsWith("00.25")) { //$NON-NLS-1$ continue; } // int summand = tl.getAL() >> 1; // sum.addCent(summand * v.getZahl()); sum += (tl.getAL() * v.getZahl()); } } // check.setPreis(sum.multiply(factor)); newVerrechnet.setTP(sum); newVerrechnet.setDetail(AL, Double.toString(sum)); newVerrechnet.setPrimaryScaleFactor(0.5); break; case 60: // Tel. Mo-Fr 19-22, Sa 12-22, So 7-22: 30 TP break; case 80: // Tel. von 22-7: 70 TP break; } return new Result<IVerrechenbar>(Result.SEVERITY.OK, PREISAENDERUNG, "Preis", null, false); //$NON-NLS-1$ } return new Result<IVerrechenbar>(null); } /** * Create a new mapping between an OP I reduction (35.0020) and a service from the OP I section. * * @param opVerrechnet * Verrechnet representing a service from the OP I section * @param reductionVerrechnet * Verrechnet representing the OP I reduction (35.0020) */ private void mapOpReduction(Verrechnet opVerrechnet, Verrechnet reductionVerrechnet){ TarmedLeistung opVerrechenbar = (TarmedLeistung) opVerrechnet.getVerrechenbar(); reductionVerrechnet.setZahl(opVerrechnet.getZahl()); reductionVerrechnet.setDetail(TL, Double.toString(opVerrechenbar.getTL())); reductionVerrechnet.setDetail(AL, Double.toString(0.0)); reductionVerrechnet.setTP(opVerrechenbar.getTL()); reductionVerrechnet.setPrimaryScaleFactor(-0.4); reductionVerrechnet.setDetail("Bezug", opVerrechenbar.getCode()); } /** * Update existing OP I reductions (35.0020), and return a list of all not yet mapped OP I * services. * * @param opCodes * list of all available OP I codes see {@link #getOPList(List)} * @param opReduction * list of all available reduction codes see {@link #getOPReduction(List)} * @return list of not unmapped OP I codes */ private List<Verrechnet> updateOPReductions(List<Verrechnet> opCodes, List<Verrechnet> opReduction){ List<Verrechnet> notMappedCodes = new ArrayList<Verrechnet>(); notMappedCodes.addAll(opCodes); // update already mapped for (Verrechnet reductionVerrechnet : opReduction) { boolean isMapped = false; String bezug = reductionVerrechnet.getDetail("Bezug"); if (bezug != null && !bezug.isEmpty()) { for (Verrechnet opVerrechnet : opCodes) { TarmedLeistung opVerrechenbar = (TarmedLeistung) opVerrechnet.getVerrechenbar(); String opCodeString = opVerrechenbar.getCode(); if (bezug.equals(opCodeString)) { // update reductionVerrechnet.setZahl(opVerrechnet.getZahl()); reductionVerrechnet.setDetail(TL, Double.toString(opVerrechenbar.getTL())); reductionVerrechnet.setDetail(AL, Double.toString(0.0)); reductionVerrechnet.setPrimaryScaleFactor(-0.4); notMappedCodes.remove(opVerrechnet); isMapped = true; break; } } } if (!isMapped) { reductionVerrechnet.setZahl(0); reductionVerrechnet.setDetail("Bezug", ""); } } return notMappedCodes; } private List<Verrechnet> getOPList(List<Verrechnet> lst){ List<Verrechnet> ret = new ArrayList<Verrechnet>(); for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); if (tl.getSparteAsText().equals("OP I")) { //$NON-NLS-1$ ret.add(v); } } } return ret; } private List<Verrechnet> getOPReduction(List<Verrechnet> lst){ List<Verrechnet> ret = new ArrayList<Verrechnet>(); for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); if (tl.getCode().equals("35.0020")) { //$NON-NLS-1$ ret.add(v); } } } return ret; } /** * Always toggle the side of a specific code. Starts with left, then right, then add to the * respective side. * * @param code * @param lst * @return */ private String getNewVerrechnetSideOrIncrement(IVerrechenbar code, List<Verrechnet> lst){ int countSideLeft = 0; Verrechnet leftVerrechnet = null; int countSideRight = 0; Verrechnet rightVerrechnet = null; for (Verrechnet v : lst) { if (v.isInstance(code)) { String side = v.getDetail(TarmedLeistung.SIDE); if (side.equals(TarmedLeistung.SIDE_L)) { countSideLeft += v.getZahl(); leftVerrechnet = v; } else { countSideRight += v.getZahl(); rightVerrechnet = v; } } } if (countSideLeft > 0 || countSideRight > 0) { if ((countSideLeft > countSideRight) && rightVerrechnet != null) { newVerrechnet = rightVerrechnet; newVerrechnet.setZahl(newVerrechnet.getZahl() + 1); } else if ((countSideLeft <= countSideRight) && leftVerrechnet != null) { newVerrechnet = leftVerrechnet; newVerrechnet.setZahl(newVerrechnet.getZahl() + 1); } else if ((countSideLeft > countSideRight) && rightVerrechnet == null) { return TarmedLeistung.SIDE_R; } } return TarmedLeistung.SIDE_L; } /** * check compatibility of one tarmed with another * * @param tarmedCode * the tarmed and it's parents code are check whether they have to be excluded * @param tarmed * TarmedLeistung who incompatibilities are examined * @return true OK if they are compatible, WARNING if it matches an exclusion case */ public Result<IVerrechenbar> isCompatible(TarmedLeistung tarmedCode, TarmedLeistung tarmed){ String notCompatible = tarmed.getExclusion(); // there are some exclusions to consider if (!StringTool.isNothing(notCompatible)) { String code = tarmedCode.getCode(); String codeParent = tarmedCode.getParent(); for (String nc : notCompatible.split(",")) { if (code.equals(nc) || codeParent.startsWith(nc)) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, EXKLUSION, tarmed.getCode() + " nicht kombinierbar mit " + code, //$NON-NLS-1$ null, false); } } } return new Result<IVerrechenbar>(Result.SEVERITY.OK, OK, "compatible", null, false); } /** * Eine Verrechnungsposition entfernen. Der Optifier sollte prüfen, ob die Konsultation nach * Entfernung dieses Codes noch konsistent verrechnet wäre und ggf. anpassen oder das Entfernen * verweigern. Diese Version macht keine Prüfungen, sondern erfüllt nur die Anfrage.. */ public Result<Verrechnet> remove(Verrechnet code, Konsultation kons){ List<Verrechnet> l = kons.getLeistungen(); l.remove(code); code.delete(); return new Result<Verrechnet>(code); } @Override public Verrechnet getCreatedVerrechnet(){ return newVerrechnet; } }