org.openmrs.module.emrapi.visit.VisitDomainWrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.emrapi.visit.VisitDomainWrapper.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.emrapi.visit;

import org.apache.commons.collections.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.DateMidnight;
import org.openmrs.Encounter;
import org.openmrs.EncounterType;
import org.openmrs.Location;
import org.openmrs.Obs;
import org.openmrs.User;
import org.openmrs.Visit;
import org.openmrs.VisitAttribute;
import org.openmrs.module.emrapi.EmrApiProperties;
import org.openmrs.module.emrapi.adt.reporting.query.AwaitingAdmissionVisitQuery;
import org.openmrs.module.emrapi.descriptor.MissingConceptException;
import org.openmrs.module.emrapi.diagnosis.CodedOrFreeTextAnswer;
import org.openmrs.module.emrapi.diagnosis.Diagnosis;
import org.openmrs.module.emrapi.diagnosis.DiagnosisMetadata;
import org.openmrs.module.emrapi.disposition.Disposition;
import org.openmrs.module.emrapi.disposition.DispositionDescriptor;
import org.openmrs.module.emrapi.disposition.DispositionService;
import org.openmrs.module.emrapi.disposition.DispositionType;
import org.openmrs.module.emrapi.domainwrapper.DomainWrapper;
import org.openmrs.module.emrapi.encounter.EncounterDomainWrapper;
import org.openmrs.module.reporting.evaluation.EvaluationException;
import org.openmrs.module.reporting.evaluation.context.VisitEvaluationContext;
import org.openmrs.module.reporting.query.visit.VisitIdSet;
import org.openmrs.module.reporting.query.visit.VisitQueryResult;
import org.openmrs.module.reporting.query.visit.service.VisitQueryService;
import org.openmrs.util.OpenmrsUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static java.util.Collections.EMPTY_LIST;
import static java.util.Collections.reverseOrder;
import static java.util.Collections.sort;
import static org.apache.commons.collections.CollectionUtils.find;
import static org.apache.commons.collections.CollectionUtils.select;

/**
 * Wrapper around a Visit, that provides convenience methods to find particular encounters of interest.
 */
public class VisitDomainWrapper implements DomainWrapper {

    public enum SortOrder {
        EARLIEST_FIRST, MOST_RECENT_FIRST
    }

    private static final Log log = LogFactory.getLog(VisitDomainWrapper.class);

    @Qualifier("emrApiProperties")
    @Autowired
    protected EmrApiProperties emrApiProperties;

    @Qualifier("dispositionService")
    @Autowired
    protected DispositionService dispositionService;

    @Autowired
    protected VisitQueryService visitQueryService;

    private Visit visit;

    public VisitDomainWrapper() {
    }

    @Deprecated // use new VisitDomainWrapperFactory instead to instantiate a visit domain wrapper
    public VisitDomainWrapper(Visit visit) {
        this.visit = visit;
    }

    @Deprecated // use new VisitDomainWrapperFactory instead to instantiate a visit domain wrapper
    public VisitDomainWrapper(Visit visit, EmrApiProperties emrApiProperties) {
        this.visit = visit;
        this.emrApiProperties = emrApiProperties;
    }

    @Deprecated // use new VisitDomainWrapperFactory instead to instantiate a visit domain wrapper
    public VisitDomainWrapper(Visit visit, EmrApiProperties emrApiProperties,
            DispositionService dispositionService) {
        this.visit = visit;
        this.emrApiProperties = emrApiProperties;
        this.dispositionService = dispositionService;
    }

    /**
     * @return the visit
     */
    public Visit getVisit() {
        return visit;
    }

    public void setVisit(Visit visit) {
        this.visit = visit;
    }

    public int getVisitId() {
        return visit.getVisitId();
    }

    // setters for mocking purposes
    public void setEmrApiProperties(EmrApiProperties emrApiProperties) {
        this.emrApiProperties = emrApiProperties;
    }

    public void setDispositionService(DispositionService dispositionService) {
        this.dispositionService = dispositionService;
    }

    public void setVisitQueryService(VisitQueryService visitQueryService) {
        this.visitQueryService = visitQueryService;
    }

