diff --git a/web/src/main/java/org/devgateway/ocds/web/annotate/LanguageValidation.java b/web/src/main/java/org/devgateway/ocds/web/annotate/LanguageValidation.java index a85baca2e..c89db07f4 100644 --- a/web/src/main/java/org/devgateway/ocds/web/annotate/LanguageValidation.java +++ b/web/src/main/java/org/devgateway/ocds/web/annotate/LanguageValidation.java @@ -1,11 +1,11 @@ package org.devgateway.ocds.web.annotate; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; /** * Created by mpostelnicu on 2/17/17. diff --git a/web/src/main/java/org/devgateway/ocds/web/annotate/OrganizationIdValidation.java b/web/src/main/java/org/devgateway/ocds/web/annotate/OrganizationIdValidation.java new file mode 100644 index 000000000..d87148e35 --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/annotate/OrganizationIdValidation.java @@ -0,0 +1,34 @@ +package org.devgateway.ocds.web.annotate; + +import cz.jirutka.validator.collection.constraints.EachPattern; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.validation.Constraint; +import javax.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; + +/** + * Created by mpostelnicu + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target({METHOD, FIELD, ANNOTATION_TYPE}) +@Documented +@EachPattern(regexp = "^[a-zA-Z0-9\\-]*$", message = "Invalid organization id!") +@Constraint(validatedBy = {}) +@NotEmpty +public @interface OrganizationIdValidation { + + String message() default "Organization Id is not valid"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java index 6045e3d7e..5d840e4c7 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/FrequentTenderersController.java @@ -15,6 +15,7 @@ import io.swagger.annotations.ApiOperation; import org.devgateway.ocds.persistence.mongo.Award; import org.devgateway.ocds.persistence.mongo.constants.MongoConstants; +import org.devgateway.ocds.web.rest.controller.request.WinningTupleYearFilterPagingRequest; import org.devgateway.ocds.web.rest.controller.request.YearFilterPagingRequest; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; @@ -31,6 +32,7 @@ import javax.validation.Valid; import java.util.List; +import java.util.stream.Collectors; import static org.springframework.data.mongodb.core.aggregation.Aggregation.group; import static org.springframework.data.mongodb.core.aggregation.Aggregation.limit; @@ -122,4 +124,38 @@ public List activeAwardsCount(@ModelAttribute @Valid final YearFilterP return releaseAgg(agg); } + @ApiOperation(value = "Counts the tenders/awards where the given supplier id is among the winners. " + + "This assumes there is only one active award, which always seems to be the case, per tender. ") + @RequestMapping(value = "/api/tendererTupleActiveAwardsCount", + method = {RequestMethod.POST, RequestMethod.GET}, + produces = "application/json") + public List tendererTupleActiveAwardsCount(@ModelAttribute @Valid + final WinningTupleYearFilterPagingRequest filter) { + + List metaFilter = filter.getTendererTuple() + .stream() + .map(t -> where("bids.details.tenderers._id").is(t)) + .collect(Collectors.toList()); + + metaFilter.add(getYearDefaultFilterCriteria(filter, MongoConstants.FieldNames.TENDER_PERIOD_START_DATE)); + + Aggregation agg = newAggregation( + match(where(MongoConstants.FieldNames.AWARDS_STATUS).is(Award.Status.active.toString()) + .andOperator(metaFilter.toArray(new Criteria[0]))), + unwind("awards"), + match(where(MongoConstants.FieldNames.AWARDS_STATUS).is(Award.Status.active.toString()) + .andOperator(getYearDefaultFilterCriteria( + filter.awardFiltering(), + MongoConstants.FieldNames.TENDER_PERIOD_START_DATE + ))), + unwind("awards.suppliers"), + group(MongoConstants.FieldNames.AWARDS_SUPPLIERS_ID).count().as("cnt"), + project("cnt").and(Fields.UNDERSCORE_ID).as("supplierId") + .andExclude(Fields.UNDERSCORE_ID) + ); + + return releaseAgg(agg); + } + + } \ No newline at end of file diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java index e73434a42..34f7d5642 100644 --- a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/DefaultFilterPagingRequest.java @@ -6,6 +6,7 @@ import cz.jirutka.validator.collection.constraints.EachPattern; import cz.jirutka.validator.collection.constraints.EachRange; import io.swagger.annotations.ApiModelProperty; +import org.devgateway.ocds.web.annotate.OrganizationIdValidation; import java.math.BigDecimal; import java.util.TreeSet; @@ -31,17 +32,17 @@ public class DefaultFilterPagingRequest extends GenericPagingRequest { "This corresponds the negated bidTypeId filter, matches elements that are NOT in the TreeSet of Ids") private TreeSet notBidTypeId; - @EachPattern(regexp = "^[a-zA-Z0-9\\-]*$") + @OrganizationIdValidation @ApiModelProperty(value = "This is the id of the organization/procuring entity. " + "Corresponds to the OCDS Organization.identifier") private TreeSet procuringEntityId; - @EachPattern(regexp = "^[a-zA-Z0-9\\-]*$") + @OrganizationIdValidation @ApiModelProperty(value = "This corresponds the negated procuringEntityId filter," + " matches elements that are NOT in the TreeSet of Ids") private TreeSet notProcuringEntityId; - @EachPattern(regexp = "^[a-zA-Z0-9\\-]*$") + @OrganizationIdValidation @ApiModelProperty(value = "This is the id of the organization/supplier entity. " + "Corresponds to the OCDS Organization.identifier") private TreeSet supplierId; diff --git a/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/WinningTupleYearFilterPagingRequest.java b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/WinningTupleYearFilterPagingRequest.java new file mode 100644 index 000000000..dcb788e3b --- /dev/null +++ b/web/src/main/java/org/devgateway/ocds/web/rest/controller/request/WinningTupleYearFilterPagingRequest.java @@ -0,0 +1,28 @@ +/** + * + */ +package org.devgateway.ocds.web.rest.controller.request; + +import io.swagger.annotations.ApiModelProperty; +import org.devgateway.ocds.web.annotate.OrganizationIdValidation; + +import java.util.TreeSet; + +/** + * @author mpostelnicu + * + */ +public class WinningTupleYearFilterPagingRequest extends YearFilterPagingRequest { + + @OrganizationIdValidation + @ApiModelProperty(value = "This is a tuple of tenderers among which one was part of a winning award") + private TreeSet tendererTuple; + + public TreeSet getTendererTuple() { + return tendererTuple; + } + + public void setTendererTuple(TreeSet tendererTuple) { + this.tendererTuple = tendererTuple; + } +}