package eu.reply.lea.services.yms.task.transitqueueing;

import static eu.reply.lea.services.yms.ModuleConfiguration.MODULE_NAME;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.JoinTable;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import lombok.Getter;
import lombok.NoArgsConstructor;

import eu.reply.lea.services.masterdata.reference.LocationReference;
import eu.reply.lea.services.yms.task.TaskReference;
import eu.reply.lea.services.yms.task.queue.ElementDequeueProcess;
import eu.reply.lea.services.yms.task.queue.ElementEnqueueProcess;
import eu.reply.lea.services.yms.task.queue.event.ElementQueueProcessCreatedEvent;
import eu.reply.lea.services.yms.task.transitqueueing.exception.DequeueProcessPendingException;
import eu.reply.lea.services.yms.task.transitqueueing.exception.EmptyQueueRequestException;
import eu.reply.lea.services.yms.task.transitqueueing.exception.EnqueueProcessPendingException;
import eu.reply.lea.services.yms.util.ProcessReference;
import eu.reply.sce.backend.core.entity.EntityReference;
import eu.reply.sce.backend.core.entity.StandardEntity;
import eu.reply.sce.backend.core.jee.event.Producer;


/**
 * Tracks the queuing process of transits through multiple tasks.
 * This class manages the assignment of tasks to the queue for execution.
 * It handles tasks pending queue placement and queued tasks.
 */
@NoArgsConstructor
@Entity
@NamedQueries(value = {
    @NamedQuery(name = QueueProcessTracker.Q_FIND_BY_LOGISTIC_UNIT_REFERENCE, query =
        "select tq from QueueProcessTracker tq where tq.logisticUnitReference.entityId = :logisticUnitReferenceId "
        + "and tq.logisticUnitReference.entityName = :logisticUnitReferenceEntityName "
        + "and tq.logisticUnitReference.module = :logisticUnitReferenceModule"),
    @NamedQuery(name = QueueProcessTracker.Q_FIND_BY_PROCESS_REFERENCE, query =
        "select tq from QueueProcessTracker tq where " +
        "(tq.executingEnqueueProcessReference.entityId = :executingProcessReferenceId " +
        "and tq.executingEnqueueProcessReference.entityName = :executingProcessReferenceEntityName " +
        "and tq.executingEnqueueProcessReference.module = :executingProcessReferenceModule) " +
        "or " +
        "(tq.executingDequeueProcessReference.entityId = :executingProcessReferenceId " +
        "and tq.executingDequeueProcessReference.entityName = :executingProcessReferenceEntityName " +
        "and tq.executingDequeueProcessReference.module = :executingProcessReferenceModule)")})
@Table(indexes = {
    @Index(columnList = "logisticUnitRef_entityId,logisticUnitRef_entityName,logisticUnitRef_module"),
    @Index(columnList = "executingEnqueueProcessReference_entityId,executingEnqueueProcessReference_entityName,executingEnqueueProcessReference_module"),
    @Index(columnList = "executingDequeueProcessReference_entityId,executingDequeueProcessReference_entityName,executingDequeueProcessReference_module")
})
public class QueueProcessTracker extends StandardEntity {


    public static final String Q_FIND_BY_LOGISTIC_UNIT_REFERENCE = "QueueProcessTracker.Q_FIND_BY_LOGISTIC_UNIT_REFERENCE";
    public static final String Q_FIND_BY_PROCESS_REFERENCE = "QueueProcessTracker.Q_FIND_BY_PROCESS_REFERENCE";