    public Encounter getAdmissionEncounter() {
        return (Encounter) find(getSortedEncounters(SortOrder.MOST_RECENT_FIRST),
                new EncounterTypePredicate(emrApiProperties.getAdmissionEncounterType()));
    }

    // TODO: refactor this to use EncounterTypePredicate
    public Encounter getLatestAdtEncounter() {
        for (Encounter e : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) {
            if (emrApiProperties.getAdmissionEncounterType().equals(e.getEncounterType())
                    || emrApiProperties.getTransferWithinHospitalEncounterType().equals(e.getEncounterType()))
                return e;
        }
        return null;
    }

    public boolean isActive() {
        return visit.getStopDatetime() == null;
    }

    @Deprecated // renamed to is Active
    public boolean isOpen() {
        return isActive();
    }

    /**
     * Returns the most recent  (non-voided) check-in encounter from this visit
     * @return
     */
    public Encounter getMostRecentCheckInEncounter() {
        return (Encounter) find(getSortedEncounters(SortOrder.MOST_RECENT_FIRST),
                new EncounterTypePredicate(emrApiProperties.getCheckInEncounterType()));
    }

    /**
     * Returns the first   (non-voided) check-in encounter from this visit
     * @return
     */
    public Encounter getEarliestCheckInEncounter() {
        return (Encounter) find(getSortedEncounters(SortOrder.EARLIEST_FIRST),
                new EncounterTypePredicate(emrApiProperties.getCheckInEncounterType()));
    }

    @Deprecated // use getMostRecentCheckInEncounter, as this is a more accurate method name
    public Encounter getCheckInEncounter() {
        return getMostRecentCheckInEncounter();
    }

    public Encounter getMostRecentEncounter() {
        List<Encounter> encounters = getSortedEncounters(SortOrder.MOST_RECENT_FIRST);
        if (encounters.size() > 0)
            return encounters.get(0);
        return null;
    }

    @Deprecated //use getMostRecentEncounter because this method name doesn't make sense
    public Encounter getLastEncounter() {
        return getMostRecentEncounter();
    }

    public Encounter getEarliestEncounter() {
        List<Encounter> encounters = getSortedEncounters(SortOrder.MOST_RECENT_FIRST);
        if (encounters.size() != 0)
            return encounters.get(encounters.size() - 1);
        return null;
    }

    @Deprecated // we are standardizing on "Earliest" and "Most Recent" to the
    public Encounter getOldestEncounter() {
        return getEarliestEncounter();
    }

    /**
     * Fetches the most recent (non-voided) visit note encounter from this visit
     * @return
     */
    public Encounter getMostRecentVisitNote() {
        return (Encounter) find(getSortedEncounters(SortOrder.MOST_RECENT_FIRST),
                new EncounterTypePredicate(emrApiProperties.getVisitNoteEncounterType()));
    }

    /**
     * Fetches the most recent (non-voided) visit note encounter from this visit at the  specified location
     * @return
     */
    public Encounter getMostRecentVisitNoteAtLocation(Location location) {
        return (Encounter) find(getSortedEncounters(SortOrder.MOST_RECENT_FIRST),
                new EncounterTypeAndLocationPredicate(emrApiProperties.getVisitNoteEncounterType(), location));

    }

    /**
     * True/false whether there is a non-voided visit encounter associated with this visit
     * @return
     */
    public boolean hasVisitNote() {
        return getMostRecentVisitNote() != null;
    }

    /**
     * True/false whether there is a non-voided visit encounter associated with this visit at the specified location
     * @return
     *
     */
    public boolean hasVisitNoteAtLocation(Location location) {
        return getMostRecentVisitNoteAtLocation(location) != null;
    }

    // default is to return most recent encounter first
    public List<Encounter> getSortedEncounters() {
        return getSortedEncounters(SortOrder.MOST_RECENT_FIRST);
    }

    /**
     * Returns all non-voided encounters in the visit
     *
     * @param order whether to return the most recent first, or the earliest first
     * @return
     */
    public List<Encounter> getSortedEncounters(SortOrder order) {

        Comparator<Encounter> datetimeComparator = EncounterDomainWrapper.DATETIME_COMPARATOR;

        if (order == SortOrder.MOST_RECENT_FIRST) {
            datetimeComparator = reverseOrder(datetimeComparator);
        }

        if (visit.getEncounters() != null) {
            List<Encounter> nonVoidedEncounters = (List<Encounter>) select(visit.getEncounters(),
                    EncounterDomainWrapper.NON_VOIDED_PREDICATE);
            sort(nonVoidedEncounters, datetimeComparator);
            return nonVoidedEncounters;
        }
        return EMPTY_LIST;
    }

