com.marand.thinkmed.medications.therapy.impl.TherapyUpdaterImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.marand.thinkmed.medications.therapy.impl.TherapyUpdaterImpl.java

Source

/*
 * Copyright (c) 2010-2014 Marand d.o.o. (www.marand.com)
 *
 * This file is part of Think!Med Clinical Medication Management.
 *
 * Think!Med Clinical Medication Management is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Think!Med Clinical Medication Management is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Think!Med Clinical Medication Management.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.marand.thinkmed.medications.therapy.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.annotation.Nullable;

import com.google.common.base.Preconditions;
import com.marand.ispek.ehr.common.EhrLinkType;
import com.marand.maf.core.JsonUtil;
import com.marand.maf.core.Opt;
import com.marand.maf.core.Pair;
import com.marand.maf.core.PartialList;
import com.marand.maf.core.eventbus.EventProducer;
import com.marand.maf.core.exception.UserWarning;
import com.marand.maf.core.openehr.visitor.IspekTdoDataSupport;
import com.marand.maf.core.openehr.visitor.TdoPopulatingVisitor;
import com.marand.maf.core.service.RequestContextHolder;
import com.marand.maf.core.time.DateTimeFormatters;
import com.marand.maf.core.time.Intervals;
import com.marand.maf.core.valueholder.ValueHolder;
import com.marand.openehr.medications.tdo.MedicationActionAction;
import com.marand.openehr.medications.tdo.MedicationInstructionInstruction;
import com.marand.openehr.medications.tdo.MedicationOnAdmissionComposition;
import com.marand.openehr.medications.tdo.MedicationOrderComposition;
import com.marand.openehr.medications.tdo.PharmacyReviewReportComposition;
import com.marand.openehr.rm.RmPath;
import com.marand.openehr.rm.TdoPathable;
import com.marand.openehr.util.DataValueUtils;
import com.marand.openehr.util.OpenEhrRefUtils;
import com.marand.thinkmed.api.externals.data.object.NamedExternalDto;
import com.marand.thinkmed.medications.ActionReasonType;
import com.marand.thinkmed.medications.AdministrationResultEnum;
import com.marand.thinkmed.medications.AdministrationTypeEnum;
import com.marand.thinkmed.medications.MedicationActionEnum;
import com.marand.thinkmed.medications.MedicationOrderActionEnum;
import com.marand.thinkmed.medications.MedicationRouteTypeEnum;
import com.marand.thinkmed.medications.ParticipationTypeEnum;
import com.marand.thinkmed.medications.PharmacistReviewTaskStatusEnum;
import com.marand.thinkmed.medications.PrescriptionChangeTypeEnum;
import com.marand.thinkmed.medications.SelfAdministeringActionEnum;
import com.marand.thinkmed.medications.TaskTypeEnum;
import com.marand.thinkmed.medications.TherapyAssigneeEnum;
import com.marand.thinkmed.medications.administration.AdministrationHandler;
import com.marand.thinkmed.medications.administration.AdministrationProvider;
import com.marand.thinkmed.medications.administration.AdministrationTaskCreator;
import com.marand.thinkmed.medications.admission.MedicationOnAdmissionHandler;
import com.marand.thinkmed.medications.business.MedicationsBo;
import com.marand.thinkmed.medications.business.impl.TherapyDisplayProvider;
import com.marand.thinkmed.medications.business.util.MedicationsEhrUtils;
import com.marand.thinkmed.medications.business.util.TherapyIdUtils;
import com.marand.thinkmed.medications.change.TherapyChangeCalculator;
import com.marand.thinkmed.medications.converter.therapy.MedicationConverterSelector;
import com.marand.thinkmed.medications.converter.therapy.MedicationToEhrConverter;
import com.marand.thinkmed.medications.dao.MedicationsDao;
import com.marand.thinkmed.medications.dao.openehr.MedicationsOpenEhrDao;
import com.marand.thinkmed.medications.dto.CodedNameDto;
import com.marand.thinkmed.medications.dto.ComplexTherapyDto;
import com.marand.thinkmed.medications.dto.MedicationHolderDto;
import com.marand.thinkmed.medications.dto.OxygenTherapyDto;
import com.marand.thinkmed.medications.dto.SaveMedicationOrderDto;
import com.marand.thinkmed.medications.dto.TherapyChangeReasonDto;
import com.marand.thinkmed.medications.dto.TherapyDoseDto;
import com.marand.thinkmed.medications.dto.TherapyDto;
import com.marand.thinkmed.medications.dto.administration.AdjustInfusionAdministrationDto;
import com.marand.thinkmed.medications.dto.administration.AdministrationDto;
import com.marand.thinkmed.medications.dto.administration.AdministrationTaskDto;
import com.marand.thinkmed.medications.dto.administration.StartAdministrationDto;
import com.marand.thinkmed.medications.dto.administration.StopAdministrationDto;
import com.marand.thinkmed.medications.dto.change.TherapyChangeDto;
import com.marand.thinkmed.medications.dto.change.TherapyChangeType;
import com.marand.thinkmed.medications.pharmacist.PharmacistTaskHandler;
import com.marand.thinkmed.medications.pharmacist.PreparePerfusionSyringeProcessHandler;
import com.marand.thinkmed.medications.service.impl.MedicationsServiceEvents.AbortTherapy;
import com.marand.thinkmed.medications.service.impl.MedicationsServiceEvents.ReissueTherapy;
import com.marand.thinkmed.medications.service.impl.MedicationsServiceEvents.SuspendTherapy;
import com.marand.thinkmed.medications.task.AdministrationTaskCreateActionEnum;
import com.marand.thinkmed.medications.task.AdministrationTaskDef;
import com.marand.thinkmed.medications.task.DoctorReviewTaskDef;
import com.marand.thinkmed.medications.task.MedicationsTasksHandler;
import com.marand.thinkmed.medications.task.MedicationsTasksProvider;
import com.marand.thinkmed.medications.task.MedsTaskDef;
import com.marand.thinkmed.medications.task.SwitchToOralTaskDef;
import com.marand.thinkmed.medications.task.TherapyTaskDef;
import com.marand.thinkmed.medications.task.TherapyTaskUtils;
import com.marand.thinkmed.medications.therapy.TherapyUpdater;
import com.marand.thinkmed.process.dto.AbstractTaskDto;
import com.marand.thinkmed.process.dto.NewTaskRequestDto;
import com.marand.thinkmed.process.dto.TaskDetailsEnum;
import com.marand.thinkmed.process.dto.TaskDto;
import com.marand.thinkmed.process.service.ProcessService;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.openehr.jaxb.rm.DvCount;
import org.openehr.jaxb.rm.DvEhrUri;
import org.openehr.jaxb.rm.DvParsable;
import org.openehr.jaxb.rm.Link;
import org.openehr.jaxb.rm.PartyIdentified;
import org.springframework.beans.factory.annotation.Required;

/**
 * @author Mitja Lapajne
 */