    @NotNull
    @Getter
    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "entityId", column = @Column(name = "logisticUnitRef_entityId", length = 50)),
        @AttributeOverride(name = "entityName", column = @Column(name = "logisticUnitRef_entityName")),
        @AttributeOverride(name = "module", column = @Column(name = "logisticUnitRef_module", length = 20)),
        @AttributeOverride(name = "businessKey", column = @Column(name = "logisticUnitRef_businessKey"))})
    private EntityReference logisticUnitReference;


    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinTable(
        name = "QueueProcessTracker_tasksPendingQueuePlacement"
    )
    private List<QueueProcessTrackerTask> tasksPendingQueuePlacement = new ArrayList<>();

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinTable(
        name = "QueueProcessTracker_tasksPendingDequeue"
    )
    private List<QueueProcessTrackerTask> tasksPendingDequeue = new ArrayList<>();


    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinTable(
        name = "QueueProcessTracker_tasksQueued"
    )
    private List<QueueProcessTrackerTask> tasksQueued = new ArrayList<>();

    @Getter
    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "entityId", column = @Column(name = "executingEnqueueProcessReference_entityId", length = 50)),
        @AttributeOverride(name = "entityName", column = @Column(name = "executingEnqueueProcessReference_entityName")),
        @AttributeOverride(name = "module", column = @Column(name = "executingEnqueueProcessReference_module", length = 20)),
        @AttributeOverride(name = "businessKey", column = @Column(name = "executingEnqueueProcessReference_businessKey"))})
    private ProcessReference executingEnqueueProcessReference;

    @Getter
    @Embedded
    @AttributeOverrides({@AttributeOverride(name = "entityId", column = @Column(name = "executingDequeueProcessReference_entityId", length = 50)),
        @AttributeOverride(name = "entityName", column = @Column(name = "executingDequeueProcessReference_entityName")),
        @AttributeOverride(name = "module", column = @Column(name = "executingDequeueProcessReference_module", length = 20)),
        @AttributeOverride(name = "businessKey", column = @Column(name = "executingDequeueProcessReference_businessKey"))})
    private ProcessReference executingDequeueProcessReference;


    @Getter
    public boolean isEnqueueProcessPending = false;

    @Getter
    public boolean isDequeueProcessPending = false;

    /**
     * Retrieves a read-only list of tasks that are pending placement in the queue.
     * @return A list of {@link QueueProcessTrackerTask}.
     */
    public List<QueueProcessTrackerTask> getTasksPendingQueuePlacement() {
        return Collections.unmodifiableList(tasksPendingQueuePlacement);
    }

    /**
     * Retrieves a read-only list of tasks that are pending dequeue.
     * @return A list of {@link QueueProcessTrackerTask}.
     */
    public List<QueueProcessTrackerTask> getTasksPendingDequeue() {
        return Collections.unmodifiableList(tasksPendingDequeue);
    }

    /**
     * Adds a task to the list of tasks pending queue placement.
     * @param taskReference The task reference to be added.
     * @return true if the task was added successfully.
     */
    protected boolean addTaskPendingQueuePlacement(final TaskReference taskReference){
        return this.tasksPendingQueuePlacement.add(new QueueProcessTrackerTask(taskReference, this.toEntityReference()));
    }

    /**
     * Adds a task to the list of tasks pending dequeue.
     * @param taskReference The task reference to be added.
     * @return true if the task was added successfully.
     */
    protected boolean addTaskPendingDequeue(final TaskReference taskReference){
        return this.tasksPendingDequeue.add(new QueueProcessTrackerTask(taskReference, this.toEntityReference()));
    }

    /**
     * Checks if a given task is queued.
     * @param taskReferences The task reference to check.
     * @return true if the task is queued, false otherwise.
     */
    public boolean isTaskQueued(final TaskReference taskReferences){
        return this.tasksQueued.contains(new QueueProcessTrackerTask(taskReferences, this.toEntityReference()));
    }

    /**
     * Checks if a given task is pending queue placement.
     * @param taskReferences The task reference to check.
     * @return true if the task is pending queue placement, false otherwise.
     */
    public boolean isTaskPendingQueuePlacement(final TaskReference taskReferences){
        return this.tasksPendingQueuePlacement.contains(new QueueProcessTrackerTask(taskReferences, this.toEntityReference()));
    }


    /**
     * Retrieves a read-only list of tasks that have been placed in the queue.
     * @return A list of {@link TaskReference}.
     */
    public List<QueueProcessTrackerTask> getTasksQueued() {
        return Collections.unmodifiableList(tasksQueued);
    }

    public QueueProcessTracker(final EntityReference logisticUnitReference) {
        this.logisticUnitReference = logisticUnitReference;
    }


    /**
     * Sets the process reference and flags the process as pending. It also fires a creation event for the element queue process.
     * @param processReference The process reference to set.
     * @param elementQueueProcessCreatedEventProducer Producer to fire the event.
     */
    public void setExecutingEnqueueProcessReference(final ProcessReference processReference,
                                                    final Producer<ElementQueueProcessCreatedEvent> elementQueueProcessCreatedEventProducer) {
        this.executingEnqueueProcessReference = processReference;
        this.setEnqueueProcessPending(true);
        elementQueueProcessCreatedEventProducer.fire(new ElementQueueProcessCreatedEvent(processReference));
    }

    /**
     * Sets the process reference and flags the process as pending. It also fires a creation event for the element dequeue process.
     * @param processReference The process reference to set.
     * @param elementQueueProcessCreatedEventProducer Producer to fire the event.
     */
    public void setExecutingDequeueProcessReference(final ProcessReference processReference,
                                                    final Producer<ElementQueueProcessCreatedEvent> elementQueueProcessCreatedEventProducer) {
        this.executingDequeueProcessReference = processReference;
        this.setDequeueProcessPending(true);
        elementQueueProcessCreatedEventProducer.fire(new ElementQueueProcessCreatedEvent(processReference));
    }

    private void setEnqueueProcessPending(final boolean enqueueProcessPending) {
        this.isEnqueueProcessPending = enqueueProcessPending;
    }

    private void setDequeueProcessPending(final boolean dequeueProcessPending) {
        this.isDequeueProcessPending = dequeueProcessPending;
    }

    /**
     * Completes the current process, moves all pending tasks to the queued list, and clears pending tasks.
     */
    public void completedEnqueueProcess(){
        this.executingEnqueueProcessReference = null;
        this.tasksQueued.addAll(this.getTasksPendingQueuePlacement());
        this.tasksPendingQueuePlacement.clear();
        this.setEnqueueProcessPending(false);
    }

    /**
     * Completes the current process, moves all pending tasks to the queued list, and clears pending tasks.
     */
    public void completedDequeueProcess(){
        this.executingDequeueProcessReference = null;
        this.tasksQueued.removeAll(this.getTasksPendingDequeue());
        this.tasksPendingDequeue.clear();
        this.setDequeueProcessPending(false);
    }

    /**
     * Aborts the current queue process, clearing all pending tasks and resetting the process state.
     */
    public void abortedEnqueueProcess(){
        this.executingEnqueueProcessReference = null;
        this.tasksPendingQueuePlacement.clear();
        this.setEnqueueProcessPending(false);
    }

    /**
     * Creates a element queue process for the queueRequest, ensuring no process is currently pending.
     * @param queueRequest request that generates the element queue process.
     * @return {@link ElementEnqueueProcess} Newly created element queue process.
     */
    public ElementEnqueueProcess createElementQueueProcess(final QueueRequest queueRequest) {
        if (this.isEnqueueProcessPending()) {
            throw new EnqueueProcessPendingException();
        }

        if (this.isDequeueProcessPending()) {
            throw new DequeueProcessPendingException();
        }
        ElementEnqueueProcess elementEnqueueProcess = new ElementEnqueueProcess(logisticUnitReference);
        boolean taskAdded = false;
        for (QueueRequestInfo queueRequestInfo : queueRequest.getTaskInfos()){
            if (isTaskQueued(queueRequestInfo.getTaskReference())){
                continue;
            }
            taskAdded = true;
            elementEnqueueProcess.addExecutionLocation(queueRequestInfo.getExecutionLocation(), queueRequestInfo.isScheduled());
            this.addTaskPendingQueuePlacement(queueRequestInfo.getTaskReference());
        }
        if(!taskAdded){
            throw new EmptyQueueRequestException();
        }
        return elementEnqueueProcess;
    }

    /**
     * Creates an element dequeue process for the next location destinations, ensuring no dequeue process is currently pending.
     * @param nextLocation List of next location destinations to dequeue from.
     * @return {@link ElementDequeueProcess} Newly created element dequeue process, or null if no locations to dequeue from.
     * @throws IllegalStateException if a dequeue process is already pending.
     */
    public ElementDequeueProcess createElementDequeueProcess(final List<LocationReference>  nextLocation) {
        if (this.isDequeueProcessPending()) {
            throw new IllegalStateException("It's not possible to create a new dequeue process since one is still running.");
        }
        if (nextLocation.isEmpty()){ //No Location to dequeue from
            return null;
        }
        ElementDequeueProcess elementDequeueProcess = new ElementDequeueProcess(logisticUnitReference, nextLocation);//TODO scheduled here is not appropriate
        return elementDequeueProcess;
    }



    public EntityReference toEntityReference() {
        return new EntityReference(MODULE_NAME, getClass().getSimpleName(), String.valueOf(this.getId()), null, "-");
    }


}
package eu.reply.lea.services.yms.task.queue;

