TraceFilterSpecificationBuilder.java

package fr.avenirsesr.portfolio.trace.infrastructure.adapter.specification;

import fr.avenirsesr.portfolio.common.data.infrastructure.adapter.specification.FilterSpecificationBuilder;
import fr.avenirsesr.portfolio.file.domain.model.shared.EFileType;
import fr.avenirsesr.portfolio.file.infrastructure.adapter.model.TraceAttachmentEntity;
import fr.avenirsesr.portfolio.trace.domain.filter.ETraceFilterKey;
import fr.avenirsesr.portfolio.trace.domain.model.ETraceStatus;
import fr.avenirsesr.portfolio.trace.infrastructure.adapter.model.TraceEntity;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.springframework.data.jpa.domain.Specification;

public class TraceFilterSpecificationBuilder
    extends FilterSpecificationBuilder<TraceEntity, ETraceFilterKey> {

  @Override
  public Specification<TraceEntity> getSpecification(ETraceFilterKey key, Object value) {
    return switch (key) {
      case FILE_TYPE -> fileType((List<EFileType>) value);
      case SKILL -> skill((List<UUID>) value);
      case STATUS -> status((List<ETraceStatus>) value);
      case IS_ASSOCIATED -> {
        if (value == null) yield null;
        yield ((Boolean) value)
            ? TraceSpecification.associated()
            : TraceSpecification.unassociated();
      }
    };
  }

  private Specification<TraceEntity> fileType(List<EFileType> fileTypes) {
    return (root, query, cb) -> {
      if (fileTypes == null || fileTypes.isEmpty() || query == null) return null;

      Subquery<TraceAttachmentEntity> attachSub = query.subquery(TraceAttachmentEntity.class);
      Root<TraceAttachmentEntity> attachRoot = attachSub.from(TraceAttachmentEntity.class);
      Predicate fileTypePredicate = attachRoot.get("fileType").in(fileTypes);
      Predicate belongsToTrace = cb.equal(attachRoot.get("trace").get("id"), root.get("id"));
      Predicate isActive = cb.isTrue(attachRoot.get("isActiveVersion"));
      attachSub.where(cb.and(belongsToTrace, isActive, fileTypePredicate));

      return cb.exists(attachSub);
    };
  }

  private Specification<TraceEntity> skill(List<UUID> values) {
    return (root, query, cb) -> {
      if (values == null || values.isEmpty() || query == null) return null;
      var skill =
          root.join("skillLevels", JoinType.LEFT)
              .join("skillLevel", JoinType.LEFT)
              .join("skill", JoinType.LEFT);
      query.distinct(true);
      return skill.get("id").in(values);
    };
  }

  private Specification<TraceEntity> status(List<ETraceStatus> traceStatus) {
    return (root, query, cb) -> {
      if (traceStatus == null || query == null) return null;

      query.distinct(true);
      List<Predicate> predicates = new ArrayList<>();
      for (ETraceStatus status : traceStatus) {
        var amsJoin = root.join("amses", JoinType.LEFT);
        var skillLevelJoin = root.join("skillLevels", JoinType.LEFT);

        Predicate amsPredicate = null;
        Predicate skillPredicate = null;

        if (!status.getAmsStatuses().isEmpty()) {
          amsPredicate = amsJoin.get("status").in(status.getAmsStatuses());
        }

        if (!status.getSkillLevelStatuses().isEmpty()) {
          skillPredicate = skillLevelJoin.get("status").in(status.getSkillLevelStatuses());
        }

        if (amsPredicate != null) predicates.add(amsPredicate);
        if (skillPredicate != null) predicates.add(skillPredicate);
      }

      return cb.or(predicates.toArray(new Predicate[0]));
    };
  }
}