Java tutorial
/******************************************************************************* * 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.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import org.apache.commons.lang.StringUtils; 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.data.TarmedKumulation.TarmedKumulationType; import ch.elexis.data.TarmedLimitation.LimitationUnit; import ch.elexis.data.importer.TarmedLeistungAge; import ch.elexis.data.importer.TarmedReferenceDataImporter; 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 durchfhrt * * @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$ private static final String AL_NOTSCALED = "AL_NOTSCALED"; //$NON-NLS-1$ private static final String AL_SCALINGFACTOR = "AL_SCALINGFACTOR"; //$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; public static final int PATIENTAGE = 9; public static final int EXKLUSIVE = 10; public static final int EXKLUSIONSIDE = 11; private static final String CHAPTER_XRAY = "39.02"; private static final String DEFAULT_TAX_XRAY_ROOM = "39.2000"; boolean bOptify = true; private Verrechnet newVerrechnet; private String newVerrechnetSide; private Map<String, Object> contextMap; /** * Hier kann eine Konsultation als Ganzes nochmal berprft 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; } @Override public synchronized void putContext(String key, Object value) { if (contextMap == null) { contextMap = new HashMap<String, Object>(); } contextMap.put(key, value); } @Override public void clearContext() { if (contextMap != null) { contextMap.clear(); } } /** * Eine Verrechnungsposition zufgen. Der Optifier muss prfen, ob die Verrechnungsposition im * Kontext der bergebenen Konsultation verwendet werden kann und kann sie ggf. zurckweisen * 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(); /* * TODO Hier checken, ob dieser code mit der Dignitt und * Fachspezialisierung des aktuellen Mandanten usw. vereinbar ist */ Hashtable<String, String> ext = tc.loadExtension(); // Gltigkeit gemss Datum und Alter prfen 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); } } String ageLimits = ext.get(TarmedLeistung.EXT_FLD_SERVICE_AGE); if (ageLimits != null && !ageLimits.isEmpty()) { String errorMessage = checkAge(ageLimits, kons); if (errorMessage != null) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, PATIENTAGE, errorMessage, null, false); } } } newVerrechnet = null; newVerrechnetSide = null; // Korrekter Fall Typ prfen, und ggf. den code ndern if (tc.getCode().matches("39.002[01]") || tc.getCode().matches("39.001[0156]")) { String gesetz = kons.getFall().getConfiguredBillingSystemLaw().name(); if (gesetz == null || gesetz.isEmpty()) { gesetz = kons.getFall().getAbrechnungsSystem(); } if (gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0011")) { return this.add(getKonsVerrechenbar("39.0010", kons), kons); } else if (!gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0010")) { return this.add(getKonsVerrechenbar("39.0011", kons), kons); } if (gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0016")) { return this.add(getKonsVerrechenbar("39.0015", kons), kons); } else if (!gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0015")) { return this.add(getKonsVerrechenbar("39.0016", kons), kons); } if (gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0021")) { return this.add(getKonsVerrechenbar("39.0020", kons), kons); } else if (!gesetz.equalsIgnoreCase("KVG") && tc.getCode().matches("39.0020")) { return this.add(getKonsVerrechenbar("39.0021", kons), kons); } } if (tc.getCode().matches("35.0020")) { List<Verrechnet> opCodes = getOPList(lst); List<Verrechnet> opReduction = getVerrechnetMatchingCode(lst, "35.0020"); // 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 Hinzuzufgende Code vielleicht schon in der Liste? Dann // nur Zahl erhhen. for (Verrechnet v : lst) { if (v.isInstance(code)) { if (!tc.requiresSide()) { newVerrechnet = v; newVerrechnet.setZahl(newVerrechnet.getZahl() + 1); break; } } } if (tc.requiresSide()) { newVerrechnetSide = getNewVerrechnetSideOrIncrement(code, lst); } // Ausschliessende Kriterien prfen ("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(v, tarmed, newVerrechnet, newTarmed, kons); if (resCompatible.isOK()) { // check if existing tarmed has exclusion for // new one resCompatible = isCompatible(newVerrechnet, newTarmed, v, tarmed, kons); } 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(kons.getMandant()))); setALScalingInfo(tc, newVerrechnet, kons.getMandant(), false); newVerrechnet.setDetail(TL, Integer.toString(tc.getTL())); lst.add(newVerrechnet); } // set bezug of zuschlagsleistung and referenzleistung if (isReferenceInfoAvailable() && shouldDetermineReference(tc)) { // lookup available masters List<Verrechnet> masters = getPossibleMasters(newVerrechnet, lst); if (masters.isEmpty()) { decrementOrDelete(newVerrechnet); return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, KOMBINATION, "Fr die Zuschlagsleistung " + code.getCode() + " konnte keine passende Hauptleistung gefunden werden.", null, false); } if (!masters.isEmpty()) { String bezug = newVerrechnet.getDetail("Bezug"); if (bezug == null) { // set bezug to first available master newVerrechnet.setDetail("Bezug", masters.get(0).getCode()); } else { boolean found = false; // lookup matching, or create new Verrechnet for (Verrechnet mVerr : masters) { if (mVerr.getCode().equals(bezug)) { // just mark as found as amount is already increased found = true; } } if (!found) { // create a new Verrechent and decrease amount newVerrechnet.setZahl(newVerrechnet.getZahl() - 1); newVerrechnet = new Verrechnet(code, kons, 1); newVerrechnet.setDetail("Bezug", masters.get(0).getCode()); } } } } Result<IVerrechenbar> limitResult = checkLimitations(kons, tc, newVerrechnet); if (!limitResult.isOK()) { decrementOrDelete(newVerrechnet); return limitResult; } 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)) { if (CoreHub.userCfg.get(Preferences.LEISTUNGSCODES_OPTIFY_XRAY, true)) { add(getKonsVerrechenbar(DEFAULT_TAX_XRAY_ROOM, kons), kons); // add 39.0020, will be changed according to case (see above) add(getKonsVerrechenbar("39.0020", kons), 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(kons.getMandant())) / 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) getKonsVerrechenbar("00.0040", kons); add(tl, kons); } } } } } // Zuschlge fr 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(kons.getMandant()) * 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); } // Zuschlge fr 04.0620 sollte sich diese mit 70% auf die Positionen 04.0630 & 04.0640 beziehen else if (tcid.equals("04.0620")) { 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.0610") || tlc.equals("04.0630") || tlc.equals("04.0640")) { sumAL += tl.getAL(kons.getMandant()) * z; sumTL += tl.getTL() * z; } } } newVerrechnet.setTP(sumAL + sumTL); newVerrechnet.setDetail(AL, Double.toString(sumAL)); newVerrechnet.setDetail(TL, Double.toString(sumTL)); newVerrechnet.setPrimaryScaleFactor(0.7); } // Notfall-Zuschlge 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(kons.getMandant()) * 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(kons.getMandant()) * 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); } private void decrementOrDelete(Verrechnet verrechnet) { int zahl = verrechnet.getZahl(); if (zahl > 1) { verrechnet.setZahl(zahl - 1); } else { verrechnet.delete(); } } private boolean isContext(String key) { return getContextValue(key) != null; } private Object getContextValue(String key) { if (contextMap != null) { return contextMap.get(key); } return null; } /** * If there is a AL scaling used to calculate the AL value, provide original AL and AL scaling * factor in the ExtInfo of the {@link Verrechnet}. * * @param tarmed * @param verrechnet * @param mandant */ private void setALScalingInfo(TarmedLeistung tarmed, Verrechnet verrechnet, Mandant mandant, boolean isComposite) { double scaling = tarmed.getALScaling(mandant); if (scaling != 100) { newVerrechnet.setDetail(AL_NOTSCALED, Integer.toString(tarmed.getAL())); newVerrechnet.setDetail(AL_SCALINGFACTOR, Double.toString(scaling / 100)); } } /** * Get double as int rounded half up. * * @param value * @return */ private int doubleToInt(double value) { BigDecimal bd = new BigDecimal(value); bd = bd.setScale(0, RoundingMode.HALF_UP); return bd.intValue(); } private Result<IVerrechenbar> checkLimitations(Konsultation kons, TarmedLeistung tarmedLeistung, Verrechnet newVerrechnet) { if (bOptify) { // service limitations List<TarmedLimitation> limitations = tarmedLeistung.getLimitations(); for (TarmedLimitation tarmedLimitation : limitations) { if (tarmedLimitation.isTestable()) { Result<IVerrechenbar> result = tarmedLimitation.test(kons, newVerrechnet); if (!result.isOK()) { return result; } } } // group limitations TimeTool date = new TimeTool(kons.getDatum()); List<String> groups = tarmedLeistung.getServiceGroups(date); for (String groupName : groups) { Optional<TarmedGroup> group = TarmedGroup.find(groupName, tarmedLeistung.get(TarmedLeistung.FLD_LAW), date); if (group.isPresent()) { limitations = group.get().getLimitations(); for (TarmedLimitation tarmedLimitation : limitations) { if (tarmedLimitation.isTestable()) { Result<IVerrechenbar> result = tarmedLimitation.test(kons, newVerrechnet); if (!result.isOK()) { return result; } } } } } } return new Result<IVerrechenbar>(null); } private String checkAge(String limitsString, Konsultation kons) { LocalDateTime consDate = new TimeTool(kons.getDatum()).toLocalDateTime(); Patient patient = kons.getFall().getPatient(); String geburtsdatum = patient.getGeburtsdatum(); if (StringUtils.isEmpty(geburtsdatum)) { return "Patienten Alter nicht ok, kein Geburtsdatum angegeben"; } long patientAgeDays = patient.getAgeAt(consDate, ChronoUnit.DAYS); List<TarmedLeistungAge> ageLimits = TarmedLeistungAge.of(limitsString, consDate); for (TarmedLeistungAge tarmedLeistungAge : ageLimits) { if (tarmedLeistungAge.isValidOn(consDate.toLocalDate())) { // if only one of the limits is set, check only that limit if (tarmedLeistungAge.getFromDays() >= 0 && !(tarmedLeistungAge.getToDays() >= 0)) { if (patientAgeDays < tarmedLeistungAge.getFromDays()) { return "Patient ist zu jung, verrechenbar ab " + tarmedLeistungAge.getFromText(); } } else if (tarmedLeistungAge.getToDays() >= 0 && !(tarmedLeistungAge.getFromDays() >= 0)) { if (patientAgeDays > tarmedLeistungAge.getToDays()) { return "Patient ist zu alt, verrechenbar bis " + tarmedLeistungAge.getToText(); } } else if (tarmedLeistungAge.getToDays() >= 0 && tarmedLeistungAge.getFromDays() >= 0) { if (tarmedLeistungAge.getToDays() < tarmedLeistungAge.getFromDays()) { if (patientAgeDays > tarmedLeistungAge.getToDays() && patientAgeDays < tarmedLeistungAge.getFromDays()) { return "Patienten Alter nicht ok, verrechenbar " + tarmedLeistungAge.getText(); } } else { if (patientAgeDays > tarmedLeistungAge.getToDays() || patientAgeDays < tarmedLeistungAge.getFromDays()) { return "Patienten Alter nicht ok, verrechenbar " + tarmedLeistungAge.getText(); } } } } } return null; } private IVerrechenbar getKonsVerrechenbar(String code, Konsultation kons) { TimeTool date = new TimeTool(kons.getDatum()); if (kons.getFall() != null) { String law = kons.getFall().getConfiguredBillingSystemLaw().name(); return TarmedLeistung.getFromCode(code, date, law); } return null; } private boolean isReferenceInfoAvailable() { return CoreHub.globalCfg.get(TarmedReferenceDataImporter.CFG_REFERENCEINFO_AVAILABLE, false); } private boolean shouldDetermineReference(TarmedLeistung tc) { String typ = tc.getServiceTyp(); boolean becauseOfType = typ.equals("Z"); if (becauseOfType) { String text = tc.getText(); return text.startsWith("+") || text.startsWith("-"); } return false; } private List<Verrechnet> getAvailableMasters(TarmedLeistung slave, List<Verrechnet> lst) { List<Verrechnet> ret = new LinkedList<Verrechnet>(); TimeTool konsDate = null; for (Verrechnet v : lst) { if (konsDate == null) { konsDate = new TimeTool(v.getKons().getDatum()); } if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); if (tl.getHierarchy(konsDate).contains(slave.getCode())) { //$NON-NLS-1$ ret.add(v); } } } return ret; } private List<Verrechnet> getPossibleMasters(Verrechnet newSlave, List<Verrechnet> lst) { TarmedLeistung slaveTarmed = (TarmedLeistung) newSlave.getVerrechenbar(); // lookup available masters List<Verrechnet> masters = getAvailableMasters(slaveTarmed, lst); // check which masters are left to be referenced int maxPerMaster = getMaxPerMaster(slaveTarmed); if (maxPerMaster > 0) { Map<Verrechnet, List<Verrechnet>> masterSlavesMap = getMasterToSlavesMap(newSlave, lst); for (Verrechnet master : masterSlavesMap.keySet()) { int masterCount = master.getZahl(); int slaveCount = 0; for (Verrechnet slave : masterSlavesMap.get(master)) { slaveCount += slave.getZahl(); if (slave.equals(newSlave)) { slaveCount--; } } if (masterCount <= (slaveCount * maxPerMaster)) { masters.remove(master); } } } return masters; } /** * Creates a map of masters associated to slaves by the Bezug. This map will not contain the * newSlave, as it has no Bezug set yet. * * @param newSlave * @param lst * @return */ private Map<Verrechnet, List<Verrechnet>> getMasterToSlavesMap(Verrechnet newSlave, List<Verrechnet> lst) { Map<Verrechnet, List<Verrechnet>> ret = new HashMap<>(); TarmedLeistung slaveTarmed = (TarmedLeistung) newSlave.getVerrechenbar(); // lookup available masters List<Verrechnet> masters = getAvailableMasters(slaveTarmed, lst); for (Verrechnet verrechnet : masters) { ret.put(verrechnet, new ArrayList<Verrechnet>()); } // lookup other slaves with same code List<Verrechnet> slaves = getVerrechnetMatchingCode(lst, newSlave.getCode()); // add slaves to separate master list for (Verrechnet slave : slaves) { String bezug = slave.getDetail("Bezug"); if (bezug != null && !bezug.isEmpty()) { for (Verrechnet master : ret.keySet()) { if (master.getCode().equals(bezug)) { ret.get(master).add(slave); } } } } return ret; } private int getMaxPerMaster(TarmedLeistung slave) { List<TarmedLimitation> limits = slave.getLimitations(); for (TarmedLimitation limit : limits) { if (limit.getLimitationUnit() == LimitationUnit.MAINSERVICE) { // only an integer makes sense here return (int) limit.getAmount(); } } // default to unknown return -1; } /** * 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 #getVerrechnetMatchingCode(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> getVerrechnetMatchingCode(List<Verrechnet> lst, String code) { List<Verrechnet> ret = new ArrayList<Verrechnet>(); for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { TarmedLeistung tl = (TarmedLeistung) v.getVerrechenbar(); if (tl.getCode().equals(code)) { //$NON-NLS-1$ ret.add(v); } } } return ret; } private List<Verrechnet> getVerrechnetWithBezugMatchingCode(List<Verrechnet> lst, String code) { List<Verrechnet> ret = new ArrayList<Verrechnet>(); for (Verrechnet v : lst) { if (v.getVerrechenbar() instanceof TarmedLeistung) { if (code.equals(v.getDetail("Bezug"))) { //$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 side is provided by context use that side if (isContext(TarmedLeistung.SIDE)) { String side = (String) getContextValue(TarmedLeistung.SIDE); if (TarmedLeistung.SIDE_L.equals(side) && countSideLeft > 0) { newVerrechnet = leftVerrechnet; newVerrechnet.setZahl(newVerrechnet.getZahl() + 1); } else if (TarmedLeistung.SIDE_R.equals(side) && countSideRight > 0) { newVerrechnet = rightVerrechnet; newVerrechnet.setZahl(newVerrechnet.getZahl() + 1); } return side; } // toggle side if no side provided by context 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 * @param kons * {@link Konsultation} providing context * @return true OK if they are compatible, WARNING if it matches an exclusion case */ public Result<IVerrechenbar> isCompatible(TarmedLeistung tarmedCode, TarmedLeistung tarmed, Konsultation kons) { return isCompatible(null, tarmedCode, null, tarmed, kons); } /** * check compatibility of one tarmed with another * * @param tarmedCodeVerrechnet * the {@link Verrechnet} representing tarmedCode * @param tarmedCode * the tarmed and it's parents code are check whether they have to be excluded * @param tarmedVerrechnet * the {@link Verrechnet} representing tarmed * @param tarmed * TarmedLeistung who incompatibilities are examined * @param kons * {@link Konsultation} providing context * @return true OK if they are compatible, WARNING if it matches an exclusion case */ public Result<IVerrechenbar> isCompatible(Verrechnet tarmedCodeVerrechnet, TarmedLeistung tarmedCode, Verrechnet tarmedVerrechnet, TarmedLeistung tarmed, Konsultation kons) { TimeTool date = new TimeTool(kons.getDatum()); List<TarmedExclusion> exclusions = tarmed.getExclusions(kons); for (TarmedExclusion tarmedExclusion : exclusions) { if (tarmedExclusion.isMatching(tarmedCode, date)) { // exclude only if side matches if (tarmedExclusion.isValidSide() && tarmedCodeVerrechnet != null && tarmedVerrechnet != null) { String tarmedCodeSide = tarmedCodeVerrechnet.getDetail(TarmedLeistung.SIDE); String tarmedSide = tarmedVerrechnet.getDetail(TarmedLeistung.SIDE); if (tarmedSide != null && tarmedCodeSide != null) { if (tarmedSide.equals(tarmedCodeSide)) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, EXKLUSIONSIDE, tarmed.getCode() + " nicht kombinierbar mit " //$NON-NLS-1$ + tarmedExclusion.toString() + " auf der selben Seite", //$NON-NLS-1$ null, false); } else { // no exclusion due to different side continue; } } } return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, EXKLUSION, tarmed.getCode() + " nicht kombinierbar mit " + tarmedExclusion.toString(), //$NON-NLS-1$ null, false); } } // skip group exclusions check for the same service code if (!tarmedCode.getCode().equals(tarmed.getCode())) { List<String> groups = tarmed.getServiceGroups(date); for (String groupName : groups) { Optional<TarmedGroup> group = TarmedGroup.find(groupName, tarmed.get(TarmedLeistung.FLD_LAW), date); if (group.isPresent() && !tarmedCode.getServiceTyp().equals("Z")) { List<TarmedExclusion> groupExclusions = group.get().getExclusions(kons); for (TarmedExclusion tarmedExclusion : groupExclusions) { if (tarmedExclusion.isMatching(tarmedCode, date)) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, EXKLUSION, tarmed.getCode() + " nicht kombinierbar mit " //$NON-NLS-1$ + tarmedExclusion.toString(), null, false); } } } } } List<String> blocks = tarmed.getServiceBlocks(date); for (String blockName : blocks) { if (skipBlockExclusives(blockName)) { continue; } List<TarmedExclusive> exclusives = TarmedKumulation.getExclusives(blockName, TarmedKumulationType.BLOCK, date, tarmed.get(TarmedLeistung.FLD_LAW)); // currently only test blocks exclusives, exclude hierarchy matches if (canHandleAllExculives(exclusives) && !isMatchingHierarchy(tarmedCode, tarmed, date) && !tarmedCode.getServiceTyp().equals("Z")) { boolean included = false; for (TarmedExclusive tarmedExclusive : exclusives) { if (tarmedExclusive.isMatching(tarmedCode, date)) { included = true; } } if (!included) { return new Result<IVerrechenbar>(Result.SEVERITY.WARNING, EXKLUSIVE, tarmed.getCode() + " nicht kombinierbar mit " //$NON-NLS-1$ + tarmedCode.getCode() + ", wegen Block Kumulation", null, false); } } } return new Result<IVerrechenbar>(Result.SEVERITY.OK, OK, "compatible", null, false); } private boolean skipBlockExclusives(String blockName) { try { Integer blockNumber = Integer.valueOf(blockName); if (blockNumber > 50 && blockNumber < 60) { return true; } } catch (NumberFormatException nfe) { // ignore and do not skip } return false; } private boolean isMatchingHierarchy(TarmedLeistung tarmedCode, TarmedLeistung tarmed, TimeTool date) { return tarmed.getHierarchy(date).contains(tarmedCode.getCode()); } /** * Test if we can handle all {@link TarmedExclusive}. * * @param exclusives * @return */ private boolean canHandleAllExculives(List<TarmedExclusive> exclusives) { for (TarmedExclusive tarmedExclusive : exclusives) { if (tarmedExclusive.getSlaveType() != TarmedKumulationType.BLOCK && tarmedExclusive.getSlaveType() != TarmedKumulationType.CHAPTER && tarmedExclusive.getSlaveType() != TarmedKumulationType.SERVICE) { return false; } } return true; } /** * Eine Verrechnungsposition entfernen. Der Optifier sollte prfen, ob die Konsultation nach * Entfernung dieses Codes noch konsistent verrechnet wre und ggf. anpassen oder das Entfernen * verweigern. Diese Version macht keine Prfungen, sondern erfllt nur die Anfrage.. */ public Result<Verrechnet> remove(Verrechnet code, Konsultation kons) { List<Verrechnet> l = kons.getLeistungen(); l.remove(code); code.delete(); // if no more left, check for bezug and remove List<Verrechnet> left = getVerrechnetMatchingCode(l, code.getCode()); if (left.isEmpty()) { List<Verrechnet> verrechnetWithBezug = getVerrechnetWithBezugMatchingCode(kons.getLeistungen(), code.getCode()); for (Verrechnet verrechnet : verrechnetWithBezug) { remove(verrechnet, kons); } } return new Result<Verrechnet>(code); } @Override public Verrechnet getCreatedVerrechnet() { return newVerrechnet; } }