import static eu.reply.lea.services.yms.ModuleConfiguration.MODULE_NAME;

import java.util.Collections;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.Index;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.validation.constraints.NotNull;

import org.hibernate.annotations.SortNatural;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;

import eu.reply.lea.services.masterdata.reference.LocationReference;
import eu.reply.lea.services.masterdata.reference.SiteReference;
import eu.reply.lea.services.yms.task.queue.event.ElementDequeuedEvent;
import eu.reply.lea.services.yms.task.queue.event.ElementEnqueuedEvent;
import eu.reply.sce.backend.core.entity.EntityReference;
import eu.reply.sce.backend.core.entity.StandardEntity;
import eu.reply.sce.backend.core.entity.configuration.BusinessKey;
import eu.reply.sce.backend.core.jee.event.Producer;

@Getter
@Entity
@DiscriminatorColumn(name = "LOC_TYPE")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@NamedQueries(value = {
    @NamedQuery(name = Queue.Q_FIND_BY_LOCATION_REFERENCE, query =
        "select q from Queue q where q.executionLocationReference.entityId = :locationReferenceId "
        + "and q.executionLocationReference.entityName = :locationReferenceEntityName "
        + "and q.executionLocationReference.module = :locationReferenceModule"),
    @NamedQuery(name = Queue.Q_FIND_BY_WAITING_AREA, query =
        "select q from Queue q where q.waitingArea.entityId = :waitingAreaReferenceId "
        + "and q.waitingArea.entityName = :waitingAreaReferenceEntityName "
        + "and q.waitingArea.module = :waitingAreaReferenceModule"),
})
@Table(indexes = {
    @Index(columnList = "waitingArea_entityId,waitingArea_entityName,waitingArea_module"),
    @Index(columnList = "executionArea_entityId,executionArea_entityName,executionArea_module"),
    @Index(columnList = "site_entityId,site_entityName,site_module"),
    },
    uniqueConstraints = @UniqueConstraint(
        columnNames = {"executionArea_entityId", "executionArea_entityName", "executionArea_module"}))
