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

[enh] Add Acost program on test database

[enh] Add filters on operation
[enh] Add filter (onGearIds) on metier
[enh] Add filter (includedIds) on trip
parent 3e2289e3
......@@ -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,10 +23,10 @@ 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;
......@@ -35,12 +35,13 @@ 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.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 +59,8 @@ import java.util.stream.Collectors;
*/
@Slf4j
public class OperationRepositoryImpl
extends DataRepositoryImpl<Operation, OperationVO, OperationFilterVO, DataFetchOptions>
implements OperationSpecifications {
extends DataRepositoryImpl<Operation, OperationVO, OperationFilterVO, DataFetchOptions>
implements OperationSpecifications {
@Autowired
private PhysicalGearRepository physicalGearRepository;
......@@ -123,10 +124,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 +135,7 @@ public class OperationRepositoryImpl
.withChildrenEntities(false) // Use flat list, not a tree
.withRecorderDepartment(false)
.withMeasurementValues(true)
.build()));
.build()));
}
// Measurements
......@@ -143,6 +144,18 @@ public class OperationRepositoryImpl
target.setGearMeasurements(measurementDao.getOperationGearUseMeasurements(operationId));
}
// ParentOperation
if (source.getParentOperation() != null) {
target.setParentOperationId(source.getParentOperation().getId());
target.setParentOperation(this.findById(target.getParentOperationId(), fetchOptions).orElse(null));
}
// ChildOperation
if (fetchOptions != null && fetchOptions.isWithChildrenEntities() && target.getParentOperation() == null && source.getChildOperation() != null) {
target.setChildOperationId(source.getChildOperation().getId());
target.setChildOperation(this.findById(target.getChildOperationId(), fetchOptions).orElse(null));
}
}
@Override
......@@ -170,8 +183,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 +217,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 +248,55 @@ 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) {
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()))
.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,167 @@ 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.Trip;
import net.sumaris.core.model.referential.IItemReferentialEntity;
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";
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> 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;
}
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);
return criteriaBuilder.in(root.get(Trip.Fields.ID)).value(param);
})
.addBind(INCLUDED_IDS_PARAMETER, Arrays.asList(includedIds));
}
}
......@@ -137,7 +137,8 @@ public class MetierRepositoryImpl
@Override
protected Specification<Metier> toSpecification(IReferentialFilter filter, ReferentialFetchOptions fetchOptions) {
return super.toSpecification(filter, fetchOptions)
.and(alreadyPracticedMetier(filter));
.and(alreadyPracticedMetier(filter))
.and(inGearIds(filter));
}
/* -- protected method -- */
......@@ -150,4 +151,11 @@ public class MetierRepositoryImpl
return alreadyPracticedMetier(metierFilter);
}
private Specification<Metier> inGearIds(IReferentialFilter filter) {
if (!(filter instanceof MetierFilterVO)) return null;
MetierFilterVO metierFilter = (MetierFilterVO) filter;
return inGearIds(metierFilter.getGearIds());
}
}
......@@ -25,6 +25,7 @@ package net.sumaris.core.model.data;
import lombok.Data;
import lombok.ToString;
import lombok.experimental.FieldNameConstants;
import net.sumaris.core.model.administration.programStrategy.Program;
import net.sumaris.core.model.administration.user.Department;
import net.sumaris.core.model.referential.QualityFlag;
import net.sumaris.core.model.referential.metier.Metier;
......@@ -101,11 +102,16 @@ public class Operation implements IDataEntity<Integer>,
@JoinColumn(name = "quality_flag_fk", nullable = false)
private QualityFlag qualityFlag;
@ManyToOne(fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.LAZY, targetEntity = Trip.class )
@JoinColumn(name = "trip_fk", nullable = false)
@ToString.Exclude
private Trip trip;
@OneToOne(fetch = FetchType.LAZY, targetEntity = Operation.class )
@JoinColumn(name = "operation_fk")
@ToString.Exclude
private Operation parentOperation;
@Column(name = "start_date_time", nullable = false)
private Date startDateTime;
......@@ -163,5 +169,8 @@ public class Operation implements IDataEntity<Integer>,
@Cascade(org.hibernate.annotations.CascadeType.DELETE)
private List<FishingArea> fishingAreas = new ArrayList<>();
@OneToOne(fetch = FetchType.LAZY, targetEntity = Operation.class, mappedBy = Fields.PARENT_OPERATION)
private Operation childOperation;
}
......@@ -26,7 +26,7 @@ package net.sumaris.core.service.data;
import net.sumaris.core.dao.technical.SortDirection;
import net.sumaris.core.vo.data.DataFetchOptions;
import net.sumaris.core.vo.data.OperationVO;
import net.sumaris.core.vo.filter.TripFilterVO;
import net.sumaris.core.vo.filter.OperationFilterVO;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
......@@ -47,9 +47,15 @@ public interface OperationService {
@Transactional(readOnly = true)
List<OperationVO> findAllByTripId(int tripId, int offset, int size, String sortAttribute, SortDirection sortDirection, DataFetchOptions fetchOptions);
@Transactional(readOnly = true)
List<OperationVO> findAllByFilter(OperationFilterVO filter, int offset, int size, String sortAttribute, SortDirection sortDirection, DataFetchOptions fetchOptions);
@Transactional(readOnly = true)
Long countByTripId(int tripId);
@Transactional(readOnly = true)
Long countByFilter(OperationFilterVO filter);
@Transactional(readOnly = true)
OperationVO get(int id);
......
......@@ -24,9 +24,8 @@ package net.sumaris.core.service.data;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import lombok.NonNull;
import net.sumaris.core.config.SumarisConfiguration;
import lombok.extern.slf4j.Slf4j;
import net.sumaris.core.dao.data.MeasurementDao;
import net.sumaris.core.dao.data.VesselPositionDao;
import net.sumaris.core.dao.data.operation.OperationRepository;
......@@ -53,7 +52,9 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service("operationService")
......@@ -78,15 +79,15 @@ public class OperationServiceImpl implements OperationService {
@Autowired
protected FishingAreaService fishingAreaService;
@Autowired
private ApplicationEventPublisher publisher;
@Autowired
private ApplicationEventPublisher publisher;
private boolean enableTrash = false;
private boolean enableTrash = false;
@EventListener({ConfigurationReadyEvent.class, ConfigurationUpdatedEvent.class})