Commit 5d04a55b authored by COTONNEC's avatar COTONNEC
Browse files

Merge branch 'feature/ACOST' into develop

# Conflicts:
#	sumaris-core/src/main/resources/net/sumaris/core/db/changelog/hsqldb/db-changelog.xml
parents d6ad81b7 7ecf39a9
......@@ -24,19 +24,17 @@ package net.sumaris.core.dao.data.operation;
import net.sumaris.core.dao.data.DataRepository;
import net.sumaris.core.model.data.Operation;
import net.sumaris.core.vo.data.DataFetchOptions;
import net.sumaris.core.vo.data.OperationFetchOptions;
import net.sumaris.core.vo.data.OperationVO;
import net.sumaris.core.vo.filter.OperationFilterVO;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
/**
* @author peck7 on 01/09/2020.
*/
public interface OperationRepository
extends DataRepository<Operation, OperationVO, OperationFilterVO, DataFetchOptions>,
extends DataRepository<Operation, OperationVO, OperationFilterVO, OperationFetchOptions>,
OperationSpecifications {
@Query("select p.id from Operation o inner join o.trip t inner join t.program p where o.id = :id")
......
......@@ -10,12 +10,12 @@ package net.sumaris.core.dao.data.operation;
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program 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 General Public License for more details.
*
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
......@@ -23,24 +23,27 @@ package net.sumaris.core.dao.data.operation;
*/
import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.dao.data.VesselPositionDao;
import net.sumaris.core.dao.data.batch.BatchRepository;
import net.sumaris.core.dao.data.DataRepositoryImpl;
import net.sumaris.core.dao.data.MeasurementDao;
import net.sumaris.core.dao.data.VesselPositionDao;
import net.sumaris.core.dao.data.batch.BatchRepository;
import net.sumaris.core.dao.data.fishingArea.FishingAreaRepository;
import net.sumaris.core.dao.data.physicalGear.PhysicalGearRepository;
import net.sumaris.core.dao.data.sample.SampleRepository;
import net.sumaris.core.dao.data.trip.TripRepository;
import net.sumaris.core.dao.referential.metier.MetierRepository;
import net.sumaris.core.dao.technical.Daos;
import net.sumaris.core.model.data.Operation;
import net.sumaris.core.model.data.PhysicalGear;
import net.sumaris.core.model.data.Trip;
import net.sumaris.core.model.referential.QualityFlag;
import net.sumaris.core.model.referential.metier.Metier;
import net.sumaris.core.util.Beans;
import net.sumaris.core.util.Dates;
import net.sumaris.core.vo.data.batch.BatchFetchOptions;
import net.sumaris.core.vo.data.DataFetchOptions;
import net.sumaris.core.vo.data.OperationFetchOptions;
import net.sumaris.core.vo.data.OperationVO;
import net.sumaris.core.vo.data.batch.BatchFetchOptions;
import net.sumaris.core.vo.data.sample.SampleFetchOptions;
import net.sumaris.core.vo.filter.OperationFilterVO;
import org.apache.commons.collections4.CollectionUtils;
......@@ -58,8 +61,8 @@ import java.util.stream.Collectors;
*/
@Slf4j
public class OperationRepositoryImpl
extends DataRepositoryImpl<Operation, OperationVO, OperationFilterVO, DataFetchOptions>
implements OperationSpecifications {
extends DataRepositoryImpl<Operation, OperationVO, OperationFilterVO, OperationFetchOptions>
implements OperationSpecifications {
@Autowired
private PhysicalGearRepository physicalGearRepository;
......@@ -79,21 +82,31 @@ public class OperationRepositoryImpl
@Autowired
private BatchRepository batchRepository;
@Autowired
private TripRepository tripRepository;
@Autowired
protected VesselPositionDao vesselPositionDao;
protected OperationRepositoryImpl(EntityManager entityManager) {
super(Operation.class, OperationVO.class, entityManager);
setLockForUpdate(true);
}
@Override
public void toVO(Operation source, OperationVO target, DataFetchOptions fetchOptions, boolean copyIfNull) {
public void toVO(Operation source, OperationVO target, OperationFetchOptions fetchOptions, boolean copyIfNull) {
super.toVO(source, target, fetchOptions, copyIfNull);
// Trip
if (source.getTrip() != null) {
target.setTripId(source.getTrip().getId());
if (fetchOptions != null && fetchOptions.isWithTrip()){
target.setTrip(tripRepository.toVO(source.getTrip(), DataFetchOptions.builder()
.withRecorderDepartment(false)
.withRecorderPerson(false)
.build()));
}
}
// Physical gear
......@@ -123,10 +136,10 @@ public class OperationRepositoryImpl
// Batches
target.setBatches(batchRepository.findAllVO(batchRepository.hasOperationId(operationId),
BatchFetchOptions.builder()
.withChildrenEntities(false) // Use flat list, not a tree
.withRecorderDepartment(false)
.withMeasurementValues(true)
.build()));
.withChildrenEntities(false) // Use flat list, not a tree
.withRecorderDepartment(false)
.withMeasurementValues(true)
.build()));
// Samples
target.setSamples(sampleRepository.findAllVO(sampleRepository.hasOperationId(operationId),
......@@ -134,7 +147,7 @@ public class OperationRepositoryImpl
.withChildrenEntities(false) // Use flat list, not a tree
.withRecorderDepartment(false)
.withMeasurementValues(true)
.build()));
.build()));
}
// Measurements
......@@ -143,6 +156,29 @@ public class OperationRepositoryImpl
target.setGearMeasurements(measurementDao.getOperationGearUseMeasurements(operationId));
}
// ParentOperation
if (source.getParentOperation() != null) {
target.setParentOperationId(source.getParentOperation().getId());
if (fetchOptions == null || fetchOptions.isWithLinkedOperation()) {
if (fetchOptions == null) {
fetchOptions = OperationFetchOptions.builder().build();
}
fetchOptions.setWithLinkedOperation(false);
target.setParentOperation(this.findById(target.getParentOperationId(), fetchOptions).orElse(null));
}
}
// ChildOperation
if (target.getParentOperation() == null && source.getChildOperation() != null) {
target.setChildOperationId(source.getChildOperation().getId());
if (fetchOptions == null || fetchOptions.isWithLinkedOperation()) {
if (fetchOptions == null) {
fetchOptions = OperationFetchOptions.builder().build();
}
fetchOptions.setWithLinkedOperation(false);
target.setChildOperation(this.findById(target.getChildOperationId(), fetchOptions).orElse(null));
}
}
}
@Override
......@@ -170,8 +206,8 @@ public class OperationRepositoryImpl
// Update the parent entity
Daos.replaceEntities(parent.getOperations(),
result,
(vo) -> getReference(Operation.class, vo.getId()));
result,
(vo) -> getReference(Operation.class, vo.getId()));
return result;
}
......@@ -204,23 +240,23 @@ public class OperationRepositoryImpl
{
// Read physical gear id
Integer physicalGearId = source.getPhysicalGearId() != null
? source.getPhysicalGearId()
: source.getPhysicalGear() != null ? source.getPhysicalGear().getId() : null;
? source.getPhysicalGearId()
: source.getPhysicalGear() != null ? source.getPhysicalGear().getId() : null;
// If not found, try using the rankOrder
if (physicalGearId == null && source.getPhysicalGear() != null && source.getPhysicalGear().getRankOrder() != null && target.getTrip() != null) {
Integer rankOrder = source.getPhysicalGear().getRankOrder();
physicalGearId = target.getTrip().getPhysicalGears()
.stream()
.filter(g -> rankOrder != null && Objects.equals(g.getRankOrder(), rankOrder))
.map(PhysicalGear::getId)
.findFirst().orElse(null);
.stream()
.filter(g -> rankOrder != null && Objects.equals(g.getRankOrder(), rankOrder))
.map(PhysicalGear::getId)
.findFirst().orElse(null);
if (physicalGearId == null) {
throw new DataIntegrityViolationException(
String.format("Operation {starDateTime: '%s'} use a unknown PhysicalGear. PhysicalGear with {rankOrder: %s} not found in gears Trip.",
Dates.toISODateTimeString(source.getStartDateTime()),
source.getPhysicalGear().getRankOrder()
));
String.format("Operation {starDateTime: '%s'} use a unknown PhysicalGear. PhysicalGear with {rankOrder: %s} not found in gears Trip.",
Dates.toISODateTimeString(source.getStartDateTime()),
source.getPhysicalGear().getRankOrder()
));
}
source.setPhysicalGearId(physicalGearId);
source.setPhysicalGear(null);
......@@ -235,11 +271,57 @@ public class OperationRepositoryImpl
}
}
// Parent Operation
Integer parentOperationId = source.getParentOperationId() != null ? source.getParentOperationId() : (source.getParentOperation() != null ? source.getParentOperation().getId() : null);
if (copyIfNull || parentOperationId != null) {
if (parentOperationId == null) {
target.setParentOperation(null);
} else {
target.setParentOperation(getReference(Operation.class, parentOperationId));
}
}
// Child Operation
Integer childOperationId = source.getChildOperationId() != null ? source.getChildOperationId() : (source.getChildOperation() != null ? source.getChildOperation().getId() : null);
if (copyIfNull || childOperationId != null) {
if (childOperationId == null) {
target.setChildOperation(null);
} else {
target.setChildOperation(getReference(Operation.class, childOperationId));
}
}
//Quality Flag
Integer qualityFlag = source.getQualityFlagId();
if (qualityFlag != null) {
target.setQualityFlag(getReference(QualityFlag.class, qualityFlag));
} else {
target.setQualityFlag(getReference(QualityFlag.class, getConfig().getDefaultQualityFlagId()));
}
}
@Override
protected Specification<Operation> toSpecification(OperationFilterVO filter, DataFetchOptions fetchOptions) {
protected Specification<Operation> toSpecification(OperationFilterVO filter, OperationFetchOptions fetchOptions) {
return super.toSpecification(filter, fetchOptions)
.and(hasTripId(filter.getTripId()));
.and(hasTripId(filter.getTripId()))
.and(hasProgramLabel(filter.getProgramLabel()))
.and(hasVesselId(filter.getVesselId()))
.and(excludedIds(filter.getExcludedIds()))
.and(notChildOperation(filter.getExcludeChildOperation()))
.and(hasNoChildOperation(filter.getExcludeChildOperation()))
.and(isBetweenDates(filter.getStartDate(), filter.getEndDate()))
.and(inGearIds(filter.getGearIds()))
.and(inTaxonGroupLabels(filter.getTaxonGroupLabels()))
.and(hasQualityFlagId(filter.getQualityFlagId()))
.or(includedIds(filter.getIncludedIds()));
}
@Override
protected void onAfterSaveEntity(OperationVO vo, Operation savedEntity, boolean isNew) {
super.onAfterSaveEntity(vo, savedEntity, isNew);
if (vo.getParentOperation() == null && vo.getParentOperationId() != null) {
vo.setParentOperation(this.get(vo.getParentOperationId()));
}
}
}
......@@ -10,12 +10,12 @@ package net.sumaris.core.dao.data.operation;
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
*
* This program 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 General Public License for more details.
*
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
......@@ -26,34 +26,210 @@ import net.sumaris.core.dao.data.DataSpecifications;
import net.sumaris.core.dao.technical.jpa.BindableSpecification;
import net.sumaris.core.dao.technical.model.IEntity;
import net.sumaris.core.model.data.Operation;
import net.sumaris.core.model.data.PhysicalGear;
import net.sumaris.core.model.data.Trip;
import net.sumaris.core.model.referential.IItemReferentialEntity;
import net.sumaris.core.model.referential.QualityFlag;
import net.sumaris.core.model.referential.metier.Metier;
import net.sumaris.core.model.referential.taxon.TaxonGroup;
import net.sumaris.core.vo.data.OperationVO;
import net.sumaris.core.vo.filter.OperationFilterVO;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.ParameterExpression;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* @author peck7 on 01/09/2020.
*/
public interface OperationSpecifications
extends DataSpecifications<Operation> {
extends DataSpecifications<Operation> {
String TRIP_ID_PARAM = "tripId";
String VESSEL_ID_PARAM = "vesselId";
String INCLUDED_IDS_PARAMETER = "includedIds";
String EXCLUDED_IDS_PARAMETER = "excludedIds";
String PROGRAM_LABEL_PARAM = "programLabel";
String EXCLUDE_CHILD_OPERATION_PARAM = "excludeChildOperation";
String HAS_NO_CHILD_OPERATION_PARAM = "hasNoChildOperation";
String START_DATE_PARAM = "startDate";
String END_DATE_PARAM = "endDate";
String GEAR_IDS_PARAMETER = "gearIds";
String TAXON_GROUP_LABELS_PARAMETER = "targetSpecieIds";
String QUALITY_FLAG_ID_PARAMETER = "qualityFlagId";
default Specification<Operation> hasTripId(Integer tripId) {
if (tripId == null) return null;
BindableSpecification<Operation> specification = 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(Operation.Fields.TRIP).get(IEntity.Fields.ID), param)
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(Operation.Fields.TRIP).get(IEntity.Fields.ID), param)
);
});
specification.addBind(TRIP_ID_PARAM, tripId);
return specification;
}
default Specification<Operation> includedIds(Integer[] includedIds) {
if (ArrayUtils.isEmpty(includedIds)) return null;
return BindableSpecification.<Operation>where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, INCLUDED_IDS_PARAMETER);
return criteriaBuilder.in(root.get(IEntity.Fields.ID)).value(param);
})
.addBind(INCLUDED_IDS_PARAMETER, Arrays.asList(includedIds));
}
default Specification<Operation> excludedIds(Integer[] excludedIds) {
if (ArrayUtils.isEmpty(excludedIds)) return null;
return BindableSpecification.<Operation>where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, EXCLUDED_IDS_PARAMETER);
return criteriaBuilder.not(
criteriaBuilder.in(root.get(IEntity.Fields.ID)).value(param)
);
})
.addBind(EXCLUDED_IDS_PARAMETER, Arrays.asList(excludedIds));
}
default Specification<Operation> hasProgramLabel(String programLabel) {
BindableSpecification<Operation> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<String> param = criteriaBuilder.parameter(String.class, PROGRAM_LABEL_PARAM);
Join<Operation, Trip> tripJoin = root.join(Operation.Fields.TRIP, JoinType.INNER);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(tripJoin.get(Trip.Fields.PROGRAM).get(IItemReferentialEntity.Fields.LABEL), param)
);
});
specification.addBind(PROGRAM_LABEL_PARAM, programLabel);
return specification;
}
default Specification<Operation> hasVesselId(Integer vesselId) {
if (vesselId == null) return null;
BindableSpecification<Operation> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, VESSEL_ID_PARAM);
Join<Operation, Trip> tripJoin = root.join(Operation.Fields.TRIP, JoinType.INNER);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(tripJoin.get(Trip.Fields.VESSEL).get(IEntity.Fields.ID), param)
);
});
specification.addBind(VESSEL_ID_PARAM, vesselId);
return specification;
}
default Specification<Operation> notChildOperation(Boolean excludeChildOperation) {
BindableSpecification<Operation> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Boolean> param = criteriaBuilder.parameter(Boolean.class, EXCLUDE_CHILD_OPERATION_PARAM);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.isFalse(param),
criteriaBuilder.isNull(root.get(Operation.Fields.PARENT_OPERATION)));
}
);
specification.addBind(EXCLUDE_CHILD_OPERATION_PARAM, excludeChildOperation);
return specification;
}
default Specification<Operation> hasNoChildOperation(Boolean hasNotChildOperation) {
BindableSpecification<Operation> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Boolean> param = criteriaBuilder.parameter(Boolean.class, HAS_NO_CHILD_OPERATION_PARAM);
Join<Operation, Operation> operationJoin = root.join(Operation.Fields.CHILD_OPERATION, JoinType.LEFT);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.isFalse(param),
criteriaBuilder.isNull(operationJoin));
}
);
specification.addBind(HAS_NO_CHILD_OPERATION_PARAM, hasNotChildOperation);
return specification;
}
default Specification<Operation> inGearIds(Integer[] gearIds) {
if (ArrayUtils.isEmpty(gearIds)) return null;
return BindableSpecification.<Operation>where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, GEAR_IDS_PARAMETER);
Join<Operation, PhysicalGear> physicalGearJoin = root.join(Operation.Fields.PHYSICAL_GEAR, JoinType.INNER);
return criteriaBuilder.in(physicalGearJoin.get(PhysicalGear.Fields.GEAR).get(IEntity.Fields.ID)).value(param);
})
.addBind(GEAR_IDS_PARAMETER, Arrays.asList(gearIds));
}
default Specification<Operation> inTaxonGroupLabels(String[] taxonGroupLabels) {
if (ArrayUtils.isEmpty(taxonGroupLabels)) return null;
return BindableSpecification.<Operation>where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, TAXON_GROUP_LABELS_PARAMETER);
Join<Operation, Metier> metierJoin = root.join(Operation.Fields.METIER, JoinType.INNER);
return criteriaBuilder.in(metierJoin.get(Metier.Fields.TAXON_GROUP).get(TaxonGroup.Fields.LABEL)).value(param);
})
.addBind(TAXON_GROUP_LABELS_PARAMETER, Arrays.asList(taxonGroupLabels));
}
default Specification<Operation> isBetweenDates(Date startDate, Date endDate) {
BindableSpecification<Operation> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Date> startDateparam = criteriaBuilder.parameter(Date.class, START_DATE_PARAM);
ParameterExpression<Date> endDateparam = criteriaBuilder.parameter(Date.class, END_DATE_PARAM);
return criteriaBuilder.and(
criteriaBuilder.or(
criteriaBuilder.isNull(startDateparam.as(String.class)),
criteriaBuilder.or(
criteriaBuilder.and(
criteriaBuilder.isNotNull(root.get(Operation.Fields.END_DATE_TIME)),
criteriaBuilder.greaterThan(root.get(Operation.Fields.END_DATE_TIME), startDateparam)
),
criteriaBuilder.and(
criteriaBuilder.isNotNull(root.get(Operation.Fields.FISHING_START_DATE_TIME)),
criteriaBuilder.greaterThan(root.get(Operation.Fields.FISHING_START_DATE_TIME), startDateparam)
)
)
),
criteriaBuilder.or(
criteriaBuilder.isNull(endDateparam.as(String.class)),
criteriaBuilder.or(
criteriaBuilder.and(
criteriaBuilder.isNotNull(root.get(Operation.Fields.END_DATE_TIME)),
criteriaBuilder.lessThan(root.get(Operation.Fields.END_DATE_TIME), endDateparam)
),
criteriaBuilder.and(
criteriaBuilder.isNotNull(root.get(Operation.Fields.FISHING_START_DATE_TIME)),
criteriaBuilder.lessThan(root.get(Operation.Fields.FISHING_START_DATE_TIME), endDateparam)
)
)
)
);
}
);
specification.addBind(START_DATE_PARAM, startDate);
specification.addBind(END_DATE_PARAM, endDate);
return specification;
}
default Specification<Operation> hasQualityFlagId(Integer qualityFlagId) {
if (qualityFlagId == null) return null;
BindableSpecification<Operation> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
ParameterExpression<Integer> param = criteriaBuilder.parameter(Integer.class, QUALITY_FLAG_ID_PARAMETER);
return criteriaBuilder.or(
criteriaBuilder.isNull(param),
criteriaBuilder.equal(root.get(Operation.Fields.QUALITY_FLAG).get(QualityFlag.Fields.ID), param)
);
});
specification.addBind(QUALITY_FLAG_ID_PARAMETER, qualityFlagId);
return specification;
}
List<OperationVO> saveAllByTripId(int tripId, List<OperationVO> operations);
}
......@@ -41,8 +41,8 @@ import java.util.Objects;
@Slf4j
public class TripRepositoryImpl
extends RootDataRepositoryImpl<Trip, TripVO, TripFilterVO, DataFetchOptions>
implements TripSpecifications {
extends RootDataRepositoryImpl<Trip, TripVO, TripFilterVO, DataFetchOptions>
implements TripSpecifications {
private final LocationRepository locationRepository;
private final LandingRepository landingRepository;
......@@ -57,10 +57,11 @@ public class TripRepositoryImpl
@Override
public Specification<Trip> toSpecification(TripFilterVO filter, DataFetchOptions fetchOptions) {
return super.toSpecification(filter, fetchOptions)
.and(id(filter.getTripId()))
.and(betweenDate(filter.getStartDate(), filter.getEndDate()))
.and(hasLocationId(filter.getLocationId()))
.and(hasVesselId(filter.getVesselId()));
.and(id(filter.getTripId()))
.and(betweenDate(filter.getStartDate(), filter.getEndDate()))
.and(hasLocationId(filter.getLocationId()))
.and(hasVesselId(filter.getVesselId()))
.and(includedIds(filter.getIncludedIds()));
}
@Override
......
......@@ -26,15 +26,19 @@ import net.sumaris.core.dao.data.RootDataSpecifications;
import net.sumaris.core.dao.technical.jpa.BindableSpecification;
import net.sumaris.core.dao.technical.model.IEntity;
import net.sumaris.core.model.data.Trip;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.ParameterExpression;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
public interface TripSpecifications extends RootDataSpecifications<Trip> {
String VESSEL_ID_PARAM = "vesselId";
String LOCATION_ID_PARAM = "locationId";
String INCLUDED_IDS_PARAMETER = "includedIds";
default Specification<Trip> hasLocationId(Integer locationId) {
BindableSpecification<Trip> specification = BindableSpecification.where((root, query, criteriaBuilder) -> {
......@@ -84,4 +88,13 @@ public interface TripSpecifications extends RootDataSpecifications<Trip> {
};
}
default Specification<Trip> includedIds(Integer[] includedIds) {
if (ArrayUtils.isEmpty(includedIds)) return null;
return BindableSpecification.<Trip>where((root, query, criteriaBuilder) -> {
ParameterExpression<Collection> param = criteriaBuilder.parameter(Collection.class, INCLUDED_IDS_PARAMETER);