    public int getDifferenceInDaysBetweenCurrentDateAndStartDate() {
        Date today = Calendar.getInstance().getTime();

        Date startDateVisit = getStartDate();

        int millisecondsInADay = 1000 * 60 * 60 * 24;

        return (int) ((today.getTime() - startDateVisit.getTime()) / millisecondsInADay);
    }

    // note that the disposition must be on the top level for this to pick it up
    // (seemed like this made sense to do for performance reasons)
    // also, if encounter has multiple disposition (is this possible?) it just returns the first one it finds
    public Disposition getMostRecentDisposition() {

        if (dispositionService.dispositionsSupported()) { // prevents against stace trace if dispositions are supported
            DispositionDescriptor dispositionDescriptor = dispositionService.getDispositionDescriptor();

            for (Encounter encounter : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) {
                for (Obs obs : encounter.getObsAtTopLevel(false)) {
                    if (dispositionDescriptor.isDisposition(obs)) {
                        return dispositionService.getDispositionFromObsGroup(obs);
                    }
                }
            }
        }

        return null;
    }

    /**
     * Finds the most recent encounter in the visit with a disposition of a specific type, and retrieves
     * all diagnoses stored on that encounter. Can be used to prepopulate an admission, transfer, or discharge note;
     * example workflow: a doctor writes a visit note and on that note sets a disposition of admission; later on,
     * another doctor writes the actual admission note; that doctor may want a way to prepopulate this
     * note with the dispositions from the visit note recommending admission; see the EncounterDisposition
     * HFE tag in CoreApps for an example of how this method is used
     */
    public List<Diagnosis> getDiagnosesFromMostRecentDispositionByType(DispositionType type) {

        if (dispositionService.dispositionsSupported()) { // prevents against stack trace if dispositions not configured
            DispositionDescriptor dispositionDescriptor = dispositionService.getDispositionDescriptor();

            for (Encounter encounter : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) { // getSortedEncounters already excludes voided encounters
                for (Obs obs : encounter.getObsAtTopLevel(false)) {
                    if (dispositionDescriptor.isDisposition(obs)
                            && dispositionService.getDispositionFromObsGroup(obs).getType() == type) {
                        return getDiagnosesFromEncounter(encounter);
                    }
                }
            }
        }

        return new ArrayList<Diagnosis>(); // return empty list if no matches
    }

    public List<Diagnosis> getPrimaryDiagnoses() {
        List<Diagnosis> diagnoses = new ArrayList<Diagnosis>();
        for (Encounter encounter : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) {
            diagnoses.addAll(
                    getDiagnosesFromEncounter(encounter, Collections.singletonList(Diagnosis.Order.PRIMARY)));
        }
        return diagnoses;
    }

    /**
     * @return the unique list of diagnoses recorded in this visit, where uniqueness is based only on
     * whether the CodedOrNonCoded diagnosis is the same, not on whether the order or certainty are the same
     * if the primaryOnly flag is true, return only primary diagnoses
     * if the confirmedOnly flag is true, return only confirmed diagnoses
      * will return null if diagnosis support not currently configured
     */
    public List<Diagnosis> getUniqueDiagnoses(Boolean primaryOnly, Boolean confirmedOnly) {
        Map<CodedOrFreeTextAnswer, Diagnosis> diagnoses = new LinkedHashMap<CodedOrFreeTextAnswer, Diagnosis>();
        for (Encounter encounter : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) {

            List<Diagnosis> diagnosesFromEncounter = getDiagnosesFromEncounter(encounter);
            if (diagnosesFromEncounter == null) {
                return null;
            }

            for (Diagnosis d : diagnosesFromEncounter) {
                if (!primaryOnly || d.getOrder() == Diagnosis.Order.PRIMARY) {
                    if (!confirmedOnly || d.getCertainty() == Diagnosis.Certainty.CONFIRMED) {
                        if (!diagnoses.containsKey(d.getDiagnosis())) {
                            diagnoses.put(d.getDiagnosis(), d);
                        }
                    }
                }
            }

        }
        return new ArrayList<Diagnosis>(diagnoses.values());
    }

