/*******************************************************************************
* 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;
}
}