@BusinessKey(attributes = {"executionLocationReference.businessKey"})
public abstract class Queue extends StandardEntity {

    public static final String Q_FIND_BY_LOCATION_REFERENCE = "AbstractQueue.Q_FIND_BY_LOCATION_REFERENCE";
    public static final String Q_FIND_BY_WAITING_AREA = "AbstractQueue.Q_FIND_BY_WAITING_AREA";
    private static final long serialVersionUID = 2213504911524892516L;


    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "entityId", column = @Column(name = "waitingArea_entityId")),
        @AttributeOverride(name = "entityName", column = @Column(name = "waitingArea_entityName")),
        @AttributeOverride(name = "module", column = @Column(name = "waitingArea_module")),
        @AttributeOverride(name = "usageType", column = @Column(name = "waitingArea_usageType")),
        @AttributeOverride(name = "businessKey", column = @Column(name = "waitingArea_businessKey"))
    })
    @Getter
    @Setter(AccessLevel.PRIVATE)
    private EntityReference waitingArea;

    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "entityId", column = @Column(name = "executionArea_entityId")),
        @AttributeOverride(name = "entityName", column = @Column(name = "executionArea_entityName")),
        @AttributeOverride(name = "module", column = @Column(name = "executionArea_module")),
        @AttributeOverride(name = "usageType", column = @Column(name = "executionArea_usageType")),
        @AttributeOverride(name = "businessKey", column = @Column(name = "executionArea_businessKey"))
    })
    @Getter
    @Setter(AccessLevel.PRIVATE)
    private EntityReference executionArea;


    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "entityId", column = @Column(name = "execution_location_entityId")),
        @AttributeOverride(name = "entityName", column = @Column(name = "execution_location_entityName")),
        @AttributeOverride(name = "module", column = @Column(name = "execution_location_module")),
        @AttributeOverride(name = "businessKey", column = @Column(name = "execution_location_businessKey"))
    })
    @Getter
    @Setter
    private LocationReference executionLocationReference;

    @NotNull
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "entityName", column = @Column(name = "site_entityName")),
        @AttributeOverride(name = "businessKey", column = @Column(name = "site_businesskey")),
        @AttributeOverride(name = "module", column = @Column(name = "site_module", length = 20)),
        @AttributeOverride(name = "entityId", column = @Column(name = "site_entityId", length = 50)) })
    @Getter
    @Setter(AccessLevel.PRIVATE)
    private SiteReference site;

    private int queuedElementsCount = 0;


    @OneToMany(fetch = FetchType.LAZY)
    @SortNatural
    private final SortedSet<QueuedElement> queuedElements = new TreeSet<>();


    protected Queue() {
        super();

    }

    protected Queue(final EntityReference executionArea, final EntityReference waitingArea, final LocationReference locationRef,
                    final SiteReference siteReference) {
        super();
        this.setExecutionArea(executionArea);
        this.setWaitingArea(waitingArea);
        this.setExecutionLocationReference(locationRef);
        this.setSite(siteReference);
    }

    public int getQueuedElementsCount() {
        return queuedElementsCount;
    }

    private void setQueuedElementsCount(final int queuedElementsCount) {
        this.queuedElementsCount = queuedElementsCount;
    }

    /**
     * Returns an unmodifiable and sorted view of the set of queued elements.
     * <p>
     * This method provides access to the current sorted set of {@link QueuedElement} instances,
     * ensuring that the returned set cannot be modified by the caller. Any attempt to
     * modify the returned set will result in an {@link UnsupportedOperationException}.
     * </p>
     *
     * @return an unmodifiable and sorted set of {@link QueuedElement} instances
     */
    public SortedSet<QueuedElement> getQueuedElements() {
        return Collections.unmodifiableSortedSet(queuedElements);
    }

    @JsonIgnore
    public QueuedElement queue(final EntityReference externalEntityReference, EntityManager entityManager, boolean scheduled,
                         final Producer<ElementEnqueuedEvent> elementEnqueuedEventProducer) {
        QueuedElement queuedElement = this.createQueuedElement(externalEntityReference, scheduled);
        if (this.queuedElements.add(queuedElement)) {
            entityManager.persist(queuedElement);
            this.setQueuedElementsCount(this.getQueuedElements().size());
            elementEnqueuedEventProducer.fire(new ElementEnqueuedEvent(
                this.toEntityReference(),
                this.getWaitingArea(),
                this.getExecutionArea(),
                new QueuedElementReference(queuedElement)
            ));
            return queuedElement;
        }
        return null;
    }

    //This method is useful for test purposes
    QueuedElement createQueuedElement(final EntityReference externalEntityReference, boolean scheduled){
        return new QueuedElement(externalEntityReference, scheduled, this.toEntityReference(),
                          this.getExecutionLocationReference());
    }

    @JsonIgnore
    public boolean dequeue(final QueuedElement queuedElement,
                           final Producer<ElementDequeuedEvent> elementDequeuedEventProducer) {
        if (this.queuedElements.remove(queuedElement)) {
            this.setQueuedElementsCount(this.getQueuedElements().size());
            elementDequeuedEventProducer.fire(new ElementDequeuedEvent(
                this.toEntityReference(),
                this.getWaitingArea(),
                this.getExecutionArea(),
                this.getExecutionLocationReference(),
                new QueuedElementReference(queuedElement),
                queuedElement.getExternalEntityRef()));
                queuedElement.removeFromQueue();
            return true;
        }
        return false;
    }

    public EntityReference toEntityReference(){
        return new EntityReference(MODULE_NAME, getClass().getSimpleName(), String.valueOf(this.getId()), null, this.getExecutionLocationReference().getBusinessKey());
    }
}
package eu.reply.lea.services.yms.task.queue;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Index;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.OneToMany;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;