    private List<Diagnosis> getDiagnosesFromEncounter(Encounter encounter) {
        return getDiagnosesFromEncounter(encounter, null);
    }

    private List<Diagnosis> getDiagnosesFromEncounter(Encounter encounter, List<Diagnosis.Order> diagnosisOrders) {

        DiagnosisMetadata diagnosisMetadata;

        try {
            diagnosisMetadata = emrApiProperties.getDiagnosisMetadata();
        } catch (MissingConceptException ex) {
            // this isn't a hard error, because some implementations will not be using diagnoses functionality
            log.info("Diagnosis metadata not configured", ex);
            return null;
        }

        List<Diagnosis> diagnoses = new ArrayList<Diagnosis>();

        for (Obs obs : encounter.getObsAtTopLevel(false)) {
            if (diagnosisMetadata.isDiagnosis(obs)) {
                try {
                    Diagnosis diagnosis = diagnosisMetadata.toDiagnosis(obs);
                    if (diagnosisOrders == null || diagnosisOrders.contains(diagnosis.getOrder())) {
                        diagnoses.add(diagnosis);
                    }
                } catch (Exception ex) {
                    log.warn("malformed diagnosis obs group with obsId " + obs.getObsId(), ex);
                }
            }
        }
        return diagnoses;
    }

    public boolean hasEncounters() {
        List<Encounter> encounters = getSortedEncounters(SortOrder.MOST_RECENT_FIRST);
        if (encounters != null && encounters.size() > 0) {
            return true;
        }
        return false;
    }

    public boolean hasEncounterWithoutSubsequentEncounter(EncounterType lookForEncounterType,
            EncounterType withoutSubsequentEncounterType) {
        return hasEncounterWithoutSubsequentEncounter(lookForEncounterType, withoutSubsequentEncounterType, null);
    }

    private boolean hasEncounterWithoutSubsequentEncounter(EncounterType lookForEncounterType,
            EncounterType withoutSubsequentEncounterType, Date onDate) {

        if (visit.getEncounters() == null) {
            return false;
        }

        for (Encounter encounter : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) {
            if (onDate == null || encounter.getEncounterDatetime().before(onDate)
                    || encounter.getEncounterDatetime().equals(onDate)) {
                if (encounter.getEncounterType().equals(lookForEncounterType)) {
                    return true;
                } else if (encounter.getEncounterType().equals(withoutSubsequentEncounterType)) {
                    return false;
                }
            }
        }

        return false;
    }

    /**
     * @return true if the visit includes an admission encounter with no discharge encounter after it
     */
    public boolean isAdmitted() {
        EncounterType admissionEncounterType = emrApiProperties.getAdmissionEncounterType();
        EncounterType dischargeEncounterType = emrApiProperties.getExitFromInpatientEncounterType();
        if (admissionEncounterType == null) {
            return false;
        }
        return hasEncounterWithoutSubsequentEncounter(admissionEncounterType, dischargeEncounterType);
    }

    public boolean isAdmitted(Date onDate) {

        if (visit.getStartDatetime().after(onDate)
                || (visit.getStopDatetime() != null && visit.getStopDatetime().before(onDate))) {
            throw new IllegalArgumentException("date does not fall within visit");
        }

        EncounterType admissionEncounterType = emrApiProperties.getAdmissionEncounterType();
        EncounterType dischargeEncounterType = emrApiProperties.getExitFromInpatientEncounterType();
        if (admissionEncounterType == null) {
            return false;
        }

        return hasEncounterWithoutSubsequentEncounter(admissionEncounterType, dischargeEncounterType, onDate);
    }

    public boolean isAwaitingAdmission() {

        if (!isActive() || !dispositionService.dispositionsSupported()) { // prevents a stack trace if dispositions are supported
            return false;
        }

        VisitQueryResult result = null;

        VisitEvaluationContext context = new VisitEvaluationContext();
        context.setBaseVisits(new VisitIdSet(getVisitId()));

        AwaitingAdmissionVisitQuery query = new AwaitingAdmissionVisitQuery();
        query.setLocation(visit.getLocation());

        try {
            result = visitQueryService.evaluate(query, context);
        } catch (EvaluationException e) {
            throw new IllegalStateException("Unable to evaluate awaiting admission query", e);
        }

        return result != null && result.getMemberIds().size() > 0;
    }

