package ch.elexis.core.services; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.eclipse.core.runtime.IStatus; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.osgi.service.component.annotations.ReferenceCardinality; import org.osgi.service.component.annotations.ReferencePolicy; import org.osgi.service.component.annotations.ReferencePolicyOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.elexis.core.ac.AccessControlDefaults; import ch.elexis.core.common.ElexisEventTopics; import ch.elexis.core.model.IArticle; import ch.elexis.core.model.IBillable; import ch.elexis.core.model.IBillableOptifier; import ch.elexis.core.model.IBilled; import ch.elexis.core.model.IBillingSystemFactor; import ch.elexis.core.model.ICoverage; import ch.elexis.core.model.IEncounter; import ch.elexis.core.model.IInvoice; import ch.elexis.core.model.IMandator; import ch.elexis.core.model.IPrescription; import ch.elexis.core.model.InvoiceState; import ch.elexis.core.model.ModelPackage; import ch.elexis.core.model.prescription.EntryType; import ch.elexis.core.model.verrechnet.Constants; import ch.elexis.core.services.IQuery.COMPARATOR; import ch.elexis.core.services.holder.ContextServiceHolder; import ch.elexis.core.services.holder.CoreModelServiceHolder; import ch.elexis.core.status.StatusUtil; import ch.rgw.tools.Result; import ch.rgw.tools.Result.SEVERITY; @Component public class BillingService implements IBillingService { private static Logger logger = LoggerFactory.getLogger(BillingService.class); @Reference(target = "(" + IModelService.SERVICEMODELNAME + "=ch.elexis.core.model)") private IModelService coreModelService; @Reference private IAccessControlService accessControlService; @Reference private IStockService stockService; @Reference private IContextService contextService; private List<IBilledAdjuster> billedAdjusters = new ArrayList<>(); @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) public void setBilledAdjuster(IBilledAdjuster adjuster){ if (!billedAdjusters.contains(adjuster)) { billedAdjusters.add(adjuster); } } public void unsetBilledAdjuster(IBilledAdjuster adjuster){ if (billedAdjusters.contains(adjuster)) { billedAdjusters.remove(adjuster); } } private List<IBillableAdjuster> billableAdjusters = new ArrayList<>(); @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY) public void setBillableAdjuster(IBillableAdjuster adjuster){ if (!billableAdjusters.contains(adjuster)) { billableAdjusters.add(adjuster); } } public void unsetBillableAdjuster(IBillableAdjuster adjuster){ if (billableAdjusters.contains(adjuster)) { billableAdjusters.remove(adjuster); } } @Override public Result<IEncounter> isEditable(IEncounter encounter){ ICoverage coverage = encounter.getCoverage(); if (coverage != null) { if (!coverage.isOpen()) { return new Result<>(SEVERITY.WARNING, 0, "Diese Konsultation gehört zu einem abgeschlossenen Fall", encounter, false); } } IMandator encounterMandator = encounter.getMandator(); boolean checkMandant = !accessControlService.request(AccessControlDefaults.LSTG_CHARGE_FOR_ALL); boolean mandatorOk = true; boolean invoiceOk = true; IMandator activeMandator = ContextServiceHolder.get().getActiveMandator().orElse(null); boolean mandatorLoggedIn = (activeMandator != null); // if m is null, ignore checks (return true) if (encounterMandator != null && activeMandator != null) { if (checkMandant && !(encounterMandator.getId().equals(activeMandator.getId()))) { mandatorOk = false; } IInvoice rn = encounter.getInvoice(); if (rn == null) { invoiceOk = true; } else { InvoiceState state = rn.getState(); if (state == InvoiceState.CANCELLED) { invoiceOk = true; } else { invoiceOk = false; } } } boolean ok = invoiceOk && mandatorOk && mandatorLoggedIn; if (ok) { return new Result<>(encounter); } else { String msg = ""; if (!mandatorLoggedIn) { msg = "Es ist kein Mandant eingeloggt"; } else { if (!invoiceOk) { msg = "Für diese Behandlung wurde bereits eine Rechnung erstellt."; } else { msg = "Diese Behandlung ist nicht von Ihnen"; } } return new Result<>(SEVERITY.WARNING, 0, msg, encounter, false); } } @Override public Result<IBilled> bill(IBillable billable, IEncounter encounter, double amount){ IBillable beforeAdjust = billable; CoreModelServiceHolder.get().refresh(encounter, true); logger.info("Billing [" + amount + "] of [" + billable + "] on [" + encounter + "]"); for (IBillableAdjuster iBillableAdjuster : billableAdjusters) { billable = iBillableAdjuster.adjust(billable, encounter); } if (billable != null) { Result<IBillable> verificationResult = billable.getVerifier().verifyAdd(billable, encounter, amount); if (verificationResult.isOK()) { IBillableOptifier optifier = billable.getOptifier(); Result<IBilled> optifierResult = optifier.add(billable, encounter, amount); if (billable instanceof IArticle) { IStatus status = stockService.performSingleDisposal((IArticle) billable, doubleToInt(amount), contextService.getActiveMandator().map(m -> m.getId()).orElse(null)); if (!status.isOK()) { StatusUtil.logStatus(logger, status, true); } } // TODO refactor if (!optifierResult.isOK() && optifierResult.getCode() == 11) { String initialResult = optifierResult.toString(); // code 11 is tarmed exclusion due to side see TarmedOptifier#EXKLUSIONSIDE // set a context variable to specify the side see TarmedLeistung#SIDE, TarmedLeistung#SIDE_L, TarmedLeistung#SIDE_R optifier.putContext("Seite", "r"); optifierResult = optifier.add(billable, encounter, amount); if (!optifierResult.isOK() && optifierResult.getCode() == 11) { optifier.putContext("Seite", "l"); optifierResult = optifier.add(billable, encounter, amount); } if (optifierResult.isOK()) { String message = "Achtung: " + initialResult + "\n Es wurde bei der Position " + billable.getCode() + " automatisch die Seite gewechselt." + " Bitte korrigieren Sie die Leistung falls dies nicht korrekt ist."; optifierResult.addMessage(SEVERITY.OK, message); } optifier.clearContext(); } if (optifierResult.get() != null) { for (IBilledAdjuster iBilledAdjuster : billedAdjusters) { iBilledAdjuster.adjust(optifierResult.get()); } } return optifierResult; } else { return translateResult(verificationResult); } } else { return new Result<IBilled>(Result.SEVERITY.WARNING, 1, "Folgende Leistung '" + beforeAdjust.getCode() + "' konnte im aktuellen Kontext (Fall, Konsultation, Gesetz) nicht verrechnet werden.", null, false); } } /** * 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(); } @Override public Result<?> removeBilled(IBilled billed, IEncounter encounter){ Result<IEncounter> editable = isEditable(encounter); if(!editable.isOK()) { return editable; } encounter.removeBilled(billed); IBillable billable = billed.getBillable(); if(billable instanceof IArticle) { // TODO stock return via event IArticle article = (IArticle) billable; String mandatorId = contextService.getActiveMandator().map(m->m.getId()).orElse(null); stockService.performSingleReturn(article, (int) billed.getAmount(), mandatorId); // TODO prescription via event Object prescId = billed.getExtInfo(Constants.FLD_EXT_PRESC_ID); if(prescId instanceof String) { IPrescription prescription = coreModelService.load((String)prescId, IPrescription.class).orElse(null); if(prescription != null && EntryType.SELF_DISPENSED == prescription.getEntryType()) { coreModelService.remove(prescription); ContextServiceHolder.get().postEvent(ElexisEventTopics.EVENT_RELOAD, prescription); } } } return Result.OK(); } private Result<IBilled> translateResult(Result<IBillable> verificationResult){ Result<IBilled> ret = new Result<>(); verificationResult.getMessages() .forEach(msg -> ret.addMessage(msg.getSeverity(), msg.getText())); return ret; } @Override public Optional<IBillingSystemFactor> getBillingSystemFactor(String system, LocalDate date){ IQuery<IBillingSystemFactor> query = coreModelService.getQuery(IBillingSystemFactor.class); query.and(ModelPackage.Literals.IBILLING_SYSTEM_FACTOR__SYSTEM, COMPARATOR.EQUALS, system); query.and(ModelPackage.Literals.IBILLING_SYSTEM_FACTOR__VALID_FROM, COMPARATOR.LESS_OR_EQUAL, date); query.and(ModelPackage.Literals.IBILLING_SYSTEM_FACTOR__VALID_TO, COMPARATOR.GREATER_OR_EQUAL, date); return query.executeSingleResult(); } @Override public void setBillingSystemFactor(LocalDate from, LocalDate to, double factor, String system){ if (to == null) { // 20380118, TimeTool.END_OF_UNIX_EPOCH to = LocalDate.of(2038, 1, 18); } IQuery<IBillingSystemFactor> query = coreModelService.getQuery(IBillingSystemFactor.class); query.and(ModelPackage.Literals.IBILLING_SYSTEM_FACTOR__SYSTEM, COMPARATOR.EQUALS, system); List<IBillingSystemFactor> existingWithSystem = query.execute(); for (IBillingSystemFactor iBillingSystemFactor : existingWithSystem) { if (iBillingSystemFactor.getValidTo() == null || iBillingSystemFactor.getValidTo().isAfter(from)) { iBillingSystemFactor.setValidTo(from); coreModelService.save(iBillingSystemFactor); } } IBillingSystemFactor billingSystemFactor = coreModelService.create(IBillingSystemFactor.class); billingSystemFactor.setFactor(factor); billingSystemFactor.setSystem(system); billingSystemFactor.setValidFrom(from); billingSystemFactor.setValidTo(to); coreModelService.save(billingSystemFactor); } }