import eu.reply.lea.services.masterdata.reference.LocationReference;
import eu.reply.lea.services.yms.task.queue.event.ElementQueueProcessCompletedEvent;
import eu.reply.lea.services.yms.task.queue.event.ElementQueueProcessPartialConfirmEvent;
import eu.reply.lea.services.yms.util.AbstractProcess;
import eu.reply.lea.services.yms.util.ProcessReference;
import eu.reply.sce.backend.core.entity.EntityReference;
import eu.reply.sce.backend.core.jee.event.Producer;

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(indexes = {
    @Index(name="idx_process_external_entity_status", columnList = "externalEntityRef_entity_id, externalEntityRef_entity_name, externalEntityRef_module, processCompletionType")
})
public abstract class ElementQueueProcess extends AbstractProcess {

    @OneToMany(mappedBy = "elementQueueProcess", cascade = CascadeType.ALL)
    private final List<QueueProcessItem> queueProcessItems = new ArrayList<>();

    @Getter
    @Embedded
    @AttributeOverrides({
        @AttributeOverride(name = "module", column = @Column(name = "externalEntityRef_module")),
        @AttributeOverride(name = "entityName", column = @Column(name = "externalEntityRef_entity_name")),
        @AttributeOverride(name = "entityId", column = @Column(name = "externalEntityRef_entity_id")),
        @AttributeOverride(name = "businessKey", column = @Column(name = "externalEntityRef_business_key")),
        @AttributeOverride(name = "usageType", column = @Column(name = "externalEntityRef_usage_type"))
    })
    private EntityReference externalEntityReference;