public class TherapyUpdaterImpl implements TherapyUpdater {
    private MedicationsOpenEhrDao medicationsOpenEhrDao;
    private MedicationsBo medicationsBo;
    private ValueHolder<Map<Long, MedicationHolderDto>> medicationsValueHolder;
    private TherapyDisplayProvider therapyDisplayProvider;
    private MedicationOnAdmissionHandler medicationOnAdmissionHandler;
    private AdministrationHandler administrationHandler;
    private MedicationsDao medicationsDao;
    private PharmacistTaskHandler pharmacistTaskHandler;
    private PreparePerfusionSyringeProcessHandler preparePerfusionSyringeProcessHandler;
    private MedicationsTasksHandler medicationsTasksHandler;
    private MedicationsTasksProvider medicationsTasksProvider;
    private AdministrationTaskCreator administrationTaskCreator;
    private ProcessService processService;
    private AdministrationProvider administrationProvider;
    private TherapyChangeCalculator therapyChangeCalculator;

    @Required
    public void setMedicationsOpenEhrDao(final MedicationsOpenEhrDao medicationsOpenEhrDao) {
        this.medicationsOpenEhrDao = medicationsOpenEhrDao;
    }

    @Required
    public void setMedicationsBo(final MedicationsBo medicationsBo) {
        this.medicationsBo = medicationsBo;
    }

    @Required
    public void setMedicationsValueHolder(
            final ValueHolder<Map<Long, MedicationHolderDto>> medicationsValueHolder) {
        this.medicationsValueHolder = medicationsValueHolder;
    }

    @Required
    public void setTherapyDisplayProvider(final TherapyDisplayProvider therapyDisplayProvider) {
        this.therapyDisplayProvider = therapyDisplayProvider;
    }

    @Required
    public void setProcessService(final ProcessService processService) {
        this.processService = processService;
    }

    @Required
    public void setMedicationOnAdmissionHandler(final MedicationOnAdmissionHandler medicationOnAdmissionHandler) {
        this.medicationOnAdmissionHandler = medicationOnAdmissionHandler;
    }

    @Required
    public void setAdministrationHandler(final AdministrationHandler administrationHandler) {
        this.administrationHandler = administrationHandler;
    }

    @Required
    public void setMedicationsDao(final MedicationsDao medicationsDao) {
        this.medicationsDao = medicationsDao;
    }

    public void setAdministrationTaskCreator(final AdministrationTaskCreator administrationTaskCreator) {
        this.administrationTaskCreator = administrationTaskCreator;
    }

    @Required
    public void setPharmacistTaskHandler(final PharmacistTaskHandler pharmacistTaskHandler) {
        this.pharmacistTaskHandler = pharmacistTaskHandler;
    }

    @Required
    public void setMedicationsTasksHandler(final MedicationsTasksHandler medicationsTasksHandler) {
        this.medicationsTasksHandler = medicationsTasksHandler;
    }

    @Required
    public void setMedicationsTasksProvider(final MedicationsTasksProvider medicationsTasksProvider) {
        this.medicationsTasksProvider = medicationsTasksProvider;
    }

    @Required
    public void setPreparePerfusionSyringeProcessHandler(
            final PreparePerfusionSyringeProcessHandler preparePerfusionSyringeProcessHandler) {
        this.preparePerfusionSyringeProcessHandler = preparePerfusionSyringeProcessHandler;
    }

    @Required
    public void setAdministrationProvider(final AdministrationProvider administrationProvider) {
        this.administrationProvider = administrationProvider;
    }

    @Required
    public void setTherapyChangeCalculator(final TherapyChangeCalculator therapyChangeCalculator) {
        this.therapyChangeCalculator = therapyChangeCalculator;
    }

    @Override
    public List<MedicationOrderComposition> saveTherapies(final String patientId,
            final List<SaveMedicationOrderDto> medicationOrders, final String centralCaseId,
            @Nullable final String careProviderId, final NamedExternalDto prescriber, final DateTime when,
            final Locale locale) {
        final List<MedicationOrderComposition> savedTherapies = new ArrayList<>();
        final Map<String, MedicationOrderComposition> savedLinkedTherapies = new HashMap<>();

        final List<SaveMedicationOrderDto> sortedMedicationOrders = sortMedicationOrdersByLinkName(
                medicationOrders);
        for (final SaveMedicationOrderDto medicationOrder : sortedMedicationOrders) {
            final TherapyDto therapy = medicationOrder.getTherapy();
            therapy.setCompositionUid(null);

            final MedicationOrderActionEnum actionEnum = medicationOrder.getActionEnum();
            final String sourceId = medicationOrder.getSourceId();
            final TherapyChangeReasonDto changeReasonDto = medicationOrder.getChangeReasonDto();

            if (sourceId != null) {
                medicationOnAdmissionHandler.updateMedicationOnAdmissionAction(patientId, sourceId, actionEnum,
                        changeReasonDto, when, locale);
            }

            if (actionEnum != MedicationOrderActionEnum.SUSPEND_ADMISSION) {
                final MedicationOrderComposition composition = buildMedicationOrderComposition(therapy, actionEnum,
                        changeReasonDto, centralCaseId, careProviderId, prescriber, when, locale);

                final MedicationInstructionInstruction instruction = composition.getMedicationDetail()
                        .getMedicationInstruction().get(0);

                final String linkName = therapy.getLinkName();
                if (linkName != null) {
                    createTherapyFollowLink(patientId, instruction, linkName,
                            medicationOrder.getLinkCompositionUid(), savedLinkedTherapies);
                }

                if (sourceId != null) {
                    addMedicationOnAdmissionLinkToInstruction(patientId, sourceId, instruction);
                }

                final MedicationOrderComposition savedComposition = medicationsOpenEhrDao
                        .saveNewMedicationOrderComposition(patientId, composition);

                final String therapyId = TherapyIdUtils.createTherapyId(savedComposition,
                        savedComposition.getMedicationDetail().getMedicationInstruction().get(0));
                createReminders(therapy, therapyId, therapy.getReviewReminderDays(), patientId, locale);

                final boolean activeTherapy = actionEnum == MedicationOrderActionEnum.PRESCRIBE
                        || actionEnum == MedicationOrderActionEnum.EDIT;
                if (activeTherapy) {
                    createTherapyTasks(patientId, savedComposition, AdministrationTaskCreateActionEnum.PRESCRIBE,
                            null, when);
                }

                if (linkName != null) {
                    savedLinkedTherapies.put(linkName, savedComposition);
                }
                savedTherapies.add(savedComposition);
            }
        }
        return savedTherapies;
    }

    private void createReminders(final TherapyDto therapyDto, final String therapyId,
            final Integer reviewReminderDays, final String patientId, final Locale locale) {
        final Long mainMedicationId = therapyDto.getMainMedicationId();
        final MedicationHolderDto medicationHolderDto = medicationsValueHolder.getValue().get(mainMedicationId);

        final DateTime therapyStart = therapyDto.getStart();

        if (medicationHolderDto != null && medicationHolderDto.isSuggestSwitchToOral()) {
            if (therapyDto.getRoutes().size() == 1
                    && therapyDto.getRoutes().get(0).getType() == MedicationRouteTypeEnum.IV) {
                final DateTime dueDate = therapyStart.withTimeAtStartOfDay().plusDays(2); //TODO TMC-7170 antibiotic - from preference (care provider)
                final NewTaskRequestDto taskRequest = new NewTaskRequestDto(SwitchToOralTaskDef.INSTANCE,
                        SwitchToOralTaskDef.getTaskTypeEnum().buildKey(String.valueOf(patientId)),
                        "Switch to oral medication " + dueDate.toString(DateTimeFormatters.shortDate(locale)),
                        "Switch to oral medication " + dueDate.toString(DateTimeFormatters.shortDate(locale)),
                        TherapyAssigneeEnum.DOCTOR.name(), dueDate, null,
                        Pair.of(MedsTaskDef.PATIENT_ID, patientId),
                        Pair.of(TherapyTaskDef.ORIGINAL_THERAPY_ID, therapyId));

                processService.createTasks(taskRequest);
            }
        }
        if (reviewReminderDays != null) {
            final DateTime dueDate = therapyStart.withTimeAtStartOfDay().plusDays(reviewReminderDays);
            final NewTaskRequestDto taskRequest = new NewTaskRequestDto(DoctorReviewTaskDef.INSTANCE,
                    DoctorReviewTaskDef.getTaskTypeEnum().buildKey(String.valueOf(patientId)),
                    "Doctor review task " + dueDate.toString(DateTimeFormatters.shortDate(locale)),
                    "Doctor review task " + dueDate.toString(DateTimeFormatters.shortDate(locale)),
                    TherapyAssigneeEnum.DOCTOR.name(), dueDate, null, Pair.of(MedsTaskDef.PATIENT_ID, patientId),
                    Pair.of(TherapyTaskDef.ORIGINAL_THERAPY_ID, therapyId));
            processService.createTasks(taskRequest);
        }
    }