    public Location getInpatientLocation(Date onDate) {

        if (!isAdmitted(onDate)) {
            return null;
        }

        EncounterType admissionEncounterType = emrApiProperties.getAdmissionEncounterType();
        EncounterType transferEncounterType = emrApiProperties.getTransferWithinHospitalEncounterType();

        for (Encounter encounter : getSortedEncounters(SortOrder.MOST_RECENT_FIRST)) {
            if (onDate == null || encounter.getEncounterDatetime().before(onDate)
                    || encounter.getEncounterDatetime().equals(onDate)) {
                if (encounter.getEncounterType().equals(admissionEncounterType)
                        || encounter.getEncounterType().equals(transferEncounterType)) {
                    return encounter.getLocation();
                }
            }
        }

        // should never get here if isAdmitted == true
        return null;
    }

    public Date getStartDatetime() {
        return visit.getStartDatetime();
    }

    public Date getStopDatetime() {
        return visit.getStopDatetime();
    }

    public Date getStartDate() {
        return visit.getStartDatetime() != null ? new DateMidnight(visit.getStartDatetime()).toDate() : null;
    }

    public Date getStopDate() {
        return visit.getStopDatetime() != null ? new DateMidnight(visit.getStopDatetime()).toDate() : null;
    }

    /**
     * @param encounter
     * @return this, for call chaining
     */
    public VisitDomainWrapper addEncounter(Encounter encounter) {
        visit.addEncounter(encounter);
        return this;
    }

    public void closeOnLastEncounterDatetime() {

        Encounter mostRecentEncounter = getMostRecentEncounter();

        if (mostRecentEncounter == null) {
            throw new IllegalStateException("Visit has no encounters");
        }

        visit.setStopDatetime(mostRecentEncounter.getEncounterDatetime());
    }

    /**
     * Throws an {@link IllegalArgumentException} if checkDatetime is not within the start/stop date bounds of this visit
     * @param checkDatetime
     * @param errorMessage base of the error message to throw (some details may be added)
     */
    public void errorIfOutsideVisit(Date checkDatetime, String errorMessage) throws IllegalArgumentException {
        if (visit.getStartDatetime() != null && OpenmrsUtil.compare(checkDatetime, visit.getStartDatetime()) < 0) {
            throw new IllegalArgumentException(errorMessage + ": visit started at " + visit.getStartDatetime()
                    + " but testing an earlier date");
        }
        if (visit.getStopDatetime() != null && OpenmrsUtil.compare(visit.getStopDatetime(), checkDatetime) < 0) {
            throw new IllegalArgumentException(
                    errorMessage + ": visit stopped at " + visit.getStopDatetime() + " but testing a later date");
        }
    }

    public Date getEncounterStopDateRange() {
        return getStopDatetime() == null ? new Date() : getStopDatetime();
    }

    public boolean verifyIfUserIsTheCreatorOfVisit(User currentUser) {
        return visit.getCreator().equals(currentUser);
    }

    public Object getVisitAttribute(String uuidOrName) {
        for (VisitAttribute attribute : visit.getActiveAttributes()) {
            if (attribute.getAttributeType().getUuid().equals(uuidOrName)
                    || attribute.getAttributeType().getName().equals(uuidOrName)) {
                // note the assumption of this method is that there is only one attribute
                return attribute.getValue();
            }
        }
        return null;
    }

    private class EncounterTypePredicate implements Predicate {

        private EncounterType type;

        public EncounterTypePredicate(EncounterType type) {
            this.type = type;
        }

        @Override
        public boolean evaluate(Object o) {
            return type.equals(((Encounter) o).getEncounterType());
        }
    }

    private class EncounterTypeAndLocationPredicate implements Predicate {

        private EncounterType type;

        private Location location;

        public EncounterTypeAndLocationPredicate(EncounterType type, Location location) {
            this.type = type;
            this.location = location;
        }

        @Override
        public boolean evaluate(Object o) {
            return type.equals(((Encounter) o).getEncounterType())
                    && location.equals(((Encounter) o).getLocation());
        }
    }
}