    protected ElementQueueProcess() {
        super();
    }

    public ElementQueueProcess(final EntityReference externalEntityReference){
        this.externalEntityReference = externalEntityReference;
    }

    public List<QueueProcessItem> getQueueProcessItems() {
        return Collections.unmodifiableList(this.queueProcessItems);
    }

    /**
     * Confirms the process at a specific location and fires events based on the process state.
     *
     * @param locationReference                              The location to confirm.
     * @param elementQueueProcessPartialConfirmEventProducer Producer for firing partial confirmation events.
     * @param elementQueueProcessCompletedEventProducer      Producer for firing completion events.
     * @throws IllegalStateException if the process has already been completed.
     */
    public void confirm(final LocationReference locationReference,
                        final Producer<ElementQueueProcessPartialConfirmEvent> elementQueueProcessPartialConfirmEventProducer,
                        final Producer<ElementQueueProcessCompletedEvent> elementQueueProcessCompletedEventProducer) {
        if (this.isCompleted()) {
            throw new IllegalStateException("The process has already been completed");
        }
        boolean confirmed = this.confirmElement(locationReference);
        this.completeProcess(ProcessCompletionType.CompletedNormally);
        if (!this.isCompleted() && confirmed) {
            elementQueueProcessPartialConfirmEventProducer.fire(
                new ElementQueueProcessPartialConfirmEvent(new ProcessReference(this)));
        }
        if (this.isCompleted()) {
            elementQueueProcessCompletedEventProducer.fire(
                new ElementQueueProcessCompletedEvent(new ProcessReference(this), externalEntityReference,
                                                      this.getProcessCompletionType()));
        }
    }

