Commit 2beb35f4 authored by LAVENIER's avatar LAVENIER
Browse files

[enh] Root data (e.g. ObservedLocation, Sale, Sample, etc.) : filter on data quality status

parent d8cbb365
......@@ -48,41 +48,38 @@ public interface RootDataSpecifications<E extends IRootDataEntity<? extends Seri
String PROGRAM_LABEL_PARAM = "programLabel";
default Specification<E> hasRecorderPersonId(Integer recorderPersonId) {
BindableSpecification<E> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, RECORDER_PERSON_ID_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(E.Fields.RECORDER_PERSON).get(IEntity.Fields.ID), param)
);
});
specification.addBind(RECORDER_PERSON_ID_PARAM, recorderPersonId);
return specification;
}).addBind(RECORDER_PERSON_ID_PARAM, recorderPersonId);
}
default Specification<E> hasProgramLabel(String programLabel) {
BindableSpecification<E> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<String> param = criteriaBuilder.parameter(String.class, PROGRAM_LABEL_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(E.Fields.PROGRAM).get(IItemReferentialEntity.Fields.LABEL), param)
);
});
specification.addBind(PROGRAM_LABEL_PARAM, programLabel);
return specification;
}).addBind(PROGRAM_LABEL_PARAM, programLabel);
}
default Specification<E> inDataQualityStatus(DataQualityStatusEnum... dataQualityStatus) {
if (ArrayUtils.isEmpty(dataQualityStatus)) return null;
if (dataQualityStatus.length == 1) {
return withDataQualityStatus(dataQualityStatus[0]);
}
default Specification<E> isValidated() {
return (root, query, criteriaBuilder) ->
// Validation date not null
criteriaBuilder.isNotNull(root.get(IRootDataEntity.Fields.VALIDATION_DATE));
}
return (root, query, criteriaBuilder) -> criteriaBuilder.or(
Arrays.stream(dataQualityStatus)
.map(this::withDataQualityStatus)
.filter(Objects::nonNull)
.toArray(Predicate[]::new)
);
default Specification<E> isQualified() {
return (root, query, criteriaBuilder) -> criteriaBuilder.and(
// Qualification date not null
criteriaBuilder.isNotNull(root.get(IRootDataEntity.Fields.QUALIFICATION_DATE)),
// Quality flag != 0
criteriaBuilder.notEqual(criteriaBuilder.coalesce(root.get(IRootDataEntity.Fields.QUALITY_FLAG).get(QualityFlag.Fields.ID), QualityFlagEnum.NOT_QUALIFIED.getId()), QualityFlagEnum.NOT_QUALIFIED.getId())
);
}
default Specification<E> withDataQualityStatus(DataQualityStatusEnum status) {
......@@ -101,18 +98,17 @@ public interface RootDataSpecifications<E extends IRootDataEntity<? extends Seri
return null;
}
default Specification<E> isValidated() {
return (root, query, criteriaBuilder) ->
// Validation date not null
criteriaBuilder.isNotNull(root.get(IRootDataEntity.Fields.VALIDATION_DATE));
}
default Specification<E> inDataQualityStatus(DataQualityStatusEnum... dataQualityStatus) {
if (ArrayUtils.isEmpty(dataQualityStatus)) return null;
if (dataQualityStatus.length == 1) {
return withDataQualityStatus(dataQualityStatus[0]);
}
default Specification<E> isQualified() {
return (root, query, criteriaBuilder) -> criteriaBuilder.and(
// Qualification date not null
criteriaBuilder.isNotNull(root.get(IRootDataEntity.Fields.QUALIFICATION_DATE)),
// Quality flag != 0
criteriaBuilder.notEqual(criteriaBuilder.coalesce(root.get(IRootDataEntity.Fields.QUALITY_FLAG).get(QualityFlag.Fields.ID), QualityFlagEnum.NOT_QUALIFIED.getId()), QualityFlagEnum.NOT_QUALIFIED.getId())
);
return (root, query, criteriaBuilder) -> criteriaBuilder.or(
Arrays.stream(dataQualityStatus)
.map(this::withDataQualityStatus)
.filter(Objects::nonNull)
.toArray(Predicate[]::new)
);
}
}
......@@ -85,7 +85,8 @@ public class LandingRepositoryImpl
.and(hasLocationId(filter.getLocationId()))
.and(inLocationIds(filter.getLocationIds()))
.and(hasVesselId(filter.getVesselId()))
.and(hasExcludeVesselIds(filter.getExcludeVesselIds()));
.and(hasExcludeVesselIds(filter.getExcludeVesselIds()))
.and(inDataQualityStatus(filter.getDataQualityStatus()));
}
@Override
......
......@@ -22,6 +22,7 @@ package net.sumaris.core.dao.data.landing;
* #L%
*/
import com.google.common.collect.ImmutableList;
import net.sumaris.core.dao.data.RootDataSpecifications;
import net.sumaris.core.dao.technical.Page;
import net.sumaris.core.dao.technical.jpa.BindableSpecification;
......@@ -44,97 +45,80 @@ public interface LandingSpecifications extends RootDataSpecifications<Landing> {
String OBSERVED_LOCATION_ID_PARAM = "observedLocationId";
String TRIP_ID_PARAM = "tripId";
String TRIP_IDS_PARAM = "tripIds";
String TRIP_IDS_SET_PARAM = "tripIdsSet";
String LOCATION_ID_PARAM = "locationId";
String LOCATION_IDS_PARAM = "locationIds";
String VESSEL_ID_PARAM = "vesselId";
String EXCLUDE_VESSEL_IDS_PARAM = "excludeVesselIds";
String EXCLUDE_VESSEL_IDS_SET_PARAM = "excludeVesselIdsSet";
default Specification<Landing> hasObservedLocationId(Integer observedLocationId) {
BindableSpecification<Landing> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, OBSERVED_LOCATION_ID_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(Landing.Fields.OBSERVED_LOCATION).get(IEntity.Fields.ID), param)
);
});
specification.addBind(OBSERVED_LOCATION_ID_PARAM, observedLocationId);
return specification;
}).addBind(OBSERVED_LOCATION_ID_PARAM, observedLocationId);
}
default Specification<Landing> hasTripId(Integer tripId) {
BindableSpecification<Landing> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, TRIP_ID_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(Landing.Fields.TRIP).get(IEntity.Fields.ID), param)
);
});
specification.addBind(TRIP_ID_PARAM, tripId);
return specification;
}).addBind(TRIP_ID_PARAM, tripId);
}
default Specification<Landing> hasTripIds(Collection<Integer> tripIds) {
BindableSpecification<Landing> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Boolean> setParam = criteriaBuilder.parameter(Boolean.class, TRIP_IDS_SET_PARAM);
if (CollectionUtils.isEmpty(tripIds)) return null;
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, TRIP_IDS_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isFalse(setParam),
criteriaBuilder.in(root.get(Landing.Fields.TRIP).get(IEntity.Fields.ID)).value(param)
);
});
specification.addBind(TRIP_IDS_SET_PARAM, CollectionUtils.isNotEmpty(tripIds));
specification.addBind(TRIP_IDS_PARAM, CollectionUtils.isEmpty(tripIds) ? null : tripIds);
return specification;
return criteriaBuilder.in(root.get(Landing.Fields.TRIP).get(IEntity.Fields.ID)).value(param);
}).addBind(TRIP_IDS_PARAM, tripIds);
}
default Specification<Landing> hasLocationId(Integer locationId) {
BindableSpecification<Landing> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
if (locationId == null) return null;
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, LOCATION_ID_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(Landing.Fields.LOCATION).get(IEntity.Fields.ID), param)
);
});
specification.addBind(LOCATION_ID_PARAM, locationId);
return specification;
return criteriaBuilder.equal(root.get(Landing.Fields.LOCATION).get(IEntity.Fields.ID), param);
}).addBind(LOCATION_ID_PARAM, locationId);
}
default Specification<Landing> inLocationIds(Integer... locationIds) {
if (ArrayUtils.isEmpty(locationIds)) return null;
BindableSpecification<Landing> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, LOCATION_IDS_PARAM);
return criteriaBuilder.in(root.get(Landing.Fields.LOCATION).get(IEntity.Fields.ID)).value(param);
});
specification.addBind(LOCATION_IDS_PARAM, Arrays.asList(locationIds));
return specification;
})
.addBind(LOCATION_IDS_PARAM, Arrays.asList(locationIds));
}
default Specification<Landing> hasVesselId(Integer vesselId) {
BindableSpecification<Landing> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
if (vesselId == null) return null;
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, VESSEL_ID_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(Landing.Fields.VESSEL).get(IEntity.Fields.ID), param)
);
});
specification.addBind(VESSEL_ID_PARAM, vesselId);
return specification;
return criteriaBuilder.equal(root.get(Landing.Fields.VESSEL).get(IEntity.Fields.ID), param);
})
.addBind(VESSEL_ID_PARAM, vesselId);
}
default Specification<Landing> hasExcludeVesselIds(Integer... excludeVesselIds) {
return hasExcludeVesselIds(ImmutableList.copyOf(excludeVesselIds));
}
default Specification<Landing> hasExcludeVesselIds(List<Integer> excludeVesselIds) {
BindableSpecification<Landing> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Boolean> setParam = criteriaBuilder.parameter(Boolean.class, EXCLUDE_VESSEL_IDS_SET_PARAM);
if (CollectionUtils.isNotEmpty(excludeVesselIds)) return null;
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, EXCLUDE_VESSEL_IDS_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isFalse(setParam),
criteriaBuilder.not(root.get(Landing.Fields.VESSEL).get(IEntity.Fields.ID).in(param))
);
});
specification.addBind(EXCLUDE_VESSEL_IDS_SET_PARAM, CollectionUtils.isNotEmpty(excludeVesselIds));
specification.addBind(EXCLUDE_VESSEL_IDS_PARAM, CollectionUtils.isEmpty(excludeVesselIds) ? null : excludeVesselIds);
return specification;
return criteriaBuilder.not(root.get(Landing.Fields.VESSEL).get(IEntity.Fields.ID).in(param));
}).addBind(EXCLUDE_VESSEL_IDS_PARAM, excludeVesselIds);
}
// fixme : not used but could be mixed with TripSpecifications & PhysicalGearSpecifications
......
......@@ -66,7 +66,8 @@ public class ObservedLocationRepositoryImpl
.and(hasLocationId(filter.getLocationId()))
.and(withStartDate(filter.getStartDate()))
.and(withEndDate(filter.getEndDate()))
.and(hasObserverPersonIds(filter.getObserverPersonIds()));
.and(hasObserverPersonIds(filter.getObserverPersonIds()))
.and(inDataQualityStatus(filter.getDataQualityStatus()));
}
@Override
......
......@@ -67,9 +67,12 @@ public class PhysicalGearRepositoryImpl
@Override
public Specification<PhysicalGear> toSpecification(PhysicalGearFilterVO filter, DataFetchOptions fetchOptions) {
return super.toSpecification(filter, fetchOptions)
.and(betweenDate(filter.getStartDate(), filter.getEndDate()))
// Parent
.and(hasVesselId(filter.getVesselId()))
.and(hasTripId(filter.getTripId()))
.and(betweenDate(filter.getStartDate(), filter.getEndDate()));
// Quality
.and(inDataQualityStatus(filter.getDataQualityStatus()));
}
@Override
......
......@@ -175,7 +175,12 @@ public class SaleRepositoryImpl
@Override
protected Specification<Sale> toSpecification(SaleFilterVO filter, DataFetchOptions fetchOptions) {
return super.toSpecification(filter, fetchOptions)
.and(hasTripId(filter.getTripId()));
// Location
.and(hasSaleLocation(filter.getLocationId()))
// Parent
.and(hasTripId(filter.getTripId()))
// Quality
.and(inDataQualityStatus(filter.getDataQualityStatus()));
}
}
......@@ -38,17 +38,24 @@ import java.util.List;
public interface SaleSpecifications extends RootDataSpecifications<Sale> {
String TRIP_ID_PARAM = "tripId";
String LOCATION_ID_PARAM = "locationId";
default Specification<Sale> hasTripId(Integer tripId) {
BindableSpecification<Sale> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, TRIP_ID_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(Sale.Fields.TRIP).get(IEntity.Fields.ID), param)
);
});
specification.addBind(TRIP_ID_PARAM, tripId);
return specification;
}).addBind(TRIP_ID_PARAM, tripId);
}
default Specification<Sale> hasSaleLocation(Integer locationId) {
if (locationId == null) return null;
return BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, TRIP_ID_PARAM);
return criteriaBuilder.equal(root.get(Sale.Fields.SALE_LOCATION).get(IEntity.Fields.ID), param);
}).addBind(LOCATION_ID_PARAM, locationId);
}
List<SaleVO> saveAllByTripId(int tripId, List<SaleVO> sales);
......
......@@ -134,7 +134,10 @@ public class VesselRepositoryImpl
.and(betweenFeaturesDate(filter.getStartDate(), filter.getEndDate()))
.and(betweenRegistrationDate(filter.getStartDate(), filter.getEndDate()))
// By text
.and(searchText(filter.getSearchAttributes(), filter.getSearchText()));
.and(searchText(filter.getSearchAttributes(), filter.getSearchText()))
// Quality
.and(inDataQualityStatus(filter.getDataQualityStatus()))
;
}
@Override
......
......@@ -22,6 +22,8 @@ package net.sumaris.core.vo.filter;
* #L%
*/
import net.sumaris.core.model.data.DataQualityStatusEnum;
import java.util.Date;
/**
......@@ -48,4 +50,8 @@ public interface IRootDataFilter extends IDataFilter {
Integer getLocationId();
void setLocationId(Integer locationId);
DataQualityStatusEnum[] getDataQualityStatus();
void setDataQualityStatus(DataQualityStatusEnum[] dataQualityStatus);
}
......@@ -28,6 +28,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;
import net.sumaris.core.model.data.DataQualityStatusEnum;
import net.sumaris.core.util.StringUtils;
import org.apache.commons.collections4.CollectionUtils;
......@@ -59,10 +60,11 @@ public class LandingFilterVO implements IRootDataFilter, IVesselFilter {
private Integer recorderDepartmentId;
private Integer recorderPersonId;
private List<Integer> excludeVesselIds;
private Integer[] excludeVesselIds;
// Parent
private Integer observedLocationId;
private Integer tripId;
private DataQualityStatusEnum[] dataQualityStatus;
}
......@@ -27,6 +27,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;
import net.sumaris.core.model.data.DataQualityStatusEnum;
import java.util.Date;
......@@ -50,4 +51,6 @@ public class ObservedLocationFilterVO implements IRootDataFilter {
private Integer recorderPersonId;
private Integer[] observerPersonIds;
private DataQualityStatusEnum[] dataQualityStatus;
}
......@@ -22,29 +22,33 @@ package net.sumaris.core.vo.filter;
* #L%
*/
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;
import net.sumaris.core.model.data.DataQualityStatusEnum;
import java.util.Date;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldNameConstants
public class PhysicalGearFilterVO implements IRootDataFilter, IVesselFilter {
private Integer tripId;
private Integer vesselId;
private Date startDate;
private Date endDate;
private String programLabel;
private Integer recorderDepartmentId;
private Integer recorderPersonId;
private Integer locationId;
private Integer locationId; // Not used in repository
// Parent
private Integer vesselId;
private Integer tripId;
private DataQualityStatusEnum[] dataQualityStatus;
}
......@@ -22,8 +22,12 @@ package net.sumaris.core.vo.filter;
* #L%
*/
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;
import net.sumaris.core.model.data.DataQualityStatusEnum;
import java.util.Date;
......@@ -32,20 +36,20 @@ import java.util.Date;
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@FieldNameConstants
public class SaleFilterVO implements IRootDataFilter {
private Date startDate;
private Date endDate;
private String programLabel;
private Integer locationId;
private Integer recorderDepartmentId;
private Integer recorderPersonId;
// Parent
private Integer tripId;
private DataQualityStatusEnum[] dataQualityStatus;
}
......@@ -27,6 +27,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;
import net.sumaris.core.model.data.DataQualityStatusEnum;
import java.util.Date;
......@@ -41,28 +42,21 @@ import java.util.Date;
public class SampleFilterVO implements IRootDataFilter {
private Date startDate;
private Date endDate;
private String programLabel;
private Integer locationId;
private Integer recorderDepartmentId;
private Integer recorderPersonId;
// Parent entities
private Integer operationId;
private Integer landingId;
private Integer observedLocationId;
private Integer[] observedLocationIds;
private Integer parentId;
private String tagId;
private Boolean withTagId;
private DataQualityStatusEnum[] dataQualityStatus;
}
......@@ -24,6 +24,7 @@ package net.sumaris.core.vo.filter;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.sumaris.core.model.data.DataQualityStatusEnum;
import net.sumaris.core.util.Beans;
import java.util.Date;
......@@ -61,6 +62,7 @@ public class VesselFilterVO implements IRootDataFilter {
private Integer registrationLocationId;
private Integer basePortLocationId;
private DataQualityStatusEnum[] dataQualityStatus; // Not used
public void setDate(Date date) {
this.startDate = date;
......
......@@ -22,7 +22,9 @@ package net.sumaris.core.service.data;
* #L%
*/
import lombok.NonNull;
import net.sumaris.core.dao.DatabaseResource;
import net.sumaris.core.model.data.DataQualityStatusEnum;
import net.sumaris.core.service.AbstractServiceTest;
import net.sumaris.core.vo.administration.user.DepartmentVO;
import net.sumaris.core.vo.administration.user.PersonVO;
......@@ -82,4 +84,37 @@ public class ObservedLocationServiceReadTest extends AbstractServiceTest{
.map(PersonVO::getId)
.forEach(personId -> Assert.assertTrue(personId != null && personId == recorderPersonId));
}
@Test
public void findByFilterWithDataQuality() {
assertFindAll(ObservedLocationFilterVO.builder()
.dataQualityStatus(new DataQualityStatusEnum[]{DataQualityStatusEnum.DRAFT})
.build(),
3);
assertFindAll(ObservedLocationFilterVO.builder()
.dataQualityStatus(new DataQualityStatusEnum[]{DataQualityStatusEnum.CONTROLLED})
.build(),
1);
assertFindAll(ObservedLocationFilterVO.builder()
.dataQualityStatus(new DataQualityStatusEnum[]{DataQualityStatusEnum.VALIDATED})
.build(),
0);
assertFindAll(ObservedLocationFilterVO.builder()
.dataQualityStatus(new DataQualityStatusEnum[]{DataQualityStatusEnum.QUALIFIED})
.build(),
0);
}
/* -- protected -- */
protected List<ObservedLocationVO> assertFindAll(@NonNull ObservedLocationFilterVO filter, int expectedSize) {
List<ObservedLocationVO> vos = service.findAll(filter, 0, 100);
Assert.assertNotNull(vos);