    private MedicationOrderComposition buildMedicationOrderComposition(final TherapyDto therapy,
            final MedicationOrderActionEnum actionEnum, final TherapyChangeReasonDto changeReasonDto,
            final String centralCaseId, @Nullable final String careProviderId, final NamedExternalDto prescriber,
            final DateTime when, final Locale locale) {
        final MedicationOrderComposition composition = MedicationsEhrUtils.createEmptyMedicationOrderComposition();
        if (therapy.getTherapyDescription() == null) {
            therapyDisplayProvider.fillDisplayValues(therapy, true, true, locale);
        }
        final MedicationToEhrConverter<?> therapyConverter = MedicationConverterSelector.getConverter(therapy);
        final MedicationInstructionInstruction instruction = therapyConverter.createInstructionFromTherapy(therapy);

        MedicationsEhrUtils.addInstructionTo(composition, instruction);

        MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.SCHEDULE, prescriber, when);

        final boolean therapyStartsWhenAnotherTherapyEnds = therapy.getLinkName() != null
                && (therapy.getLinkName().length() > 2 || !therapy.getLinkName().endsWith("1"));
        if (!therapyStartsWhenAnotherTherapyEnds) {
            final DateTime therapyStart = therapy.getStart().isAfter(when) ? therapy.getStart() : when;
            MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.START, prescriber,
                    therapyStart);
        }

        if (actionEnum == MedicationOrderActionEnum.SUSPEND) {
            final NamedExternalDto user = RequestContextHolder.getContext().getUserMetadata()
                    .map(meta -> new NamedExternalDto(meta.getId(), meta.getFullName())).get();

            MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.CANCEL, user, when);
        } else if (actionEnum == MedicationOrderActionEnum.ABORT) {
            MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.ABORT, prescriber,
                    changeReasonDto, when);
        }

        addMedicationOrderCompositionEventContext(composition, prescriber, centralCaseId, careProviderId, when);
        return composition;
    }

    private void createTherapyFollowLink(final String patientId, final MedicationInstructionInstruction instruction,
            final String linkName, final String linkCompositionUid,
            final Map<String, MedicationOrderComposition> savedTherapiesWithLinks) {
        final String previousLink = MedicationsEhrUtils.getPreviousLinkName(linkName);

        final MedicationOrderComposition linkOrder = linkCompositionUid == null
                ? savedTherapiesWithLinks.get(previousLink)
                : medicationsOpenEhrDao.loadMedicationOrderComposition(patientId, linkCompositionUid);

        // link: linkInstruction <- instruction
        if (linkOrder != null) {
            final Link link = new Link();
            link.setMeaning(DataValueUtils.getText(linkName));
            link.setType(DataValueUtils.getText(EhrLinkType.FOLLOW.getName()));

            final MedicationInstructionInstruction linkInstruction = linkOrder.getMedicationDetail()
                    .getMedicationInstruction().get(0);

            final RmPath rmPath = TdoPathable.pathOfItem(linkOrder, linkInstruction);
            final DvEhrUri linkEhrUri = DataValueUtils.getEhrUri(linkOrder.getUid().getValue(), rmPath);

            link.setTarget(linkEhrUri);
            instruction.getLinks().add(link);
        }
    }

    @Override
    public void addMedicationOnAdmissionLinkToInstruction(final String patientId,
            final String onAdmissionCompositionId, final MedicationInstructionInstruction instruction) {
        final MedicationOnAdmissionComposition linkedTherapy = medicationsOpenEhrDao
                .loadMedicationOnAdmissionComposition(patientId, onAdmissionCompositionId);

        if (linkedTherapy != null) {
            final MedicationInstructionInstruction linkedInstruction = linkedTherapy.getMedicationDetail()
                    .getMedicationInstruction().get(0);

            final Link link = new Link();
            final RmPath rmPath = TdoPathable.pathOfItem(linkedTherapy, linkedInstruction);
            final DvEhrUri linkEhrUri = DataValueUtils.getEhrUri(linkedTherapy.getUid().getValue(), rmPath);

            link.setType(DataValueUtils.getText(EhrLinkType.MEDICATION_ON_ADMISSION.getName()));
            link.setMeaning(DataValueUtils.getText(EhrLinkType.MEDICATION_ON_ADMISSION.getName()));
            link.setTarget(linkEhrUri);
            link.setMeaning(link.getType());

            instruction.getLinks().add(link);
        }
    }

    private List<SaveMedicationOrderDto> sortMedicationOrdersByLinkName(
            final List<SaveMedicationOrderDto> medicationOrders) {
        final List<SaveMedicationOrderDto> sortedOrders = new ArrayList<>();
        sortedOrders.addAll(medicationOrders);
        Collections.sort(sortedOrders, (therapy1, therapy2) -> {
            if (therapy1.getTherapy().getLinkName() != null && therapy2.getTherapy().getLinkName() != null) {
                return therapy1.getTherapy().getLinkName().compareTo(therapy2.getTherapy().getLinkName());
            }
            if (therapy1.getTherapy().getLinkName() != null) {
                return 1;
            }
            if (therapy2.getTherapy().getLinkName() != null) {
                return -1;
            }
            return Integer.valueOf(medicationOrders.indexOf(therapy1))
                    .compareTo(medicationOrders.indexOf(therapy2));
        });
        return sortedOrders;
    }

    @Override
    public void modifyTherapy(final String patientId, final TherapyDto modifiedTherapy,
            final TherapyChangeReasonDto changeReason, final String centralCaseId,
            @Nullable final String careProviderId, final NamedExternalDto prescriber,
            final boolean therapyAlreadyStarted, final String basedOnPharmacyReviewId, final DateTime when,
            final Locale locale) {
        medicationsBo.fillDisplayValues(modifiedTherapy, null, null, false, locale);

        final Pair<MedicationOrderComposition, MedicationInstructionInstruction> oldInstructionPair = medicationsOpenEhrDao
                .getTherapyInstructionPair(patientId, modifiedTherapy.getCompositionUid(),
                        modifiedTherapy.getEhrOrderName());

        final TherapyDto oldTherapy = medicationsBo.convertInstructionToTherapyDtoWithDisplayValues(
                oldInstructionPair.getFirst(), oldInstructionPair.getSecond(), null, null, when, false, locale);

        final List<TherapyChangeDto<?, ?>> changes = therapyChangeCalculator.calculateTherapyChanges(oldTherapy,
                modifiedTherapy, true, locale);

        final boolean significantChange = changes.stream()
                .anyMatch(c -> c.getType().getLevel() == TherapyChangeType.TherapyChangeLevel.SIGNIFICANT);

        final boolean startChange = changes.stream().anyMatch(c -> c.getType() == TherapyChangeType.START);

        final Pair<MedicationOrderComposition, MedicationInstructionInstruction> newInstructionPair = modifyTherapy(
                patientId, modifiedTherapy, changeReason, centralCaseId, careProviderId, prescriber,
                therapyAlreadyStarted, significantChange, when, locale);

        final boolean basedOnLinksRemoved = MedicationsEhrUtils.removeLinksOfType(newInstructionPair.getSecond(),
                EhrLinkType.BASED_ON);

        if (basedOnPharmacyReviewId == null) {
            pharmacistTaskHandler.handleReviewTaskOnTherapiesChange(patientId, null, when,
                    prescriber != null ? prescriber.getName() : null, when,
                    PrescriptionChangeTypeEnum.ADDITION_TO_EXISTING_PRESCRIPTION,
                    PharmacistReviewTaskStatusEnum.PENDING);

            if (basedOnLinksRemoved) {
                medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, newInstructionPair.getFirst());
            }
        } else {
            final PharmacyReviewReportComposition pharmacistsReviewComposition = medicationsOpenEhrDao
                    .loadPharmacistsReviewComposition(patientId, basedOnPharmacyReviewId);

            final Link linkToPharmacyReview = OpenEhrRefUtils.getLinkToTdoTarget("based on pharmacy review",
                    EhrLinkType.BASED_ON.getName(), pharmacistsReviewComposition, pharmacistsReviewComposition);

            newInstructionPair.getSecond().getLinks().add(linkToPharmacyReview);
            medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, newInstructionPair.getFirst());
        }

        // generate tasks

        final AdministrationTaskCreateActionEnum action;
        if (medicationsBo.isTherapySuspended(oldInstructionPair.getFirst(), oldInstructionPair.getSecond())) {
            action = AdministrationTaskCreateActionEnum.REISSUE;
        } else if (when.isBefore(oldTherapy.getStart())) {
            action = AdministrationTaskCreateActionEnum.MODIFY_BEFORE_START;
        } else {
            action = AdministrationTaskCreateActionEnum.MODIFY;
        }

        final boolean startChangeOnNotStartedTherapy = !therapyAlreadyStarted && startChange;
        if (startChangeOnNotStartedTherapy || significantChange
                || action == AdministrationTaskCreateActionEnum.REISSUE) {
            final DateTime newTherapyStart = modifiedTherapy.getStart();
            final DateTime deleteTasksFrom = when.isBefore(newTherapyStart) ? when : newTherapyStart;
            deleteTherapyAdministrationTasks(patientId,
                    TherapyIdUtils.createTherapyId(modifiedTherapy.getCompositionUid(),
                            modifiedTherapy.getEhrOrderName()),
                    oldInstructionPair, false, false, deleteTasksFrom, when);

            createTherapyTasks(patientId, newInstructionPair.getFirst(), action, null, when);
        }
    }

    private <M extends TherapyDto> Pair<MedicationOrderComposition, MedicationInstructionInstruction> modifyTherapy(
            final String patientId, final M therapy, final TherapyChangeReasonDto changeReason,
            final String centralCaseId, @Nullable final String careProviderId, final NamedExternalDto prescriber,
            final boolean therapyAlreadyStarted, final boolean significantChange, final DateTime when,
            final Locale locale) {
        final DateTime updateTime = new DateTime(when.withSecondOfMinute(0).withMillisOfSecond(0));
        therapyDisplayProvider.fillDisplayValues(therapy, true, true, locale);
        final MedicationOrderComposition oldComposition = medicationsOpenEhrDao
                .loadMedicationOrderComposition(patientId, therapy.getCompositionUid());
        final MedicationInstructionInstruction oldInstruction = MedicationsEhrUtils
                .getMedicationInstructionByEhrName(oldComposition, therapy.getEhrOrderName());

        final DateTime oldTherapyStart = DataValueUtils
                .getDateTime(oldInstruction.getOrder().get(0).getMedicationTiming().getStartDate());

        //noinspection unchecked
        final MedicationToEhrConverter<M> converter = (MedicationToEhrConverter<M>) MedicationConverterSelector
                .getConverter(therapy);

        final Pair<MedicationOrderComposition, MedicationInstructionInstruction> savedTherapy;

        if (therapyAlreadyStarted && significantChange) {
            //create new instruction
            final MedicationOrderComposition newComposition = MedicationsEhrUtils
                    .createEmptyMedicationOrderComposition();
            final MedicationInstructionInstruction newInstruction = converter.createInstructionFromTherapy(therapy);
            newComposition.getMedicationDetail().getMedicationInstruction().add(newInstruction);

            //link new instruction to old instruction
            final Link linkToExisting = OpenEhrRefUtils.getLinkToTdoTarget("update", EhrLinkType.UPDATE.getName(),
                    oldComposition, oldInstruction);
            newInstruction.getLinks().add(linkToExisting);

            //link new instruction to first instruction
            linkToOriginInstruction(oldComposition, oldInstruction, newInstruction);

            //new therapy must keep FOLLOW links from old therapy
            final List<Link> followLinks = MedicationsEhrUtils.getLinksOfType(oldInstruction, EhrLinkType.FOLLOW);
            newInstruction.getLinks().addAll(followLinks);

            //new therapy must keep links to medication on ADMISSION
            final List<Link> admissionLinks = MedicationsEhrUtils.getLinksOfType(oldInstruction,
                    EhrLinkType.MEDICATION_ON_ADMISSION);
            newInstruction.getLinks().addAll(admissionLinks);

            rewriteSelfAdministeringType(newInstruction, oldInstruction);

            //add SCHEDULE and START actions
            final DateTime therapyStart = therapy.getStart().isAfter(when) ? therapy.getStart() : when;
            MedicationsEhrUtils.addMedicationActionTo(newComposition, MedicationActionEnum.SCHEDULE, prescriber,
                    when);
            MedicationsEhrUtils.addMedicationActionTo(newComposition, MedicationActionEnum.START, prescriber,
                    therapyStart);

            addMedicationOrderCompositionEventContext(newComposition, prescriber, centralCaseId, careProviderId,
                    when);

            final String newCompositionUid = medicationsOpenEhrDao
                    .saveNewMedicationOrderComposition(patientId, newComposition).getUid().getValue();
            newComposition.setUid(OpenEhrRefUtils.getObjectVersionId(newCompositionUid));
            newInstruction.setName(DataValueUtils.getText("Medication instruction"));

            //fix FOLLOW links that point to this therapy
            fixLinksToTherapy(patientId, oldComposition, oldInstruction, newComposition, newInstruction,
                    EhrLinkType.FOLLOW);

            //old composition
            final DateTime oldCompositionStopDate = updateTime.isBefore(therapy.getStart()) ? updateTime
                    : therapy.getStart();

            if (therapy.getStart().isBefore(oldTherapyStart)) {
                throw new UserWarning("Cannot edit therapy in the past.");
            }

            for (final MedicationInstructionInstruction.OrderActivity orderActivity : oldInstruction.getOrder()) {
                orderActivity.getMedicationTiming().setStopDate(DataValueUtils.getDateTime(oldCompositionStopDate));
            }

            //only when client time is not synced
            if (oldCompositionStopDate.isBefore(oldTherapyStart)) {
                for (final MedicationInstructionInstruction.OrderActivity orderActivity : oldInstruction
                        .getOrder()) {
                    orderActivity.getMedicationTiming()
                            .setStartDate(orderActivity.getMedicationTiming().getStopDate());
                }
            }
            MedicationsEhrUtils.addMedicationActionTo(oldComposition, MedicationActionEnum.COMPLETE, prescriber,
                    changeReason, when);

            medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, oldComposition);
            savedTherapy = Pair.of(newComposition, newInstruction);
        } else {
            converter.fillInstructionFromTherapy(oldInstruction, therapy);

            if (therapyAlreadyStarted) // and insignificantChange
            {
                oldInstruction.getOrder().forEach(
                        o -> o.getMedicationTiming().setStartDate(DataValueUtils.getDateTime(oldTherapyStart)));
            } else // fix start
            {
                final MedicationActionAction startAction = medicationsBo.getInstructionAction(oldComposition,
                        oldInstruction, MedicationActionEnum.START, null);
                startAction.setTime(DataValueUtils.getDateTime(updateTime));
            }

            final boolean therapySuspended = medicationsBo.isTherapySuspended(oldComposition, oldInstruction);
            if (therapySuspended) {
                MedicationsEhrUtils.addMedicationActionTo(oldComposition, MedicationActionEnum.REISSUE, prescriber,
                        when);
            }

            MedicationsEhrUtils.addMedicationActionTo(oldComposition, MedicationActionEnum.MODIFY_EXISTING,
                    prescriber, changeReason, when);

            MedicationsEhrUtils.visitEhrBean(oldInstruction, prescriber, when);
            medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, oldComposition);
            savedTherapy = Pair.of(oldComposition, oldInstruction);
        }

        handleTherapyTasksOnModify(patientId, therapy, savedTherapy.getSecond(),
                TherapyIdUtils.createTherapyId(savedTherapy.getFirst(), savedTherapy.getSecond()), when);

        return savedTherapy;
    }

    private void handleTherapyTasksOnModify(final String patientId, final TherapyDto therapy,
            final MedicationInstructionInstruction instruction, final String therapyId, final DateTime when) {
        final List<String> taskKeys = new ArrayList<>();
        taskKeys.add(DoctorReviewTaskDef.getTaskTypeEnum().buildKey(String.valueOf(patientId)));
        taskKeys.add(SwitchToOralTaskDef.getTaskTypeEnum().buildKey(String.valueOf(patientId)));

        //noinspection unchecked
        final PartialList<TaskDto> tasks = processService.findTasks(TherapyAssigneeEnum.DOCTOR.name(), null, null,
                false, null, null, taskKeys, EnumSet.of(TaskDetailsEnum.VARIABLES));

        final List<Link> originLinks = MedicationsEhrUtils.getLinksOfType(instruction, EhrLinkType.ORIGIN);

        final String originalTherapyId = originLinks.isEmpty() ? therapyId
                : TherapyIdUtils.getTherapyIdFromLink(originLinks.get(0));

        if (tasks != null) {
            for (final TaskDto task : tasks) {
                final String taskTherapyId = (String) task.getVariables()
                        .get(TherapyTaskDef.ORIGINAL_THERAPY_ID.getName());
                if (originalTherapyId.equals(taskTherapyId)) {
                    if (task.getTaskExecutionStrategyId().equals(TaskTypeEnum.DOCTOR_REVIEW.getName())) {
                        if (!when.withTimeAtStartOfDay().isBefore(task.getDueTime().withTimeAtStartOfDay())) {
                            processService.completeTasks(task.getId());
                        }
                    } else {
                        if (task.getTaskExecutionStrategyId().equals(TaskTypeEnum.SWITCH_TO_ORAL.getName())) {
                            final boolean ivRouteExists = therapy.getRoutes().stream()
                                    .filter(route -> route.getType() == MedicationRouteTypeEnum.IV)
                                    .anyMatch(Objects::nonNull);

                            if (!ivRouteExists) {
                                processService.completeTasks(task.getId());
                            }
                        }
                    }
                }
            }
        }
    }

    private void rewriteSelfAdministeringType(final MedicationInstructionInstruction newInstruction,
            final MedicationInstructionInstruction oldInstruction) {
        // TODO Nejc change where self admin is saved
        newInstruction.getOrder().get(0)
                .setParsableDoseDescription(oldInstruction.getOrder().get(0).getParsableDoseDescription());
    }

    private void linkToOriginInstruction(final MedicationOrderComposition oldComposition,
            final MedicationInstructionInstruction oldInstruction,
            final MedicationInstructionInstruction newInstruction) {
        Link originLink = null;
        boolean oldInstructionIsFirstInstruction = true;
        for (final Link link : oldInstruction.getLinks()) {
            if (link.getType().getValue().equals(EhrLinkType.ORIGIN.getName())) {
                originLink = link;
            }
            if (link.getType().getValue().equals(EhrLinkType.UPDATE.getName())) {
                oldInstructionIsFirstInstruction = false;
            }
        }
        if (oldInstructionIsFirstInstruction) {
            final Link linkToFirst = OpenEhrRefUtils.getLinkToTdoTarget("origin", EhrLinkType.ORIGIN.getName(),
                    oldComposition, oldInstruction);
            newInstruction.getLinks().add(linkToFirst);
        } else if (originLink != null) {
            newInstruction.getLinks().add(originLink);
        }
    }

    private void fixLinksToTherapy(final String patientId, final MedicationOrderComposition oldComposition,
            final MedicationInstructionInstruction oldInstruction, final MedicationOrderComposition newComposition,
            final MedicationInstructionInstruction newInstruction, final EhrLinkType linkType) {
        final List<Pair<MedicationOrderComposition, MedicationInstructionInstruction>> linkedTherapies = medicationsOpenEhrDao
                .getLinkedTherapies(patientId, oldComposition.getUid().getValue(),
                        oldInstruction.getName().getValue(), linkType);

        if (!linkedTherapies.isEmpty()) {
            final Pair<MedicationOrderComposition, MedicationInstructionInstruction> linkedTherapy = linkedTherapies
                    .get(0);
            Link linkToFix = null;
            for (final Link link : linkedTherapy.getSecond().getLinks()) {
                if (link.getType().getValue().equals(linkType.getName())) {
                    linkToFix = link;
                    break;
                }
            }
            linkedTherapy.getSecond().getLinks().remove(linkToFix);

            if (linkToFix != null) {
                final Link link = new Link();
                link.setMeaning(DataValueUtils.getText(linkToFix.getMeaning().getValue()));
                link.setType(DataValueUtils.getText(linkType.getName()));
                final RmPath rmPath = TdoPathable.pathOfItem(newComposition, newInstruction);
                final DvEhrUri linkEhrUri = DataValueUtils.getEhrUri(newComposition.getUid().getValue(), rmPath);
                link.setTarget(linkEhrUri);
                linkedTherapy.getSecond().getLinks().add(link);
                medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, linkedTherapy.getFirst());
            }
        }
    }

    @Override
    @EventProducer(AbortTherapy.class)
    public void abortTherapy(final String patientId, final String compositionUid, final String ehrOrderName,
            final TherapyChangeReasonDto changeReason, final DateTime when) {
        final String userId = RequestContextHolder.getContext().getUserId();

        final MedicationOrderComposition composition = medicationsOpenEhrDao
                .loadMedicationOrderComposition(patientId, compositionUid);
        final MedicationInstructionInstruction instruction = MedicationsEhrUtils
                .getMedicationInstructionByEhrName(composition, ehrOrderName);

        abortTherapy(patientId, composition, instruction, userId, changeReason, when);
        handleTasksOnTherapyStop(patientId, compositionUid, ehrOrderName, true, false, when);

        final String originalTherapyId = medicationsBo.getOriginalTherapyId(patientId, compositionUid);
        cancelTherapyRelatedTasks(patientId, originalTherapyId);
    }

    private void cancelTherapyRelatedTasks(final String patientId, final String originalTherapyId) {
        preparePerfusionSyringeProcessHandler.handleTherapyCancellationMessage(patientId, originalTherapyId);

        medicationsTasksHandler.deleteTherapyTasksOfType(patientId,
                EnumSet.of(TaskTypeEnum.PHARMACIST_REMINDER, TaskTypeEnum.PHARMACIST_REVIEW),
                RequestContextHolder.getContext().getUserId(), originalTherapyId);
    }

    private void handleTasksOnTherapyStop(final String patientId, final String compositionUid,
            final String instructionName, final boolean completePreviousTasks, final boolean suspend,
            final DateTime when) {
        final String therapyId = TherapyIdUtils.createTherapyId(compositionUid, instructionName);

        final Pair<MedicationOrderComposition, MedicationInstructionInstruction> instructionPair = medicationsOpenEhrDao
                .getTherapyInstructionPair(patientId, compositionUid, instructionName);

        deleteTherapyAdministrationTasks(patientId, therapyId, instructionPair, completePreviousTasks, true, when,
                when);

        final MedicationOrderComposition composition = instructionPair.getFirst();
        final MedicationInstructionInstruction instruction = instructionPair.getSecond();

        final List<Link> originLinks = MedicationsEhrUtils.getLinksOfType(instruction, EhrLinkType.ORIGIN);

        medicationsTasksHandler.deleteTherapyTasksOfType(patientId,
                EnumSet.of(TaskTypeEnum.SUPPLY_REMINDER, TaskTypeEnum.DISPENSE_MEDICATION,
                        TaskTypeEnum.SUPPLY_REVIEW, TaskTypeEnum.PHARMACIST_REMINDER, TaskTypeEnum.SWITCH_TO_ORAL,
                        TaskTypeEnum.INFUSION_BAG_CHANGE_TASK, TaskTypeEnum.DOCTOR_REVIEW),
                RequestContextHolder.getContext().getUserId(),
                originLinks.isEmpty() ? therapyId : TherapyIdUtils.getTherapyIdFromLink(originLinks.get(0)));

        final TherapyDto therapyDto = medicationsBo.convertInstructionToTherapyDtoWithDisplayValues(composition,
                instruction, null, null, when, false, null);

        final boolean continuousInfusion = therapyDto instanceof ComplexTherapyDto
                && ((ComplexTherapyDto) therapyDto).isContinuousInfusion();
        final boolean therapyWithRate = therapyDto.isWithRate();
        final boolean oxygenTherapy = therapyDto instanceof OxygenTherapyDto;

        if (continuousInfusion || therapyWithRate || oxygenTherapy) {
            if (suspend || !medicationsBo.isTherapySuspended(composition, instruction)) {
                final DateTime originalTherapyStart = medicationsBo.getOriginalTherapyStart(patientId,
                        instructionPair.getFirst());
                final boolean therapyAlreadyStarted = originalTherapyStart.isBefore(when);
                //noinspection OverlyComplexBooleanExpression
                if ((oxygenTherapy || continuousInfusion) && therapyAlreadyStarted
                        || isLastAdministrationNotStop(patientId, when, therapyId, composition)) {
                    createSingleAdministrationTask(therapyDto, patientId, AdministrationTypeEnum.STOP, when, null);
                }
            }
        }
    }

    private boolean isLastAdministrationNotStop(final String patientId, final DateTime until,
            final String therapyId, final MedicationOrderComposition composition) {
        final Interval interval = Intervals.infiniteTo(until);

        final Opt<AdministrationTaskDto> lastTask = medicationsTasksProvider
                .findLastAdministrationTaskForTherapy(patientId, therapyId, interval, false);

        final Opt<AdministrationDto> lastGivenAdministration = Opt.from(administrationProvider
                .getTherapiesAdministrations(patientId,
                        Collections.singletonList(Pair.of(composition,
                                composition.getMedicationDetail().getMedicationInstruction().get(0))),
                        interval)
                .stream().filter(a -> AdministrationResultEnum.ADMINISTERED.contains(a.getAdministrationResult()))
                .max(Comparator.comparing(AdministrationDto::getAdministrationTime)));

        final boolean administrationNotStop = Opt
                .resolve(() -> lastGivenAdministration.get().getAdministrationType())
                .map(AdministrationTypeEnum.NOT_STOP::contains).orElse(false);

        final boolean taskNotStop = Opt.resolve(() -> lastTask.get().getAdministrationTypeEnum())
                .map(AdministrationTypeEnum.NOT_STOP::contains).orElse(false);

        if (lastTask.isPresent()) {
            if (lastGivenAdministration.isPresent()) {
                if (lastGivenAdministration.get().getAdministrationTime()
                        .isAfter(lastTask.get().getPlannedAdministrationTime())) {
                    return administrationNotStop;
                }
                return taskNotStop;
            }
            return taskNotStop;
        }
        return administrationNotStop;
    }

    private void deleteTherapyAdministrationTasks(final String patientId, final String therapyId,
            final Pair<MedicationOrderComposition, MedicationInstructionInstruction> therapyInstructionPair,
            final boolean completePastTasks, final boolean therapyStop, final DateTime fromDate,
            final DateTime when) {
        final List<TaskDto> tasks = medicationsTasksProvider.findAdministrationTasks(patientId,
                Collections.singletonList(therapyId), null, null, null, false);

        final CodedNameDto notRecordedReason = completePastTasks
                ? medicationsDao.getActionReasons(when, ActionReasonType.NOT_RECORDED)
                        .get(ActionReasonType.NOT_RECORDED).get(0)
                : null;

        final List<String> futureTasksIds = new ArrayList<>();
        for (final TaskDto task : tasks) {
            final DateTime taskTimestamp = task.getDueTime();
            if (taskTimestamp.isAfter(fromDate) || taskTimestamp.equals(fromDate)) {
                futureTasksIds.add(task.getId());
            } else if (completePastTasks) {
                final AdministrationDto administrationDto;
                final AdministrationTypeEnum administrationTypeEnum = getAdministrationType(task);
                if (administrationTypeEnum == AdministrationTypeEnum.START) {
                    administrationDto = new StartAdministrationDto();
                    final TherapyDoseDto therapyDoseDto = TherapyTaskUtils.buildTherapyDoseDtoFromTask(task);
                    ((StartAdministrationDto) administrationDto).setPlannedDose(therapyDoseDto);
                } else if (administrationTypeEnum == AdministrationTypeEnum.ADJUST_INFUSION) {
                    administrationDto = new AdjustInfusionAdministrationDto();
                    final TherapyDoseDto therapyDoseDto = TherapyTaskUtils.buildTherapyDoseDtoFromTask(task);
                    ((AdjustInfusionAdministrationDto) administrationDto).setPlannedDose(therapyDoseDto);
                } else if (administrationTypeEnum == AdministrationTypeEnum.STOP) {
                    administrationDto = new StopAdministrationDto();
                } else {
                    throw new IllegalArgumentException("Inconsistent AdministrationTypeEnum.");
                }

                administrationDto.setTaskId(task.getId());
                administrationDto.setPlannedTime(taskTimestamp);
                administrationDto.setAdministrationTime(taskTimestamp);
                administrationDto.setNotAdministeredReason(notRecordedReason);

                final String userId = RequestContextHolder.getContext().getUserId();
                final String administrationUid = administrationHandler.confirmTherapyAdministration(
                        therapyInstructionPair.getFirst(), patientId, userId, administrationDto,
                        MedicationActionEnum.WITHHOLD, false, null, null, when);

                medicationsTasksHandler.associateTaskWithAdministration(administrationDto.getTaskId(),
                        administrationUid);
                processService.completeTasks(administrationDto.getTaskId());
            }
        }

        if (!therapyStop) {
            final List<String> activeStopOrAdjustTaskIdsWithGivenStart = getActiveStopOrAdjustTaskIdsWithGivenStart(
                    tasks);
            futureTasksIds.removeAll(activeStopOrAdjustTaskIdsWithGivenStart);
        }

        if (!futureTasksIds.isEmpty()) {
            processService.deleteTasks(futureTasksIds);
        }
    }

    private List<String> getActiveStopOrAdjustTaskIdsWithGivenStart(final List<TaskDto> tasks) {
        final Map<String, List<TaskDto>> groupedTasks = tasks.stream()
                .filter(t -> t.getVariables().get(AdministrationTaskDef.GROUP_UUID.getName()) != null)
                .collect(Collectors.groupingBy(
                        t -> ((String) t.getVariables().get(AdministrationTaskDef.GROUP_UUID.getName()))));

        final List<String> stopTaskIds = new ArrayList<>();
        for (final Map.Entry<String, List<TaskDto>> entry : groupedTasks.entrySet()) {
            if (entry.getValue().stream()
                    .noneMatch(t -> getAdministrationType(t) == AdministrationTypeEnum.START)) {
                entry.getValue().stream()
                        .filter(t -> getAdministrationType(t) == AdministrationTypeEnum.STOP
                                || getAdministrationType(t) == AdministrationTypeEnum.ADJUST_INFUSION)
                        .filter(Objects::nonNull).findAny().map(AbstractTaskDto::getId).ifPresent(stopTaskIds::add);
            }
        }
        return stopTaskIds;
    }

    private AdministrationTypeEnum getAdministrationType(final TaskDto t) {
        return AdministrationTypeEnum
                .valueOf((String) t.getVariables().get(AdministrationTaskDef.ADMINISTRATION_TYPE.getName()));
    }

    private void abortTherapy(final String patientId, final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction, final String userId,
            final TherapyChangeReasonDto changeReason, final DateTime when) {
        final DateTime medicationTimingStart = DataValueUtils
                .getDateTime(instruction.getOrder().get(0).getMedicationTiming().getStartDate());
        final DateTime medicationTimingEnd = DataValueUtils
                .getDateTime(instruction.getOrder().get(0).getMedicationTiming().getStopDate());

        //if medication not started yet
        final NamedExternalDto user = RequestContextHolder.getContext().getUserMetadata()
                .map(meta -> new NamedExternalDto(meta.getId(), meta.getFullName())).get();
        if (medicationTimingStart.isAfter(when)) {
            for (final MedicationInstructionInstruction.OrderActivity orderActivity : instruction.getOrder()) {
                orderActivity.getMedicationTiming().setStartDate(DataValueUtils.getDateTime(when));
                orderActivity.getMedicationTiming().setStopDate(DataValueUtils.getDateTime(when));
            }
            MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.CANCEL, user, changeReason,
                    when);
        } else {
            if (medicationTimingEnd == null || medicationTimingEnd.isAfter(when)) {
                for (final MedicationInstructionInstruction.OrderActivity orderActivity : instruction.getOrder()) {
                    orderActivity.getMedicationTiming().setStopDate(DataValueUtils.getDateTime(when));
                }
            }
            MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.ABORT, user, changeReason,
                    when);
        }

        medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, composition);
    }

    private void addMedicationOrderCompositionEventContext(final MedicationOrderComposition medicationOrder,
            final NamedExternalDto prescriber, final String centralCaseId, @Nullable final String careProviderId,
            final DateTime when) {
        MedicationsEhrUtils.addContext(medicationOrder, centralCaseId, careProviderId, when);

        final PartyIdentified compositionComposer = RequestContextHolder.getContext().getUserMetadata()
                .map(meta -> IspekTdoDataSupport.getPartyIdentified(meta.getFullName(), meta.getId())).get();

        final TdoPopulatingVisitor.DataContext dataContext = TdoPopulatingVisitor.getSloveneContext(when)
                .withCompositionDynamic(true).withReplaceParticipation(true)
                .withCompositionComposer(compositionComposer);

        MedicationsEhrUtils.setContextParticipation(dataContext, prescriber, ParticipationTypeEnum.PRESCRIBER);

        new TdoPopulatingVisitor().visitBean(medicationOrder, dataContext);
    }

    @Override
    @EventProducer(SuspendTherapy.class)
    public String suspendTherapy(final String patientId, final String compositionUid, final String ehrOrderName,
            final TherapyChangeReasonDto changeReason, final DateTime when) {
        final String newCompositionUid = addMedicationActionAndSaveComposition(patientId, compositionUid,
                MedicationActionEnum.SUSPEND, changeReason, when);

        handleTasksOnTherapyStop(patientId, compositionUid, ehrOrderName, false, true, when);

        return newCompositionUid;
    }

    private String addMedicationActionAndSaveComposition(final String patientId, final String compositionUid,
            final MedicationActionEnum actionEnum, final TherapyChangeReasonDto changeReason, final DateTime when) {
        final MedicationOrderComposition composition = medicationsOpenEhrDao
                .loadMedicationOrderComposition(patientId, compositionUid);

        final NamedExternalDto user = RequestContextHolder.getContext().getUserMetadata()
                .map(meta -> new NamedExternalDto(meta.getId(), meta.getFullName())).get();

        MedicationsEhrUtils.addMedicationActionTo(composition, actionEnum, user, changeReason, when);

        return medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, composition);
    }

    @Override
    @EventProducer(ReissueTherapy.class)
    public void reissueTherapy(final String patientId, final String compositionUid, final String ehrOrderName,
            final DateTime when) {
        final MedicationOrderComposition composition = medicationsOpenEhrDao
                .loadMedicationOrderComposition(patientId, compositionUid);

        final NamedExternalDto user = RequestContextHolder.getContext().getUserMetadata()
                .map(meta -> new NamedExternalDto(meta.getId(), meta.getFullName())).get();
        MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.REISSUE, user, when);

        medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, composition);

        final DateTime lastTaskTimestamp = medicationsTasksProvider.findLastAdministrationTaskTimeForTherapy(
                patientId, TherapyIdUtils.createTherapyId(compositionUid, ehrOrderName),
                Intervals.infiniteFrom(when.minusDays(30)), true).orElse(when);

        createTherapyTasks(patientId,
                medicationsOpenEhrDao.getTherapyInstructionPair(patientId, compositionUid, ehrOrderName).getFirst(),
                AdministrationTaskCreateActionEnum.REISSUE, lastTaskTimestamp, when);
    }

    @Override
    public String reviewTherapy(final String patientId, final String compositionUid) {
        final DateTime when = RequestContextHolder.getContext().getRequestTimestamp();
        return addMedicationActionAndSaveComposition(patientId, compositionUid, MedicationActionEnum.REVIEW, null,
                when);
    }

    @Override
    public void saveConsecutiveDays(final String patientId, final String compositionUid, final String ehrOrderName,
            final String userId, final Integer consecutiveDays) {
        final MedicationOrderComposition composition = medicationsOpenEhrDao
                .loadMedicationOrderComposition(patientId, compositionUid);
        final MedicationInstructionInstruction instruction = MedicationsEhrUtils
                .getMedicationInstructionByEhrName(composition, ehrOrderName);

        updateConsecutiveDays(instruction, consecutiveDays);
        medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, composition);
    }

    private void updateConsecutiveDays(final MedicationInstructionInstruction instruction,
            final Integer consecutiveDays) {
        for (final MedicationInstructionInstruction.OrderActivity order : instruction.getOrder()) {
            if (consecutiveDays != null) {
                order.setPastDaysOfTherapy(new DvCount());
                order.getPastDaysOfTherapy().setMagnitude(consecutiveDays);
            } else {
                order.setPastDaysOfTherapy(null);
            }
        }
    }

    @Override
    public boolean startLinkedTherapy(final String patientId,
            final Pair<MedicationOrderComposition, MedicationInstructionInstruction> linkedTherapy,
            final DateTime startTimestamp) {
        final MedicationOrderComposition composition = linkedTherapy.getFirst();
        final MedicationInstructionInstruction instruction = linkedTherapy.getSecond();

        final boolean therapyCancelledOrAborted = medicationsBo.isTherapyCancelledOrAborted(composition,
                instruction);
        if (!therapyCancelledOrAborted) {
            final PartyIdentified composer = (PartyIdentified) composition.getComposer();
            final NamedExternalDto composersName = new NamedExternalDto(
                    composer.getExternalRef().getId().getValue(), composer.getName());

            MedicationsEhrUtils.addMedicationActionTo(composition, MedicationActionEnum.START, composersName,
                    startTimestamp);
            moveTherapyInterval(patientId, composition, instruction, startTimestamp, false);
            medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, composition);
            return true;
        }
        return false;
    }

    @Override
    public void updateTherapySelfAdministeringStatus(final String patientId,
            final MedicationOrderComposition orderComposition,
            final SelfAdministeringActionEnum selfAdministeringActionEnum, final String userId,
            final DateTime when) {
        final MedicationInstructionInstruction instruction = orderComposition.getMedicationDetail()
                .getMedicationInstruction().get(0);

        if (selfAdministeringActionEnum == SelfAdministeringActionEnum.STOP_SELF_ADMINISTERING) {
            instruction.getOrder().get(0).setParsableDoseDescription(null);
        } else {
            final DvParsable parsableDoseDescription = new DvParsable();
            parsableDoseDescription.setValue(selfAdministeringActionEnum.name());
            parsableDoseDescription.setFormalism(JsonUtil.toJson(when));
            instruction.getOrder().get(0).setParsableDoseDescription(parsableDoseDescription);
        }
        //TODO nejc save differently?

        medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, orderComposition);
    }

    private void moveTherapyInterval(final String patientId, final MedicationOrderComposition composition,
            final MedicationInstructionInstruction instruction, final DateTime startTimestamp, final boolean save) {
        DateTime endDate = null;
        for (final MedicationInstructionInstruction.OrderActivity orderActivity : instruction.getOrder()) {
            final MedicationInstructionInstruction.OrderActivity.MedicationTimingCluster medicationTiming = orderActivity
                    .getMedicationTiming();

            if (medicationTiming.getStopDate() != null) {
                final DateTime start = DataValueUtils.getDateTime(medicationTiming.getStartDate());
                final DateTime end = DataValueUtils.getDateTime(medicationTiming.getStopDate());
                final long durationInMillis = end.getMillis() - start.getMillis();
                final DateTime newEnd = new DateTime(startTimestamp.getMillis() + durationInMillis);
                medicationTiming.setStopDate(DataValueUtils.getDateTime(newEnd));
                endDate = newEnd;
            }
            medicationTiming.setStartDate(DataValueUtils.getDateTime(startTimestamp));
        }
        if (save) {
            medicationsOpenEhrDao.modifyMedicationOrderComposition(patientId, composition);
        }
        if (endDate != null) {
            final List<Pair<MedicationOrderComposition, MedicationInstructionInstruction>> linkedTherapies = medicationsOpenEhrDao
                    .getLinkedTherapies(patientId, composition.getUid().getValue(),
                            instruction.getName().getValue(), EhrLinkType.FOLLOW);

            Preconditions.checkArgument(linkedTherapies.size() < 2, "not more than 1 follow therapy should exist");

            if (!linkedTherapies.isEmpty()) {
                final Pair<MedicationOrderComposition, MedicationInstructionInstruction> linkedTherapy = linkedTherapies
                        .get(0);
                moveTherapyInterval(patientId, linkedTherapy.getFirst(), linkedTherapy.getSecond(), endDate, true);
            }
        }
    }

    @Override
    public void createTherapyTasks(final String patientId, final MedicationOrderComposition composition,
            final AdministrationTaskCreateActionEnum action, final DateTime lastTaskTimestamp,
            final DateTime when) {
        final MedicationActionAction startAction = medicationsBo.getInstructionAction(composition,
                composition.getMedicationDetail().getMedicationInstruction().get(0), MedicationActionEnum.START,
                null);

        //noinspection VariableNotUsedInsideIf
        if (startAction != null) {
            final TherapyDto therapyDto = medicationsBo.convertInstructionToTherapyDtoWithDisplayValues(composition,
                    composition.getMedicationDetail().getMedicationInstruction().get(0), null, null, when, false,
                    null);

            final List<NewTaskRequestDto> taskRequests = administrationTaskCreator.createTaskRequests(patientId,
                    therapyDto, action, when, lastTaskTimestamp);

            processService.createTasks(taskRequests.toArray(new NewTaskRequestDto[taskRequests.size()]));
        }
    }

    private void createSingleAdministrationTask(final TherapyDto therapy, final String patientId,
            final AdministrationTypeEnum administrationTypeEnum, final DateTime when, final TherapyDoseDto dose) {
        final NewTaskRequestDto taskRequest = administrationTaskCreator.createMedicationTaskRequest(patientId,
                therapy, administrationTypeEnum, when, dose);

        processService.createTasks(taskRequest);
    }

    @Override
    public void createAdditionalAdministrationTask(final MedicationOrderComposition composition,
            final String patientId, final DateTime timestamp, final AdministrationTypeEnum type,
            final TherapyDoseDto dose) {
        final TherapyDto therapy = medicationsBo.convertInstructionToTherapyDtoWithDisplayValues(composition,
                composition.getMedicationDetail().getMedicationInstruction().get(0), null, null, timestamp, false,
                null);

        final List<NewTaskRequestDto> requests = administrationTaskCreator
                .createTaskRequestsForAdditionalAdministration(patientId, therapy, type, timestamp, dose);

        processService.createTasks(requests.toArray(new NewTaskRequestDto[requests.size()]));
    }
}