    /**
     * Aborts the process at a specific location and fires a completion event if the process is completed.
     *
     * @param executionLocation                         The location where the process should be aborted.
     * @param elementQueueProcessCompletedEventProducer Producer for firing completion events.
     * @throws IllegalStateException if the process has already been completed.
     */
    public void abort(final LocationReference executionLocation,
                      final Producer<ElementQueueProcessCompletedEvent> elementQueueProcessCompletedEventProducer) {
        if (this.isCompleted()) {
            throw new IllegalStateException("The process has already been completed");
        }
        this.queueProcessItems.stream().filter(
            queueProcessItem -> queueProcessItem.getExecutionLocation().equals(executionLocation)).forEach(
            queueProcessItem -> queueProcessItem.abort()
        );
        this.abortProcess(ProcessCompletionType.Aborted);
        if (this.isCompleted()) {
            elementQueueProcessCompletedEventProducer.fire(
                new ElementQueueProcessCompletedEvent(new ProcessReference(this), externalEntityReference,
                                                      this.getProcessCompletionType()));
        }
    }

    private boolean confirmElement(final LocationReference locationReference) {
        Long numberOfInitialConfirmedElements = this.numberOfConfirmations();
        this.queueProcessItems.stream().filter(
            queueProcessItem -> queueProcessItem.getExecutionLocation().equals(locationReference)).forEach(
            queueProcessItem -> queueProcessItem.confirm()
        );
        Long numberOfFinalConfirmedElements = this.numberOfConfirmations();
        return numberOfFinalConfirmedElements.compareTo(numberOfInitialConfirmedElements) > 0;
    }

    private Long numberOfConfirmations() {
        return this.queueProcessItems.stream().filter(QueueProcessItem::isConfirmed).count();
    }

    /**
     * Add a new execution location.
     *
     * @param executionLocations List of locations to be added.
     * @param scheduled boolean to indicate if the queueing was scheduled
     * @return A read-only list of location statuses.
     */
    public boolean addExecutionLocation(final LocationReference executionLocations,
                                                 final boolean scheduled) {
        return queueProcessItems.add(new QueueProcessItem(this,
                                                          executionLocations, scheduled,
                                                          queueProcessItems.size()));
    }



    /**
     * Verifies if all element locations within the process have been confirmed.
     * It is a crucial part of the process lifecycle, ensuring that no further action is required for this process
     * to be considered complete.
     *
     * @return true if all element location statuses are confirmed, false otherwise.
     */
    @Override
    protected boolean completenessVerified() {
        return queueProcessItems.stream().allMatch(QueueProcessItem::isConfirmed);
    }

    @Override
    protected boolean abortionVerified() {
        return queueProcessItems.stream().allMatch(el -> el.isAborted() || !el.isStarted());
    }

    /**
     * Retrieves the next Item containing information about the next queue location.
     *
     * @return The next QueueProcessItem to be confirmed, or null if all locations are confirmed.
     */
    @JsonIgnore
    public QueueProcessItem getNextQueueProcessItem() {
        QueueProcessItem queueProcessItem = null;

        for (QueueProcessItem status : this.queueProcessItems) {
            if (!status.isConfirmed() && (queueProcessItem == null
                                          || status.getEnqueueOrder() < queueProcessItem.getEnqueueOrder())) {
                queueProcessItem = status;
            }
        }

        return queueProcessItem;
    }

    /**
     * Marks the queueing process as started for a specific location.
     *
     * @param executionLocation The location where queueing has started.
     */
    public void started(final LocationReference executionLocation) {
        for (QueueProcessItem queueProcessItem : this.queueProcessItems) {
            if (queueProcessItem.getExecutionLocation().equals(executionLocation)) {
                queueProcessItem.start();
            }
        }
    }

}

do you think these belong all to the same domain(module)?